diff options
Diffstat (limited to 'Source/WebCore/rendering')
369 files changed, 103531 insertions, 0 deletions
diff --git a/Source/WebCore/rendering/AutoTableLayout.cpp b/Source/WebCore/rendering/AutoTableLayout.cpp new file mode 100644 index 0000000..bb0df0b --- /dev/null +++ b/Source/WebCore/rendering/AutoTableLayout.cpp @@ -0,0 +1,750 @@ +/* + * Copyright (C) 2002 Lars Knoll (knoll@kde.org) + * (C) 2002 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2006, 2008, 2010 Apple Inc. All rights reserved. + * + * 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. + * + * 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 "AutoTableLayout.h" + +#include "RenderTable.h" +#include "RenderTableCell.h" +#include "RenderTableCol.h" +#include "RenderTableSection.h" + +using namespace std; + +namespace WebCore { + +AutoTableLayout::AutoTableLayout(RenderTable* table) + : TableLayout(table) + , m_hasPercent(false) + , m_effectiveLogicalWidthDirty(true) +{ +} + +AutoTableLayout::~AutoTableLayout() +{ +} + +void AutoTableLayout::recalcColumn(int effCol) +{ + Layout& columnLayout = m_layoutStruct[effCol]; + + RenderTableCell* fixedContributor = 0; + RenderTableCell* maxContributor = 0; + + for (RenderObject* child = m_table->firstChild(); child; child = child->nextSibling()) { + if (child->isTableCol()) + toRenderTableCol(child)->computePreferredLogicalWidths(); + else if (child->isTableSection()) { + RenderTableSection* section = toRenderTableSection(child); + int numRows = section->numRows(); + for (int i = 0; i < numRows; i++) { + RenderTableSection::CellStruct current = section->cellAt(i, effCol); + RenderTableCell* cell = current.primaryCell(); + + bool cellHasContent = cell && !current.inColSpan && (cell->firstChild() || cell->style()->hasBorder() || cell->style()->hasPadding()); + if (cellHasContent) + columnLayout.emptyCellsOnly = false; + + if (current.inColSpan || !cell) + continue; + + if (cell->colSpan() == 1) { + // A cell originates in this column. Ensure we have + // a min/max width of at least 1px for this column now. + columnLayout.minLogicalWidth = max(columnLayout.minLogicalWidth, cellHasContent ? 1 : 0); + columnLayout.maxLogicalWidth = max(columnLayout.maxLogicalWidth, 1); + if (cell->preferredLogicalWidthsDirty()) + cell->computePreferredLogicalWidths(); + columnLayout.minLogicalWidth = max(cell->minPreferredLogicalWidth(), columnLayout.minLogicalWidth); + if (cell->maxPreferredLogicalWidth() > columnLayout.maxLogicalWidth) { + columnLayout.maxLogicalWidth = cell->maxPreferredLogicalWidth(); + maxContributor = cell; + } + + Length cellLogicalWidth = cell->styleOrColLogicalWidth(); + // FIXME: What is this arbitrary value? + if (cellLogicalWidth.rawValue() > 32760) + cellLogicalWidth.setRawValue(32760); + if (cellLogicalWidth.isNegative()) + cellLogicalWidth.setValue(0); + switch (cellLogicalWidth.type()) { + case Fixed: + // ignore width=0 + if (cellLogicalWidth.value() > 0 && columnLayout.logicalWidth.type() != Percent) { + int logicalWidth = cell->computeBorderBoxLogicalWidth(cellLogicalWidth.value()); + if (columnLayout.logicalWidth.isFixed()) { + // Nav/IE weirdness + if ((logicalWidth > columnLayout.logicalWidth.value()) || + ((columnLayout.logicalWidth.value() == logicalWidth) && (maxContributor == cell))) { + columnLayout.logicalWidth.setValue(logicalWidth); + fixedContributor = cell; + } + } else { + columnLayout.logicalWidth.setValue(Fixed, logicalWidth); + fixedContributor = cell; + } + } + break; + case Percent: + m_hasPercent = true; + if (cellLogicalWidth.isPositive() && (!columnLayout.logicalWidth.isPercent() || cellLogicalWidth.rawValue() > columnLayout.logicalWidth.rawValue())) + columnLayout.logicalWidth = cellLogicalWidth; + break; + case Relative: + // FIXME: Need to understand this case and whether it makes sense to compare values + // which are not necessarily of the same type. + if (cellLogicalWidth.isAuto() || (cellLogicalWidth.isRelative() && cellLogicalWidth.value() > columnLayout.logicalWidth.rawValue())) + columnLayout.logicalWidth = cellLogicalWidth; + default: + break; + } + } else if (!effCol || section->primaryCellAt(i, effCol - 1) != cell) { + // This spanning cell originates in this column. Ensure we have + // a min/max width of at least 1px for this column now. + columnLayout.minLogicalWidth = max(columnLayout.minLogicalWidth, cellHasContent ? 1 : 0); + columnLayout.maxLogicalWidth = max(columnLayout.maxLogicalWidth, 1); + insertSpanCell(cell); + } + } + } + } + + // Nav/IE weirdness + if (columnLayout.logicalWidth.isFixed()) { + if (m_table->document()->inQuirksMode() && columnLayout.maxLogicalWidth > columnLayout.logicalWidth.value() && fixedContributor != maxContributor) { + columnLayout.logicalWidth = Length(); + fixedContributor = 0; + } + } + + columnLayout.maxLogicalWidth = max(columnLayout.maxLogicalWidth, columnLayout.minLogicalWidth); +} + +void AutoTableLayout::fullRecalc() +{ + m_hasPercent = false; + m_effectiveLogicalWidthDirty = true; + + int nEffCols = m_table->numEffCols(); + m_layoutStruct.resize(nEffCols); + m_layoutStruct.fill(Layout()); + m_spanCells.fill(0); + + RenderObject* child = m_table->firstChild(); + Length groupLogicalWidth; + int currentColumn = 0; + while (child && child->isTableCol()) { + RenderTableCol* col = toRenderTableCol(child); + int span = col->span(); + if (col->firstChild()) + groupLogicalWidth = col->style()->logicalWidth(); + else { + Length colLogicalWidth = col->style()->logicalWidth(); + if (colLogicalWidth.isAuto()) + colLogicalWidth = groupLogicalWidth; + if ((colLogicalWidth.isFixed() || colLogicalWidth.isPercent()) && colLogicalWidth.isZero()) + colLogicalWidth = Length(); + int effCol = m_table->colToEffCol(currentColumn); + if (!colLogicalWidth.isAuto() && span == 1 && effCol < nEffCols && m_table->spanOfEffCol(effCol) == 1) { + m_layoutStruct[effCol].logicalWidth = colLogicalWidth; + if (colLogicalWidth.isFixed() && m_layoutStruct[effCol].maxLogicalWidth < colLogicalWidth.value()) + m_layoutStruct[effCol].maxLogicalWidth = colLogicalWidth.value(); + } + currentColumn += span; + } + + RenderObject* next = child->firstChild(); + if (!next) + next = child->nextSibling(); + if (!next && child->parent()->isTableCol()) { + next = child->parent()->nextSibling(); + groupLogicalWidth = Length(); + } + child = next; + } + + for (int i = 0; i < nEffCols; i++) + recalcColumn(i); +} + +// FIXME: This needs to be adapted for vertical writing modes. +static bool shouldScaleColumns(RenderTable* table) +{ + // A special case. If this table is not fixed width and contained inside + // a cell, then don't bloat the maxwidth by examining percentage growth. + bool scale = true; + while (table) { + Length tw = table->style()->width(); + if ((tw.isAuto() || tw.isPercent()) && !table->isPositioned()) { + RenderBlock* cb = table->containingBlock(); + while (cb && !cb->isRenderView() && !cb->isTableCell() && + cb->style()->width().isAuto() && !cb->isPositioned()) + cb = cb->containingBlock(); + + table = 0; + if (cb && cb->isTableCell() && + (cb->style()->width().isAuto() || cb->style()->width().isPercent())) { + if (tw.isPercent()) + scale = false; + else { + RenderTableCell* cell = toRenderTableCell(cb); + if (cell->colSpan() > 1 || cell->table()->style()->width().isAuto()) + scale = false; + else + table = cell->table(); + } + } + } + else + table = 0; + } + return scale; +} + +void AutoTableLayout::computePreferredLogicalWidths(int& minWidth, int& maxWidth) +{ + fullRecalc(); + + int spanMaxLogicalWidth = calcEffectiveLogicalWidth(); + minWidth = 0; + maxWidth = 0; + float maxPercent = 0; + float maxNonPercent = 0; + bool scaleColumns = shouldScaleColumns(m_table); + + // We substitute 0 percent by (epsilon / percentScaleFactor) percent in two places below to avoid division by zero. + // FIXME: Handle the 0% cases properly. + const int epsilon = 1; + + int remainingPercent = 100 * percentScaleFactor; + for (size_t i = 0; i < m_layoutStruct.size(); ++i) { + minWidth += m_layoutStruct[i].effectiveMinLogicalWidth; + maxWidth += m_layoutStruct[i].effectiveMaxLogicalWidth; + if (scaleColumns) { + if (m_layoutStruct[i].effectiveLogicalWidth.isPercent()) { + int percent = min(m_layoutStruct[i].effectiveLogicalWidth.rawValue(), remainingPercent); + float logicalWidth = static_cast<float>(m_layoutStruct[i].effectiveMaxLogicalWidth) * 100 * percentScaleFactor / max(percent, epsilon); + maxPercent = max(logicalWidth, maxPercent); + remainingPercent -= percent; + } else + maxNonPercent += m_layoutStruct[i].effectiveMaxLogicalWidth; + } + } + + if (scaleColumns) { + maxNonPercent = maxNonPercent * 100 * percentScaleFactor / max(remainingPercent, epsilon); + maxWidth = max(maxWidth, static_cast<int>(min(maxNonPercent, INT_MAX / 2.0f))); + maxWidth = max(maxWidth, static_cast<int>(min(maxPercent, INT_MAX / 2.0f))); + } + + maxWidth = max(maxWidth, spanMaxLogicalWidth); + + int bordersPaddingAndSpacing = m_table->bordersPaddingAndSpacingInRowDirection(); + minWidth += bordersPaddingAndSpacing; + maxWidth += bordersPaddingAndSpacing; + + Length tableLogicalWidth = m_table->style()->logicalWidth(); + if (tableLogicalWidth.isFixed() && tableLogicalWidth.value() > 0) { + minWidth = max(minWidth, tableLogicalWidth.value()); + maxWidth = minWidth; + } +} + +/* + This method takes care of colspans. + effWidth is the same as width for cells without colspans. If we have colspans, they get modified. + */ +int AutoTableLayout::calcEffectiveLogicalWidth() +{ + float maxLogicalWidth = 0; + + size_t nEffCols = m_layoutStruct.size(); + int spacingInRowDirection = m_table->hBorderSpacing(); + + for (size_t i = 0; i < nEffCols; ++i) { + m_layoutStruct[i].effectiveLogicalWidth = m_layoutStruct[i].logicalWidth; + m_layoutStruct[i].effectiveMinLogicalWidth = m_layoutStruct[i].minLogicalWidth; + m_layoutStruct[i].effectiveMaxLogicalWidth = m_layoutStruct[i].maxLogicalWidth; + } + + for (size_t i = 0; i < m_spanCells.size(); ++i) { + RenderTableCell* cell = m_spanCells[i]; + if (!cell) + break; + + int span = cell->colSpan(); + + Length cellLogicalWidth = cell->styleOrColLogicalWidth(); + if (!cellLogicalWidth.isRelative() && cellLogicalWidth.isZero()) + cellLogicalWidth = Length(); // make it Auto + + int effCol = m_table->colToEffCol(cell->col()); + size_t lastCol = effCol; + int cellMinLogicalWidth = cell->minPreferredLogicalWidth() + spacingInRowDirection; + float cellMaxLogicalWidth = cell->maxPreferredLogicalWidth() + spacingInRowDirection; + int totalPercent = 0; + int spanMinLogicalWidth = 0; + float spanMaxLogicalWidth = 0; + bool allColsArePercent = true; + bool allColsAreFixed = true; + bool haveAuto = false; + bool spanHasEmptyCellsOnly = true; + int fixedWidth = 0; + while (lastCol < nEffCols && span > 0) { + Layout& columnLayout = m_layoutStruct[lastCol]; + switch (columnLayout.logicalWidth.type()) { + case Percent: + totalPercent += columnLayout.logicalWidth.rawValue(); + allColsAreFixed = false; + break; + case Fixed: + if (columnLayout.logicalWidth.value() > 0) { + fixedWidth += columnLayout.logicalWidth.value(); + allColsArePercent = false; + // IE resets effWidth to Auto here, but this breaks the konqueror about page and seems to be some bad + // legacy behaviour anyway. mozilla doesn't do this so I decided we don't neither. + break; + } + // fall through + case Auto: + haveAuto = true; + // fall through + default: + // If the column is a percentage width, do not let the spanning cell overwrite the + // width value. This caused a mis-rendering on amazon.com. + // Sample snippet: + // <table border=2 width=100%>< + // <tr><td>1</td><td colspan=2>2-3</tr> + // <tr><td>1</td><td colspan=2 width=100%>2-3</td></tr> + // </table> + if (!columnLayout.effectiveLogicalWidth.isPercent()) { + columnLayout.effectiveLogicalWidth = Length(); + allColsArePercent = false; + } else + totalPercent += columnLayout.effectiveLogicalWidth.rawValue(); + allColsAreFixed = false; + } + if (!columnLayout.emptyCellsOnly) + spanHasEmptyCellsOnly = false; + span -= m_table->spanOfEffCol(lastCol); + spanMinLogicalWidth += columnLayout.effectiveMinLogicalWidth; + spanMaxLogicalWidth += columnLayout.effectiveMaxLogicalWidth; + lastCol++; + cellMinLogicalWidth -= spacingInRowDirection; + cellMaxLogicalWidth -= spacingInRowDirection; + } + + // adjust table max width if needed + if (cellLogicalWidth.isPercent()) { + if (totalPercent > cellLogicalWidth.rawValue() || allColsArePercent) { + // can't satify this condition, treat as variable + cellLogicalWidth = Length(); + } else { + maxLogicalWidth = max(maxLogicalWidth, max(spanMaxLogicalWidth, cellMaxLogicalWidth) * 100 * percentScaleFactor / cellLogicalWidth.rawValue()); + + // all non percent columns in the span get percent values to sum up correctly. + int percentMissing = cellLogicalWidth.rawValue() - totalPercent; + float totalWidth = 0; + for (unsigned pos = effCol; pos < lastCol; ++pos) { + if (!m_layoutStruct[pos].effectiveLogicalWidth.isPercent()) + totalWidth += m_layoutStruct[pos].effectiveMaxLogicalWidth; + } + + for (unsigned pos = effCol; pos < lastCol && totalWidth > 0; ++pos) { + if (!m_layoutStruct[pos].effectiveLogicalWidth.isPercent()) { + int percent = static_cast<int>(percentMissing * static_cast<float>(m_layoutStruct[pos].effectiveMaxLogicalWidth) / totalWidth); + totalWidth -= m_layoutStruct[pos].effectiveMaxLogicalWidth; + percentMissing -= percent; + if (percent > 0) + m_layoutStruct[pos].effectiveLogicalWidth.setRawValue(Percent, percent); + else + m_layoutStruct[pos].effectiveLogicalWidth = Length(); + } + } + } + } + + // make sure minWidth and maxWidth of the spanning cell are honoured + if (cellMinLogicalWidth > spanMinLogicalWidth) { + if (allColsAreFixed) { + for (unsigned pos = effCol; fixedWidth > 0 && pos < lastCol; ++pos) { + int cellLogicalWidth = max(m_layoutStruct[pos].effectiveMinLogicalWidth, cellMinLogicalWidth * m_layoutStruct[pos].logicalWidth.value() / fixedWidth); + fixedWidth -= m_layoutStruct[pos].logicalWidth.value(); + cellMinLogicalWidth -= cellLogicalWidth; + m_layoutStruct[pos].effectiveMinLogicalWidth = cellLogicalWidth; + } + } else { + float remainingMaxLogicalWidth = spanMaxLogicalWidth; + int remainingMinLogicalWidth = spanMinLogicalWidth; + + // Give min to variable first, to fixed second, and to others third. + for (unsigned pos = effCol; remainingMaxLogicalWidth >= 0 && pos < lastCol; ++pos) { + if (m_layoutStruct[pos].logicalWidth.isFixed() && haveAuto && fixedWidth <= cellMinLogicalWidth) { + int colMinLogicalWidth = max(m_layoutStruct[pos].effectiveMinLogicalWidth, m_layoutStruct[pos].logicalWidth.value()); + fixedWidth -= m_layoutStruct[pos].logicalWidth.value(); + remainingMinLogicalWidth -= m_layoutStruct[pos].effectiveMinLogicalWidth; + remainingMaxLogicalWidth -= m_layoutStruct[pos].effectiveMaxLogicalWidth; + cellMinLogicalWidth -= colMinLogicalWidth; + m_layoutStruct[pos].effectiveMinLogicalWidth = colMinLogicalWidth; + } + } + + for (unsigned pos = effCol; remainingMaxLogicalWidth >= 0 && pos < lastCol && remainingMinLogicalWidth < cellMinLogicalWidth; ++pos) { + if (!(m_layoutStruct[pos].logicalWidth.isFixed() && haveAuto && fixedWidth <= cellMinLogicalWidth)) { + int colMinLogicalWidth = max(m_layoutStruct[pos].effectiveMinLogicalWidth, static_cast<int>(remainingMaxLogicalWidth ? cellMinLogicalWidth * static_cast<float>(m_layoutStruct[pos].effectiveMaxLogicalWidth) / remainingMaxLogicalWidth : cellMinLogicalWidth)); + colMinLogicalWidth = min(m_layoutStruct[pos].effectiveMinLogicalWidth + (cellMinLogicalWidth - remainingMinLogicalWidth), colMinLogicalWidth); + remainingMaxLogicalWidth -= m_layoutStruct[pos].effectiveMaxLogicalWidth; + remainingMinLogicalWidth -= m_layoutStruct[pos].effectiveMinLogicalWidth; + cellMinLogicalWidth -= colMinLogicalWidth; + m_layoutStruct[pos].effectiveMinLogicalWidth = colMinLogicalWidth; + } + } + } + } + if (!cellLogicalWidth.isPercent()) { + if (cellMaxLogicalWidth > spanMaxLogicalWidth) { + for (unsigned pos = effCol; spanMaxLogicalWidth >= 0 && pos < lastCol; ++pos) { + int colMaxLogicalWidth = max(m_layoutStruct[pos].effectiveMaxLogicalWidth, static_cast<int>(spanMaxLogicalWidth ? cellMaxLogicalWidth * static_cast<float>(m_layoutStruct[pos].effectiveMaxLogicalWidth) / spanMaxLogicalWidth : cellMaxLogicalWidth)); + spanMaxLogicalWidth -= m_layoutStruct[pos].effectiveMaxLogicalWidth; + cellMaxLogicalWidth -= colMaxLogicalWidth; + m_layoutStruct[pos].effectiveMaxLogicalWidth = colMaxLogicalWidth; + } + } + } else { + for (unsigned pos = effCol; pos < lastCol; ++pos) + m_layoutStruct[pos].maxLogicalWidth = max(m_layoutStruct[pos].maxLogicalWidth, m_layoutStruct[pos].minLogicalWidth); + } + // treat span ranges consisting of empty cells only as if they had content + if (spanHasEmptyCellsOnly) { + for (unsigned pos = effCol; pos < lastCol; ++pos) + m_layoutStruct[pos].emptyCellsOnly = false; + } + } + m_effectiveLogicalWidthDirty = false; + + return static_cast<int>(min(maxLogicalWidth, INT_MAX / 2.0f)); +} + +/* gets all cells that originate in a column and have a cellspan > 1 + Sorts them by increasing cellspan +*/ +void AutoTableLayout::insertSpanCell(RenderTableCell *cell) +{ + ASSERT_ARG(cell, cell && cell->colSpan() != 1); + if (!cell || cell->colSpan() == 1) + return; + + int size = m_spanCells.size(); + if (!size || m_spanCells[size-1] != 0) { + m_spanCells.grow(size + 10); + for (int i = 0; i < 10; i++) + m_spanCells[size+i] = 0; + size += 10; + } + + // add them in sort. This is a slow algorithm, and a binary search or a fast sorting after collection would be better + unsigned int pos = 0; + int span = cell->colSpan(); + while (pos < m_spanCells.size() && m_spanCells[pos] && span > m_spanCells[pos]->colSpan()) + pos++; + memmove(m_spanCells.data()+pos+1, m_spanCells.data()+pos, (size-pos-1)*sizeof(RenderTableCell *)); + m_spanCells[pos] = cell; +} + + +void AutoTableLayout::layout() +{ +#ifdef ANDROID_LAYOUT + if (m_table->isSingleColumn()) + return; +#endif + // table layout based on the values collected in the layout structure. + int tableLogicalWidth = m_table->logicalWidth() - m_table->bordersPaddingAndSpacingInRowDirection(); + int available = tableLogicalWidth; + size_t nEffCols = m_table->numEffCols(); + + if (nEffCols != m_layoutStruct.size()) { + fullRecalc(); + nEffCols = m_table->numEffCols(); + } + + if (m_effectiveLogicalWidthDirty) + calcEffectiveLogicalWidth(); + + bool havePercent = false; + int totalRelative = 0; + int numAuto = 0; + int numFixed = 0; + float totalAuto = 0; + float totalFixed = 0; + int totalPercent = 0; + int allocAuto = 0; + unsigned numAutoEmptyCellsOnly = 0; + + // fill up every cell with its minWidth + for (size_t i = 0; i < nEffCols; ++i) { + int cellLogicalWidth = m_layoutStruct[i].effectiveMinLogicalWidth; + m_layoutStruct[i].computedLogicalWidth = cellLogicalWidth; + available -= cellLogicalWidth; + Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth; + switch (logicalWidth.type()) { + case Percent: + havePercent = true; + totalPercent += logicalWidth.rawValue(); + break; + case Relative: + totalRelative += logicalWidth.value(); + break; + case Fixed: + numFixed++; + totalFixed += m_layoutStruct[i].effectiveMaxLogicalWidth; + // fall through + break; + case Auto: + case Static: + if (m_layoutStruct[i].emptyCellsOnly) + numAutoEmptyCellsOnly++; + else { + numAuto++; + totalAuto += m_layoutStruct[i].effectiveMaxLogicalWidth; + allocAuto += cellLogicalWidth; + } + break; + default: + break; + } + } + + // allocate width to percent cols + if (available > 0 && havePercent) { + for (size_t i = 0; i < nEffCols; ++i) { + Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth; + if (logicalWidth.isPercent()) { + int cellLogicalWidth = max(m_layoutStruct[i].effectiveMinLogicalWidth, logicalWidth.calcMinValue(tableLogicalWidth)); + available += m_layoutStruct[i].computedLogicalWidth - cellLogicalWidth; + m_layoutStruct[i].computedLogicalWidth = cellLogicalWidth; + } + } + if (totalPercent > 100 * percentScaleFactor) { + // remove overallocated space from the last columns + int excess = tableLogicalWidth * (totalPercent - 100 * percentScaleFactor) / (100 * percentScaleFactor); + for (int i = nEffCols - 1; i >= 0; --i) { + if (m_layoutStruct[i].effectiveLogicalWidth.isPercent()) { + int cellLogicalWidth = m_layoutStruct[i].computedLogicalWidth; + int reduction = min(cellLogicalWidth, excess); + // the lines below might look inconsistent, but that's the way it's handled in mozilla + excess -= reduction; + int newLogicalWidth = max(m_layoutStruct[i].effectiveMinLogicalWidth, cellLogicalWidth - reduction); + available += cellLogicalWidth - newLogicalWidth; + m_layoutStruct[i].computedLogicalWidth = newLogicalWidth; + } + } + } + } + + // then allocate width to fixed cols + if (available > 0) { + for (size_t i = 0; i < nEffCols; ++i) { + Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth; + if (logicalWidth.isFixed() && logicalWidth.value() > m_layoutStruct[i].computedLogicalWidth) { + available += m_layoutStruct[i].computedLogicalWidth - logicalWidth.value(); + m_layoutStruct[i].computedLogicalWidth = logicalWidth.value(); + } + } + } + + // now satisfy relative + if (available > 0) { + for (size_t i = 0; i < nEffCols; ++i) { + Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth; + if (logicalWidth.isRelative() && logicalWidth.value() != 0) { + // width=0* gets effMinWidth. + int cellLogicalWidth = logicalWidth.value() * tableLogicalWidth / totalRelative; + available += m_layoutStruct[i].computedLogicalWidth - cellLogicalWidth; + m_layoutStruct[i].computedLogicalWidth = cellLogicalWidth; + } + } + } + + // now satisfy variable + if (available > 0 && numAuto) { + available += allocAuto; // this gets redistributed + for (size_t i = 0; i < nEffCols; ++i) { + Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth; + if (logicalWidth.isAuto() && totalAuto && !m_layoutStruct[i].emptyCellsOnly) { + int cellLogicalWidth = max(m_layoutStruct[i].computedLogicalWidth, static_cast<int>(available * static_cast<float>(m_layoutStruct[i].effectiveMaxLogicalWidth) / totalAuto)); + available -= cellLogicalWidth; + totalAuto -= m_layoutStruct[i].effectiveMaxLogicalWidth; + m_layoutStruct[i].computedLogicalWidth = cellLogicalWidth; + } + } + } + + // spread over fixed columns + if (available > 0 && numFixed) { + for (size_t i = 0; i < nEffCols; ++i) { + Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth; + if (logicalWidth.isFixed()) { + int cellLogicalWidth = static_cast<int>(available * static_cast<float>(m_layoutStruct[i].effectiveMaxLogicalWidth) / totalFixed); + available -= cellLogicalWidth; + totalFixed -= m_layoutStruct[i].effectiveMaxLogicalWidth; + m_layoutStruct[i].computedLogicalWidth += cellLogicalWidth; + } + } + } + + // spread over percent colums + if (available > 0 && m_hasPercent && totalPercent < 100 * percentScaleFactor) { + for (size_t i = 0; i < nEffCols; ++i) { + Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth; + if (logicalWidth.isPercent()) { + int cellLogicalWidth = available * logicalWidth.rawValue() / totalPercent; + available -= cellLogicalWidth; + totalPercent -= logicalWidth.rawValue(); + m_layoutStruct[i].computedLogicalWidth += cellLogicalWidth; + if (!available || !totalPercent) + break; + } + } + } + + // spread over the rest + if (available > 0 && nEffCols > numAutoEmptyCellsOnly) { + int total = nEffCols - numAutoEmptyCellsOnly; + // still have some width to spread + for (int i = nEffCols - 1; i >= 0; --i) { + // variable columns with empty cells only don't get any width + if (m_layoutStruct[i].effectiveLogicalWidth.isAuto() && m_layoutStruct[i].emptyCellsOnly) + continue; + int cellLogicalWidth = available / total; + available -= cellLogicalWidth; + total--; + m_layoutStruct[i].computedLogicalWidth += cellLogicalWidth; + } + } + + // If we have overallocated, reduce every cell according to the difference between desired width and minwidth + // this seems to produce to the pixel exact results with IE. Wonder is some of this also holds for width distributing. + if (available < 0) { + // Need to reduce cells with the following prioritization: + // (1) Auto + // (2) Relative + // (3) Fixed + // (4) Percent + // This is basically the reverse of how we grew the cells. + if (available < 0) { + int logicalWidthBeyondMin = 0; + for (int i = nEffCols - 1; i >= 0; --i) { + Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth; + if (logicalWidth.isAuto()) + logicalWidthBeyondMin += m_layoutStruct[i].computedLogicalWidth - m_layoutStruct[i].effectiveMinLogicalWidth; + } + + for (int i = nEffCols - 1; i >= 0 && logicalWidthBeyondMin > 0; --i) { + Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth; + if (logicalWidth.isAuto()) { + int minMaxDiff = m_layoutStruct[i].computedLogicalWidth - m_layoutStruct[i].effectiveMinLogicalWidth; + int reduce = available * minMaxDiff / logicalWidthBeyondMin; + m_layoutStruct[i].computedLogicalWidth += reduce; + available -= reduce; + logicalWidthBeyondMin -= minMaxDiff; + if (available >= 0) + break; + } + } + } + + if (available < 0) { + int logicalWidthBeyondMin = 0; + for (int i = nEffCols - 1; i >= 0; --i) { + Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth; + if (logicalWidth.isRelative()) + logicalWidthBeyondMin += m_layoutStruct[i].computedLogicalWidth - m_layoutStruct[i].effectiveMinLogicalWidth; + } + + for (int i = nEffCols - 1; i >= 0 && logicalWidthBeyondMin > 0; --i) { + Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth; + if (logicalWidth.isRelative()) { + int minMaxDiff = m_layoutStruct[i].computedLogicalWidth - m_layoutStruct[i].effectiveMinLogicalWidth; + int reduce = available * minMaxDiff / logicalWidthBeyondMin; + m_layoutStruct[i].computedLogicalWidth += reduce; + available -= reduce; + logicalWidthBeyondMin -= minMaxDiff; + if (available >= 0) + break; + } + } + } + + if (available < 0) { + int logicalWidthBeyondMin = 0; + for (int i = nEffCols - 1; i >= 0; --i) { + Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth; + if (logicalWidth.isFixed()) + logicalWidthBeyondMin += m_layoutStruct[i].computedLogicalWidth - m_layoutStruct[i].effectiveMinLogicalWidth; + } + + for (int i = nEffCols - 1; i >= 0 && logicalWidthBeyondMin > 0; --i) { + Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth; + if (logicalWidth.isFixed()) { + int minMaxDiff = m_layoutStruct[i].computedLogicalWidth - m_layoutStruct[i].effectiveMinLogicalWidth; + int reduce = available * minMaxDiff / logicalWidthBeyondMin; + m_layoutStruct[i].computedLogicalWidth += reduce; + available -= reduce; + logicalWidthBeyondMin -= minMaxDiff; + if (available >= 0) + break; + } + } + } + + if (available < 0) { + int logicalWidthBeyondMin = 0; + for (int i = nEffCols - 1; i >= 0; --i) { + Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth; + if (logicalWidth.isPercent()) + logicalWidthBeyondMin += m_layoutStruct[i].computedLogicalWidth - m_layoutStruct[i].effectiveMinLogicalWidth; + } + + for (int i = nEffCols-1; i >= 0 && logicalWidthBeyondMin > 0; i--) { + Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth; + if (logicalWidth.isPercent()) { + int minMaxDiff = m_layoutStruct[i].computedLogicalWidth - m_layoutStruct[i].effectiveMinLogicalWidth; + int reduce = available * minMaxDiff / logicalWidthBeyondMin; + m_layoutStruct[i].computedLogicalWidth += reduce; + available -= reduce; + logicalWidthBeyondMin -= minMaxDiff; + if (available >= 0) + break; + } + } + } + } + + int pos = 0; + for (size_t i = 0; i < nEffCols; ++i) { + m_table->columnPositions()[i] = pos; + pos += m_layoutStruct[i].computedLogicalWidth + m_table->hBorderSpacing(); + } + m_table->columnPositions()[m_table->columnPositions().size() - 1] = pos; +} + +} diff --git a/Source/WebCore/rendering/AutoTableLayout.h b/Source/WebCore/rendering/AutoTableLayout.h new file mode 100644 index 0000000..7ade0d6 --- /dev/null +++ b/Source/WebCore/rendering/AutoTableLayout.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2002 Lars Knoll (knoll@kde.org) + * (C) 2002 Dirk Mueller (mueller@kde.org) + * + * 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. + * + * 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. + */ + +#ifndef AutoTableLayout_h +#define AutoTableLayout_h + +#include "Length.h" +#include "TableLayout.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class RenderTable; +class RenderTableCell; + +class AutoTableLayout : public TableLayout { +public: + AutoTableLayout(RenderTable*); + ~AutoTableLayout(); + + virtual void computePreferredLogicalWidths(int& minWidth, int& maxWidth); + virtual void layout(); + +private: + void fullRecalc(); + void recalcColumn(int effCol); + + int calcEffectiveLogicalWidth(); + + void insertSpanCell(RenderTableCell*); + + struct Layout { + Layout() + : minLogicalWidth(0) + , maxLogicalWidth(0) + , effectiveMinLogicalWidth(0) + , effectiveMaxLogicalWidth(0) + , computedLogicalWidth(0) + , emptyCellsOnly(true) + { + } + + Length logicalWidth; + Length effectiveLogicalWidth; + int minLogicalWidth; + int maxLogicalWidth; + int effectiveMinLogicalWidth; + int effectiveMaxLogicalWidth; + int computedLogicalWidth; + bool emptyCellsOnly; + }; + + Vector<Layout, 4> m_layoutStruct; + Vector<RenderTableCell*, 4> m_spanCells; + bool m_hasPercent : 1; + mutable bool m_effectiveLogicalWidthDirty : 1; +}; + +} // namespace WebCore + +#endif // AutoTableLayout_h diff --git a/Source/WebCore/rendering/BidiRun.cpp b/Source/WebCore/rendering/BidiRun.cpp new file mode 100644 index 0000000..ac13046 --- /dev/null +++ b/Source/WebCore/rendering/BidiRun.cpp @@ -0,0 +1,74 @@ +/** + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2003, 2004, 2005, 2006, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net) + * + * 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 "BidiRun.h" +#include "InlineBox.h" +#include "RenderArena.h" +#include <wtf/RefCountedLeakCounter.h> + +using namespace WTF; + +namespace WebCore { + +#ifndef NDEBUG +static RefCountedLeakCounter bidiRunCounter("BidiRun"); + +static bool inBidiRunDestroy; +#endif + +void BidiRun::destroy() +{ +#ifndef NDEBUG + inBidiRunDestroy = true; +#endif + RenderArena* renderArena = m_object->renderArena(); + delete this; +#ifndef NDEBUG + inBidiRunDestroy = false; +#endif + + // Recover the size left there for us by operator delete and free the memory. + renderArena->free(*reinterpret_cast<size_t*>(this), this); +} + +void* BidiRun::operator new(size_t sz, RenderArena* renderArena) throw() +{ +#ifndef NDEBUG + bidiRunCounter.increment(); +#endif + return renderArena->allocate(sz); +} + +void BidiRun::operator delete(void* ptr, size_t sz) +{ +#ifndef NDEBUG + bidiRunCounter.decrement(); +#endif + ASSERT(inBidiRunDestroy); + + // Stash size where destroy() can find it. + *(size_t*)ptr = sz; +} + +} diff --git a/Source/WebCore/rendering/BidiRun.h b/Source/WebCore/rendering/BidiRun.h new file mode 100644 index 0000000..5dbb07b --- /dev/null +++ b/Source/WebCore/rendering/BidiRun.h @@ -0,0 +1,67 @@ +/** + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2003, 2004, 2005, 2006, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net) + * + * 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. + * + */ + +#ifndef BidiRun_h +#define BidiRun_h + +#include <wtf/StdLibExtras.h> +#include "BidiResolver.h" +#include "RenderText.h" + +namespace WebCore { + +class BidiContext; +class InlineBox; + +struct BidiRun : BidiCharacterRun { + BidiRun(int start, int stop, RenderObject* object, BidiContext* context, WTF::Unicode::Direction dir) + : BidiCharacterRun(start, stop, context, dir) + , m_object(object) + , m_box(0) + , m_hasHyphen(false) + { + } + + void destroy(); + + // Overloaded new operator. + void* operator new(size_t, RenderArena*) throw(); + + // Overridden to prevent the normal delete from being called. + void operator delete(void*, size_t); + + BidiRun* next() { return static_cast<BidiRun*>(m_next); } + +private: + // The normal operator new is disallowed. + void* operator new(size_t) throw(); + +public: + RenderObject* m_object; + InlineBox* m_box; + bool m_hasHyphen; +}; + +} + +#endif // BidiRun_h diff --git a/Source/WebCore/rendering/ColumnInfo.h b/Source/WebCore/rendering/ColumnInfo.h new file mode 100644 index 0000000..5e6f619 --- /dev/null +++ b/Source/WebCore/rendering/ColumnInfo.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ColumnInfo_h +#define ColumnInfo_h + +#include <wtf/Vector.h> +#include "IntRect.h" + +namespace WebCore { + +class ColumnInfo : public Noncopyable { +public: + ColumnInfo() + : m_desiredColumnWidth(0) + , m_desiredColumnCount(1) + , m_columnCount(1) + , m_columnHeight(0) + , m_minimumColumnHeight(0) + , m_forcedBreaks(0) + , m_maximumDistanceBetweenForcedBreaks(0) + , m_forcedBreakOffset(0) + { } + + int desiredColumnWidth() const { return m_desiredColumnWidth; } + void setDesiredColumnWidth(int width) { m_desiredColumnWidth = width; } + + unsigned desiredColumnCount() const { return m_desiredColumnCount; } + void setDesiredColumnCount(unsigned count) { m_desiredColumnCount = count; } + + unsigned columnCount() const { return m_columnCount; } + int columnHeight() const { return m_columnHeight; } + + // Set our count and height. This is enough info for a RenderBlock to compute page rects + // dynamically. + void setColumnCountAndHeight(int count, int height) + { + m_columnCount = count; + m_columnHeight = height; + } + void setColumnHeight(int height) { m_columnHeight = height; } + + void updateMinimumColumnHeight(int height) { m_minimumColumnHeight = std::max(height, m_minimumColumnHeight); } + int minimumColumnHeight() const { return m_minimumColumnHeight; } + + int forcedBreaks() const { return m_forcedBreaks; } + int forcedBreakOffset() const { return m_forcedBreakOffset; } + int maximumDistanceBetweenForcedBreaks() const { return m_maximumDistanceBetweenForcedBreaks; } + void clearForcedBreaks() + { + m_forcedBreaks = 0; + m_maximumDistanceBetweenForcedBreaks = 0; + m_forcedBreakOffset = 0; + } + void addForcedBreak(int offsetFromFirstPage) + { + ASSERT(!m_columnHeight); + int distanceFromLastBreak = offsetFromFirstPage - m_forcedBreakOffset; + if (!distanceFromLastBreak) + return; + m_forcedBreaks++; + m_maximumDistanceBetweenForcedBreaks = std::max(m_maximumDistanceBetweenForcedBreaks, distanceFromLastBreak); + m_forcedBreakOffset = offsetFromFirstPage; + } + +private: + int m_desiredColumnWidth; + unsigned m_desiredColumnCount; + + unsigned m_columnCount; + int m_columnHeight; + int m_minimumColumnHeight; + int m_forcedBreaks; // FIXME: We will ultimately need to cache more information to balance around forced breaks properly. + int m_maximumDistanceBetweenForcedBreaks; + int m_forcedBreakOffset; +}; + +} + +#endif diff --git a/Source/WebCore/rendering/CounterNode.cpp b/Source/WebCore/rendering/CounterNode.cpp new file mode 100644 index 0000000..ac83d5a --- /dev/null +++ b/Source/WebCore/rendering/CounterNode.cpp @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * 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 "CounterNode.h" + +#include "RenderCounter.h" +#include "RenderObject.h" +#include <stdio.h> + +namespace WebCore { + +CounterNode::CounterNode(RenderObject* o, bool hasResetType, int value) + : m_hasResetType(hasResetType) + , m_value(value) + , m_countInParent(0) + , m_renderer(o) + , m_parent(0) + , m_previousSibling(0) + , m_nextSibling(0) + , m_firstChild(0) + , m_lastChild(0) +{ +} + +PassRefPtr<CounterNode> CounterNode::create(RenderObject* renderer, bool hasResetType, int value) +{ + return adoptRef(new CounterNode(renderer, hasResetType, value)); +} + +CounterNode* CounterNode::nextInPreOrderAfterChildren(const CounterNode* stayWithin) const +{ + if (this == stayWithin) + return 0; + + const CounterNode* current = this; + CounterNode* next; + while (!(next = current->m_nextSibling)) { + current = current->m_parent; + if (!current || current == stayWithin) + return 0; + } + return next; +} + +CounterNode* CounterNode::nextInPreOrder(const CounterNode* stayWithin) const +{ + if (CounterNode* next = m_firstChild) + return next; + + return nextInPreOrderAfterChildren(stayWithin); +} + +CounterNode* CounterNode::lastDescendant() const +{ + CounterNode* last = m_lastChild; + if (!last) + return 0; + + while (CounterNode* lastChild = last->m_lastChild) + last = lastChild; + + return last; +} + +CounterNode* CounterNode::previousInPreOrder() const +{ + CounterNode* previous = m_previousSibling; + if (!previous) + return m_parent; + + while (CounterNode* lastChild = previous->m_lastChild) + previous = lastChild; + + return previous; +} + +int CounterNode::computeCountInParent() const +{ + int increment = actsAsReset() ? 0 : m_value; + if (m_previousSibling) + return m_previousSibling->m_countInParent + increment; + ASSERT(m_parent->m_firstChild == this); + return m_parent->m_value + increment; +} + +void CounterNode::resetRenderer(const AtomicString& identifier) const +{ + if (!m_renderer || m_renderer->documentBeingDestroyed()) + return; + if (RenderObjectChildList* children = m_renderer->virtualChildren()) + children->invalidateCounters(m_renderer, identifier); +} + +void CounterNode::resetRenderers(const AtomicString& identifier) const +{ + const CounterNode* node = this; + do { + node->resetRenderer(identifier); + node = node->nextInPreOrder(this); + } while (node); +} + +void CounterNode::recount(const AtomicString& identifier) +{ + for (CounterNode* node = this; node; node = node->m_nextSibling) { + int oldCount = node->m_countInParent; + int newCount = node->computeCountInParent(); + if (oldCount == newCount) + break; + node->m_countInParent = newCount; + node->resetRenderers(identifier); + } +} + +void CounterNode::insertAfter(CounterNode* newChild, CounterNode* refChild, const AtomicString& identifier) +{ + ASSERT(newChild); + ASSERT(!newChild->m_parent); + ASSERT(!newChild->m_previousSibling); + ASSERT(!newChild->m_nextSibling); + ASSERT(!refChild || refChild->m_parent == this); + + if (newChild->m_hasResetType) { + while (m_lastChild != refChild) + RenderCounter::destroyCounterNode(m_lastChild->renderer(), identifier); + } + + CounterNode* next; + + if (refChild) { + next = refChild->m_nextSibling; + refChild->m_nextSibling = newChild; + } else { + next = m_firstChild; + m_firstChild = newChild; + } + + newChild->m_parent = this; + newChild->m_previousSibling = refChild; + + if (!newChild->m_firstChild || newChild->m_hasResetType) { + newChild->m_nextSibling = next; + if (next) { + ASSERT(next->m_previousSibling == refChild); + next->m_previousSibling = newChild; + } else { + ASSERT(m_lastChild == refChild); + m_lastChild = newChild; + } + + newChild->m_countInParent = newChild->computeCountInParent(); + newChild->resetRenderers(identifier); + if (next) + next->recount(identifier); + return; + } + + // The code below handles the case when a formerly root increment counter is loosing its root position + // and therefore its children become next siblings. + CounterNode* last = newChild->m_lastChild; + CounterNode* first = newChild->m_firstChild; + + newChild->m_nextSibling = first; + first->m_previousSibling = newChild; + // The case when the original next sibling of the inserted node becomes a child of + // one of the former children of the inserted node is not handled as it is believed + // to be impossible since: + // 1. if the increment counter node lost it's root position as a result of another + // counter node being created, it will be inserted as the last child so next is null. + // 2. if the increment counter node lost it's root position as a result of a renderer being + // inserted into the document's render tree, all its former children counters are attached + // to children of the inserted renderer and hence cannot be in scope for counter nodes + // attached to renderers that were already in the document's render tree. + last->m_nextSibling = next; + if (next) + next->m_previousSibling = last; + else + m_lastChild = last; + for (next = first; ; next = next->m_nextSibling) { + next->m_parent = this; + if (last == next) + break; + } + newChild->m_firstChild = 0; + newChild->m_lastChild = 0; + newChild->m_countInParent = newChild->computeCountInParent(); + newChild->resetRenderer(identifier); + first->recount(identifier); +} + +void CounterNode::removeChild(CounterNode* oldChild, const AtomicString& identifier) +{ + ASSERT(oldChild); + ASSERT(!oldChild->m_firstChild); + ASSERT(!oldChild->m_lastChild); + + CounterNode* next = oldChild->m_nextSibling; + CounterNode* previous = oldChild->m_previousSibling; + + oldChild->m_nextSibling = 0; + oldChild->m_previousSibling = 0; + oldChild->m_parent = 0; + + if (previous) + previous->m_nextSibling = next; + else { + ASSERT(m_firstChild == oldChild); + m_firstChild = next; + } + + if (next) + next->m_previousSibling = previous; + else { + ASSERT(m_lastChild == oldChild); + m_lastChild = previous; + } + + if (next) + next->recount(identifier); +} + +#ifndef NDEBUG + +static void showTreeAndMark(const CounterNode* node) +{ + const CounterNode* root = node; + while (root->parent()) + root = root->parent(); + + for (const CounterNode* current = root; current; current = current->nextInPreOrder()) { + fwrite((current == node) ? "*" : " ", 1, 1, stderr); + for (const CounterNode* parent = current; parent && parent != root; parent = parent->parent()) + fwrite(" ", 1, 2, stderr); + fprintf(stderr, "%p %s: %d %d P:%p PS:%p NS:%p R:%p\n", + current, current->actsAsReset() ? "reset____" : "increment", current->value(), + current->countInParent(), current->parent(), current->previousSibling(), + current->nextSibling(), current->renderer()); + } +} + +#endif + +} // namespace WebCore + +#ifndef NDEBUG + +void showTree(const WebCore::CounterNode* counter) +{ + if (counter) + showTreeAndMark(counter); +} + +#endif diff --git a/Source/WebCore/rendering/CounterNode.h b/Source/WebCore/rendering/CounterNode.h new file mode 100644 index 0000000..529d409 --- /dev/null +++ b/Source/WebCore/rendering/CounterNode.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * 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. + * +*/ + +#ifndef CounterNode_h +#define CounterNode_h + +#include <wtf/Forward.h> +#include <wtf/Noncopyable.h> +#include <wtf/RefCounted.h> + +// This implements a counter tree that is used for finding parents in counters() lookup, +// and for propagating count changes when nodes are added or removed. + +// Parents represent unique counters and their scope, which are created either explicitly +// by "counter-reset" style rules or implicitly by referring to a counter that is not in scope. +// Such nodes are tagged as "reset" nodes, although they are not all due to "counter-reset". + +// Not that render tree children are often counter tree siblings due to counter scoping rules. + +namespace WebCore { + +class RenderObject; + +class CounterNode : public RefCounted<CounterNode> { +public: + static PassRefPtr<CounterNode> create(RenderObject*, bool isReset, int value); + + bool actsAsReset() const { return m_hasResetType || !m_parent; } + bool hasResetType() const { return m_hasResetType; } + int value() const { return m_value; } + int countInParent() const { return m_countInParent; } + RenderObject* renderer() const { return m_renderer; } + + CounterNode* parent() const { return m_parent; } + CounterNode* previousSibling() const { return m_previousSibling; } + CounterNode* nextSibling() const { return m_nextSibling; } + CounterNode* firstChild() const { return m_firstChild; } + CounterNode* lastChild() const { return m_lastChild; } + CounterNode* lastDescendant() const; + CounterNode* previousInPreOrder() const; + CounterNode* nextInPreOrder(const CounterNode* stayWithin = 0) const; + CounterNode* nextInPreOrderAfterChildren(const CounterNode* stayWithin = 0) const; + + void insertAfter(CounterNode* newChild, CounterNode* beforeChild, const AtomicString& identifier); + + // identifier must match the identifier of this counter. + void removeChild(CounterNode*, const AtomicString& identifier); + +private: + CounterNode(RenderObject*, bool isReset, int value); + int computeCountInParent() const; + void recount(const AtomicString& identifier); + + // Invalidates the text in the renderer of this counter, if any. + // identifier must match the identifier of this counter. + void resetRenderer(const AtomicString& identifier) const; + + // Invalidates the text in the renderer of this counter, if any, + // and in the renderers of all descendants of this counter, if any. + // identifier must match the identifier of this counter. + void resetRenderers(const AtomicString& identifier) const; + + bool m_hasResetType; + int m_value; + int m_countInParent; + RenderObject* m_renderer; + + CounterNode* m_parent; + CounterNode* m_previousSibling; + CounterNode* m_nextSibling; + CounterNode* m_firstChild; + CounterNode* m_lastChild; +}; + +} // namespace WebCore + +#ifndef NDEBUG +// Outside the WebCore namespace for ease of invocation from gdb. +void showTree(const WebCore::CounterNode*); +#endif + +#endif // CounterNode_h diff --git a/Source/WebCore/rendering/EllipsisBox.cpp b/Source/WebCore/rendering/EllipsisBox.cpp new file mode 100644 index 0000000..f9c4f03 --- /dev/null +++ b/Source/WebCore/rendering/EllipsisBox.cpp @@ -0,0 +1,126 @@ +/** + * Copyright (C) 2003, 2006 Apple Computer, Inc. + * + * 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 "EllipsisBox.h" + +#include "Document.h" +#include "GraphicsContext.h" +#include "HitTestResult.h" +#include "RootInlineBox.h" + +namespace WebCore { + +void EllipsisBox::paint(PaintInfo& paintInfo, int tx, int ty) +{ + GraphicsContext* context = paintInfo.context; + RenderStyle* style = m_renderer->style(m_firstLine); + Color textColor = style->visitedDependentColor(CSSPropertyColor); + if (textColor != context->fillColor()) + context->setFillColor(textColor, style->colorSpace()); + bool setShadow = false; + if (style->textShadow()) { + context->setShadow(IntSize(style->textShadow()->x(), style->textShadow()->y()), + style->textShadow()->blur(), style->textShadow()->color(), style->colorSpace()); + setShadow = true; + } + + if (selectionState() != RenderObject::SelectionNone) { + paintSelection(context, tx, ty, style, style->font()); + + // Select the correct color for painting the text. + Color foreground = paintInfo.forceBlackText ? Color::black : renderer()->selectionForegroundColor(); + if (foreground.isValid() && foreground != textColor) + context->setFillColor(foreground, style->colorSpace()); + } + + const String& str = m_str; + context->drawText(style->font(), TextRun(str.characters(), str.length(), false, 0, 0, false, style->visuallyOrdered()), IntPoint(m_x + tx, m_y + ty + style->font().ascent())); + + // Restore the regular fill color. + if (textColor != context->fillColor()) + context->setFillColor(textColor, style->colorSpace()); + + if (setShadow) + context->clearShadow(); + + if (m_markupBox) { + // Paint the markup box + tx += m_x + m_logicalWidth - m_markupBox->x(); + ty += m_y + style->font().ascent() - (m_markupBox->y() + m_markupBox->renderer()->style(m_firstLine)->font().ascent()); + m_markupBox->paint(paintInfo, tx, ty); + } +} + +IntRect EllipsisBox::selectionRect(int tx, int ty) +{ + RenderStyle* style = m_renderer->style(m_firstLine); + const Font& f = style->font(); + return enclosingIntRect(f.selectionRectForText(TextRun(m_str.characters(), m_str.length(), false, 0, 0, false, style->visuallyOrdered()), + IntPoint(m_x + tx, m_y + ty + root()->selectionTop()), root()->selectionHeight())); +} + +void EllipsisBox::paintSelection(GraphicsContext* context, int tx, int ty, RenderStyle* style, const Font& font) +{ + Color textColor = style->visitedDependentColor(CSSPropertyColor); + Color c = m_renderer->selectionBackgroundColor(); + if (!c.isValid() || !c.alpha()) + return; + + // If the text color ends up being the same as the selection background, invert the selection + // background. + if (textColor == c) + c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue()); + + context->save(); + int y = root()->selectionTop(); + int h = root()->selectionHeight(); + context->clip(IntRect(m_x + tx, y + ty, m_logicalWidth, h)); + context->drawHighlightForText(font, TextRun(m_str.characters(), m_str.length(), false, 0, 0, false, style->visuallyOrdered()), + IntPoint(m_x + tx, m_y + ty + y), h, c, style->colorSpace()); + context->restore(); +} + +bool EllipsisBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty) +{ + tx += m_x; + ty += m_y; + + // Hit test the markup box. + if (m_markupBox) { + RenderStyle* style = m_renderer->style(m_firstLine); + int mtx = tx + m_logicalWidth - m_markupBox->x(); + int mty = ty + style->font().ascent() - (m_markupBox->y() + m_markupBox->renderer()->style(m_firstLine)->font().ascent()); + if (m_markupBox->nodeAtPoint(request, result, x, y, mtx, mty)) { + renderer()->updateHitTestResult(result, IntPoint(x - mtx, y - mty)); + return true; + } + } + + IntRect boundsRect = IntRect(tx, ty, m_logicalWidth, m_height); + if (visibleToHitTesting() && boundsRect.intersects(result.rectForPoint(x, y))) { + renderer()->updateHitTestResult(result, IntPoint(x - tx, y - ty)); + if (!result.addNodeToRectBasedTestResult(renderer()->node(), x, y, boundsRect)) + return true; + } + + return false; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/EllipsisBox.h b/Source/WebCore/rendering/EllipsisBox.h new file mode 100644 index 0000000..ec1b00b --- /dev/null +++ b/Source/WebCore/rendering/EllipsisBox.h @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2003, 2006 Apple Computer, Inc. + * + * 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. + */ + +#ifndef EllipsisBox_h +#define EllipsisBox_h + +#include "InlineBox.h" + +namespace WebCore { + +class HitTestRequest; +class HitTestResult; + +class EllipsisBox : public InlineBox { +public: + EllipsisBox(RenderObject* obj, const AtomicString& ellipsisStr, InlineFlowBox* parent, + int width, int height, int y, bool firstLine, bool isVertical, InlineBox* markupBox) + : InlineBox(obj, 0, y, width, firstLine, true, false, false, isVertical, 0, 0, parent) + , m_height(height) + , m_str(ellipsisStr) + , m_markupBox(markupBox) + , m_selectionState(RenderObject::SelectionNone) + { + } + + virtual void paint(PaintInfo&, int tx, int ty); + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty); + void setSelectionState(RenderObject::SelectionState s) { m_selectionState = s; } + IntRect selectionRect(int tx, int ty); + +private: + virtual int height() const { return m_height; } + virtual RenderObject::SelectionState selectionState() { return m_selectionState; } + void paintSelection(GraphicsContext*, int tx, int ty, RenderStyle*, const Font&); + + int m_height; + AtomicString m_str; + InlineBox* m_markupBox; + RenderObject::SelectionState m_selectionState; +}; + +} // namespace WebCore + +#endif // EllipsisBox_h diff --git a/Source/WebCore/rendering/FixedTableLayout.cpp b/Source/WebCore/rendering/FixedTableLayout.cpp new file mode 100644 index 0000000..3285d15 --- /dev/null +++ b/Source/WebCore/rendering/FixedTableLayout.cpp @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2002 Lars Knoll (knoll@kde.org) + * (C) 2002 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. + * + * 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. + * + * 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 "FixedTableLayout.h" + +#include "RenderTable.h" +#include "RenderTableCell.h" +#include "RenderTableCol.h" +#include "RenderTableSection.h" + +/* + The text below is from the CSS 2.1 specs. + + Fixed table layout + + With this (fast) algorithm, the horizontal layout of the table does + not depend on the contents of the cells; it only depends on the + table's width, the width of the columns, and borders or cell + spacing. + + The table's width may be specified explicitly with the 'width' + property. A value of 'auto' (for both 'display: table' and 'display: + inline-table') means use the automatic table layout algorithm. + + In the fixed table layout algorithm, the width of each column is + determined as follows: + + 1. A column element with a value other than 'auto' for the 'width' + property sets the width for that column. + + 2. Otherwise, a cell in the first row with a value other than + 'auto' for the 'width' property sets the width for that column. If + the cell spans more than one column, the width is divided over the + columns. + + 3. Any remaining columns equally divide the remaining horizontal + table space (minus borders or cell spacing). + + The width of the table is then the greater of the value of the + 'width' property for the table element and the sum of the column + widths (plus cell spacing or borders). If the table is wider than + the columns, the extra space should be distributed over the columns. + + + In this manner, the user agent can begin to lay out the table once + the entire first row has been received. Cells in subsequent rows do + not affect column widths. Any cell that has content that overflows + uses the 'overflow' property to determine whether to clip the + overflow content. +*/ + +using namespace std; + +namespace WebCore { + +FixedTableLayout::FixedTableLayout(RenderTable* table) + : TableLayout(table) +{ +} + +int FixedTableLayout::calcWidthArray(int) +{ + int usedWidth = 0; + + // iterate over all <col> elements + RenderObject* child = m_table->firstChild(); + int nEffCols = m_table->numEffCols(); + m_width.resize(nEffCols); + m_width.fill(Length(Auto)); + + int currentEffectiveColumn = 0; + Length grpWidth; + while (child && child->isTableCol()) { + RenderTableCol* col = toRenderTableCol(child); + if (col->firstChild()) + grpWidth = col->style()->logicalWidth(); + else { + Length w = col->style()->logicalWidth(); + if (w.isAuto()) + w = grpWidth; + int effWidth = 0; + if (w.isFixed() && w.value() > 0) + effWidth = w.value(); + + int span = col->span(); + while (span) { + int spanInCurrentEffectiveColumn; + if (currentEffectiveColumn >= nEffCols) { + m_table->appendColumn(span); + nEffCols++; + m_width.append(Length()); + spanInCurrentEffectiveColumn = span; + } else { + if (span < m_table->spanOfEffCol(currentEffectiveColumn)) { + m_table->splitColumn(currentEffectiveColumn, span); + nEffCols++; + m_width.append(Length()); + } + spanInCurrentEffectiveColumn = m_table->spanOfEffCol(currentEffectiveColumn); + } + if ((w.isFixed() || w.isPercent()) && w.isPositive()) { + m_width[currentEffectiveColumn].setRawValue(w.type(), w.rawValue() * spanInCurrentEffectiveColumn); + usedWidth += effWidth * spanInCurrentEffectiveColumn; + } + span -= spanInCurrentEffectiveColumn; + currentEffectiveColumn++; + } + } + col->computePreferredLogicalWidths(); + + RenderObject* next = child->firstChild(); + if (!next) + next = child->nextSibling(); + if (!next && child->parent()->isTableCol()) { + next = child->parent()->nextSibling(); + grpWidth = Length(); + } + child = next; + } + + // Iterate over the first row in case some are unspecified. + RenderTableSection* section = m_table->header(); + if (!section) + section = m_table->firstBody(); + if (!section) + section = m_table->footer(); + if (section && !section->numRows()) + section = m_table->sectionBelow(section, true); + if (section) { + int cCol = 0; + RenderObject* firstRow = section->firstChild(); + child = firstRow->firstChild(); + while (child) { + if (child->isTableCell()) { + RenderTableCell* cell = toRenderTableCell(child); + if (cell->preferredLogicalWidthsDirty()) + cell->computePreferredLogicalWidths(); + + Length w = cell->styleOrColLogicalWidth(); + int span = cell->colSpan(); + int effWidth = 0; + if (w.isFixed() && w.isPositive()) + effWidth = w.value(); + + int usedSpan = 0; + int i = 0; + while (usedSpan < span && cCol + i < nEffCols) { + int eSpan = m_table->spanOfEffCol(cCol + i); + // Only set if no col element has already set it. + if (m_width[cCol + i].isAuto() && w.type() != Auto) { + m_width[cCol + i].setRawValue(w.type(), w.rawValue() * eSpan / span); + usedWidth += effWidth * eSpan / span; + } + usedSpan += eSpan; + i++; + } + cCol += i; + } + child = child->nextSibling(); + } + } + + return usedWidth; +} + +// Use a very large value (in effect infinite). But not too large! +// numeric_limits<int>::max() will too easily overflow widths. +// Keep this in synch with BLOCK_MAX_WIDTH in RenderBlock.cpp +#define TABLE_MAX_WIDTH 15000 + +void FixedTableLayout::computePreferredLogicalWidths(int& minWidth, int& maxWidth) +{ + // FIXME: This entire calculation is incorrect for both minwidth and maxwidth. + + // we might want to wait until we have all of the first row before + // layouting for the first time. + + // only need to calculate the minimum width as the sum of the + // cols/cells with a fixed width. + // + // The maximum width is max(minWidth, tableWidth). + int bordersPaddingAndSpacing = m_table->bordersPaddingAndSpacingInRowDirection(); + + int tableLogicalWidth = m_table->style()->logicalWidth().isFixed() ? m_table->style()->logicalWidth().value() - bordersPaddingAndSpacing : 0; + int mw = calcWidthArray(tableLogicalWidth) + bordersPaddingAndSpacing; + + minWidth = max(mw, tableLogicalWidth); + maxWidth = minWidth; + + // This quirk is very similar to one that exists in RenderBlock::calcBlockPrefWidths(). + // Here's the example for this one: + /* + <table style="width:100%; background-color:red"><tr><td> + <table style="background-color:blue"><tr><td> + <table style="width:100%; background-color:green; table-layout:fixed"><tr><td> + Content + </td></tr></table> + </td></tr></table> + </td></tr></table> + */ + // In this example, the two inner tables should be as large as the outer table. + // We can achieve this effect by making the maxwidth of fixed tables with percentage + // widths be infinite. + if (m_table->document()->inQuirksMode() && m_table->style()->logicalWidth().isPercent() && maxWidth < TABLE_MAX_WIDTH) + maxWidth = TABLE_MAX_WIDTH; +} + +void FixedTableLayout::layout() +{ + int tableLogicalWidth = m_table->logicalWidth() - m_table->bordersPaddingAndSpacingInRowDirection(); + int nEffCols = m_table->numEffCols(); + Vector<int> calcWidth(nEffCols, 0); + + int numAuto = 0; + int autoSpan = 0; + int totalFixedWidth = 0; + int totalPercentWidth = 0; + int totalRawPercent = 0; + + // Compute requirements and try to satisfy fixed and percent widths. + // Percentages are of the table's width, so for example + // for a table width of 100px with columns (40px, 10%), the 10% compute + // to 10px here, and will scale up to 20px in the final (80px, 20px). + for (int i = 0; i < nEffCols; i++) { + if (m_width[i].isFixed()) { + calcWidth[i] = m_width[i].value(); + totalFixedWidth += calcWidth[i]; + } else if (m_width[i].isPercent()) { + calcWidth[i] = m_width[i].calcValue(tableLogicalWidth); + totalPercentWidth += calcWidth[i]; + totalRawPercent += m_width[i].rawValue(); + } else if (m_width[i].isAuto()) { + numAuto++; + autoSpan += m_table->spanOfEffCol(i); + } + } + + int hspacing = m_table->hBorderSpacing(); + int totalWidth = totalFixedWidth + totalPercentWidth; + if (!numAuto || totalWidth > tableLogicalWidth) { + // If there are no auto columns, or if the total is too wide, take + // what we have and scale it to fit as necessary. + if (totalWidth != tableLogicalWidth) { + // Fixed widths only scale up + if (totalFixedWidth && totalWidth < tableLogicalWidth) { + totalFixedWidth = 0; + for (int i = 0; i < nEffCols; i++) { + if (m_width[i].isFixed()) { + calcWidth[i] = calcWidth[i] * tableLogicalWidth / totalWidth; + totalFixedWidth += calcWidth[i]; + } + } + } + if (totalRawPercent) { + totalPercentWidth = 0; + for (int i = 0; i < nEffCols; i++) { + if (m_width[i].isPercent()) { + calcWidth[i] = m_width[i].rawValue() * (tableLogicalWidth - totalFixedWidth) / totalRawPercent; + totalPercentWidth += calcWidth[i]; + } + } + } + totalWidth = totalFixedWidth + totalPercentWidth; + } + } else { + // Divide the remaining width among the auto columns. + int remainingWidth = tableLogicalWidth - totalFixedWidth - totalPercentWidth - hspacing * (autoSpan - numAuto); + int lastAuto = 0; + for (int i = 0; i < nEffCols; i++) { + if (m_width[i].isAuto()) { + int span = m_table->spanOfEffCol(i); + int w = remainingWidth * span / autoSpan; + calcWidth[i] = w + hspacing * (span - 1); + remainingWidth -= w; + if (!remainingWidth) + break; + lastAuto = i; + numAuto--; + autoSpan -= span; + } + } + // Last one gets the remainder. + if (remainingWidth) + calcWidth[lastAuto] += remainingWidth; + totalWidth = tableLogicalWidth; + } + + if (totalWidth < tableLogicalWidth) { + // Spread extra space over columns. + int remainingWidth = tableLogicalWidth - totalWidth; + int total = nEffCols; + while (total) { + int w = remainingWidth / total; + remainingWidth -= w; + calcWidth[--total] += w; + } + if (nEffCols > 0) + calcWidth[nEffCols - 1] += remainingWidth; + } + + int pos = 0; + for (int i = 0; i < nEffCols; i++) { + m_table->columnPositions()[i] = pos; + pos += calcWidth[i] + hspacing; + } + int colPositionsSize = m_table->columnPositions().size(); + if (colPositionsSize > 0) + m_table->columnPositions()[colPositionsSize - 1] = pos; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/FixedTableLayout.h b/Source/WebCore/rendering/FixedTableLayout.h new file mode 100644 index 0000000..6c135c0 --- /dev/null +++ b/Source/WebCore/rendering/FixedTableLayout.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2002 Lars Knoll (knoll@kde.org) + * (C) 2002 Dirk Mueller (mueller@kde.org) + * + * 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. + * + * 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. + */ + +#ifndef FixedTableLayout_h +#define FixedTableLayout_h + +#include "Length.h" +#include "TableLayout.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class RenderTable; + +class FixedTableLayout : public TableLayout { +public: + FixedTableLayout(RenderTable*); + + virtual void computePreferredLogicalWidths(int& minWidth, int& maxWidth); + virtual void layout(); + +private: + int calcWidthArray(int tableWidth); + + Vector<Length> m_width; +}; + +} // namespace WebCore + +#endif // FixedTableLayout_h diff --git a/Source/WebCore/rendering/GapRects.h b/Source/WebCore/rendering/GapRects.h new file mode 100644 index 0000000..a762ae5 --- /dev/null +++ b/Source/WebCore/rendering/GapRects.h @@ -0,0 +1,62 @@ +/* + Copyright (C) 2005, 2006 Apple Inc. All rights reserved. + + 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. + + + Some useful definitions needed for laying out elements +*/ + +#ifndef GapRects_h +#define GapRects_h + +#include "IntRect.h" + +namespace WebCore { + + struct GapRects { + const IntRect& left() const { return m_left; } + const IntRect& center() const { return m_center; } + const IntRect& right() const { return m_right; } + + void uniteLeft(const IntRect& r) { m_left.unite(r); } + void uniteCenter(const IntRect& r) { m_center.unite(r); } + void uniteRight(const IntRect& r) { m_right.unite(r); } + void unite(const GapRects& o) { uniteLeft(o.left()); uniteCenter(o.center()); uniteRight(o.right()); } + + operator IntRect() const + { + IntRect result = m_left; + result.unite(m_center); + result.unite(m_right); + return result; + } + + bool operator==(const GapRects& other) + { + return m_left == other.left() && m_center == other.center() && m_right == other.right(); + } + bool operator!=(const GapRects& other) { return !(*this == other); } + + private: + IntRect m_left; + IntRect m_center; + IntRect m_right; + }; + +} // namespace WebCore + +#endif // GapRects_h diff --git a/Source/WebCore/rendering/HitTestRequest.h b/Source/WebCore/rendering/HitTestRequest.h new file mode 100644 index 0000000..745791a --- /dev/null +++ b/Source/WebCore/rendering/HitTestRequest.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. + * Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.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. + * +*/ + +#ifndef HitTestRequest_h +#define HitTestRequest_h + +namespace WebCore { + +class HitTestRequest { +public: + enum RequestType { + ReadOnly = 1 << 1, + Active = 1 << 2, + MouseMove = 1 << 3, + MouseUp = 1 << 4, + IgnoreClipping = 1 << 5, + SVGClipContent = 1 << 6 + }; + + typedef unsigned HitTestRequestType; + + HitTestRequest(HitTestRequestType requestType) + : m_requestType(requestType) + { + } + + bool readOnly() const { return m_requestType & ReadOnly; } + bool active() const { return m_requestType & Active; } + bool mouseMove() const { return m_requestType & MouseMove; } + bool mouseUp() const { return m_requestType & MouseUp; } + bool ignoreClipping() const { return m_requestType & IgnoreClipping; } + bool svgClipContent() const { return m_requestType & SVGClipContent; } + +private: + HitTestRequestType m_requestType; +}; + +} // namespace WebCore + +#endif // HitTestRequest_h diff --git a/Source/WebCore/rendering/HitTestResult.cpp b/Source/WebCore/rendering/HitTestResult.cpp new file mode 100644 index 0000000..5d9b3df --- /dev/null +++ b/Source/WebCore/rendering/HitTestResult.cpp @@ -0,0 +1,579 @@ +/* + * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. + * + * 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 "HitTestResult.h" + +#include "DocumentMarkerController.h" +#include "Frame.h" +#include "FrameTree.h" +#include "HTMLAnchorElement.h" +#include "HTMLVideoElement.h" +#include "HTMLImageElement.h" +#include "HTMLInputElement.h" +#include "HTMLMediaElement.h" +#include "HTMLNames.h" +#include "HTMLParserIdioms.h" +#include "RenderImage.h" +#include "Scrollbar.h" +#include "SelectionController.h" + +#if ENABLE(SVG) +#include "SVGNames.h" +#include "XLinkNames.h" +#endif + +#if ENABLE(WML) +#include "WMLImageElement.h" +#include "WMLNames.h" +#endif + +namespace WebCore { + +using namespace HTMLNames; + +HitTestResult::HitTestResult() + : m_isOverWidget(false) + , m_isRectBased(false) + , m_topPadding(0) + , m_rightPadding(0) + , m_bottomPadding(0) + , m_leftPadding(0) +{ +} + +HitTestResult::HitTestResult(const IntPoint& point) + : m_point(point) + , m_isOverWidget(false) + , m_isRectBased(false) + , m_topPadding(0) + , m_rightPadding(0) + , m_bottomPadding(0) + , m_leftPadding(0) +{ +} + +HitTestResult::HitTestResult(const IntPoint& centerPoint, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding) + : m_point(centerPoint) + , m_isOverWidget(false) + , m_topPadding(topPadding) + , m_rightPadding(rightPadding) + , m_bottomPadding(bottomPadding) + , m_leftPadding(leftPadding) +{ + // If all padding values passed in are zero then it is not a rect based hit test. + m_isRectBased = topPadding || rightPadding || bottomPadding || leftPadding; + + // Make sure all padding values are clamped to zero if it is not a rect hit test. + if (!m_isRectBased) + m_topPadding = m_rightPadding = m_bottomPadding = m_leftPadding = 0; +} + +HitTestResult::HitTestResult(const HitTestResult& other) + : m_innerNode(other.innerNode()) + , m_innerNonSharedNode(other.innerNonSharedNode()) + , m_point(other.point()) + , m_localPoint(other.localPoint()) + , m_innerURLElement(other.URLElement()) + , m_scrollbar(other.scrollbar()) + , m_isOverWidget(other.isOverWidget()) +{ + // Only copy the padding and ListHashSet in case of rect hit test. + // Copying the later is rather expensive. + if ((m_isRectBased = other.isRectBasedTest())) { + m_topPadding = other.m_topPadding; + m_rightPadding = other.m_rightPadding; + m_bottomPadding = other.m_bottomPadding; + m_leftPadding = other.m_leftPadding; + m_rectBasedTestResult = other.rectBasedTestResult(); + } else + m_topPadding = m_rightPadding = m_bottomPadding = m_leftPadding = 0; +} + +HitTestResult::~HitTestResult() +{ +} + +HitTestResult& HitTestResult::operator=(const HitTestResult& other) +{ + m_innerNode = other.innerNode(); + m_innerNonSharedNode = other.innerNonSharedNode(); + m_point = other.point(); + m_localPoint = other.localPoint(); + m_innerURLElement = other.URLElement(); + m_scrollbar = other.scrollbar(); + m_isOverWidget = other.isOverWidget(); + // Only copy the padding and ListHashSet in case of rect hit test. + // Copying the later is rather expensive. + if ((m_isRectBased = other.isRectBasedTest())) { + m_topPadding = other.m_topPadding; + m_rightPadding = other.m_rightPadding; + m_bottomPadding = other.m_bottomPadding; + m_leftPadding = other.m_leftPadding; + m_rectBasedTestResult = other.rectBasedTestResult(); + } else + m_topPadding = m_rightPadding = m_bottomPadding = m_leftPadding = 0; + return *this; +} + +void HitTestResult::setToNonShadowAncestor() +{ + Node* node = innerNode(); + if (node) + node = node->shadowAncestorNode(); + setInnerNode(node); + node = innerNonSharedNode(); + if (node) + node = node->shadowAncestorNode(); + setInnerNonSharedNode(node); +} + +void HitTestResult::setInnerNode(Node* n) +{ + m_innerNode = n; +} + +void HitTestResult::setInnerNonSharedNode(Node* n) +{ + m_innerNonSharedNode = n; +} + +void HitTestResult::setURLElement(Element* n) +{ + m_innerURLElement = n; +} + +void HitTestResult::setScrollbar(Scrollbar* s) +{ + m_scrollbar = s; +} + +Frame* HitTestResult::targetFrame() const +{ + if (!m_innerURLElement) + return 0; + + Frame* frame = m_innerURLElement->document()->frame(); + if (!frame) + return 0; + + return frame->tree()->find(m_innerURLElement->target()); +} + +bool HitTestResult::isSelected() const +{ + if (!m_innerNonSharedNode) + return false; + + Frame* frame = m_innerNonSharedNode->document()->frame(); + if (!frame) + return false; + + return frame->selection()->contains(m_point); +} + +String HitTestResult::spellingToolTip(TextDirection& dir) const +{ + dir = LTR; + // Return the tool tip string associated with this point, if any. Only markers associated with bad grammar + // currently supply strings, but maybe someday markers associated with misspelled words will also. + if (!m_innerNonSharedNode) + return String(); + + DocumentMarker* marker = m_innerNonSharedNode->document()->markers()->markerContainingPoint(m_point, DocumentMarker::Grammar); + if (!marker) + return String(); + + if (RenderObject* renderer = m_innerNonSharedNode->renderer()) + dir = renderer->style()->direction(); + return marker->description; +} + +String HitTestResult::replacedString() const +{ + // Return the replaced string associated with this point, if any. This marker is created when a string is autocorrected, + // and is used for generating a contextual menu item that allows it to easily be changed back if desired. + if (!m_innerNonSharedNode) + return String(); + + DocumentMarker* marker = m_innerNonSharedNode->document()->markers()->markerContainingPoint(m_point, DocumentMarker::Replacement); + if (!marker) + return String(); + + return marker->description; +} + +String HitTestResult::title(TextDirection& dir) const +{ + dir = LTR; + // Find the title in the nearest enclosing DOM node. + // For <area> tags in image maps, walk the tree for the <area>, not the <img> using it. + for (Node* titleNode = m_innerNode.get(); titleNode; titleNode = titleNode->parentNode()) { + if (titleNode->isElementNode()) { + String title = static_cast<Element*>(titleNode)->title(); + if (!title.isEmpty()) { + if (RenderObject* renderer = titleNode->renderer()) + dir = renderer->style()->direction(); + return title; + } + } + } + return String(); +} + +String displayString(const String& string, const Node* node) +{ + if (!node) + return string; + return node->document()->displayStringModifiedByEncoding(string); +} + +String HitTestResult::altDisplayString() const +{ + if (!m_innerNonSharedNode) + return String(); + + if (m_innerNonSharedNode->hasTagName(imgTag)) { + HTMLImageElement* image = static_cast<HTMLImageElement*>(m_innerNonSharedNode.get()); + return displayString(image->getAttribute(altAttr), m_innerNonSharedNode.get()); + } + + if (m_innerNonSharedNode->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(m_innerNonSharedNode.get()); + return displayString(input->alt(), m_innerNonSharedNode.get()); + } + +#if ENABLE(WML) + if (m_innerNonSharedNode->hasTagName(WMLNames::imgTag)) { + WMLImageElement* image = static_cast<WMLImageElement*>(m_innerNonSharedNode.get()); + return displayString(image->altText(), m_innerNonSharedNode.get()); + } +#endif + + return String(); +} + +Image* HitTestResult::image() const +{ + if (!m_innerNonSharedNode) + return 0; + + RenderObject* renderer = m_innerNonSharedNode->renderer(); + if (renderer && renderer->isImage()) { + RenderImage* image = static_cast<WebCore::RenderImage*>(renderer); + if (image->cachedImage() && !image->cachedImage()->errorOccurred()) + return image->cachedImage()->image(); + } + + return 0; +} + +IntRect HitTestResult::imageRect() const +{ + if (!image()) + return IntRect(); + return m_innerNonSharedNode->renderBox()->absoluteContentQuad().enclosingBoundingBox(); +} + +KURL HitTestResult::absoluteImageURL() const +{ + if (!(m_innerNonSharedNode && m_innerNonSharedNode->document())) + return KURL(); + + if (!(m_innerNonSharedNode->renderer() && m_innerNonSharedNode->renderer()->isImage())) + return KURL(); + + AtomicString urlString; + if (m_innerNonSharedNode->hasTagName(embedTag) + || m_innerNonSharedNode->hasTagName(imgTag) + || m_innerNonSharedNode->hasTagName(inputTag) + || m_innerNonSharedNode->hasTagName(objectTag) +#if ENABLE(SVG) + || m_innerNonSharedNode->hasTagName(SVGNames::imageTag) +#endif +#if ENABLE(WML) + || m_innerNonSharedNode->hasTagName(WMLNames::imgTag) +#endif + ) { + Element* element = static_cast<Element*>(m_innerNonSharedNode.get()); + urlString = element->getAttribute(element->imageSourceAttributeName()); + } else + return KURL(); + + return m_innerNonSharedNode->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(urlString)); +} + +KURL HitTestResult::absoluteMediaURL() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + return m_innerNonSharedNode->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(mediaElt->currentSrc())); + return KURL(); +#else + return KURL(); +#endif +} + +bool HitTestResult::mediaSupportsFullscreen() const +{ +#if ENABLE(VIDEO) + HTMLMediaElement* mediaElt(mediaElement()); + return (mediaElt && mediaElt->hasTagName(HTMLNames::videoTag) && mediaElt->supportsFullscreen()); +#else + return false; +#endif +} + +#if ENABLE(VIDEO) +HTMLMediaElement* HitTestResult::mediaElement() const +{ + if (!(m_innerNonSharedNode && m_innerNonSharedNode->document())) + return 0; + + if (!(m_innerNonSharedNode->renderer() && m_innerNonSharedNode->renderer()->isMedia())) + return 0; + + if (m_innerNonSharedNode->hasTagName(HTMLNames::videoTag) || m_innerNonSharedNode->hasTagName(HTMLNames::audioTag)) + return static_cast<HTMLMediaElement*>(m_innerNonSharedNode.get()); + return 0; +} +#endif + +void HitTestResult::toggleMediaControlsDisplay() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + mediaElt->setControls(!mediaElt->controls()); +#endif +} + +void HitTestResult::toggleMediaLoopPlayback() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + mediaElt->setLoop(!mediaElt->loop()); +#endif +} + +void HitTestResult::enterFullscreenForVideo() const +{ +#if ENABLE(VIDEO) + HTMLMediaElement* mediaElt(mediaElement()); + if (mediaElt && mediaElt->hasTagName(HTMLNames::videoTag)) { + HTMLVideoElement* videoElt = static_cast<HTMLVideoElement*>(mediaElt); + if (!videoElt->isFullscreen() && mediaElt->supportsFullscreen()) + videoElt->enterFullscreen(); + } +#endif +} + +bool HitTestResult::mediaControlsEnabled() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + return mediaElt->controls(); +#endif + return false; +} + +bool HitTestResult::mediaLoopEnabled() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + return mediaElt->loop(); +#endif + return false; +} + +bool HitTestResult::mediaPlaying() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + return !mediaElt->paused(); +#endif + return false; +} + +void HitTestResult::toggleMediaPlayState() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + mediaElt->togglePlayState(); +#endif +} + +bool HitTestResult::mediaHasAudio() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + return mediaElt->hasAudio(); +#endif + return false; +} + +bool HitTestResult::mediaIsVideo() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + return mediaElt->hasTagName(HTMLNames::videoTag); +#endif + return false; +} + +bool HitTestResult::mediaMuted() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + return mediaElt->muted(); +#endif + return false; +} + +void HitTestResult::toggleMediaMuteState() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + mediaElt->setMuted(!mediaElt->muted()); +#endif +} + +KURL HitTestResult::absoluteLinkURL() const +{ + if (!(m_innerURLElement && m_innerURLElement->document())) + return KURL(); + + AtomicString urlString; + if (m_innerURLElement->hasTagName(aTag) || m_innerURLElement->hasTagName(areaTag) || m_innerURLElement->hasTagName(linkTag)) + urlString = m_innerURLElement->getAttribute(hrefAttr); +#if ENABLE(SVG) + else if (m_innerURLElement->hasTagName(SVGNames::aTag)) + urlString = m_innerURLElement->getAttribute(XLinkNames::hrefAttr); +#endif +#if ENABLE(WML) + else if (m_innerURLElement->hasTagName(WMLNames::aTag)) + urlString = m_innerURLElement->getAttribute(hrefAttr); +#endif + else + return KURL(); + + return m_innerURLElement->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(urlString)); +} + +bool HitTestResult::isLiveLink() const +{ + if (!(m_innerURLElement && m_innerURLElement->document())) + return false; + + if (m_innerURLElement->hasTagName(aTag)) + return static_cast<HTMLAnchorElement*>(m_innerURLElement.get())->isLiveLink(); +#if ENABLE(SVG) + if (m_innerURLElement->hasTagName(SVGNames::aTag)) + return m_innerURLElement->isLink(); +#endif +#if ENABLE(WML) + if (m_innerURLElement->hasTagName(WMLNames::aTag)) + return m_innerURLElement->isLink(); +#endif + + return false; +} + +String HitTestResult::titleDisplayString() const +{ + if (!m_innerURLElement) + return String(); + + return displayString(m_innerURLElement->title(), m_innerURLElement.get()); +} + +String HitTestResult::textContent() const +{ + if (!m_innerURLElement) + return String(); + return m_innerURLElement->textContent(); +} + +// FIXME: This function needs a better name and may belong in a different class. It's not +// really isContentEditable(); it's more like needsEditingContextMenu(). In many ways, this +// function would make more sense in the ContextMenu class, except that WebElementDictionary +// hooks into it. Anyway, we should architect this better. +bool HitTestResult::isContentEditable() const +{ + if (!m_innerNonSharedNode) + return false; + + if (m_innerNonSharedNode->hasTagName(textareaTag) || m_innerNonSharedNode->hasTagName(isindexTag)) + return true; + + if (m_innerNonSharedNode->hasTagName(inputTag)) + return static_cast<HTMLInputElement*>(m_innerNonSharedNode.get())->isTextField(); + + return m_innerNonSharedNode->isContentEditable(); +} + +bool HitTestResult::addNodeToRectBasedTestResult(Node* node, int x, int y, const IntRect& rect) +{ + // If it is not a rect-based hit test, this method has to be no-op. + // Return false, so the hit test stops. + if (!isRectBasedTest()) + return false; + + // If node is null, return true so the hit test can continue. + if (!node) + return true; + + node = node->shadowAncestorNode(); + m_rectBasedTestResult.add(node); + + return !rect.contains(rectForPoint(x, y)); +} + +void HitTestResult::append(const HitTestResult& other) +{ + ASSERT(isRectBasedTest() && other.isRectBasedTest()); + + if (!m_innerNode && other.innerNode()) { + m_innerNode = other.innerNode(); + m_innerNonSharedNode = other.innerNonSharedNode(); + m_localPoint = other.localPoint(); + m_innerURLElement = other.URLElement(); + m_scrollbar = other.scrollbar(); + m_isOverWidget = other.isOverWidget(); + } + + const ListHashSet<RefPtr<Node> >& list = other.rectBasedTestResult(); + ListHashSet<RefPtr<Node> >::const_iterator last = list.end(); + for (ListHashSet<RefPtr<Node> >::const_iterator it = list.begin(); it != last; ++it) + m_rectBasedTestResult.add(it->get()); +} + +IntRect HitTestResult::rectForPoint(const IntPoint& point, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding) +{ + IntPoint actualPoint(point); + actualPoint -= IntSize(leftPadding, topPadding); + + IntSize actualPadding(leftPadding + rightPadding, topPadding + bottomPadding); + // As IntRect is left inclusive and right exclusive (seeing IntRect::contains(x, y)), adding "1". + actualPadding += IntSize(1, 1); + + return IntRect(actualPoint, actualPadding); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/HitTestResult.h b/Source/WebCore/rendering/HitTestResult.h new file mode 100644 index 0000000..1545da9 --- /dev/null +++ b/Source/WebCore/rendering/HitTestResult.h @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. + * + * 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. + * +*/ +#ifndef HitTestResult_h +#define HitTestResult_h + +#include "IntPoint.h" +#include "IntRect.h" +#include "IntSize.h" +#include "TextDirection.h" +#include <wtf/Forward.h> +#include <wtf/ListHashSet.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +class Element; +class Frame; +#if ENABLE(VIDEO) +class HTMLMediaElement; +#endif +class Image; +class IntRect; +class KURL; +class Node; +class Scrollbar; + +class HitTestResult { +public: + HitTestResult(); + HitTestResult(const IntPoint&); + // Pass non-negative padding values to perform a rect-based hit test. + HitTestResult(const IntPoint& centerPoint, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding); + HitTestResult(const HitTestResult&); + ~HitTestResult(); + HitTestResult& operator=(const HitTestResult&); + + Node* innerNode() const { return m_innerNode.get(); } + Node* innerNonSharedNode() const { return m_innerNonSharedNode.get(); } + IntPoint point() const { return m_point; } + IntPoint localPoint() const { return m_localPoint; } + Element* URLElement() const { return m_innerURLElement.get(); } + Scrollbar* scrollbar() const { return m_scrollbar.get(); } + bool isOverWidget() const { return m_isOverWidget; } + + void setToNonShadowAncestor(); + + void setInnerNode(Node*); + void setInnerNonSharedNode(Node*); + void setPoint(const IntPoint& p) { m_point = p; } + void setLocalPoint(const IntPoint& p) { m_localPoint = p; } + void setURLElement(Element*); + void setScrollbar(Scrollbar*); + void setIsOverWidget(bool b) { m_isOverWidget = b; } + + Frame* targetFrame() const; + bool isSelected() const; + String spellingToolTip(TextDirection&) const; + String replacedString() const; + String title(TextDirection&) const; + String altDisplayString() const; + String titleDisplayString() const; + Image* image() const; + IntRect imageRect() const; + KURL absoluteImageURL() const; + KURL absoluteMediaURL() const; + KURL absoluteLinkURL() const; + String textContent() const; + bool isLiveLink() const; + bool isContentEditable() const; + void toggleMediaControlsDisplay() const; + void toggleMediaLoopPlayback() const; + void enterFullscreenForVideo() const; + bool mediaControlsEnabled() const; + bool mediaLoopEnabled() const; + bool mediaPlaying() const; + bool mediaSupportsFullscreen() const; + void toggleMediaPlayState() const; + bool mediaHasAudio() const; + bool mediaIsVideo() const; + bool mediaMuted() const; + void toggleMediaMuteState() const; + + // Rect-based hit test related methods. + bool isRectBasedTest() const { return m_isRectBased; } + IntRect rectForPoint(int x, int y) const; + IntRect rectForPoint(const IntPoint&) const; + static IntRect rectForPoint(const IntPoint&, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding); + int topPadding() const { return m_topPadding; } + int rightPadding() const { return m_rightPadding; } + int bottomPadding() const { return m_bottomPadding; } + int leftPadding() const { return m_leftPadding; } + + // Returns true if it is rect-based hit test and needs to continue until the rect is fully + // enclosed by the boundaries of a node. + bool addNodeToRectBasedTestResult(Node*, int x, int y, const IntRect& rect = IntRect()); + const ListHashSet<RefPtr<Node> >& rectBasedTestResult() const { return m_rectBasedTestResult; } + void append(const HitTestResult&); + +private: + +#if ENABLE(VIDEO) + HTMLMediaElement* mediaElement() const; +#endif + + RefPtr<Node> m_innerNode; + RefPtr<Node> m_innerNonSharedNode; + IntPoint m_point; + IntPoint m_localPoint; // A point in the local coordinate space of m_innerNonSharedNode's renderer. Allows us to efficiently + // determine where inside the renderer we hit on subsequent operations. + RefPtr<Element> m_innerURLElement; + RefPtr<Scrollbar> m_scrollbar; + bool m_isOverWidget; // Returns true if we are over a widget (and not in the border/padding area of a RenderWidget for example). + bool m_isRectBased; + int m_topPadding; + int m_rightPadding; + int m_bottomPadding; + int m_leftPadding; + ListHashSet<RefPtr<Node> > m_rectBasedTestResult; +}; + +inline IntRect HitTestResult::rectForPoint(int x, int y) const +{ + return rectForPoint(IntPoint(x, y), m_topPadding, m_rightPadding, m_bottomPadding, m_leftPadding); +} + +// Formula: +// x = p.x() - rightPadding +// y = p.y() - topPadding +// width = leftPadding + rightPadding + 1 +// height = topPadding + bottomPadding + 1 +inline IntRect HitTestResult::rectForPoint(const IntPoint& point) const +{ + return rectForPoint(point, m_topPadding, m_rightPadding, m_bottomPadding, m_leftPadding); +} + +String displayString(const String&, const Node*); + +} // namespace WebCore + +#endif // HitTestResult_h diff --git a/Source/WebCore/rendering/InlineBox.cpp b/Source/WebCore/rendering/InlineBox.cpp new file mode 100644 index 0000000..145096b --- /dev/null +++ b/Source/WebCore/rendering/InlineBox.cpp @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2003, 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * + * 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 "InlineBox.h" + +#include "HitTestResult.h" +#include "InlineFlowBox.h" +#include "RenderArena.h" +#include "RenderBlock.h" +#include "RootInlineBox.h" + +using namespace std; + +namespace WebCore { + +#ifndef NDEBUG +static bool inInlineBoxDetach; +#endif + +#ifndef NDEBUG + +InlineBox::~InlineBox() +{ + if (!m_hasBadParent && m_parent) + m_parent->setHasBadChildList(); +} + +#endif + +void InlineBox::remove() +{ + if (parent()) + parent()->removeChild(this); +} + +void InlineBox::destroy(RenderArena* renderArena) +{ +#ifndef NDEBUG + inInlineBoxDetach = true; +#endif + delete this; +#ifndef NDEBUG + inInlineBoxDetach = false; +#endif + + // Recover the size left there for us by operator delete and free the memory. + renderArena->free(*(size_t *)this, this); +} + +void* InlineBox::operator new(size_t sz, RenderArena* renderArena) throw() +{ + return renderArena->allocate(sz); +} + +void InlineBox::operator delete(void* ptr, size_t sz) +{ + ASSERT(inInlineBoxDetach); + + // Stash size where destroy can find it. + *(size_t *)ptr = sz; +} + +#ifndef NDEBUG +void InlineBox::showTreeForThis() const +{ + if (m_renderer) + m_renderer->showTreeForThis(); +} +#endif + +int InlineBox::logicalHeight() const +{ +#if ENABLE(SVG) + if (hasVirtualLogicalHeight()) + return virtualLogicalHeight(); +#endif + + if (renderer()->isText()) + return m_isText ? renderer()->style(m_firstLine)->font().height() : 0; + if (renderer()->isBox() && parent()) + return isHorizontal() ? toRenderBox(m_renderer)->height() : toRenderBox(m_renderer)->width(); + + ASSERT(isInlineFlowBox()); + RenderBoxModelObject* flowObject = boxModelObject(); + const Font& font = renderer()->style(m_firstLine)->font(); + int result = font.height(); + if (parent()) + result += flowObject->borderAndPaddingLogicalHeight(); + return result; +} + +int InlineBox::caretMinOffset() const +{ + return m_renderer->caretMinOffset(); +} + +int InlineBox::caretMaxOffset() const +{ + return m_renderer->caretMaxOffset(); +} + +unsigned InlineBox::caretMaxRenderedOffset() const +{ + return 1; +} + +void InlineBox::dirtyLineBoxes() +{ + markDirty(); + for (InlineFlowBox* curr = parent(); curr && !curr->isDirty(); curr = curr->parent()) + curr->markDirty(); +} + +void InlineBox::deleteLine(RenderArena* arena) +{ + if (!m_extracted && m_renderer->isBox()) + toRenderBox(m_renderer)->setInlineBoxWrapper(0); + destroy(arena); +} + +void InlineBox::extractLine() +{ + m_extracted = true; + if (m_renderer->isBox()) + toRenderBox(m_renderer)->setInlineBoxWrapper(0); +} + +void InlineBox::attachLine() +{ + m_extracted = false; + if (m_renderer->isBox()) + toRenderBox(m_renderer)->setInlineBoxWrapper(this); +} + +void InlineBox::adjustPosition(int dx, int dy) +{ + m_x += dx; + m_y += dy; + if (m_renderer->isReplaced()) { + RenderBox* box = toRenderBox(m_renderer); + box->move(dx, dy); + } +} + +void InlineBox::paint(PaintInfo& paintInfo, int tx, int ty) +{ + if (!paintInfo.shouldPaintWithinRoot(renderer()) || (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection)) + return; + + IntPoint childPoint = IntPoint(tx, ty); + if (parent()->renderer()->style()->isFlippedBlocksWritingMode()) // Faster than calling containingBlock(). + childPoint = renderer()->containingBlock()->flipForWritingMode(toRenderBox(renderer()), childPoint, RenderBox::ParentToChildFlippingAdjustment); + + // Paint all phases of replaced elements atomically, as though the replaced element established its + // own stacking context. (See Appendix E.2, section 6.4 on inline block/table elements in the CSS2.1 + // specification.) + bool preservePhase = paintInfo.phase == PaintPhaseSelection || paintInfo.phase == PaintPhaseTextClip; + PaintInfo info(paintInfo); + info.phase = preservePhase ? paintInfo.phase : PaintPhaseBlockBackground; + renderer()->paint(info, childPoint.x(), childPoint.y()); + if (!preservePhase) { + info.phase = PaintPhaseChildBlockBackgrounds; + renderer()->paint(info, childPoint.x(), childPoint.y()); + info.phase = PaintPhaseFloat; + renderer()->paint(info, childPoint.x(), childPoint.y()); + info.phase = PaintPhaseForeground; + renderer()->paint(info, childPoint.x(), childPoint.y()); + info.phase = PaintPhaseOutline; + renderer()->paint(info, childPoint.x(), childPoint.y()); + } +} + +bool InlineBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty) +{ + // Hit test all phases of replaced elements atomically, as though the replaced element established its + // own stacking context. (See Appendix E.2, section 6.4 on inline block/table elements in the CSS2.1 + // specification.) + return renderer()->hitTest(request, result, IntPoint(x, y), tx, ty); +} + +const RootInlineBox* InlineBox::root() const +{ + if (m_parent) + return m_parent->root(); + ASSERT(isRootInlineBox()); + return static_cast<const RootInlineBox*>(this); +} + +RootInlineBox* InlineBox::root() +{ + if (m_parent) + return m_parent->root(); + ASSERT(isRootInlineBox()); + return static_cast<RootInlineBox*>(this); +} + +bool InlineBox::nextOnLineExists() const +{ + if (!m_determinedIfNextOnLineExists) { + m_determinedIfNextOnLineExists = true; + + if (!parent()) + m_nextOnLineExists = false; + else if (nextOnLine()) + m_nextOnLineExists = true; + else + m_nextOnLineExists = parent()->nextOnLineExists(); + } + return m_nextOnLineExists; +} + +bool InlineBox::prevOnLineExists() const +{ + if (!m_determinedIfPrevOnLineExists) { + m_determinedIfPrevOnLineExists = true; + + if (!parent()) + m_prevOnLineExists = false; + else if (prevOnLine()) + m_prevOnLineExists = true; + else + m_prevOnLineExists = parent()->prevOnLineExists(); + } + return m_prevOnLineExists; +} + +InlineBox* InlineBox::nextLeafChild() const +{ + InlineBox* leaf = 0; + for (InlineBox* box = nextOnLine(); box && !leaf; box = box->nextOnLine()) + leaf = box->isLeaf() ? box : static_cast<InlineFlowBox*>(box)->firstLeafChild(); + if (!leaf && parent()) + leaf = parent()->nextLeafChild(); + return leaf; +} + +InlineBox* InlineBox::prevLeafChild() const +{ + InlineBox* leaf = 0; + for (InlineBox* box = prevOnLine(); box && !leaf; box = box->prevOnLine()) + leaf = box->isLeaf() ? box : static_cast<InlineFlowBox*>(box)->lastLeafChild(); + if (!leaf && parent()) + leaf = parent()->prevLeafChild(); + return leaf; +} + +RenderObject::SelectionState InlineBox::selectionState() +{ + return renderer()->selectionState(); +} + +bool InlineBox::canAccommodateEllipsis(bool ltr, int blockEdge, int ellipsisWidth) +{ + // Non-replaced elements can always accommodate an ellipsis. + if (!m_renderer || !m_renderer->isReplaced()) + return true; + + IntRect boxRect(m_x, 0, m_logicalWidth, 10); + IntRect ellipsisRect(ltr ? blockEdge - ellipsisWidth : blockEdge, 0, ellipsisWidth, 10); + return !(boxRect.intersects(ellipsisRect)); +} + +int InlineBox::placeEllipsisBox(bool, int, int, int, bool&) +{ + // Use -1 to mean "we didn't set the position." + return -1; +} + +IntPoint InlineBox::locationIncludingFlipping() +{ + if (!renderer()->style()->isFlippedBlocksWritingMode()) + return IntPoint(x(), y()); + RenderBlock* block = root()->block(); + if (block->style()->isHorizontalWritingMode()) + return IntPoint(x(), block->height() - height() - y()); + else + return IntPoint(block->width() - width() - x(), y()); +} + +void InlineBox::flipForWritingMode(IntRect& rect) +{ + if (!renderer()->style()->isFlippedBlocksWritingMode()) + return; + root()->block()->flipForWritingMode(rect); +} + +IntPoint InlineBox::flipForWritingMode(const IntPoint& point) +{ + if (!renderer()->style()->isFlippedBlocksWritingMode()) + return point; + return root()->block()->flipForWritingMode(point); +} + +} // namespace WebCore + +#ifndef NDEBUG + +void showTree(const WebCore::InlineBox* b) +{ + if (b) + b->showTreeForThis(); +} + +#endif diff --git a/Source/WebCore/rendering/InlineBox.h b/Source/WebCore/rendering/InlineBox.h new file mode 100644 index 0000000..5b3f682 --- /dev/null +++ b/Source/WebCore/rendering/InlineBox.h @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2003, 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef InlineBox_h +#define InlineBox_h + +#include "RenderBR.h" +#include "RenderBoxModelObject.h" +#include "TextDirection.h" + +namespace WebCore { + +class HitTestRequest; +class HitTestResult; +class RootInlineBox; + +// InlineBox represents a rectangle that occurs on a line. It corresponds to +// some RenderObject (i.e., it represents a portion of that RenderObject). +class InlineBox { +public: + InlineBox(RenderObject* obj) + : m_next(0) + , m_prev(0) + , m_parent(0) + , m_renderer(obj) + , m_x(0) + , m_y(0) + , m_logicalWidth(0) + , m_firstLine(false) + , m_constructed(false) + , m_bidiEmbeddingLevel(0) + , m_dirty(false) + , m_extracted(false) +#if ENABLE(SVG) + , m_hasVirtualLogicalHeight(false) +#endif + , m_isHorizontal(true) + , m_endsWithBreak(false) + , m_hasSelectedChildren(false) + , m_hasEllipsisBoxOrHyphen(false) + , m_dirOverride(false) + , m_isText(false) + , m_determinedIfNextOnLineExists(false) + , m_determinedIfPrevOnLineExists(false) + , m_nextOnLineExists(false) + , m_prevOnLineExists(false) + , m_toAdd(0) +#ifndef NDEBUG + , m_hasBadParent(false) +#endif + { + } + + InlineBox(RenderObject* obj, int x, int y, int logicalWidth, bool firstLine, bool constructed, + bool dirty, bool extracted, bool isHorizontal, InlineBox* next, InlineBox* prev, InlineFlowBox* parent) + : m_next(next) + , m_prev(prev) + , m_parent(parent) + , m_renderer(obj) + , m_x(x) + , m_y(y) + , m_logicalWidth(logicalWidth) + , m_firstLine(firstLine) + , m_constructed(constructed) + , m_bidiEmbeddingLevel(0) + , m_dirty(dirty) + , m_extracted(extracted) +#if ENABLE(SVG) + , m_hasVirtualLogicalHeight(false) +#endif + , m_isHorizontal(isHorizontal) + , m_endsWithBreak(false) + , m_hasSelectedChildren(false) + , m_hasEllipsisBoxOrHyphen(false) + , m_dirOverride(false) + , m_isText(false) + , m_determinedIfNextOnLineExists(false) + , m_determinedIfPrevOnLineExists(false) + , m_nextOnLineExists(false) + , m_prevOnLineExists(false) + , m_toAdd(0) +#ifndef NDEBUG + , m_hasBadParent(false) +#endif + { + } + + virtual ~InlineBox(); + + virtual void destroy(RenderArena*); + + virtual void deleteLine(RenderArena*); + virtual void extractLine(); + virtual void attachLine(); + + virtual bool isLineBreak() const { return false; } + + virtual void adjustPosition(int dx, int dy); + void adjustLineDirectionPosition(int delta) + { + if (isHorizontal()) + adjustPosition(delta, 0); + else + adjustPosition(0, delta); + } + void adjustBlockDirectionPosition(int delta) + { + if (isHorizontal()) + adjustPosition(0, delta); + else + adjustPosition(delta, 0); + } + + virtual void paint(PaintInfo&, int tx, int ty); + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty); + + InlineBox* next() const { return m_next; } + + // Overloaded new operator. + void* operator new(size_t, RenderArena*) throw(); + + // Overridden to prevent the normal delete from being called. + void operator delete(void*, size_t); + +private: + // The normal operator new is disallowed. + void* operator new(size_t) throw(); + +public: +#ifndef NDEBUG + void showTreeForThis() const; +#endif + + bool isText() const { return m_isText; } + void setIsText(bool b) { m_isText = b; } + + virtual bool isInlineFlowBox() const { return false; } + virtual bool isInlineTextBox() const { return false; } + virtual bool isRootInlineBox() const { return false; } +#if ENABLE(SVG) + virtual bool isSVGInlineTextBox() const { return false; } + virtual bool isSVGInlineFlowBox() const { return false; } + virtual bool isSVGRootInlineBox() const { return false; } +#endif + + bool hasVirtualLogicalHeight() const { return m_hasVirtualLogicalHeight; } + void setHasVirtualLogicalHeight() { m_hasVirtualLogicalHeight = true; } + virtual int virtualLogicalHeight() const + { + ASSERT_NOT_REACHED(); + return 0; + } + + bool isHorizontal() const { return m_isHorizontal; } + void setIsHorizontal(bool horizontal) { m_isHorizontal = horizontal; } + + virtual IntRect calculateBoundaries() const + { + ASSERT_NOT_REACHED(); + return IntRect(); + } + + bool isConstructed() { return m_constructed; } + virtual void setConstructed() { m_constructed = true; } + + void setExtracted(bool b = true) { m_extracted = b; } + + void setFirstLineStyleBit(bool f) { m_firstLine = f; } + bool isFirstLineStyle() const { return m_firstLine; } + + void remove(); + + InlineBox* nextOnLine() const { return m_next; } + InlineBox* prevOnLine() const { return m_prev; } + void setNextOnLine(InlineBox* next) + { + ASSERT(m_parent || !next); + m_next = next; + } + void setPrevOnLine(InlineBox* prev) + { + ASSERT(m_parent || !prev); + m_prev = prev; + } + bool nextOnLineExists() const; + bool prevOnLineExists() const; + + virtual bool isLeaf() const { return true; } + + InlineBox* nextLeafChild() const; + InlineBox* prevLeafChild() const; + + RenderObject* renderer() const { return m_renderer; } + + InlineFlowBox* parent() const + { + ASSERT(!m_hasBadParent); + return m_parent; + } + void setParent(InlineFlowBox* par) { m_parent = par; } + + const RootInlineBox* root() const; + RootInlineBox* root(); + + // x() is the left side of the box in the containing block's coordinate system. + void setX(int x) { m_x = x; } + int x() const { return m_x; } + + // y() is the top side of the box in the containing block's coordinate system. + void setY(int y) { m_y = y; } + int y() const { return m_y; } + + int width() const { return isHorizontal() ? logicalWidth() : logicalHeight(); } + int height() const { return isHorizontal() ? logicalHeight() : logicalWidth(); } + + IntRect frameRect() const { return IntRect(x(), y(), width(), height()); } + + // The logicalLeft position is the left edge of the line box in a horizontal line and the top edge in a vertical line. + int logicalLeft() const { return isHorizontal() ? m_x : m_y; } + int logicalRight() const { return logicalLeft() + logicalWidth(); } + void setLogicalLeft(int left) + { + if (isHorizontal()) + m_x = left; + else + m_y = left; + } + + // The logicalTop[ position is the top edge of the line box in a horizontal line and the left edge in a vertical line. + int logicalTop() const { return isHorizontal() ? m_y : m_x; } + int logicalBottom() const { return logicalTop() + logicalHeight(); } + void setLogicalTop(int top) + { + if (isHorizontal()) + m_y = top; + else + m_x = top; + } + + // The logical width is our extent in the line's overall inline direction, i.e., width for horizontal text and height for vertical text. + void setLogicalWidth(int w) { m_logicalWidth = w; } + int logicalWidth() const { return m_logicalWidth; } + + // The logical height is our extent in the block flow direction, i.e., height for horizontal text and width for vertical text. + int logicalHeight() const; + + virtual int baselinePosition(FontBaseline baselineType) const { return boxModelObject()->baselinePosition(baselineType, m_firstLine, isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine); } + virtual int lineHeight() const { return boxModelObject()->lineHeight(m_firstLine, isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine); } + + virtual int caretMinOffset() const; + virtual int caretMaxOffset() const; + virtual unsigned caretMaxRenderedOffset() const; + + unsigned char bidiLevel() const { return m_bidiEmbeddingLevel; } + void setBidiLevel(unsigned char level) { m_bidiEmbeddingLevel = level; } + TextDirection direction() const { return m_bidiEmbeddingLevel % 2 ? RTL : LTR; } + bool isLeftToRightDirection() const { return direction() == LTR; } + int caretLeftmostOffset() const { return isLeftToRightDirection() ? caretMinOffset() : caretMaxOffset(); } + int caretRightmostOffset() const { return isLeftToRightDirection() ? caretMaxOffset() : caretMinOffset(); } + + virtual void clearTruncation() { } + + bool isDirty() const { return m_dirty; } + void markDirty(bool dirty = true) { m_dirty = dirty; } + + void dirtyLineBoxes(); + + virtual RenderObject::SelectionState selectionState(); + + virtual bool canAccommodateEllipsis(bool ltr, int blockEdge, int ellipsisWidth); + // visibleLeftEdge, visibleRightEdge are in the parent's coordinate system. + virtual int placeEllipsisBox(bool ltr, int visibleLeftEdge, int visibleRightEdge, int ellipsisWidth, bool&); + + void setHasBadParent(); + + int toAdd() const { return m_toAdd; } + + bool visibleToHitTesting() const { return renderer()->style()->visibility() == VISIBLE && renderer()->style()->pointerEvents() != PE_NONE; } + + // Use with caution! The type is not checked! + RenderBoxModelObject* boxModelObject() const + { + if (!m_renderer->isText()) + return toRenderBoxModelObject(m_renderer); + return 0; + } + + IntPoint locationIncludingFlipping(); + void flipForWritingMode(IntRect&); + IntPoint flipForWritingMode(const IntPoint&); + +private: + InlineBox* m_next; // The next element on the same line as us. + InlineBox* m_prev; // The previous element on the same line as us. + + InlineFlowBox* m_parent; // The box that contains us. + +public: + RenderObject* m_renderer; + + int m_x; + int m_y; + int m_logicalWidth; + + // Some of these bits are actually for subclasses and moved here to compact the structures. + + // for this class +protected: + bool m_firstLine : 1; +private: + bool m_constructed : 1; + unsigned char m_bidiEmbeddingLevel : 6; +protected: + bool m_dirty : 1; + bool m_extracted : 1; + bool m_hasVirtualLogicalHeight : 1; + + bool m_isHorizontal : 1; + + // for RootInlineBox + bool m_endsWithBreak : 1; // Whether the line ends with a <br>. + bool m_hasSelectedChildren : 1; // Whether we have any children selected (this bit will also be set if the <br> that terminates our line is selected). + bool m_hasEllipsisBoxOrHyphen : 1; + + // for InlineTextBox +public: + bool m_dirOverride : 1; + bool m_isText : 1; // Whether or not this object represents text with a non-zero height. Includes non-image list markers, text boxes. +protected: + mutable bool m_determinedIfNextOnLineExists : 1; + mutable bool m_determinedIfPrevOnLineExists : 1; + mutable bool m_nextOnLineExists : 1; + mutable bool m_prevOnLineExists : 1; + int m_toAdd : 11; // for justified text + +#ifndef NDEBUG +private: + bool m_hasBadParent; +#endif +}; + +#ifdef NDEBUG +inline InlineBox::~InlineBox() +{ +} +#endif + +inline void InlineBox::setHasBadParent() +{ +#ifndef NDEBUG + m_hasBadParent = true; +#endif +} + +} // namespace WebCore + +#ifndef NDEBUG +// Outside the WebCore namespace for ease of invocation from gdb. +void showTree(const WebCore::InlineBox*); +#endif + +#endif // InlineBox_h diff --git a/Source/WebCore/rendering/InlineFlowBox.cpp b/Source/WebCore/rendering/InlineFlowBox.cpp new file mode 100644 index 0000000..75b23c5 --- /dev/null +++ b/Source/WebCore/rendering/InlineFlowBox.cpp @@ -0,0 +1,1358 @@ +/* + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * + * 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 "InlineFlowBox.h" + +#include "CachedImage.h" +#include "CSSPropertyNames.h" +#include "Document.h" +#include "EllipsisBox.h" +#include "GraphicsContext.h" +#include "InlineTextBox.h" +#include "HitTestResult.h" +#include "RootInlineBox.h" +#include "RenderBlock.h" +#include "RenderInline.h" +#include "RenderLayer.h" +#include "RenderListMarker.h" +#include "RenderRubyBase.h" +#include "RenderRubyRun.h" +#include "RenderRubyText.h" +#include "RenderTableCell.h" +#include "RootInlineBox.h" +#include "Text.h" +#include "VerticalPositionCache.h" + +#include <math.h> + +using namespace std; + +namespace WebCore { + +#ifndef NDEBUG + +InlineFlowBox::~InlineFlowBox() +{ + if (!m_hasBadChildList) + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) + child->setHasBadParent(); +} + +#endif + +int InlineFlowBox::getFlowSpacingLogicalWidth() +{ + int totWidth = marginBorderPaddingLogicalLeft() + marginBorderPaddingLogicalRight(); + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->isInlineFlowBox()) + totWidth += static_cast<InlineFlowBox*>(curr)->getFlowSpacingLogicalWidth(); + } + return totWidth; +} + +void InlineFlowBox::addToLine(InlineBox* child) +{ + ASSERT(!child->parent()); + ASSERT(!child->nextOnLine()); + ASSERT(!child->prevOnLine()); + checkConsistency(); + + child->setParent(this); + if (!m_firstChild) { + m_firstChild = child; + m_lastChild = child; + } else { + m_lastChild->setNextOnLine(child); + child->setPrevOnLine(m_lastChild); + m_lastChild = child; + } + child->setFirstLineStyleBit(m_firstLine); + child->setIsHorizontal(isHorizontal()); + if (child->isText()) + m_hasTextChildren = true; + + checkConsistency(); +} + +void InlineFlowBox::removeChild(InlineBox* child) +{ + checkConsistency(); + + if (!m_dirty) + dirtyLineBoxes(); + + root()->childRemoved(child); + + if (child == m_firstChild) + m_firstChild = child->nextOnLine(); + if (child == m_lastChild) + m_lastChild = child->prevOnLine(); + if (child->nextOnLine()) + child->nextOnLine()->setPrevOnLine(child->prevOnLine()); + if (child->prevOnLine()) + child->prevOnLine()->setNextOnLine(child->nextOnLine()); + + child->setParent(0); + + checkConsistency(); +} + +void InlineFlowBox::deleteLine(RenderArena* arena) +{ + InlineBox* child = firstChild(); + InlineBox* next = 0; + while (child) { + ASSERT(this == child->parent()); + next = child->nextOnLine(); +#ifndef NDEBUG + child->setParent(0); +#endif + child->deleteLine(arena); + child = next; + } +#ifndef NDEBUG + m_firstChild = 0; + m_lastChild = 0; +#endif + + removeLineBoxFromRenderObject(); + destroy(arena); +} + +void InlineFlowBox::removeLineBoxFromRenderObject() +{ + toRenderInline(renderer())->lineBoxes()->removeLineBox(this); +} + +void InlineFlowBox::extractLine() +{ + if (!m_extracted) + extractLineBoxFromRenderObject(); + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) + child->extractLine(); +} + +void InlineFlowBox::extractLineBoxFromRenderObject() +{ + toRenderInline(renderer())->lineBoxes()->extractLineBox(this); +} + +void InlineFlowBox::attachLine() +{ + if (m_extracted) + attachLineBoxToRenderObject(); + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) + child->attachLine(); +} + +void InlineFlowBox::attachLineBoxToRenderObject() +{ + toRenderInline(renderer())->lineBoxes()->attachLineBox(this); +} + +void InlineFlowBox::adjustPosition(int dx, int dy) +{ + InlineBox::adjustPosition(dx, dy); + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) + child->adjustPosition(dx, dy); + if (m_overflow) + m_overflow->move(dx, dy); +} + +RenderLineBoxList* InlineFlowBox::rendererLineBoxes() const +{ + return toRenderInline(renderer())->lineBoxes(); +} + +bool InlineFlowBox::onEndChain(RenderObject* endObject) +{ + if (!endObject) + return false; + + if (endObject == renderer()) + return true; + + RenderObject* curr = endObject; + RenderObject* parent = curr->parent(); + while (parent && !parent->isRenderBlock()) { + if (parent->lastChild() != curr || parent == renderer()) + return false; + + curr = parent; + parent = curr->parent(); + } + + return true; +} + +void InlineFlowBox::determineSpacingForFlowBoxes(bool lastLine, RenderObject* endObject) +{ + // All boxes start off open. They will not apply any margins/border/padding on + // any side. + bool includeLeftEdge = false; + bool includeRightEdge = false; + + // The root inline box never has borders/margins/padding. + if (parent()) { + bool ltr = renderer()->style()->isLeftToRightDirection(); + + // Check to see if all initial lines are unconstructed. If so, then + // we know the inline began on this line (unless we are a continuation). + RenderLineBoxList* lineBoxList = rendererLineBoxes(); + if (!lineBoxList->firstLineBox()->isConstructed() && !renderer()->isInlineElementContinuation()) { + if (ltr && lineBoxList->firstLineBox() == this) + includeLeftEdge = true; + else if (!ltr && lineBoxList->lastLineBox() == this) + includeRightEdge = true; + } + + // In order to determine if the inline ends on this line, we check three things: + // (1) If we are the last line and we don't have a continuation(), then we can + // close up. + // (2) If the last line box for the flow has an object following it on the line (ltr, + // reverse for rtl), then the inline has closed. + // (3) The line may end on the inline. If we are the last child (climbing up + // the end object's chain), then we just closed as well. + if (!lineBoxList->lastLineBox()->isConstructed()) { + RenderInline* inlineFlow = toRenderInline(renderer()); + if (ltr) { + if (!nextLineBox() && + ((lastLine && !inlineFlow->continuation()) || nextOnLineExists() || onEndChain(endObject))) + includeRightEdge = true; + } else { + if ((!prevLineBox() || prevLineBox()->isConstructed()) && + ((lastLine && !inlineFlow->continuation()) || prevOnLineExists() || onEndChain(endObject))) + includeLeftEdge = true; + } + } + } + + setEdges(includeLeftEdge, includeRightEdge); + + // Recur into our children. + for (InlineBox* currChild = firstChild(); currChild; currChild = currChild->nextOnLine()) { + if (currChild->isInlineFlowBox()) { + InlineFlowBox* currFlow = static_cast<InlineFlowBox*>(currChild); + currFlow->determineSpacingForFlowBoxes(lastLine, endObject); + } + } +} + +int InlineFlowBox::placeBoxesInInlineDirection(int logicalLeft, bool& needsWordSpacing, GlyphOverflowAndFallbackFontsMap& textBoxDataMap) +{ + // Set our x position. + setLogicalLeft(logicalLeft); + + int startLogicalLeft = logicalLeft; + logicalLeft += borderLogicalLeft() + paddingLogicalLeft(); + + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->renderer()->isText()) { + InlineTextBox* text = static_cast<InlineTextBox*>(curr); + RenderText* rt = toRenderText(text->renderer()); + if (rt->textLength()) { + if (needsWordSpacing && isSpaceOrNewline(rt->characters()[text->start()])) + logicalLeft += rt->style(m_firstLine)->font().wordSpacing(); + needsWordSpacing = !isSpaceOrNewline(rt->characters()[text->end()]); + } + text->setLogicalLeft(logicalLeft); + logicalLeft += text->logicalWidth(); + } else { + if (curr->renderer()->isPositioned()) { + if (curr->renderer()->parent()->style()->isLeftToRightDirection()) + curr->setLogicalLeft(logicalLeft); + else + // Our offset that we cache needs to be from the edge of the right border box and + // not the left border box. We have to subtract |x| from the width of the block + // (which can be obtained from the root line box). + curr->setLogicalLeft(root()->block()->logicalWidth() - logicalLeft); + continue; // The positioned object has no effect on the width. + } + if (curr->renderer()->isRenderInline()) { + InlineFlowBox* flow = static_cast<InlineFlowBox*>(curr); + logicalLeft += flow->marginLogicalLeft(); + logicalLeft = flow->placeBoxesInInlineDirection(logicalLeft, needsWordSpacing, textBoxDataMap); + logicalLeft += flow->marginLogicalRight(); + } else if (!curr->renderer()->isListMarker() || toRenderListMarker(curr->renderer())->isInside()) { + // The box can have a different writing-mode than the overall line, so this is a bit complicated. + // Just get all the physical margin and overflow values by hand based off |isVertical|. + int logicalLeftMargin = isHorizontal() ? curr->boxModelObject()->marginLeft() : curr->boxModelObject()->marginTop(); + int logicalRightMargin = isHorizontal() ? curr->boxModelObject()->marginRight() : curr->boxModelObject()->marginBottom(); + + logicalLeft += logicalLeftMargin; + curr->setLogicalLeft(logicalLeft); + logicalLeft += curr->logicalWidth() + logicalRightMargin; + } + } + } + + logicalLeft += borderLogicalRight() + paddingLogicalRight(); + setLogicalWidth(logicalLeft - startLogicalLeft); + return logicalLeft; +} + +bool InlineFlowBox::requiresIdeographicBaseline(const GlyphOverflowAndFallbackFontsMap& textBoxDataMap) const +{ + if (isHorizontal()) + return false; + + if (renderer()->style(m_firstLine)->font().primaryFont()->orientation() == Vertical) + return true; + + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->renderer()->isPositioned()) + continue; // Positioned placeholders don't affect calculations. + + if (curr->isInlineFlowBox()) { + if (static_cast<InlineFlowBox*>(curr)->requiresIdeographicBaseline(textBoxDataMap)) + return true; + } else { + if (curr->renderer()->style(m_firstLine)->font().primaryFont()->orientation() == Vertical) + return true; + + const Vector<const SimpleFontData*>* usedFonts = 0; + if (curr->isInlineTextBox()) { + GlyphOverflowAndFallbackFontsMap::const_iterator it = textBoxDataMap.find(static_cast<InlineTextBox*>(curr)); + usedFonts = it == textBoxDataMap.end() ? 0 : &it->second.first; + } + + if (usedFonts) { + for (size_t i = 0; i < usedFonts->size(); ++i) { + if (usedFonts->at(i)->orientation() == Vertical) + return true; + } + } + } + } + + return false; +} + +void InlineFlowBox::adjustMaxAscentAndDescent(int& maxAscent, int& maxDescent, + int maxPositionTop, int maxPositionBottom) +{ + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + // The computed lineheight needs to be extended for the + // positioned elements + if (curr->renderer()->isPositioned()) + continue; // Positioned placeholders don't affect calculations. + if (curr->logicalTop() == PositionTop || curr->logicalTop() == PositionBottom) { + int lineHeight = curr->lineHeight(); + if (curr->logicalTop() == PositionTop) { + if (maxAscent + maxDescent < lineHeight) + maxDescent = lineHeight - maxAscent; + } + else { + if (maxAscent + maxDescent < lineHeight) + maxAscent = lineHeight - maxDescent; + } + + if (maxAscent + maxDescent >= max(maxPositionTop, maxPositionBottom)) + break; + } + + if (curr->isInlineFlowBox()) + static_cast<InlineFlowBox*>(curr)->adjustMaxAscentAndDescent(maxAscent, maxDescent, maxPositionTop, maxPositionBottom); + } +} + +static int verticalPositionForBox(InlineBox* box, FontBaseline baselineType, bool firstLine, VerticalPositionCache& verticalPositionCache) +{ + if (box->renderer()->isText()) + return box->parent()->logicalTop(); + + RenderBoxModelObject* renderer = box->boxModelObject(); + ASSERT(renderer->isInline()); + if (!renderer->isInline()) + return 0; + + // This method determines the vertical position for inline elements. + if (firstLine && !renderer->document()->usesFirstLineRules()) + firstLine = false; + + // Check the cache. + bool isRenderInline = renderer->isRenderInline(); + if (isRenderInline && !firstLine) { + int verticalPosition = verticalPositionCache.get(renderer, baselineType); + if (verticalPosition != PositionUndefined) + return verticalPosition; + } + + int verticalPosition = 0; + EVerticalAlign verticalAlign = renderer->style()->verticalAlign(); + if (verticalAlign == TOP) + verticalPosition = PositionTop; + else if (verticalAlign == BOTTOM) + verticalPosition = PositionBottom; + else { + RenderObject* parent = renderer->parent(); + if (parent->isRenderInline() && parent->style()->verticalAlign() != TOP && parent->style()->verticalAlign() != BOTTOM) + verticalPosition = box->parent()->logicalTop(); + + if (verticalAlign != BASELINE) { + const Font& font = parent->style(firstLine)->font(); + int fontSize = font.pixelSize(); + + LineDirectionMode lineDirection = parent->style()->isHorizontalWritingMode() ? HorizontalLine : VerticalLine; + + if (verticalAlign == SUB) + verticalPosition += fontSize / 5 + 1; + else if (verticalAlign == SUPER) + verticalPosition -= fontSize / 3 + 1; + else if (verticalAlign == TEXT_TOP) + verticalPosition += renderer->baselinePosition(baselineType, firstLine, lineDirection) - font.ascent(baselineType); + else if (verticalAlign == MIDDLE) + verticalPosition += -static_cast<int>(font.xHeight() / 2) - renderer->lineHeight(firstLine, lineDirection) / 2 + renderer->baselinePosition(baselineType, firstLine, lineDirection); + else if (verticalAlign == TEXT_BOTTOM) { + verticalPosition += font.descent(baselineType); + // lineHeight - baselinePosition is always 0 for replaced elements (except inline blocks), so don't bother wasting time in that case. + if (!renderer->isReplaced() || renderer->isInlineBlockOrInlineTable()) + verticalPosition -= (renderer->lineHeight(firstLine, lineDirection) - renderer->baselinePosition(baselineType, firstLine, lineDirection)); + } else if (verticalAlign == BASELINE_MIDDLE) + verticalPosition += -renderer->lineHeight(firstLine, lineDirection) / 2 + renderer->baselinePosition(baselineType, firstLine, lineDirection); + else if (verticalAlign == LENGTH) + verticalPosition -= renderer->style()->verticalAlignLength().calcValue(renderer->lineHeight(firstLine, lineDirection)); + } + } + + // Store the cached value. + if (isRenderInline && !firstLine) + verticalPositionCache.set(renderer, baselineType, verticalPosition); + + return verticalPosition; +} + +void InlineFlowBox::computeLogicalBoxHeights(int& maxPositionTop, int& maxPositionBottom, + int& maxAscent, int& maxDescent, bool& setMaxAscent, bool& setMaxDescent, + bool strictMode, GlyphOverflowAndFallbackFontsMap& textBoxDataMap, + FontBaseline baselineType, VerticalPositionCache& verticalPositionCache) +{ + // The primary purpose of this function is to compute the maximal ascent and descent values for + // a line. + // + // The maxAscent value represents the distance of the highest point of any box (including line-height) from + // the root box's baseline. The maxDescent value represents the distance of the lowest point of any box + // (also including line-height) from the root box baseline. These values can be negative. + // + // A secondary purpose of this function is to store the offset of very box's baseline from the root box's + // baseline. This information is cached in the logicalTop() of every box. We're effectively just using + // the logicalTop() as scratch space. + if (isRootInlineBox()) { + // Examine our root box. + int height = lineHeight(); + int baseline = baselinePosition(baselineType); + if (hasTextChildren() || strictMode) { + int ascent = baseline; + int descent = height - ascent; + if (maxAscent < ascent || !setMaxAscent) { + maxAscent = ascent; + setMaxAscent = true; + } + if (maxDescent < descent || !setMaxDescent) { + maxDescent = descent; + setMaxDescent = true; + } + } + } + + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->renderer()->isPositioned()) + continue; // Positioned placeholders don't affect calculations. + + bool isInlineFlow = curr->isInlineFlowBox(); + + // Because a box can be positioned such that it ends up fully above or fully below the + // root line box, we only consider it to affect the maxAscent and maxDescent values if some + // part of the box (EXCLUDING line-height) is above (for ascent) or below (for descent) the root box's baseline. + bool affectsAscent = false; + bool affectsDescent = false; + + // The verticalPositionForBox function returns the distance between the child box's baseline + // and the root box's baseline. The value is negative if the child box's baseline is above the + // root box's baseline, and it is positive if the child box's baseline is below the root box's baseline. + curr->setLogicalTop(verticalPositionForBox(curr, baselineType, m_firstLine, verticalPositionCache)); + + int lineHeight; + int baseline; + Vector<const SimpleFontData*>* usedFonts = 0; + if (curr->isInlineTextBox()) { + GlyphOverflowAndFallbackFontsMap::iterator it = textBoxDataMap.find(static_cast<InlineTextBox*>(curr)); + usedFonts = it == textBoxDataMap.end() ? 0 : &it->second.first; + } + + if (usedFonts && !usedFonts->isEmpty() && curr->renderer()->style(m_firstLine)->lineHeight().isNegative()) { + usedFonts->append(curr->renderer()->style(m_firstLine)->font().primaryFont()); + bool baselineSet = false; + baseline = 0; + int baselineToBottom = 0; + for (size_t i = 0; i < usedFonts->size(); ++i) { + int halfLeading = (usedFonts->at(i)->lineSpacing() - usedFonts->at(i)->height()) / 2; + int usedFontBaseline = halfLeading + usedFonts->at(i)->ascent(baselineType); + int usedFontBaselineToBottom = usedFonts->at(i)->lineSpacing() - usedFontBaseline; + if (!baselineSet) { + baselineSet = true; + baseline = usedFontBaseline; + baselineToBottom = usedFontBaselineToBottom; + } else { + baseline = max(baseline, usedFontBaseline); + baselineToBottom = max(baselineToBottom, usedFontBaselineToBottom); + } + if (!affectsAscent) + affectsAscent = usedFonts->at(i)->ascent() - curr->logicalTop() > 0; + if (!affectsDescent) + affectsDescent = usedFonts->at(i)->descent() + curr->logicalTop() > 0; + } + lineHeight = baseline + baselineToBottom; + } else { + lineHeight = curr->lineHeight(); + baseline = curr->baselinePosition(baselineType); + if (curr->isText() || isInlineFlow) { + // Examine the font box for inline flows and text boxes to see if any part of it is above the baseline. + // If the top of our font box relative to the root box baseline is above the root box baseline, then + // we are contributing to the maxAscent value. + affectsAscent = curr->renderer()->style(m_firstLine)->font().ascent(baselineType) - curr->logicalTop() > 0; + + // Descent is similar. If any part of our font box is below the root box's baseline, then + // we contribute to the maxDescent value. + affectsDescent = curr->renderer()->style(m_firstLine)->font().descent(baselineType) + curr->logicalTop() > 0; + } else { + // Replaced elements always affect both the ascent and descent. + affectsAscent = true; + affectsDescent = true; + } + } + + if (curr->logicalTop() == PositionTop) { + if (maxPositionTop < lineHeight) + maxPositionTop = lineHeight; + } else if (curr->logicalTop() == PositionBottom) { + if (maxPositionBottom < lineHeight) + maxPositionBottom = lineHeight; + } else if ((!isInlineFlow || static_cast<InlineFlowBox*>(curr)->hasTextChildren()) || curr->boxModelObject()->hasInlineDirectionBordersOrPadding() || strictMode) { + // Note that these values can be negative. Even though we only affect the maxAscent and maxDescent values + // if our box (excluding line-height) was above (for ascent) or below (for descent) the root baseline, once you factor in line-height + // the final box can end up being fully above or fully below the root box's baseline! This is ok, but what it + // means is that ascent and descent (including leading), can end up being negative. The setMaxAscent and + // setMaxDescent booleans are used to ensure that we're willing to initially set maxAscent/Descent to negative + // values. + int ascent = baseline - curr->logicalTop(); + int descent = lineHeight - ascent; + if (affectsAscent && (maxAscent < ascent || !setMaxAscent)) { + maxAscent = ascent; + setMaxAscent = true; + } + if (affectsDescent && (maxDescent < descent || !setMaxDescent)) { + maxDescent = descent; + setMaxDescent = true; + } + } + + if (curr->isInlineFlowBox()) + static_cast<InlineFlowBox*>(curr)->computeLogicalBoxHeights(maxPositionTop, maxPositionBottom, maxAscent, maxDescent, + setMaxAscent, setMaxDescent, strictMode, textBoxDataMap, + baselineType, verticalPositionCache); + } +} + +void InlineFlowBox::placeBoxesInBlockDirection(int top, int maxHeight, int maxAscent, bool strictMode, int& lineTop, int& lineBottom, bool& setLineTop, + int& lineTopIncludingMargins, int& lineBottomIncludingMargins, bool& hasAnnotationsBefore, bool& hasAnnotationsAfter, FontBaseline baselineType) +{ + if (isRootInlineBox()) + setLogicalTop(top + maxAscent - baselinePosition(baselineType)); // Place our root box. + + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->renderer()->isPositioned()) + continue; // Positioned placeholders don't affect calculations. + + // Adjust boxes to use their real box y/height and not the logical height (as dictated by + // line-height). + bool isInlineFlow = curr->isInlineFlowBox(); + if (isInlineFlow) + static_cast<InlineFlowBox*>(curr)->placeBoxesInBlockDirection(top, maxHeight, maxAscent, strictMode, lineTop, lineBottom, setLineTop, + lineTopIncludingMargins, lineBottomIncludingMargins, hasAnnotationsBefore, hasAnnotationsAfter, baselineType); + + bool childAffectsTopBottomPos = true; + if (curr->logicalTop() == PositionTop) + curr->setLogicalTop(top); + else if (curr->logicalTop() == PositionBottom) + curr->setLogicalTop(top + maxHeight - curr->lineHeight()); + else { + if ((isInlineFlow && !static_cast<InlineFlowBox*>(curr)->hasTextChildren()) && !curr->boxModelObject()->hasInlineDirectionBordersOrPadding() && !strictMode) + childAffectsTopBottomPos = false; + int posAdjust = maxAscent - curr->baselinePosition(baselineType); + curr->setLogicalTop(curr->logicalTop() + top + posAdjust); + } + + int newLogicalTop = curr->logicalTop(); + int newLogicalTopIncludingMargins; + int boxHeight = curr->logicalHeight(); + int boxHeightIncludingMargins = boxHeight; + + if (curr->isText() || curr->isInlineFlowBox()) { + const Font& font = curr->renderer()->style(m_firstLine)->font(); + newLogicalTop += curr->baselinePosition(baselineType) - font.ascent(baselineType); + if (curr->isInlineFlowBox()) { + RenderBoxModelObject* boxObject = toRenderBoxModelObject(curr->renderer()); + newLogicalTop -= boxObject->style(m_firstLine)->isHorizontalWritingMode() ? boxObject->borderTop() + boxObject->paddingTop() : + boxObject->borderRight() + boxObject->paddingRight(); + } + newLogicalTopIncludingMargins = newLogicalTop; + } else if (!curr->renderer()->isBR()) { + RenderBox* box = toRenderBox(curr->renderer()); + newLogicalTopIncludingMargins = newLogicalTop; + int overSideMargin = curr->isHorizontal() ? box->marginTop() : box->marginRight(); + int underSideMargin = curr->isHorizontal() ? box->marginBottom() : box->marginLeft(); + newLogicalTop += overSideMargin; + boxHeightIncludingMargins += overSideMargin + underSideMargin; + } + + curr->setLogicalTop(newLogicalTop); + + if (childAffectsTopBottomPos) { + if (curr->renderer()->isRubyRun()) { + // Treat the leading on the first and last lines of ruby runs as not being part of the overall lineTop/lineBottom. + // Really this is a workaround hack for the fact that ruby should have been done as line layout and not done using + // inline-block. + if (!renderer()->style()->isFlippedLinesWritingMode()) + hasAnnotationsBefore = true; + else + hasAnnotationsAfter = true; + + RenderRubyRun* rubyRun = static_cast<RenderRubyRun*>(curr->renderer()); + if (RenderRubyBase* rubyBase = rubyRun->rubyBase()) { + int bottomRubyBaseLeading = (curr->logicalHeight() - rubyBase->logicalBottom()) + rubyBase->logicalHeight() - (rubyBase->lastRootBox() ? rubyBase->lastRootBox()->lineBottom() : 0); + int topRubyBaseLeading = rubyBase->logicalTop() + (rubyBase->firstRootBox() ? rubyBase->firstRootBox()->lineTop() : 0); + newLogicalTop += !renderer()->style()->isFlippedLinesWritingMode() ? topRubyBaseLeading : bottomRubyBaseLeading; + boxHeight -= (topRubyBaseLeading + bottomRubyBaseLeading); + } + } + if (curr->isInlineTextBox()) { + TextEmphasisPosition emphasisMarkPosition; + if (static_cast<InlineTextBox*>(curr)->getEmphasisMarkPosition(curr->renderer()->style(m_firstLine), emphasisMarkPosition)) { + bool emphasisMarkIsOver = emphasisMarkPosition == TextEmphasisPositionOver; + if (emphasisMarkIsOver != curr->renderer()->style(m_firstLine)->isFlippedLinesWritingMode()) + hasAnnotationsBefore = true; + else + hasAnnotationsAfter = true; + } + } + + if (!setLineTop) { + setLineTop = true; + lineTop = newLogicalTop; + lineTopIncludingMargins = min(lineTop, newLogicalTopIncludingMargins); + } else { + lineTop = min(lineTop, newLogicalTop); + lineTopIncludingMargins = min(lineTop, min(lineTopIncludingMargins, newLogicalTopIncludingMargins)); + } + lineBottom = max(lineBottom, newLogicalTop + boxHeight); + lineBottomIncludingMargins = max(lineBottom, max(lineBottomIncludingMargins, newLogicalTopIncludingMargins + boxHeightIncludingMargins)); + } + } + + if (isRootInlineBox()) { + const Font& font = renderer()->style(m_firstLine)->font(); + setLogicalTop(logicalTop() + baselinePosition(baselineType) - font.ascent(baselineType)); + + if (hasTextChildren() || strictMode) { + if (!setLineTop) { + setLineTop = true; + lineTop = logicalTop(); + lineTopIncludingMargins = lineTop; + } else { + lineTop = min(lineTop, logicalTop()); + lineTopIncludingMargins = min(lineTop, lineTopIncludingMargins); + } + lineBottom = max(lineBottom, logicalTop() + logicalHeight()); + lineBottomIncludingMargins = max(lineBottom, lineBottomIncludingMargins); + } + + if (renderer()->style()->isFlippedLinesWritingMode()) + flipLinesInBlockDirection(lineTopIncludingMargins, lineBottomIncludingMargins); + } +} + +void InlineFlowBox::flipLinesInBlockDirection(int lineTop, int lineBottom) +{ + // Flip the box on the line such that the top is now relative to the lineBottom instead of the lineTop. + setLogicalTop(lineBottom - (logicalTop() - lineTop) - logicalHeight()); + + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->renderer()->isPositioned()) + continue; // Positioned placeholders aren't affected here. + + if (curr->isInlineFlowBox()) + static_cast<InlineFlowBox*>(curr)->flipLinesInBlockDirection(lineTop, lineBottom); + else + curr->setLogicalTop(lineBottom - (curr->logicalTop() - lineTop) - curr->logicalHeight()); + } +} + +void InlineFlowBox::addBoxShadowVisualOverflow(IntRect& logicalVisualOverflow) +{ + if (!parent()) + return; // Box-shadow doesn't apply to root line boxes. + + int boxShadowLogicalTop; + int boxShadowLogicalBottom; + renderer()->style(m_firstLine)->getBoxShadowBlockDirectionExtent(boxShadowLogicalTop, boxShadowLogicalBottom); + + int logicalTopVisualOverflow = min(logicalTop() + boxShadowLogicalTop, logicalVisualOverflow.y()); + int logicalBottomVisualOverflow = max(logicalBottom() + boxShadowLogicalBottom, logicalVisualOverflow.bottom()); + + int boxShadowLogicalLeft; + int boxShadowLogicalRight; + renderer()->style(m_firstLine)->getBoxShadowInlineDirectionExtent(boxShadowLogicalLeft, boxShadowLogicalRight); + + int logicalLeftVisualOverflow = min(logicalLeft() + boxShadowLogicalLeft, logicalVisualOverflow.x()); + int logicalRightVisualOverflow = max(logicalRight() + boxShadowLogicalRight, logicalVisualOverflow.right()); + + logicalVisualOverflow = IntRect(logicalLeftVisualOverflow, logicalTopVisualOverflow, + logicalRightVisualOverflow - logicalLeftVisualOverflow, logicalBottomVisualOverflow - logicalTopVisualOverflow); +} + +void InlineFlowBox::addTextBoxVisualOverflow(const InlineTextBox* textBox, GlyphOverflowAndFallbackFontsMap& textBoxDataMap, IntRect& logicalVisualOverflow) +{ + RenderStyle* style = renderer()->style(m_firstLine); + int strokeOverflow = static_cast<int>(ceilf(style->textStrokeWidth() / 2.0f)); + + GlyphOverflowAndFallbackFontsMap::iterator it = textBoxDataMap.find(textBox); + GlyphOverflow* glyphOverflow = it == textBoxDataMap.end() ? 0 : &it->second.second; + + bool isFlippedLine = style->isFlippedLinesWritingMode(); + + int topGlyphEdge = glyphOverflow ? (isFlippedLine ? glyphOverflow->bottom : glyphOverflow->top) : 0; + int bottomGlyphEdge = glyphOverflow ? (isFlippedLine ? glyphOverflow->top : glyphOverflow->bottom) : 0; + int leftGlyphEdge = glyphOverflow ? glyphOverflow->left : 0; + int rightGlyphEdge = glyphOverflow ? glyphOverflow->right : 0; + + int topGlyphOverflow = -strokeOverflow - topGlyphEdge; + int bottomGlyphOverflow = strokeOverflow + bottomGlyphEdge; + int leftGlyphOverflow = -strokeOverflow - leftGlyphEdge; + int rightGlyphOverflow = strokeOverflow + rightGlyphEdge; + + TextEmphasisPosition emphasisMarkPosition; + if (style->textEmphasisMark() != TextEmphasisMarkNone && textBox->getEmphasisMarkPosition(style, emphasisMarkPosition)) { + int emphasisMarkHeight = style->font().emphasisMarkHeight(style->textEmphasisMarkString()); + if ((emphasisMarkPosition == TextEmphasisPositionOver) == (!style->isFlippedLinesWritingMode())) + topGlyphOverflow = min(topGlyphOverflow, -emphasisMarkHeight); + else + bottomGlyphOverflow = max(bottomGlyphOverflow, emphasisMarkHeight); + } + + // If letter-spacing is negative, we should factor that into right layout overflow. (Even in RTL, letter-spacing is + // applied to the right, so this is not an issue with left overflow. + int letterSpacing = min(0, (int)style->font().letterSpacing()); + rightGlyphOverflow -= letterSpacing; + + int textShadowLogicalTop; + int textShadowLogicalBottom; + style->getTextShadowBlockDirectionExtent(textShadowLogicalTop, textShadowLogicalBottom); + + int childOverflowLogicalTop = min(textShadowLogicalTop + topGlyphOverflow, topGlyphOverflow); + int childOverflowLogicalBottom = max(textShadowLogicalBottom + bottomGlyphOverflow, bottomGlyphOverflow); + + int textShadowLogicalLeft; + int textShadowLogicalRight; + style->getTextShadowInlineDirectionExtent(textShadowLogicalLeft, textShadowLogicalRight); + + int childOverflowLogicalLeft = min(textShadowLogicalLeft + leftGlyphOverflow, leftGlyphOverflow); + int childOverflowLogicalRight = max(textShadowLogicalRight + rightGlyphOverflow, rightGlyphOverflow); + + int logicalTopVisualOverflow = min(textBox->logicalTop() + childOverflowLogicalTop, logicalVisualOverflow.y()); + int logicalBottomVisualOverflow = max(textBox->logicalBottom() + childOverflowLogicalBottom, logicalVisualOverflow.bottom()); + int logicalLeftVisualOverflow = min(textBox->logicalLeft() + childOverflowLogicalLeft, logicalVisualOverflow.x()); + int logicalRightVisualOverflow = max(textBox->logicalRight() + childOverflowLogicalRight, logicalVisualOverflow.right()); + + logicalVisualOverflow = IntRect(logicalLeftVisualOverflow, logicalTopVisualOverflow, + logicalRightVisualOverflow - logicalLeftVisualOverflow, logicalBottomVisualOverflow - logicalTopVisualOverflow); +} + +void InlineFlowBox::addReplacedChildOverflow(const InlineBox* inlineBox, IntRect& logicalLayoutOverflow, IntRect& logicalVisualOverflow) +{ + RenderBox* box = toRenderBox(inlineBox->renderer()); + + // Visual overflow only propagates if the box doesn't have a self-painting layer. This rectangle does not include + // transforms or relative positioning (since those objects always have self-painting layers), but it does need to be adjusted + // for writing-mode differences. + if (!box->hasSelfPaintingLayer()) { + IntRect childLogicalVisualOverflow = box->logicalVisualOverflowRectForPropagation(renderer()->style()); + childLogicalVisualOverflow.move(inlineBox->logicalLeft(), inlineBox->logicalTop()); + logicalVisualOverflow.unite(childLogicalVisualOverflow); + } + + // Layout overflow internal to the child box only propagates if the child box doesn't have overflow clip set. + // Otherwise the child border box propagates as layout overflow. This rectangle must include transforms and relative positioning + // and be adjusted for writing-mode differences. + IntRect childLogicalLayoutOverflow = box->logicalLayoutOverflowRectForPropagation(renderer()->style()); + childLogicalLayoutOverflow.move(inlineBox->logicalLeft(), inlineBox->logicalTop()); + logicalLayoutOverflow.unite(childLogicalLayoutOverflow); +} + +void InlineFlowBox::computeOverflow(int lineTop, int lineBottom, bool strictMode, GlyphOverflowAndFallbackFontsMap& textBoxDataMap) +{ + // Any spillage outside of the line top and bottom is not considered overflow. We just ignore this, since it only happens + // from the "your ascent/descent don't affect the line" quirk. + int topOverflow = max(logicalTop(), lineTop); + int bottomOverflow = min(logicalBottom(), lineBottom); + + // Visual overflow just includes overflow for stuff we need to repaint ourselves. Self-painting layers are ignored. + // Layout overflow is used to determine scrolling extent, so it still includes child layers and also factors in + // transforms, relative positioning, etc. + IntRect logicalLayoutOverflow(logicalLeft(), topOverflow, logicalWidth(), bottomOverflow - topOverflow); + IntRect logicalVisualOverflow(logicalLayoutOverflow); + + // box-shadow on root line boxes is applying to the block and not to the lines. + addBoxShadowVisualOverflow(logicalVisualOverflow); + + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->renderer()->isPositioned()) + continue; // Positioned placeholders don't affect calculations. + + if (curr->renderer()->isText()) { + InlineTextBox* text = static_cast<InlineTextBox*>(curr); + RenderText* rt = toRenderText(text->renderer()); + if (rt->isBR()) + continue; + addTextBoxVisualOverflow(text, textBoxDataMap, logicalVisualOverflow); + } else if (curr->renderer()->isRenderInline()) { + InlineFlowBox* flow = static_cast<InlineFlowBox*>(curr); + flow->computeOverflow(lineTop, lineBottom, strictMode, textBoxDataMap); + if (!flow->boxModelObject()->hasSelfPaintingLayer()) + logicalVisualOverflow.unite(flow->logicalVisualOverflowRect()); + IntRect childLayoutOverflow = flow->logicalLayoutOverflowRect(); + childLayoutOverflow.move(flow->boxModelObject()->relativePositionLogicalOffset()); + logicalLayoutOverflow.unite(childLayoutOverflow); + } else + addReplacedChildOverflow(curr, logicalLayoutOverflow, logicalVisualOverflow); + } + + setOverflowFromLogicalRects(logicalLayoutOverflow, logicalVisualOverflow); +} + +// FIXME: You will notice there is no contains() check here. If the rect is smaller than the frame box it actually +// becomes the new overflow. The reason for this is that in quirks mode we don't let inline flow boxes paint +// outside of the root line box's lineTop and lineBottom values. We accomplish this visual clamping by actually +// insetting the overflow rect so that it's smaller than the frame rect. +// +// The reason we don't just mutate the frameRect in quirks mode is that we'd have to put the m_height member variable +// back into InlineBox. Basically the tradeoff is 4 bytes in all modes (for m_height) added to InlineFlowBox, or +// the allocation of a RenderOverflow struct for InlineFlowBoxes in quirks mode only. For now, we're opting to award +// the smaller memory consumption to strict mode pages. +// +// It might be possible to hash a custom height, or to require that lineTop and lineBottom be passed in to +// all functions that query overflow. +void InlineFlowBox::setLayoutOverflow(const IntRect& rect) +{ + IntRect frameBox = frameRect(); + if (frameBox == rect || rect.isEmpty()) + return; + + if (!m_overflow) + m_overflow.set(new RenderOverflow(frameBox, frameBox)); + + m_overflow->setLayoutOverflow(rect); +} + +void InlineFlowBox::setVisualOverflow(const IntRect& rect) +{ + IntRect frameBox = frameRect(); + if (frameBox == rect || rect.isEmpty()) + return; + + if (!m_overflow) + m_overflow.set(new RenderOverflow(frameBox, frameBox)); + + m_overflow->setVisualOverflow(rect); +} + +void InlineFlowBox::setOverflowFromLogicalRects(const IntRect& logicalLayoutOverflow, const IntRect& logicalVisualOverflow) +{ + IntRect layoutOverflow(isHorizontal() ? logicalLayoutOverflow : logicalLayoutOverflow.transposedRect()); + setLayoutOverflow(layoutOverflow); + + IntRect visualOverflow(isHorizontal() ? logicalVisualOverflow : logicalVisualOverflow.transposedRect()); + setVisualOverflow(visualOverflow); +} + +bool InlineFlowBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty) +{ + IntRect overflowRect(visualOverflowRect()); + flipForWritingMode(overflowRect); + overflowRect.move(tx, ty); + if (!overflowRect.intersects(result.rectForPoint(x, y))) + return false; + + // Check children first. + for (InlineBox* curr = lastChild(); curr; curr = curr->prevOnLine()) { + if ((curr->renderer()->isText() || !curr->boxModelObject()->hasSelfPaintingLayer()) && curr->nodeAtPoint(request, result, x, y, tx, ty)) { + renderer()->updateHitTestResult(result, IntPoint(x - tx, y - ty)); + return true; + } + } + + // Now check ourselves. + IntPoint boxOrigin = locationIncludingFlipping(); + boxOrigin.move(tx, ty); + IntRect rect(boxOrigin, IntSize(width(), height())); + if (visibleToHitTesting() && rect.intersects(result.rectForPoint(x, y))) { + renderer()->updateHitTestResult(result, flipForWritingMode(IntPoint(x - tx, y - ty))); // Don't add in m_x or m_y here, we want coords in the containing block's space. + if (!result.addNodeToRectBasedTestResult(renderer()->node(), x, y, rect)) + return true; + } + + return false; +} + +void InlineFlowBox::paint(PaintInfo& paintInfo, int tx, int ty) +{ + IntRect overflowRect(visualOverflowRect()); + overflowRect.inflate(renderer()->maximalOutlineSize(paintInfo.phase)); + flipForWritingMode(overflowRect); + overflowRect.move(tx, ty); + + if (!paintInfo.rect.intersects(overflowRect)) + return; + + if (paintInfo.phase != PaintPhaseChildOutlines) { + if (paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) { + // Add ourselves to the paint info struct's list of inlines that need to paint their + // outlines. + if (renderer()->style()->visibility() == VISIBLE && renderer()->hasOutline() && !isRootInlineBox()) { + RenderInline* inlineFlow = toRenderInline(renderer()); + + RenderBlock* cb = 0; + bool containingBlockPaintsContinuationOutline = inlineFlow->continuation() || inlineFlow->isInlineElementContinuation(); + if (containingBlockPaintsContinuationOutline) { + cb = renderer()->containingBlock()->containingBlock(); + + for (RenderBoxModelObject* box = boxModelObject(); box != cb; box = box->parent()->enclosingBoxModelObject()) { + if (box->hasSelfPaintingLayer()) { + containingBlockPaintsContinuationOutline = false; + break; + } + } + } + + if (containingBlockPaintsContinuationOutline) { + // Add ourselves to the containing block of the entire continuation so that it can + // paint us atomically. + cb->addContinuationWithOutline(toRenderInline(renderer()->node()->renderer())); + } else if (!inlineFlow->isInlineElementContinuation()) + paintInfo.outlineObjects->add(inlineFlow); + } + } else if (paintInfo.phase == PaintPhaseMask) { + paintMask(paintInfo, tx, ty); + return; + } else { + // Paint our background, border and box-shadow. + paintBoxDecorations(paintInfo, tx, ty); + } + } + + if (paintInfo.phase == PaintPhaseMask) + return; + + PaintPhase paintPhase = paintInfo.phase == PaintPhaseChildOutlines ? PaintPhaseOutline : paintInfo.phase; + PaintInfo childInfo(paintInfo); + childInfo.phase = paintPhase; + childInfo.updatePaintingRootForChildren(renderer()); + + // Paint our children. + if (paintPhase != PaintPhaseSelfOutline) { + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->renderer()->isText() || !curr->boxModelObject()->hasSelfPaintingLayer()) + curr->paint(childInfo, tx, ty); + } + } +} + +void InlineFlowBox::paintFillLayers(const PaintInfo& paintInfo, const Color& c, const FillLayer* fillLayer, int _tx, int _ty, int w, int h, CompositeOperator op) +{ + if (!fillLayer) + return; + paintFillLayers(paintInfo, c, fillLayer->next(), _tx, _ty, w, h, op); + paintFillLayer(paintInfo, c, fillLayer, _tx, _ty, w, h, op); +} + +void InlineFlowBox::paintFillLayer(const PaintInfo& paintInfo, const Color& c, const FillLayer* fillLayer, int tx, int ty, int w, int h, CompositeOperator op) +{ + StyleImage* img = fillLayer->image(); + bool hasFillImage = img && img->canRender(renderer()->style()->effectiveZoom()); + if ((!hasFillImage && !renderer()->style()->hasBorderRadius()) || (!prevLineBox() && !nextLineBox()) || !parent()) + boxModelObject()->paintFillLayerExtended(paintInfo, c, fillLayer, tx, ty, w, h, this, op); + else { + // We have a fill image that spans multiple lines. + // We need to adjust tx and ty by the width of all previous lines. + // Think of background painting on inlines as though you had one long line, a single continuous + // strip. Even though that strip has been broken up across multiple lines, you still paint it + // as though you had one single line. This means each line has to pick up the background where + // the previous line left off. + // FIXME: What the heck do we do with RTL here? The math we're using is obviously not right, + // but it isn't even clear how this should work at all. + int logicalOffsetOnLine = 0; + for (InlineFlowBox* curr = prevLineBox(); curr; curr = curr->prevLineBox()) + logicalOffsetOnLine += curr->logicalWidth(); + int totalLogicalWidth = logicalOffsetOnLine; + for (InlineFlowBox* curr = this; curr; curr = curr->nextLineBox()) + totalLogicalWidth += curr->logicalWidth(); + int stripX = tx - (isHorizontal() ? logicalOffsetOnLine : 0); + int stripY = ty - (isHorizontal() ? 0 : logicalOffsetOnLine); + int stripWidth = isHorizontal() ? totalLogicalWidth : width(); + int stripHeight = isHorizontal() ? height() : totalLogicalWidth; + paintInfo.context->save(); + paintInfo.context->clip(IntRect(tx, ty, width(), height())); + boxModelObject()->paintFillLayerExtended(paintInfo, c, fillLayer, stripX, stripY, stripWidth, stripHeight, this, op); + paintInfo.context->restore(); + } +} + +void InlineFlowBox::paintBoxShadow(GraphicsContext* context, RenderStyle* s, ShadowStyle shadowStyle, int tx, int ty, int w, int h) +{ + if ((!prevLineBox() && !nextLineBox()) || !parent()) + boxModelObject()->paintBoxShadow(context, tx, ty, w, h, s, shadowStyle); + else { + // FIXME: We can do better here in the multi-line case. We want to push a clip so that the shadow doesn't + // protrude incorrectly at the edges, and we want to possibly include shadows cast from the previous/following lines + boxModelObject()->paintBoxShadow(context, tx, ty, w, h, s, shadowStyle, includeLogicalLeftEdge(), includeLogicalRightEdge()); + } +} + +void InlineFlowBox::paintBoxDecorations(PaintInfo& paintInfo, int tx, int ty) +{ + if (!paintInfo.shouldPaintWithinRoot(renderer()) || renderer()->style()->visibility() != VISIBLE || paintInfo.phase != PaintPhaseForeground) + return; + + int x = m_x; + int y = m_y; + int w = width(); + int h = height(); + + // Constrain our background/border painting to the line top and bottom if necessary. + bool noQuirksMode = renderer()->document()->inNoQuirksMode(); + if (!hasTextChildren() && !noQuirksMode) { + RootInlineBox* rootBox = root(); + int& top = isHorizontal() ? y : x; + int& logicalHeight = isHorizontal() ? h : w; + int bottom = min(rootBox->lineBottom(), top + logicalHeight); + top = max(rootBox->lineTop(), top); + logicalHeight = bottom - top; + } + + // Move x/y to our coordinates. + IntRect localRect(x, y, w, h); + flipForWritingMode(localRect); + tx += localRect.x(); + ty += localRect.y(); + + GraphicsContext* context = paintInfo.context; + + // You can use p::first-line to specify a background. If so, the root line boxes for + // a line may actually have to paint a background. + RenderStyle* styleToUse = renderer()->style(m_firstLine); + if ((!parent() && m_firstLine && styleToUse != renderer()->style()) || (parent() && renderer()->hasBoxDecorations())) { + // Shadow comes first and is behind the background and border. + if (styleToUse->boxShadow()) + paintBoxShadow(context, styleToUse, Normal, tx, ty, w, h); + + Color c = styleToUse->visitedDependentColor(CSSPropertyBackgroundColor); + paintFillLayers(paintInfo, c, styleToUse->backgroundLayers(), tx, ty, w, h); + + if (styleToUse->boxShadow()) + paintBoxShadow(context, styleToUse, Inset, tx, ty, w, h); + + // :first-line cannot be used to put borders on a line. Always paint borders with our + // non-first-line style. + if (parent() && renderer()->style()->hasBorder()) { + StyleImage* borderImage = renderer()->style()->borderImage().image(); + bool hasBorderImage = borderImage && borderImage->canRender(styleToUse->effectiveZoom()); + if (hasBorderImage && !borderImage->isLoaded()) + return; // Don't paint anything while we wait for the image to load. + + // The simple case is where we either have no border image or we are the only box for this object. In those + // cases only a single call to draw is required. + if (!hasBorderImage || (!prevLineBox() && !nextLineBox())) + boxModelObject()->paintBorder(context, tx, ty, w, h, renderer()->style(), includeLogicalLeftEdge(), includeLogicalRightEdge()); + else { + // We have a border image that spans multiple lines. + // We need to adjust tx and ty by the width of all previous lines. + // Think of border image painting on inlines as though you had one long line, a single continuous + // strip. Even though that strip has been broken up across multiple lines, you still paint it + // as though you had one single line. This means each line has to pick up the image where + // the previous line left off. + // FIXME: What the heck do we do with RTL here? The math we're using is obviously not right, + // but it isn't even clear how this should work at all. + int logicalOffsetOnLine = 0; + for (InlineFlowBox* curr = prevLineBox(); curr; curr = curr->prevLineBox()) + logicalOffsetOnLine += curr->logicalWidth(); + int totalLogicalWidth = logicalOffsetOnLine; + for (InlineFlowBox* curr = this; curr; curr = curr->nextLineBox()) + totalLogicalWidth += curr->logicalWidth(); + int stripX = tx - (isHorizontal() ? logicalOffsetOnLine : 0); + int stripY = ty - (isHorizontal() ? 0 : logicalOffsetOnLine); + int stripWidth = isHorizontal() ? totalLogicalWidth : w; + int stripHeight = isHorizontal() ? h : totalLogicalWidth; + context->save(); + context->clip(IntRect(tx, ty, w, h)); + boxModelObject()->paintBorder(context, stripX, stripY, stripWidth, stripHeight, renderer()->style()); + context->restore(); + } + } + } +} + +void InlineFlowBox::paintMask(PaintInfo& paintInfo, int tx, int ty) +{ + if (!paintInfo.shouldPaintWithinRoot(renderer()) || renderer()->style()->visibility() != VISIBLE || paintInfo.phase != PaintPhaseMask) + return; + + int x = m_x; + int y = m_y; + int w = width(); + int h = height(); + + // Constrain our background/border painting to the line top and bottom if necessary. + bool noQuirksMode = renderer()->document()->inNoQuirksMode(); + if (!hasTextChildren() && !noQuirksMode) { + RootInlineBox* rootBox = root(); + int& top = isHorizontal() ? y : x; + int& logicalHeight = isHorizontal() ? h : w; + int bottom = min(rootBox->lineBottom(), top + logicalHeight); + top = max(rootBox->lineTop(), top); + logicalHeight = bottom - top; + } + + // Move x/y to our coordinates. + IntRect localRect(x, y, w, h); + flipForWritingMode(localRect); + tx += localRect.x(); + ty += localRect.y(); + + const NinePieceImage& maskNinePieceImage = renderer()->style()->maskBoxImage(); + StyleImage* maskBoxImage = renderer()->style()->maskBoxImage().image(); + + // Figure out if we need to push a transparency layer to render our mask. + bool pushTransparencyLayer = false; + bool compositedMask = renderer()->hasLayer() && boxModelObject()->layer()->hasCompositedMask(); + CompositeOperator compositeOp = CompositeSourceOver; + if (!compositedMask) { + if ((maskBoxImage && renderer()->style()->maskLayers()->hasImage()) || renderer()->style()->maskLayers()->next()) + pushTransparencyLayer = true; + + compositeOp = CompositeDestinationIn; + if (pushTransparencyLayer) { + paintInfo.context->setCompositeOperation(CompositeDestinationIn); + paintInfo.context->beginTransparencyLayer(1.0f); + compositeOp = CompositeSourceOver; + } + } + + paintFillLayers(paintInfo, Color(), renderer()->style()->maskLayers(), tx, ty, w, h, compositeOp); + + bool hasBoxImage = maskBoxImage && maskBoxImage->canRender(renderer()->style()->effectiveZoom()); + if (!hasBoxImage || !maskBoxImage->isLoaded()) + return; // Don't paint anything while we wait for the image to load. + + // The simple case is where we are the only box for this object. In those + // cases only a single call to draw is required. + if (!prevLineBox() && !nextLineBox()) { + boxModelObject()->paintNinePieceImage(paintInfo.context, tx, ty, w, h, renderer()->style(), maskNinePieceImage, compositeOp); + } else { + // We have a mask image that spans multiple lines. + // We need to adjust _tx and _ty by the width of all previous lines. + int logicalOffsetOnLine = 0; + for (InlineFlowBox* curr = prevLineBox(); curr; curr = curr->prevLineBox()) + logicalOffsetOnLine += curr->logicalWidth(); + int totalLogicalWidth = logicalOffsetOnLine; + for (InlineFlowBox* curr = this; curr; curr = curr->nextLineBox()) + totalLogicalWidth += curr->logicalWidth(); + int stripX = tx - (isHorizontal() ? logicalOffsetOnLine : 0); + int stripY = ty - (isHorizontal() ? 0 : logicalOffsetOnLine); + int stripWidth = isHorizontal() ? totalLogicalWidth : w; + int stripHeight = isHorizontal() ? h : totalLogicalWidth; + paintInfo.context->save(); + paintInfo.context->clip(IntRect(tx, ty, w, h)); + boxModelObject()->paintNinePieceImage(paintInfo.context, stripX, stripY, stripWidth, stripHeight, renderer()->style(), maskNinePieceImage, compositeOp); + paintInfo.context->restore(); + } + + if (pushTransparencyLayer) + paintInfo.context->endTransparencyLayer(); +} + +InlineBox* InlineFlowBox::firstLeafChild() const +{ + InlineBox* leaf = 0; + for (InlineBox* child = firstChild(); child && !leaf; child = child->nextOnLine()) + leaf = child->isLeaf() ? child : static_cast<InlineFlowBox*>(child)->firstLeafChild(); + return leaf; +} + +InlineBox* InlineFlowBox::lastLeafChild() const +{ + InlineBox* leaf = 0; + for (InlineBox* child = lastChild(); child && !leaf; child = child->prevOnLine()) + leaf = child->isLeaf() ? child : static_cast<InlineFlowBox*>(child)->lastLeafChild(); + return leaf; +} + +RenderObject::SelectionState InlineFlowBox::selectionState() +{ + return RenderObject::SelectionNone; +} + +bool InlineFlowBox::canAccommodateEllipsis(bool ltr, int blockEdge, int ellipsisWidth) +{ + for (InlineBox *box = firstChild(); box; box = box->nextOnLine()) { + if (!box->canAccommodateEllipsis(ltr, blockEdge, ellipsisWidth)) + return false; + } + return true; +} + +int InlineFlowBox::placeEllipsisBox(bool ltr, int blockLeftEdge, int blockRightEdge, int ellipsisWidth, bool& foundBox) +{ + int result = -1; + // We iterate over all children, the foundBox variable tells us when we've found the + // box containing the ellipsis. All boxes after that one in the flow are hidden. + // If our flow is ltr then iterate over the boxes from left to right, otherwise iterate + // from right to left. Varying the order allows us to correctly hide the boxes following the ellipsis. + InlineBox *box = ltr ? firstChild() : lastChild(); + + // NOTE: these will cross after foundBox = true. + int visibleLeftEdge = blockLeftEdge; + int visibleRightEdge = blockRightEdge; + + while (box) { + int currResult = box->placeEllipsisBox(ltr, visibleLeftEdge, visibleRightEdge, ellipsisWidth, foundBox); + if (currResult != -1 && result == -1) + result = currResult; + + if (ltr) { + visibleLeftEdge += box->logicalWidth(); + box = box->nextOnLine(); + } + else { + visibleRightEdge -= box->logicalWidth(); + box = box->prevOnLine(); + } + } + return result; +} + +void InlineFlowBox::clearTruncation() +{ + for (InlineBox *box = firstChild(); box; box = box->nextOnLine()) + box->clearTruncation(); +} + +int InlineFlowBox::computeOverAnnotationAdjustment(int allowedPosition) const +{ + int result = 0; + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->renderer()->isPositioned()) + continue; // Positioned placeholders don't affect calculations. + + if (curr->isInlineFlowBox()) + result = max(result, static_cast<InlineFlowBox*>(curr)->computeOverAnnotationAdjustment(allowedPosition)); + + if (curr->renderer()->isReplaced() && curr->renderer()->isRubyRun()) { + RenderRubyRun* rubyRun = static_cast<RenderRubyRun*>(curr->renderer()); + RenderRubyText* rubyText = rubyRun->rubyText(); + if (!rubyText) + continue; + + if (!rubyRun->style()->isFlippedLinesWritingMode()) { + int topOfFirstRubyTextLine = rubyText->logicalTop() + (rubyText->firstRootBox() ? rubyText->firstRootBox()->lineTop() : 0); + if (topOfFirstRubyTextLine >= 0) + continue; + topOfFirstRubyTextLine += curr->logicalTop(); + result = max(result, allowedPosition - topOfFirstRubyTextLine); + } else { + int bottomOfLastRubyTextLine = rubyText->logicalTop() + (rubyText->lastRootBox() ? rubyText->lastRootBox()->lineBottom() : rubyText->logicalHeight()); + if (bottomOfLastRubyTextLine <= curr->logicalHeight()) + continue; + bottomOfLastRubyTextLine += curr->logicalTop(); + result = max(result, bottomOfLastRubyTextLine - allowedPosition); + } + } + + if (curr->isInlineTextBox()) { + RenderStyle* style = curr->renderer()->style(m_firstLine); + TextEmphasisPosition emphasisMarkPosition; + if (style->textEmphasisMark() != TextEmphasisMarkNone && static_cast<InlineTextBox*>(curr)->getEmphasisMarkPosition(style, emphasisMarkPosition) && emphasisMarkPosition == TextEmphasisPositionOver) { + if (!style->isFlippedLinesWritingMode()) { + int topOfEmphasisMark = curr->logicalTop() - style->font().emphasisMarkHeight(style->textEmphasisMarkString()); + result = max(result, allowedPosition - topOfEmphasisMark); + } else { + int bottomOfEmphasisMark = curr->logicalBottom() + style->font().emphasisMarkHeight(style->textEmphasisMarkString()); + result = max(result, bottomOfEmphasisMark - allowedPosition); + } + } + } + } + return result; +} + +int InlineFlowBox::computeUnderAnnotationAdjustment(int allowedPosition) const +{ + int result = 0; + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->renderer()->isPositioned()) + continue; // Positioned placeholders don't affect calculations. + + if (curr->isInlineFlowBox()) + result = max(result, static_cast<InlineFlowBox*>(curr)->computeUnderAnnotationAdjustment(allowedPosition)); + + if (curr->isInlineTextBox()) { + RenderStyle* style = curr->renderer()->style(m_firstLine); + if (style->textEmphasisMark() != TextEmphasisMarkNone && style->textEmphasisPosition() == TextEmphasisPositionUnder) { + if (!style->isFlippedLinesWritingMode()) { + int bottomOfEmphasisMark = curr->logicalBottom() + style->font().emphasisMarkHeight(style->textEmphasisMarkString()); + result = max(result, bottomOfEmphasisMark - allowedPosition); + } else { + int topOfEmphasisMark = curr->logicalTop() - style->font().emphasisMarkHeight(style->textEmphasisMarkString()); + result = max(result, allowedPosition - topOfEmphasisMark); + } + } + } + } + return result; +} + +#ifndef NDEBUG + +void InlineFlowBox::checkConsistency() const +{ +#ifdef CHECK_CONSISTENCY + ASSERT(!m_hasBadChildList); + const InlineBox* prev = 0; + for (const InlineBox* child = m_firstChild; child; child = child->nextOnLine()) { + ASSERT(child->parent() == this); + ASSERT(child->prevOnLine() == prev); + prev = child; + } + ASSERT(prev == m_lastChild); +#endif +} + +#endif + +} // namespace WebCore diff --git a/Source/WebCore/rendering/InlineFlowBox.h b/Source/WebCore/rendering/InlineFlowBox.h new file mode 100644 index 0000000..63263fd --- /dev/null +++ b/Source/WebCore/rendering/InlineFlowBox.h @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2003, 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef InlineFlowBox_h +#define InlineFlowBox_h + +#include "InlineBox.h" +#include "RenderOverflow.h" + +namespace WebCore { + +class HitTestRequest; +class HitTestResult; +class InlineTextBox; +class RenderLineBoxList; +class VerticalPositionCache; + +typedef HashMap<const InlineTextBox*, pair<Vector<const SimpleFontData*>, GlyphOverflow> > GlyphOverflowAndFallbackFontsMap; + +class InlineFlowBox : public InlineBox { +public: + InlineFlowBox(RenderObject* obj) + : InlineBox(obj) + , m_firstChild(0) + , m_lastChild(0) + , m_prevLineBox(0) + , m_nextLineBox(0) + , m_includeLogicalLeftEdge(false) + , m_includeLogicalRightEdge(false) +#ifndef NDEBUG + , m_hasBadChildList(false) +#endif + { + // Internet Explorer and Firefox always create a marker for list items, even when the list-style-type is none. We do not make a marker + // in the list-style-type: none case, since it is wasteful to do so. However, in order to match other browsers we have to pretend like + // an invisible marker exists. The side effect of having an invisible marker is that the quirks mode behavior of shrinking lines with no + // text children must not apply. This change also means that gaps will exist between image bullet list items. Even when the list bullet + // is an image, the line is still considered to be immune from the quirk. + m_hasTextChildren = obj->style()->display() == LIST_ITEM; + } + +#ifndef NDEBUG + virtual ~InlineFlowBox(); +#endif + + InlineFlowBox* prevLineBox() const { return m_prevLineBox; } + InlineFlowBox* nextLineBox() const { return m_nextLineBox; } + void setNextLineBox(InlineFlowBox* n) { m_nextLineBox = n; } + void setPreviousLineBox(InlineFlowBox* p) { m_prevLineBox = p; } + + InlineBox* firstChild() const { checkConsistency(); return m_firstChild; } + InlineBox* lastChild() const { checkConsistency(); return m_lastChild; } + + virtual bool isLeaf() const { return false; } + + InlineBox* firstLeafChild() const; + InlineBox* lastLeafChild() const; + + virtual void setConstructed() + { + InlineBox::setConstructed(); + for (InlineBox* child = firstChild(); child; child = child->next()) + child->setConstructed(); + } + + void addToLine(InlineBox* child); + virtual void deleteLine(RenderArena*); + virtual void extractLine(); + virtual void attachLine(); + virtual void adjustPosition(int dx, int dy); + + virtual void extractLineBoxFromRenderObject(); + virtual void attachLineBoxToRenderObject(); + virtual void removeLineBoxFromRenderObject(); + + virtual void clearTruncation(); + + virtual void paintBoxDecorations(PaintInfo&, int tx, int ty); + virtual void paintMask(PaintInfo&, int tx, int ty); + void paintFillLayers(const PaintInfo&, const Color&, const FillLayer*, int tx, int ty, int w, int h, CompositeOperator = CompositeSourceOver); + void paintFillLayer(const PaintInfo&, const Color&, const FillLayer*, int tx, int ty, int w, int h, CompositeOperator = CompositeSourceOver); + void paintBoxShadow(GraphicsContext*, RenderStyle*, ShadowStyle, int tx, int ty, int w, int h); + virtual void paint(PaintInfo&, int tx, int ty); + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty); + + virtual RenderLineBoxList* rendererLineBoxes() const; + + // logicalLeft = left in a horizontal line and top in a vertical line. + int marginBorderPaddingLogicalLeft() const { return marginLogicalLeft() + borderLogicalLeft() + paddingLogicalLeft(); } + int marginBorderPaddingLogicalRight() const { return marginLogicalRight() + borderLogicalRight() + paddingLogicalRight(); } + int marginLogicalLeft() const + { + if (!includeLogicalLeftEdge()) + return 0; + return isHorizontal() ? boxModelObject()->marginLeft() : boxModelObject()->marginTop(); + } + int marginLogicalRight() const + { + if (!includeLogicalRightEdge()) + return 0; + return isHorizontal() ? boxModelObject()->marginRight() : boxModelObject()->marginBottom(); + } + int borderLogicalLeft() const + { + if (!includeLogicalLeftEdge()) + return 0; + return isHorizontal() ? renderer()->style()->borderLeftWidth() : renderer()->style()->borderTopWidth(); + } + int borderLogicalRight() const + { + if (!includeLogicalRightEdge()) + return 0; + return isHorizontal() ? renderer()->style()->borderRightWidth() : renderer()->style()->borderBottomWidth(); + } + int paddingLogicalLeft() const + { + if (!includeLogicalLeftEdge()) + return 0; + return isHorizontal() ? boxModelObject()->paddingLeft() : boxModelObject()->paddingTop(); + } + int paddingLogicalRight() const + { + if (!includeLogicalRightEdge()) + return 0; + return isHorizontal() ? boxModelObject()->paddingRight() : boxModelObject()->paddingBottom(); + } + + bool includeLogicalLeftEdge() const { return m_includeLogicalLeftEdge; } + bool includeLogicalRightEdge() const { return m_includeLogicalRightEdge; } + void setEdges(bool includeLeft, bool includeRight) + { + m_includeLogicalLeftEdge = includeLeft; + m_includeLogicalRightEdge = includeRight; + } + + // Helper functions used during line construction and placement. + void determineSpacingForFlowBoxes(bool lastLine, RenderObject* endObject); + int getFlowSpacingLogicalWidth(); + bool onEndChain(RenderObject* endObject); + int placeBoxesInInlineDirection(int logicalLeft, bool& needsWordSpacing, GlyphOverflowAndFallbackFontsMap&); + void computeLogicalBoxHeights(int& maxPositionTop, int& maxPositionBottom, + int& maxAscent, int& maxDescent, bool& setMaxAscent, bool& setMaxDescent, + bool strictMode, GlyphOverflowAndFallbackFontsMap&, FontBaseline, VerticalPositionCache&); + void adjustMaxAscentAndDescent(int& maxAscent, int& maxDescent, + int maxPositionTop, int maxPositionBottom); + void placeBoxesInBlockDirection(int logicalTop, int maxHeight, int maxAscent, bool strictMode, int& lineTop, int& lineBottom, bool& setLineTop, + int& lineTopIncludingMargins, int& lineBottomIncludingMargins, bool& hasAnnotationsBefore, bool& hasAnnotationsAfter, FontBaseline); + void flipLinesInBlockDirection(int lineTop, int lineBottom); + bool requiresIdeographicBaseline(const GlyphOverflowAndFallbackFontsMap&) const; + + int computeOverAnnotationAdjustment(int allowedPosition) const; + int computeUnderAnnotationAdjustment(int allowedPosition) const; + + void computeOverflow(int lineTop, int lineBottom, bool strictMode, GlyphOverflowAndFallbackFontsMap&); + + void removeChild(InlineBox* child); + + virtual RenderObject::SelectionState selectionState(); + + virtual bool canAccommodateEllipsis(bool ltr, int blockEdge, int ellipsisWidth); + virtual int placeEllipsisBox(bool ltr, int blockLeftEdge, int blockRightEdge, int ellipsisWidth, bool&); + + bool hasTextChildren() const { return m_hasTextChildren; } + + void checkConsistency() const; + void setHasBadChildList(); + + // Line visual and layout overflow are in the coordinate space of the block. This means that - unlike other unprefixed uses of the words + // top/right/bottom/left in the code - these aren't purely physical directions. For horizontal-tb and vertical-lr they will match physical + // directions, but for horizontal-bt and vertical-rl, the top/bottom and left/right respectively are inverted when compared to + // their physical counterparts. + int topLayoutOverflow() const { return m_overflow ? m_overflow->topLayoutOverflow() : m_y; } + int bottomLayoutOverflow() const { return m_overflow ? m_overflow->bottomLayoutOverflow() : m_y + height(); } + int leftLayoutOverflow() const { return m_overflow ? m_overflow->leftLayoutOverflow() : m_x; } + int rightLayoutOverflow() const { return m_overflow ? m_overflow->rightLayoutOverflow() : m_x + width(); } + IntRect layoutOverflowRect() const { return m_overflow ? m_overflow->layoutOverflowRect() : IntRect(m_x, m_y, width(), height()); } + int logicalLeftLayoutOverflow() const { return renderer()->style()->isHorizontalWritingMode() ? leftLayoutOverflow() : topLayoutOverflow(); } + int logicalRightLayoutOverflow() const { return renderer()->style()->isHorizontalWritingMode() ? rightLayoutOverflow() : bottomLayoutOverflow(); } + int logicalTopLayoutOverflow() const { return renderer()->style()->isHorizontalWritingMode() ? topVisualOverflow() : leftVisualOverflow(); } + int logicalBottomLayoutOverflow() const { return renderer()->style()->isHorizontalWritingMode() ? bottomLayoutOverflow() : rightLayoutOverflow(); } + IntRect logicalLayoutOverflowRect() const + { + IntRect result = layoutOverflowRect(); + if (!renderer()->style()->isHorizontalWritingMode()) + result = result.transposedRect(); + return result; + } + + int topVisualOverflow() const { return m_overflow ? m_overflow->topVisualOverflow() : m_y; } + int bottomVisualOverflow() const { return m_overflow ? m_overflow->bottomVisualOverflow() : m_y + height(); } + int leftVisualOverflow() const { return m_overflow ? m_overflow->leftVisualOverflow() : m_x; } + int rightVisualOverflow() const { return m_overflow ? m_overflow->rightVisualOverflow() : m_x + width(); } + IntRect visualOverflowRect() const { return m_overflow ? m_overflow->visualOverflowRect() : IntRect(m_x, m_y, width(), height()); } + int logicalLeftVisualOverflow() const { return renderer()->style()->isHorizontalWritingMode() ? leftVisualOverflow() : topVisualOverflow(); } + int logicalRightVisualOverflow() const { return renderer()->style()->isHorizontalWritingMode() ? rightVisualOverflow() : bottomVisualOverflow(); } + int logicalTopVisualOverflow() const { return renderer()->style()->isHorizontalWritingMode() ? topVisualOverflow() : leftVisualOverflow(); } + int logicalBottomVisualOverflow() const { return renderer()->style()->isHorizontalWritingMode() ? bottomVisualOverflow() : rightVisualOverflow(); } + IntRect logicalVisualOverflowRect() const + { + IntRect result = visualOverflowRect(); + if (!renderer()->style()->isHorizontalWritingMode()) + result = result.transposedRect(); + return result; + } + + void setOverflowFromLogicalRects(const IntRect& logicalLayoutOverflow, const IntRect& logicalVisualOverflow); + void setLayoutOverflow(const IntRect&); + void setVisualOverflow(const IntRect&); + +private: + void addBoxShadowVisualOverflow(IntRect& logicalVisualOverflow); + void addTextBoxVisualOverflow(const InlineTextBox*, GlyphOverflowAndFallbackFontsMap&, IntRect& logicalVisualOverflow); + void addReplacedChildOverflow(const InlineBox*, IntRect& logicalLayoutOverflow, IntRect& logicalVisualOverflow); + +protected: + OwnPtr<RenderOverflow> m_overflow; + + virtual bool isInlineFlowBox() const { return true; } + + InlineBox* m_firstChild; + InlineBox* m_lastChild; + + InlineFlowBox* m_prevLineBox; // The previous box that also uses our RenderObject + InlineFlowBox* m_nextLineBox; // The next box that also uses our RenderObject + + bool m_includeLogicalLeftEdge : 1; + bool m_includeLogicalRightEdge : 1; + bool m_hasTextChildren : 1; + +#ifndef NDEBUG + bool m_hasBadChildList; +#endif +}; + +#ifdef NDEBUG +inline void InlineFlowBox::checkConsistency() const +{ +} +#endif + +inline void InlineFlowBox::setHasBadChildList() +{ +#ifndef NDEBUG + m_hasBadChildList = true; +#endif +} + +} // namespace WebCore + +#ifndef NDEBUG +// Outside the WebCore namespace for ease of invocation from gdb. +void showTree(const WebCore::InlineFlowBox*); +#endif + +#endif // InlineFlowBox_h diff --git a/Source/WebCore/rendering/InlineIterator.h b/Source/WebCore/rendering/InlineIterator.h new file mode 100644 index 0000000..270364f --- /dev/null +++ b/Source/WebCore/rendering/InlineIterator.h @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * Copyright (C) 2003, 2004, 2006, 2007, 2008, 2009, 2010 Apple Inc. All right reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef InlineIterator_h +#define InlineIterator_h + +#include "BidiRun.h" +#include "RenderBlock.h" +#include "RenderText.h" +#include <wtf/AlwaysInline.h> +#include <wtf/StdLibExtras.h> + +namespace WebCore { + +class InlineIterator { +public: + InlineIterator() + : block(0) + , obj(0) + , pos(0) + , nextBreakablePosition(-1) + { + } + + InlineIterator(RenderBlock* b, RenderObject* o, unsigned p) + : block(b) + , obj(o) + , pos(p) + , nextBreakablePosition(-1) + { + } + + void increment(InlineBidiResolver* resolver = 0); + bool atEnd() const; + + UChar current() const; + ALWAYS_INLINE WTF::Unicode::Direction direction() const; + + RenderBlock* block; + RenderObject* obj; + unsigned pos; + int nextBreakablePosition; +}; + +inline bool operator==(const InlineIterator& it1, const InlineIterator& it2) +{ + return it1.pos == it2.pos && it1.obj == it2.obj; +} + +inline bool operator!=(const InlineIterator& it1, const InlineIterator& it2) +{ + return it1.pos != it2.pos || it1.obj != it2.obj; +} + +static inline RenderObject* bidiNext(RenderBlock* block, RenderObject* current, InlineBidiResolver* resolver = 0, bool skipInlines = true, bool* endOfInlinePtr = 0) +{ + RenderObject* next = 0; + bool oldEndOfInline = endOfInlinePtr ? *endOfInlinePtr : false; + bool endOfInline = false; + + while (current) { + next = 0; + if (!oldEndOfInline && !current->isFloating() && !current->isReplaced() && !current->isPositioned() && !current->isText()) { + next = current->firstChild(); + if (next && resolver && next->isRenderInline()) { + EUnicodeBidi ub = next->style()->unicodeBidi(); + if (ub != UBNormal) { + TextDirection dir = next->style()->direction(); + WTF::Unicode::Direction d = (ub == Embed + ? (dir == RTL ? WTF::Unicode::RightToLeftEmbedding : WTF::Unicode::LeftToRightEmbedding) + : (dir == RTL ? WTF::Unicode::RightToLeftOverride : WTF::Unicode::LeftToRightOverride)); + resolver->embed(d); + } + } + } + + if (!next) { + if (!skipInlines && !oldEndOfInline && current->isRenderInline()) { + next = current; + endOfInline = true; + break; + } + + while (current && current != block) { + if (resolver && current->isRenderInline() && current->style()->unicodeBidi() != UBNormal) + resolver->embed(WTF::Unicode::PopDirectionalFormat); + + next = current->nextSibling(); + if (next) { + if (resolver && next->isRenderInline()) { + EUnicodeBidi ub = next->style()->unicodeBidi(); + if (ub != UBNormal) { + TextDirection dir = next->style()->direction(); + WTF::Unicode::Direction d = (ub == Embed + ? (dir == RTL ? WTF::Unicode::RightToLeftEmbedding: WTF::Unicode::LeftToRightEmbedding) + : (dir == RTL ? WTF::Unicode::RightToLeftOverride : WTF::Unicode::LeftToRightOverride)); + resolver->embed(d); + } + } + break; + } + + current = current->parent(); + if (!skipInlines && current && current != block && current->isRenderInline()) { + next = current; + endOfInline = true; + break; + } + } + } + + if (!next) + break; + + if (next->isText() || next->isFloating() || next->isReplaced() || next->isPositioned() + || ((!skipInlines || !next->firstChild()) // Always return EMPTY inlines. + && next->isRenderInline())) + break; + current = next; + } + + if (endOfInlinePtr) + *endOfInlinePtr = endOfInline; + + return next; +} + +static inline RenderObject* bidiFirst(RenderBlock* block, InlineBidiResolver* resolver, bool skipInlines = true) +{ + if (!block->firstChild()) + return 0; + + RenderObject* o = block->firstChild(); + if (o->isRenderInline()) { + if (resolver) { + EUnicodeBidi ub = o->style()->unicodeBidi(); + if (ub != UBNormal) { + TextDirection dir = o->style()->direction(); + WTF::Unicode::Direction d = (ub == Embed + ? (dir == RTL ? WTF::Unicode::RightToLeftEmbedding : WTF::Unicode::LeftToRightEmbedding) + : (dir == RTL ? WTF::Unicode::RightToLeftOverride : WTF::Unicode::LeftToRightOverride)); + resolver->embed(d); + } + } + if (skipInlines && o->firstChild()) + o = bidiNext(block, o, resolver, skipInlines); + else { + // Never skip empty inlines. + if (resolver) + resolver->commitExplicitEmbedding(); + return o; + } + } + + if (o && !o->isText() && !o->isReplaced() && !o->isFloating() && !o->isPositioned()) + o = bidiNext(block, o, resolver, skipInlines); + + if (resolver) + resolver->commitExplicitEmbedding(); + return o; +} + +inline void InlineIterator::increment(InlineBidiResolver* resolver) +{ + if (!obj) + return; + if (obj->isText()) { + pos++; + if (pos >= toRenderText(obj)->textLength()) { + obj = bidiNext(block, obj, resolver); + pos = 0; + nextBreakablePosition = -1; + } + } else { + obj = bidiNext(block, obj, resolver); + pos = 0; + nextBreakablePosition = -1; + } +} + +inline bool InlineIterator::atEnd() const +{ + return !obj; +} + +inline UChar InlineIterator::current() const +{ + if (!obj || !obj->isText()) + return 0; + + RenderText* text = toRenderText(obj); + if (pos >= text->textLength()) + return 0; + + return text->characters()[pos]; +} + +ALWAYS_INLINE WTF::Unicode::Direction InlineIterator::direction() const +{ + if (UChar c = current()) + return WTF::Unicode::direction(c); + + if (obj && obj->isListMarker()) + return obj->style()->isLeftToRightDirection() ? WTF::Unicode::LeftToRight : WTF::Unicode::RightToLeft; + + return WTF::Unicode::OtherNeutral; +} + +template<> +inline void InlineBidiResolver::increment() +{ + current.increment(this); +} + +template <> +inline void InlineBidiResolver::appendRun() +{ + if (!emptyRun && !eor.atEnd()) { + int start = sor.pos; + RenderObject *obj = sor.obj; + while (obj && obj != eor.obj && obj != endOfLine.obj) { + RenderBlock::appendRunsForObject(start, obj->length(), obj, *this); + start = 0; + obj = bidiNext(sor.block, obj); + } + if (obj) { + unsigned pos = obj == eor.obj ? eor.pos : UINT_MAX; + if (obj == endOfLine.obj && endOfLine.pos <= pos) { + reachedEndOfLine = true; + pos = endOfLine.pos; + } + // It's OK to add runs for zero-length RenderObjects, just don't make the run larger than it should be + int end = obj->length() ? pos+1 : 0; + RenderBlock::appendRunsForObject(start, end, obj, *this); + } + + eor.increment(); + sor = eor; + } + + m_direction = WTF::Unicode::OtherNeutral; + m_status.eor = WTF::Unicode::OtherNeutral; +} + +} + +#endif // InlineIterator_h diff --git a/Source/WebCore/rendering/InlineTextBox.cpp b/Source/WebCore/rendering/InlineTextBox.cpp new file mode 100644 index 0000000..8884ed1 --- /dev/null +++ b/Source/WebCore/rendering/InlineTextBox.cpp @@ -0,0 +1,1292 @@ +/* + * (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * 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 "InlineTextBox.h" + +#include "Chrome.h" +#include "ChromeClient.h" +#include "Document.h" +#include "DocumentMarkerController.h" +#include "Editor.h" +#include "EllipsisBox.h" +#include "Frame.h" +#include "GraphicsContext.h" +#include "HitTestResult.h" +#include "Page.h" +#include "RenderArena.h" +#include "RenderBlock.h" +#include "RenderRubyRun.h" +#include "RenderRubyText.h" +#include "RenderTheme.h" +#include "Text.h" +#include "break_lines.h" +#include <wtf/AlwaysInline.h> + +using namespace std; + +namespace WebCore { + +int InlineTextBox::baselinePosition(FontBaseline baselineType) const +{ + if (!isText() || !parent()) + return 0; + return parent()->baselinePosition(baselineType); +} + +int InlineTextBox::lineHeight() const +{ + if (!isText() || !parent()) + return 0; + if (m_renderer->isBR()) + return toRenderBR(m_renderer)->lineHeight(m_firstLine); + return parent()->lineHeight(); +} + +int InlineTextBox::selectionTop() +{ + return root()->selectionTop(); +} + +int InlineTextBox::selectionBottom() +{ + return root()->selectionBottom(); +} + +int InlineTextBox::selectionHeight() +{ + return root()->selectionHeight(); +} + +bool InlineTextBox::isSelected(int startPos, int endPos) const +{ + int sPos = max(startPos - m_start, 0); + int ePos = min(endPos - m_start, (int)m_len); + return (sPos < ePos); +} + +RenderObject::SelectionState InlineTextBox::selectionState() +{ + RenderObject::SelectionState state = renderer()->selectionState(); + if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) { + int startPos, endPos; + renderer()->selectionStartEnd(startPos, endPos); + // The position after a hard line break is considered to be past its end. + int lastSelectable = start() + len() - (isLineBreak() ? 1 : 0); + + bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len); + bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= lastSelectable); + if (start && end) + state = RenderObject::SelectionBoth; + else if (start) + state = RenderObject::SelectionStart; + else if (end) + state = RenderObject::SelectionEnd; + else if ((state == RenderObject::SelectionEnd || startPos < m_start) && + (state == RenderObject::SelectionStart || endPos > lastSelectable)) + state = RenderObject::SelectionInside; + else if (state == RenderObject::SelectionBoth) + state = RenderObject::SelectionNone; + } + + // If there are ellipsis following, make sure their selection is updated. + if (m_truncation != cNoTruncation && root()->ellipsisBox()) { + EllipsisBox* ellipsis = root()->ellipsisBox(); + if (state != RenderObject::SelectionNone) { + int start, end; + selectionStartEnd(start, end); + // The ellipsis should be considered to be selected if the end of + // the selection is past the beginning of the truncation and the + // beginning of the selection is before or at the beginning of the + // truncation. + ellipsis->setSelectionState(end >= m_truncation && start <= m_truncation ? + RenderObject::SelectionInside : RenderObject::SelectionNone); + } else + ellipsis->setSelectionState(RenderObject::SelectionNone); + } + + return state; +} + +typedef Vector<UChar, 256> BufferForAppendingHyphen; + +static void adjustCharactersAndLengthForHyphen(BufferForAppendingHyphen& charactersWithHyphen, RenderStyle* style, const UChar*& characters, int& length) +{ + const AtomicString& hyphenString = style->hyphenString(); + charactersWithHyphen.reserveCapacity(length + hyphenString.length()); + charactersWithHyphen.append(characters, length); + charactersWithHyphen.append(hyphenString.characters(), hyphenString.length()); + characters = charactersWithHyphen.data(); + length += hyphenString.length(); +} + +IntRect InlineTextBox::selectionRect(int tx, int ty, int startPos, int endPos) +{ + int sPos = max(startPos - m_start, 0); + int ePos = min(endPos - m_start, (int)m_len); + + if (sPos > ePos) + return IntRect(); + + RenderText* textObj = textRenderer(); + int selTop = selectionTop(); + int selHeight = selectionHeight(); + RenderStyle* styleToUse = textObj->style(m_firstLine); + const Font& f = styleToUse->font(); + + const UChar* characters = textObj->text()->characters() + m_start; + int len = m_len; + BufferForAppendingHyphen charactersWithHyphen; + if (ePos == len && hasHyphen()) { + adjustCharactersAndLengthForHyphen(charactersWithHyphen, styleToUse, characters, len); + ePos = len; + } + +#ifdef ANDROID_DISABLE_ROUNDING_HACKS + TextRun textRun = TextRun(characters, len, textObj->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride); + if (m_disableRoundingHacks) + textRun.disableRoundingHacks(); + IntRect r = enclosingIntRect(f.selectionRectForText(textRun, IntPoint(), selHeight, sPos, ePos)); +#else + IntRect r = enclosingIntRect(f.selectionRectForText(TextRun(characters, len, textObj->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride), + IntPoint(), selHeight, sPos, ePos)); +#endif + + int logicalWidth = r.width(); + if (r.x() > m_logicalWidth) + logicalWidth = 0; + else if (r.right() > m_logicalWidth) + logicalWidth = m_logicalWidth - r.x(); + + IntPoint topPoint = isHorizontal() ? IntPoint(tx + m_x + r.x(), ty + selTop) : IntPoint(tx + selTop, ty + m_y + r.x()); + int width = isHorizontal() ? logicalWidth : selHeight; + int height = isHorizontal() ? selHeight : logicalWidth; + + return IntRect(topPoint, IntSize(width, height)); +} + +void InlineTextBox::deleteLine(RenderArena* arena) +{ + toRenderText(renderer())->removeTextBox(this); + destroy(arena); +} + +void InlineTextBox::extractLine() +{ + if (m_extracted) + return; + + toRenderText(renderer())->extractTextBox(this); +} + +void InlineTextBox::attachLine() +{ + if (!m_extracted) + return; + + toRenderText(renderer())->attachTextBox(this); +} + +int InlineTextBox::placeEllipsisBox(bool flowIsLTR, int visibleLeftEdge, int visibleRightEdge, int ellipsisWidth, bool& foundBox) +{ + if (foundBox) { + m_truncation = cFullTruncation; + return -1; + } + + // For LTR this is the left edge of the box, for RTL, the right edge in parent coordinates. + int ellipsisX = flowIsLTR ? visibleRightEdge - ellipsisWidth : visibleLeftEdge + ellipsisWidth; + + // Criteria for full truncation: + // LTR: the left edge of the ellipsis is to the left of our text run. + // RTL: the right edge of the ellipsis is to the right of our text run. + bool ltrFullTruncation = flowIsLTR && ellipsisX <= m_x; + bool rtlFullTruncation = !flowIsLTR && ellipsisX >= (m_x + m_logicalWidth); + if (ltrFullTruncation || rtlFullTruncation) { + // Too far. Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box. + m_truncation = cFullTruncation; + foundBox = true; + return -1; + } + + bool ltrEllipsisWithinBox = flowIsLTR && (ellipsisX < m_x + m_logicalWidth); + bool rtlEllipsisWithinBox = !flowIsLTR && (ellipsisX > m_x); + if (ltrEllipsisWithinBox || rtlEllipsisWithinBox) { + foundBox = true; + + // The inline box may have different directionality than it's parent. Since truncation + // behavior depends both on both the parent and the inline block's directionality, we + // must keep track of these separately. + bool ltr = isLeftToRightDirection(); + if (ltr != flowIsLTR) { + // Width in pixels of the visible portion of the box, excluding the ellipsis. + int visibleBoxWidth = visibleRightEdge - visibleLeftEdge - ellipsisWidth; + ellipsisX = ltr ? m_x + visibleBoxWidth : m_x + m_logicalWidth - visibleBoxWidth; + } + + int offset = offsetForPosition(ellipsisX, false); + if (offset == 0) { + // No characters should be rendered. Set ourselves to full truncation and place the ellipsis at the min of our start + // and the ellipsis edge. + m_truncation = cFullTruncation; + return min(ellipsisX, m_x); + } + + // Set the truncation index on the text run. + m_truncation = offset; + + // If we got here that means that we were only partially truncated and we need to return the pixel offset at which + // to place the ellipsis. + int widthOfVisibleText = toRenderText(renderer())->width(m_start, offset, textPos(), m_firstLine); + + // The ellipsis needs to be placed just after the last visible character. + // Where "after" is defined by the flow directionality, not the inline + // box directionality. + // e.g. In the case of an LTR inline box truncated in an RTL flow then we can + // have a situation such as |Hello| -> |...He| + if (flowIsLTR) + return m_x + widthOfVisibleText; + else + return (m_x + m_logicalWidth) - widthOfVisibleText - ellipsisWidth; + } + return -1; +} + +Color correctedTextColor(Color textColor, Color backgroundColor) +{ + // Adjust the text color if it is too close to the background color, + // by darkening or lightening it to move it further away. + + int d = differenceSquared(textColor, backgroundColor); + // semi-arbitrarily chose 65025 (255^2) value here after a few tests; + if (d > 65025) { + return textColor; + } + + int distanceFromWhite = differenceSquared(textColor, Color::white); + int distanceFromBlack = differenceSquared(textColor, Color::black); + + if (distanceFromWhite < distanceFromBlack) { + return textColor.dark(); + } + + return textColor.light(); +} + +void updateGraphicsContext(GraphicsContext* context, const Color& fillColor, const Color& strokeColor, float strokeThickness, ColorSpace colorSpace) +{ + TextDrawingModeFlags mode = context->textDrawingMode(); + if (strokeThickness > 0) { + TextDrawingModeFlags newMode = mode | TextModeStroke; + if (mode != newMode) { + context->setTextDrawingMode(newMode); + mode = newMode; + } + } + + if (mode & TextModeFill && (fillColor != context->fillColor() || colorSpace != context->fillColorSpace())) + context->setFillColor(fillColor, colorSpace); + + if (mode & TextModeStroke) { + if (strokeColor != context->strokeColor()) + context->setStrokeColor(strokeColor, colorSpace); + if (strokeThickness != context->strokeThickness()) + context->setStrokeThickness(strokeThickness); + } +} + +bool InlineTextBox::isLineBreak() const +{ + return renderer()->isBR() || (renderer()->style()->preserveNewline() && len() == 1 && (*textRenderer()->text())[start()] == '\n'); +} + +bool InlineTextBox::nodeAtPoint(const HitTestRequest&, HitTestResult& result, int x, int y, int tx, int ty) +{ + if (isLineBreak()) + return false; + + IntPoint boxOrigin = locationIncludingFlipping(); + boxOrigin.move(tx, ty); + IntRect rect(boxOrigin, IntSize(width(), height())); + if (m_truncation != cFullTruncation && visibleToHitTesting() && rect.intersects(result.rectForPoint(x, y))) { + renderer()->updateHitTestResult(result, flipForWritingMode(IntPoint(x - tx, y - ty))); + if (!result.addNodeToRectBasedTestResult(renderer()->node(), x, y, rect)) + return true; + } + return false; +} + +FloatSize InlineTextBox::applyShadowToGraphicsContext(GraphicsContext* context, const ShadowData* shadow, const FloatRect& textRect, bool stroked, bool opaque, bool horizontal) +{ + if (!shadow) + return FloatSize(); + + FloatSize extraOffset; + int shadowX = horizontal ? shadow->x() : shadow->y(); + int shadowY = horizontal ? shadow->y() : -shadow->x(); + FloatSize shadowOffset(shadowX, shadowY); + int shadowBlur = shadow->blur(); + const Color& shadowColor = shadow->color(); + + if (shadow->next() || stroked || !opaque) { + FloatRect shadowRect(textRect); + shadowRect.inflate(shadowBlur); + shadowRect.move(shadowOffset); + context->save(); + context->clip(shadowRect); + + extraOffset = FloatSize(0, 2 * textRect.height() + max(0.0f, shadowOffset.height()) + shadowBlur); + shadowOffset -= extraOffset; + } + + context->setShadow(shadowOffset, shadowBlur, shadowColor, context->fillColorSpace()); + return extraOffset; +} + +static void paintTextWithShadows(GraphicsContext* context, const Font& font, const TextRun& textRun, const AtomicString& emphasisMark, int emphasisMarkOffset, int startOffset, int endOffset, int truncationPoint, const IntPoint& textOrigin, + const IntRect& boxRect, const ShadowData* shadow, bool stroked, bool horizontal) +{ + Color fillColor = context->fillColor(); + ColorSpace fillColorSpace = context->fillColorSpace(); + bool opaque = fillColor.alpha() == 255; + if (!opaque) + context->setFillColor(Color::black, fillColorSpace); + + do { + IntSize extraOffset; + if (shadow) + extraOffset = roundedIntSize(InlineTextBox::applyShadowToGraphicsContext(context, shadow, boxRect, stroked, opaque, horizontal)); + else if (!opaque) + context->setFillColor(fillColor, fillColorSpace); + + if (startOffset <= endOffset) { + if (emphasisMark.isEmpty()) + context->drawText(font, textRun, textOrigin + extraOffset, startOffset, endOffset); + else + context->drawEmphasisMarks(font, textRun, emphasisMark, textOrigin + extraOffset + IntSize(0, emphasisMarkOffset), startOffset, endOffset); + } else { + if (endOffset > 0) { + if (emphasisMark.isEmpty()) + context->drawText(font, textRun, textOrigin + extraOffset, 0, endOffset); + else + context->drawEmphasisMarks(font, textRun, emphasisMark, textOrigin + extraOffset + IntSize(0, emphasisMarkOffset), 0, endOffset); + } if (startOffset < truncationPoint) { + if (emphasisMark.isEmpty()) + context->drawText(font, textRun, textOrigin + extraOffset, startOffset, truncationPoint); + else + context->drawEmphasisMarks(font, textRun, emphasisMark, textOrigin + extraOffset + IntSize(0, emphasisMarkOffset), startOffset, truncationPoint); + } + } + + if (!shadow) + break; + + if (shadow->next() || stroked || !opaque) + context->restore(); + else + context->clearShadow(); + + shadow = shadow->next(); + } while (shadow || stroked || !opaque); +} + +bool InlineTextBox::getEmphasisMarkPosition(RenderStyle* style, TextEmphasisPosition& emphasisPosition) const +{ + // This function returns true if there are text emphasis marks and they are suppressed by ruby text. + if (style->textEmphasisMark() == TextEmphasisMarkNone) + return false; + + emphasisPosition = style->textEmphasisPosition(); + if (emphasisPosition == TextEmphasisPositionUnder) + return true; // Ruby text is always over, so it cannot suppress emphasis marks under. + + RenderBlock* containingBlock = renderer()->containingBlock(); + if (!containingBlock->isRubyBase()) + return true; // This text is not inside a ruby base, so it does not have ruby text over it. + + if (!containingBlock->parent()->isRubyRun()) + return true; // Cannot get the ruby text. + + RenderRubyText* rubyText = static_cast<RenderRubyRun*>(containingBlock->parent())->rubyText(); + + // The emphasis marks over are suppressed only if there is a ruby text box and it not empty. + return !rubyText || !rubyText->firstLineBox(); +} + +void InlineTextBox::paint(PaintInfo& paintInfo, int tx, int ty) +{ + if (isLineBreak() || !paintInfo.shouldPaintWithinRoot(renderer()) || renderer()->style()->visibility() != VISIBLE || + m_truncation == cFullTruncation || paintInfo.phase == PaintPhaseOutline || !m_len) + return; + + ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines); + + // FIXME: Technically we're potentially incorporating other visual overflow that had nothing to do with us. + // Would it be simpler to just check our own shadow and stroke overflow by hand here? + int logicalLeftOverflow = parent()->logicalLeft() - parent()->logicalLeftVisualOverflow(); + int logicalRightOverflow = parent()->logicalRightVisualOverflow() - (parent()->logicalLeft() + parent()->logicalWidth()); + int logicalStart = logicalLeft() - logicalLeftOverflow + (isHorizontal() ? tx : ty); + int logicalExtent = logicalWidth() + logicalLeftOverflow + logicalRightOverflow; + + int paintEnd = isHorizontal() ? paintInfo.rect.right() : paintInfo.rect.bottom(); + int paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y(); + + if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart) + return; + + bool isPrinting = textRenderer()->document()->printing(); + + // Determine whether or not we're selected. + bool haveSelection = !isPrinting && paintInfo.phase != PaintPhaseTextClip && selectionState() != RenderObject::SelectionNone; + if (!haveSelection && paintInfo.phase == PaintPhaseSelection) + // When only painting the selection, don't bother to paint if there is none. + return; + + if (m_truncation != cNoTruncation) { + if (renderer()->containingBlock()->style()->isLeftToRightDirection() != isLeftToRightDirection()) { + // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin + // at which we start drawing text. + // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is: + // |Hello|CBA| -> |...He|CBA| + // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing + // farther to the right. + // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the + // truncated string i.e. |Hello|CBA| -> |...lo|CBA| + int widthOfVisibleText = toRenderText(renderer())->width(m_start, m_truncation, textPos(), m_firstLine); + int widthOfHiddenText = m_logicalWidth - widthOfVisibleText; + // FIXME: The hit testing logic also needs to take this translation int account. + if (isHorizontal()) + tx += isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText; + else + ty += isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText; + } + } + + GraphicsContext* context = paintInfo.context; + + RenderStyle* styleToUse = renderer()->style(m_firstLine); + + ty -= styleToUse->isHorizontalWritingMode() ? 0 : logicalHeight(); + + IntPoint boxOrigin = locationIncludingFlipping(); + boxOrigin.move(tx, ty); + IntRect boxRect(boxOrigin, IntSize(logicalWidth(), logicalHeight())); + IntPoint textOrigin = IntPoint(boxOrigin.x(), boxOrigin.y() + styleToUse->font().ascent()); + + if (!isHorizontal()) { + context->save(); + context->translate(boxRect.x(), boxRect.bottom()); + context->rotate(static_cast<float>(deg2rad(90.))); + context->translate(-boxRect.x(), -boxRect.bottom()); + } + + + // Determine whether or not we have composition underlines to draw. + bool containsComposition = renderer()->node() && renderer()->frame()->editor()->compositionNode() == renderer()->node(); + bool useCustomUnderlines = containsComposition && renderer()->frame()->editor()->compositionUsesCustomUnderlines(); + + // Set our font. + int d = styleToUse->textDecorationsInEffect(); + const Font& font = styleToUse->font(); + + // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection + // and composition underlines. + if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) { +#if PLATFORM(MAC) + // Custom highlighters go behind everything else. + if (styleToUse->highlight() != nullAtom && !context->paintingDisabled()) + paintCustomHighlight(tx, ty, styleToUse->highlight()); +#endif + + if (containsComposition && !useCustomUnderlines) + paintCompositionBackground(context, boxOrigin, styleToUse, font, + renderer()->frame()->editor()->compositionStart(), + renderer()->frame()->editor()->compositionEnd()); + + paintDocumentMarkers(context, boxOrigin, styleToUse, font, true); + + if (haveSelection && !useCustomUnderlines) + paintSelection(context, boxOrigin, styleToUse, font); + } + + // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only). + Color textFillColor; + Color textStrokeColor; + Color emphasisMarkColor; + float textStrokeWidth = styleToUse->textStrokeWidth(); + const ShadowData* textShadow = paintInfo.forceBlackText ? 0 : styleToUse->textShadow(); + + if (paintInfo.forceBlackText) { + textFillColor = Color::black; + textStrokeColor = Color::black; + emphasisMarkColor = Color::black; + } else { + textFillColor = styleToUse->visitedDependentColor(CSSPropertyWebkitTextFillColor); + + // Make the text fill color legible against a white background + if (styleToUse->forceBackgroundsToWhite()) + textFillColor = correctedTextColor(textFillColor, Color::white); + + textStrokeColor = styleToUse->visitedDependentColor(CSSPropertyWebkitTextStrokeColor); + + // Make the text stroke color legible against a white background + if (styleToUse->forceBackgroundsToWhite()) + textStrokeColor = correctedTextColor(textStrokeColor, Color::white); + + emphasisMarkColor = styleToUse->visitedDependentColor(CSSPropertyWebkitTextEmphasisColor); + + // Make the text stroke color legible against a white background + if (styleToUse->forceBackgroundsToWhite()) + emphasisMarkColor = correctedTextColor(emphasisMarkColor, Color::white); + } + + bool paintSelectedTextOnly = (paintInfo.phase == PaintPhaseSelection); + bool paintSelectedTextSeparately = false; + + Color selectionFillColor = textFillColor; + Color selectionStrokeColor = textStrokeColor; + Color selectionEmphasisMarkColor = emphasisMarkColor; + float selectionStrokeWidth = textStrokeWidth; + const ShadowData* selectionShadow = textShadow; + if (haveSelection) { + // Check foreground color first. + Color foreground = paintInfo.forceBlackText ? Color::black : renderer()->selectionForegroundColor(); + if (foreground.isValid() && foreground != selectionFillColor) { + if (!paintSelectedTextOnly) + paintSelectedTextSeparately = true; + selectionFillColor = foreground; + } + + Color emphasisMarkForeground = paintInfo.forceBlackText ? Color::black : renderer()->selectionEmphasisMarkColor(); + if (emphasisMarkForeground.isValid() && emphasisMarkForeground != selectionEmphasisMarkColor) { + if (!paintSelectedTextOnly) + paintSelectedTextSeparately = true; + selectionEmphasisMarkColor = emphasisMarkForeground; + } + + if (RenderStyle* pseudoStyle = renderer()->getCachedPseudoStyle(SELECTION)) { + const ShadowData* shadow = paintInfo.forceBlackText ? 0 : pseudoStyle->textShadow(); + if (shadow != selectionShadow) { + if (!paintSelectedTextOnly) + paintSelectedTextSeparately = true; + selectionShadow = shadow; + } + + float strokeWidth = pseudoStyle->textStrokeWidth(); + if (strokeWidth != selectionStrokeWidth) { + if (!paintSelectedTextOnly) + paintSelectedTextSeparately = true; + selectionStrokeWidth = strokeWidth; + } + + Color stroke = paintInfo.forceBlackText ? Color::black : pseudoStyle->visitedDependentColor(CSSPropertyWebkitTextStrokeColor); + if (stroke != selectionStrokeColor) { + if (!paintSelectedTextOnly) + paintSelectedTextSeparately = true; + selectionStrokeColor = stroke; + } + } + } + + const UChar* characters = textRenderer()->text()->characters() + m_start; + int length = m_len; + BufferForAppendingHyphen charactersWithHyphen; + if (hasHyphen()) + adjustCharactersAndLengthForHyphen(charactersWithHyphen, styleToUse, characters, length); + + TextRun textRun(characters, length, textRenderer()->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride || styleToUse->visuallyOrdered()); +#ifdef ANDROID_DISABLE_ROUNDING_HACKS + if (m_disableRoundingHacks) + textRun.disableRoundingHacks(); +#endif + + int sPos = 0; + int ePos = 0; + if (paintSelectedTextOnly || paintSelectedTextSeparately) + selectionStartEnd(sPos, ePos); + + if (m_truncation != cNoTruncation) { + sPos = min<int>(sPos, m_truncation); + ePos = min<int>(ePos, m_truncation); + length = m_truncation; + } + + int emphasisMarkOffset = 0; + TextEmphasisPosition emphasisMarkPosition; + bool hasTextEmphasis = getEmphasisMarkPosition(styleToUse, emphasisMarkPosition); + const AtomicString& emphasisMark = hasTextEmphasis ? styleToUse->textEmphasisMarkString() : nullAtom; + if (!emphasisMark.isEmpty()) + emphasisMarkOffset = emphasisMarkPosition == TextEmphasisPositionOver ? -font.ascent() - font.emphasisMarkDescent(emphasisMark) : font.descent() + font.emphasisMarkAscent(emphasisMark); + + if (!paintSelectedTextOnly) { + // For stroked painting, we have to change the text drawing mode. It's probably dangerous to leave that mutated as a side + // effect, so only when we know we're stroking, do a save/restore. + if (textStrokeWidth > 0) + context->save(); + + updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace()); + if (!paintSelectedTextSeparately || ePos <= sPos) { + // FIXME: Truncate right-to-left text correctly. + paintTextWithShadows(context, font, textRun, nullAtom, 0, 0, length, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal()); + } else + paintTextWithShadows(context, font, textRun, nullAtom, 0, ePos, sPos, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal()); + + if (!emphasisMark.isEmpty()) { + updateGraphicsContext(context, emphasisMarkColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace()); + if (!paintSelectedTextSeparately || ePos <= sPos) { + // FIXME: Truncate right-to-left text correctly. + paintTextWithShadows(context, font, textRun, emphasisMark, emphasisMarkOffset, 0, length, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal()); + } else + paintTextWithShadows(context, font, textRun, emphasisMark, emphasisMarkOffset, ePos, sPos, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal()); + } + + if (textStrokeWidth > 0) + context->restore(); + } + + if ((paintSelectedTextOnly || paintSelectedTextSeparately) && sPos < ePos) { + // paint only the text that is selected + if (selectionStrokeWidth > 0) + context->save(); + + updateGraphicsContext(context, selectionFillColor, selectionStrokeColor, selectionStrokeWidth, styleToUse->colorSpace()); + paintTextWithShadows(context, font, textRun, nullAtom, 0, sPos, ePos, length, textOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal()); + if (!emphasisMark.isEmpty()) { + updateGraphicsContext(context, selectionEmphasisMarkColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace()); + paintTextWithShadows(context, font, textRun, emphasisMark, emphasisMarkOffset, sPos, ePos, length, textOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal()); + } + if (selectionStrokeWidth > 0) + context->restore(); + } + + // Paint decorations + if (d != TDNONE && paintInfo.phase != PaintPhaseSelection) { + updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace()); + paintDecoration(context, boxOrigin, d, textShadow); + } + + if (paintInfo.phase == PaintPhaseForeground) { + paintDocumentMarkers(context, boxOrigin, styleToUse, font, false); + + if (useCustomUnderlines) { + const Vector<CompositionUnderline>& underlines = renderer()->frame()->editor()->customCompositionUnderlines(); + size_t numUnderlines = underlines.size(); + + for (size_t index = 0; index < numUnderlines; ++index) { + const CompositionUnderline& underline = underlines[index]; + + if (underline.endOffset <= start()) + // underline is completely before this run. This might be an underline that sits + // before the first run we draw, or underlines that were within runs we skipped + // due to truncation. + continue; + + if (underline.startOffset <= end()) { + // underline intersects this run. Paint it. + paintCompositionUnderline(context, boxOrigin, underline); + if (underline.endOffset > end() + 1) + // underline also runs into the next run. Bail now, no more marker advancement. + break; + } else + // underline is completely after this run, bail. A later run will paint it. + break; + } + } + } + + if (!isHorizontal()) + context->restore(); +} + +void InlineTextBox::selectionStartEnd(int& sPos, int& ePos) +{ + int startPos, endPos; + if (renderer()->selectionState() == RenderObject::SelectionInside) { + startPos = 0; + endPos = textRenderer()->textLength(); + } else { + textRenderer()->selectionStartEnd(startPos, endPos); + if (renderer()->selectionState() == RenderObject::SelectionStart) + endPos = textRenderer()->textLength(); + else if (renderer()->selectionState() == RenderObject::SelectionEnd) + startPos = 0; + } + + sPos = max(startPos - m_start, 0); + ePos = min(endPos - m_start, (int)m_len); +} + +void InlineTextBox::paintSelection(GraphicsContext* context, const IntPoint& boxOrigin, RenderStyle* style, const Font& font) +{ + // See if we have a selection to paint at all. + int sPos, ePos; + selectionStartEnd(sPos, ePos); + if (sPos >= ePos) + return; + + Color textColor = style->visitedDependentColor(CSSPropertyColor); + Color c = renderer()->selectionBackgroundColor(); + if (!c.isValid() || c.alpha() == 0) + return; + + // If the text color ends up being the same as the selection background, invert the selection + // background. This should basically never happen, since the selection has transparency. + if (textColor == c) + c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue()); + + context->save(); + updateGraphicsContext(context, c, c, 0, style->colorSpace()); // Don't draw text at all! + + // If the text is truncated, let the thing being painted in the truncation + // draw its own highlight. + int length = m_truncation != cNoTruncation ? m_truncation : m_len; + const UChar* characters = textRenderer()->text()->characters() + m_start; + + BufferForAppendingHyphen charactersWithHyphen; + if (ePos == length && hasHyphen()) { + adjustCharactersAndLengthForHyphen(charactersWithHyphen, style, characters, length); + ePos = length; + } + + int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop(); + int selHeight = selectionHeight(); + IntPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY); + context->clip(IntRect(localOrigin, IntSize(m_logicalWidth, selHeight))); +#ifdef ANDROID_DISABLE_ROUNDING_HACKS + TextRun textRun = TextRun(characters, length, textRenderer()->allowTabs(), textPos(), m_toAdd, + !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()); + if (m_disableRoundingHacks) + textRun.disableRoundingHacks(); + context->drawHighlightForText(font, textRun, localOrigin, selHeight, c, style->colorSpace(), sPos, ePos); +#else + context->drawHighlightForText(font, TextRun(characters, length, textRenderer()->allowTabs(), textPos(), m_toAdd, + !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()), + localOrigin, selHeight, c, style->colorSpace(), sPos, ePos); +#endif + context->restore(); +} + +void InlineTextBox::paintCompositionBackground(GraphicsContext* context, const IntPoint& boxOrigin, RenderStyle* style, const Font& font, int startPos, int endPos) +{ + int offset = m_start; + int sPos = max(startPos - offset, 0); + int ePos = min(endPos - offset, (int)m_len); + + if (sPos >= ePos) + return; + + context->save(); + + Color c = Color(225, 221, 85); + + updateGraphicsContext(context, c, c, 0, style->colorSpace()); // Don't draw text at all! + + int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop(); + int selHeight = selectionHeight(); + IntPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY); +#ifdef ANDROID_DISABLE_ROUNDING_HACKS + TextRun textRun = TextRun(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, + !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()); + if (m_disableRoundingHacks) + textRun.disableRoundingHacks(); + context->drawHighlightForText(font, textRun, localOrigin, selHeight, c, style->colorSpace(), sPos, ePos); +#else + context->drawHighlightForText(font, TextRun(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, + !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()), + localOrigin, selHeight, c, style->colorSpace(), sPos, ePos); +#endif + context->restore(); +} + +#if PLATFORM(MAC) + +void InlineTextBox::paintCustomHighlight(int tx, int ty, const AtomicString& type) +{ + Frame* frame = renderer()->frame(); + if (!frame) + return; + Page* page = frame->page(); + if (!page) + return; + + RootInlineBox* r = root(); + FloatRect rootRect(tx + r->x(), ty + selectionTop(), r->logicalWidth(), selectionHeight()); + FloatRect textRect(tx + x(), rootRect.y(), logicalWidth(), rootRect.height()); + + page->chrome()->client()->paintCustomHighlight(renderer()->node(), type, textRect, rootRect, true, false); +} + +#endif + +void InlineTextBox::paintDecoration(GraphicsContext* context, const IntPoint& boxOrigin, int deco, const ShadowData* shadow) +{ + if (m_truncation == cFullTruncation) + return; + + IntPoint localOrigin = boxOrigin; + + int width = m_logicalWidth; + if (m_truncation != cNoTruncation) { + width = toRenderText(renderer())->width(m_start, m_truncation, textPos(), m_firstLine); + if (!isLeftToRightDirection()) + localOrigin.move(m_logicalWidth - width, 0); + } + + // Get the text decoration colors. + Color underline, overline, linethrough; + renderer()->getTextDecorationColors(deco, underline, overline, linethrough, true); + + // Use a special function for underlines to get the positioning exactly right. + bool isPrinting = textRenderer()->document()->printing(); + context->setStrokeThickness(1.0f); // FIXME: We should improve this rule and not always just assume 1. + + bool linesAreOpaque = !isPrinting && (!(deco & UNDERLINE) || underline.alpha() == 255) && (!(deco & OVERLINE) || overline.alpha() == 255) && (!(deco & LINE_THROUGH) || linethrough.alpha() == 255); + + RenderStyle* styleToUse = renderer()->style(m_firstLine); + int baseline = styleToUse->font().ascent(); + + bool setClip = false; + int extraOffset = 0; + if (!linesAreOpaque && shadow && shadow->next()) { + context->save(); + IntRect clipRect(localOrigin, IntSize(width, baseline + 2)); + for (const ShadowData* s = shadow; s; s = s->next()) { + IntRect shadowRect(localOrigin, IntSize(width, baseline + 2)); + shadowRect.inflate(s->blur()); + int shadowX = isHorizontal() ? s->x() : s->y(); + int shadowY = isHorizontal() ? s->y() : -s->x(); + shadowRect.move(shadowX, shadowY); + clipRect.unite(shadowRect); + extraOffset = max(extraOffset, max(0, shadowY) + s->blur()); + } + context->save(); + context->clip(clipRect); + extraOffset += baseline + 2; + localOrigin.move(0, extraOffset); + setClip = true; + } + + ColorSpace colorSpace = renderer()->style()->colorSpace(); + bool setShadow = false; + + do { + if (shadow) { + if (!shadow->next()) { + // The last set of lines paints normally inside the clip. + localOrigin.move(0, -extraOffset); + extraOffset = 0; + } + int shadowX = isHorizontal() ? shadow->x() : shadow->y(); + int shadowY = isHorizontal() ? shadow->y() : -shadow->x(); + context->setShadow(IntSize(shadowX, shadowY - extraOffset), shadow->blur(), shadow->color(), colorSpace); + setShadow = true; + shadow = shadow->next(); + } + + if (deco & UNDERLINE) { + context->setStrokeColor(underline, colorSpace); + context->setStrokeStyle(SolidStroke); + // Leave one pixel of white between the baseline and the underline. + context->drawLineForText(IntPoint(localOrigin.x(), localOrigin.y() + baseline + 1), width, isPrinting); + } + if (deco & OVERLINE) { + context->setStrokeColor(overline, colorSpace); + context->setStrokeStyle(SolidStroke); + context->drawLineForText(localOrigin, width, isPrinting); + } + if (deco & LINE_THROUGH) { + context->setStrokeColor(linethrough, colorSpace); + context->setStrokeStyle(SolidStroke); + context->drawLineForText(IntPoint(localOrigin.x(), localOrigin.y() + 2 * baseline / 3), width, isPrinting); + } + } while (shadow); + + if (setClip) + context->restore(); + else if (setShadow) + context->clearShadow(); +} + +static GraphicsContext::TextCheckingLineStyle textCheckingLineStyleForMarkerType(DocumentMarker::MarkerType markerType) +{ + switch (markerType) { + case DocumentMarker::Spelling: + return GraphicsContext::TextCheckingSpellingLineStyle; + case DocumentMarker::Grammar: + return GraphicsContext::TextCheckingGrammarLineStyle; + case DocumentMarker::CorrectionIndicator: + return GraphicsContext::TextCheckingReplacementLineStyle; + default: + ASSERT_NOT_REACHED(); + return GraphicsContext::TextCheckingSpellingLineStyle; + } +} + +void InlineTextBox::paintSpellingOrGrammarMarker(GraphicsContext* pt, const IntPoint& boxOrigin, const DocumentMarker& marker, RenderStyle* style, const Font& font, bool grammar) +{ + // Never print spelling/grammar markers (5327887) + if (textRenderer()->document()->printing()) + return; + + if (m_truncation == cFullTruncation) + return; + + int start = 0; // start of line to draw, relative to tx + int width = m_logicalWidth; // how much line to draw + + // Determine whether we need to measure text + bool markerSpansWholeBox = true; + if (m_start <= (int)marker.startOffset) + markerSpansWholeBox = false; + if ((end() + 1) != marker.endOffset) // end points at the last char, not past it + markerSpansWholeBox = false; + if (m_truncation != cNoTruncation) + markerSpansWholeBox = false; + + if (!markerSpansWholeBox || grammar) { + int startPosition = max<int>(marker.startOffset - m_start, 0); + int endPosition = min<int>(marker.endOffset - m_start, m_len); + + if (m_truncation != cNoTruncation) + endPosition = min<int>(endPosition, m_truncation); + + // Calculate start & width + int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop(); + int selHeight = selectionHeight(); + IntPoint startPoint(boxOrigin.x(), boxOrigin.y() - deltaY); + TextRun run(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()); +#ifdef ANDROID_DISABLE_ROUNDING_HACKS + if (m_disableRoundingHacks) + run.disableRoundingHacks(); +#endif + + IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, startPoint, selHeight, startPosition, endPosition)); + start = markerRect.x() - startPoint.x(); + width = markerRect.width(); + + // Store rendered rects for bad grammar markers, so we can hit-test against it elsewhere in order to + // display a toolTip. We don't do this for misspelling markers. + if (grammar) { + markerRect.move(-boxOrigin.x(), -boxOrigin.y()); + markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox(); + renderer()->document()->markers()->setRenderedRectForMarker(renderer()->node(), marker, markerRect); + } + } + + // IMPORTANT: The misspelling underline is not considered when calculating the text bounds, so we have to + // make sure to fit within those bounds. This means the top pixel(s) of the underline will overlap the + // bottom pixel(s) of the glyphs in smaller font sizes. The alternatives are to increase the line spacing (bad!!) + // or decrease the underline thickness. The overlap is actually the most useful, and matches what AppKit does. + // So, we generally place the underline at the bottom of the text, but in larger fonts that's not so good so + // we pin to two pixels under the baseline. + int lineThickness = cMisspellingLineThickness; + int baseline = renderer()->style(m_firstLine)->font().ascent(); + int descent = logicalHeight() - baseline; + int underlineOffset; + if (descent <= (2 + lineThickness)) { + // Place the underline at the very bottom of the text in small/medium fonts. + underlineOffset = logicalHeight() - lineThickness; + } else { + // In larger fonts, though, place the underline up near the baseline to prevent a big gap. + underlineOffset = baseline + 2; + } + pt->drawLineForTextChecking(IntPoint(boxOrigin.x() + start, boxOrigin.y() + underlineOffset), width, textCheckingLineStyleForMarkerType(marker.type)); +} + +void InlineTextBox::paintTextMatchMarker(GraphicsContext* pt, const IntPoint& boxOrigin, const DocumentMarker& marker, RenderStyle* style, const Font& font) +{ + // Use same y positioning and height as for selection, so that when the selection and this highlight are on + // the same word there are no pieces sticking out. + int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop(); + int selHeight = selectionHeight(); + + int sPos = max(marker.startOffset - m_start, (unsigned)0); + int ePos = min(marker.endOffset - m_start, (unsigned)m_len); + TextRun run(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()); +#ifdef ANDROID_DISABLE_ROUNDING_HACKS + if (m_disableRoundingHacks) + run.disableRoundingHacks(); +#endif + + // Always compute and store the rect associated with this marker. The computed rect is in absolute coordinates. + IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, IntPoint(m_x, selectionTop()), selHeight, sPos, ePos)); + markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox(); + renderer()->document()->markers()->setRenderedRectForMarker(renderer()->node(), marker, markerRect); + + // Optionally highlight the text + if (renderer()->frame()->editor()->markedTextMatchesAreHighlighted()) { + Color color = marker.activeMatch ? + renderer()->theme()->platformActiveTextSearchHighlightColor() : + renderer()->theme()->platformInactiveTextSearchHighlightColor(); + pt->save(); + updateGraphicsContext(pt, color, color, 0, style->colorSpace()); // Don't draw text at all! + pt->clip(IntRect(boxOrigin.x(), boxOrigin.y() - deltaY, m_logicalWidth, selHeight)); + pt->drawHighlightForText(font, run, IntPoint(boxOrigin.x(), boxOrigin.y() - deltaY), selHeight, color, style->colorSpace(), sPos, ePos); + pt->restore(); + } +} + +void InlineTextBox::computeRectForReplacementMarker(const DocumentMarker& marker, RenderStyle* style, const Font& font) +{ + // Replacement markers are not actually drawn, but their rects need to be computed for hit testing. + int y = selectionTop(); + int h = selectionHeight(); + + int sPos = max(marker.startOffset - m_start, (unsigned)0); + int ePos = min(marker.endOffset - m_start, (unsigned)m_len); + TextRun run(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()); +#ifdef ANDROID_DISABLE_ROUNDING_HACKS + if (m_disableRoundingHacks) + run.disableRoundingHacks(); +#endif + IntPoint startPoint = IntPoint(m_x, y); + + // Compute and store the rect associated with this marker. + IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, startPoint, h, sPos, ePos)); + markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox(); + renderer()->document()->markers()->setRenderedRectForMarker(renderer()->node(), marker, markerRect); +} + +void InlineTextBox::paintDocumentMarkers(GraphicsContext* pt, const IntPoint& boxOrigin, RenderStyle* style, const Font& font, bool background) +{ + if (!renderer()->node()) + return; + + Vector<DocumentMarker> markers = renderer()->document()->markers()->markersForNode(renderer()->node()); + Vector<DocumentMarker>::iterator markerIt = markers.begin(); + + // Give any document markers that touch this run a chance to draw before the text has been drawn. + // Note end() points at the last char, not one past it like endOffset and ranges do. + for ( ; markerIt != markers.end(); markerIt++) { + const DocumentMarker& marker = *markerIt; + + // Paint either the background markers or the foreground markers, but not both + switch (marker.type) { + case DocumentMarker::Grammar: + case DocumentMarker::Spelling: + case DocumentMarker::Replacement: + case DocumentMarker::CorrectionIndicator: + case DocumentMarker::RejectedCorrection: + if (background) + continue; + break; + case DocumentMarker::TextMatch: + if (!background) + continue; + break; + + default: + ASSERT_NOT_REACHED(); + } + + if (marker.endOffset <= start()) + // marker is completely before this run. This might be a marker that sits before the + // first run we draw, or markers that were within runs we skipped due to truncation. + continue; + + if (marker.startOffset > end()) + // marker is completely after this run, bail. A later run will paint it. + break; + + // marker intersects this run. Paint it. + switch (marker.type) { + case DocumentMarker::Spelling: + paintSpellingOrGrammarMarker(pt, boxOrigin, marker, style, font, false); + break; + case DocumentMarker::Grammar: + paintSpellingOrGrammarMarker(pt, boxOrigin, marker, style, font, true); + break; + case DocumentMarker::TextMatch: + paintTextMatchMarker(pt, boxOrigin, marker, style, font); + break; + case DocumentMarker::CorrectionIndicator: + computeRectForReplacementMarker(marker, style, font); + paintSpellingOrGrammarMarker(pt, boxOrigin, marker, style, font, false); + break; + case DocumentMarker::Replacement: + case DocumentMarker::RejectedCorrection: + break; + default: + ASSERT_NOT_REACHED(); + } + + } +} + + +void InlineTextBox::paintCompositionUnderline(GraphicsContext* ctx, const IntPoint& boxOrigin, const CompositionUnderline& underline) +{ + if (m_truncation == cFullTruncation) + return; + + int start = 0; // start of line to draw, relative to tx + int width = m_logicalWidth; // how much line to draw + bool useWholeWidth = true; + unsigned paintStart = m_start; + unsigned paintEnd = end() + 1; // end points at the last char, not past it + if (paintStart <= underline.startOffset) { + paintStart = underline.startOffset; + useWholeWidth = false; + start = toRenderText(renderer())->width(m_start, paintStart - m_start, textPos(), m_firstLine); + } + if (paintEnd != underline.endOffset) { // end points at the last char, not past it + paintEnd = min(paintEnd, (unsigned)underline.endOffset); + useWholeWidth = false; + } + if (m_truncation != cNoTruncation) { + paintEnd = min(paintEnd, (unsigned)m_start + m_truncation); + useWholeWidth = false; + } + if (!useWholeWidth) { + width = toRenderText(renderer())->width(paintStart, paintEnd - paintStart, textPos() + start, m_firstLine); + } + + // Thick marked text underlines are 2px thick as long as there is room for the 2px line under the baseline. + // All other marked text underlines are 1px thick. + // If there's not enough space the underline will touch or overlap characters. + int lineThickness = 1; + int baseline = renderer()->style(m_firstLine)->font().ascent(); + if (underline.thick && logicalHeight() - baseline >= 2) + lineThickness = 2; + + // We need to have some space between underlines of subsequent clauses, because some input methods do not use different underline styles for those. + // We make each line shorter, which has a harmless side effect of shortening the first and last clauses, too. + start += 1; + width -= 2; + + ctx->setStrokeColor(underline.color, renderer()->style()->colorSpace()); + ctx->setStrokeThickness(lineThickness); + ctx->drawLineForText(IntPoint(boxOrigin.x() + start, boxOrigin.y() + logicalHeight() - lineThickness), width, textRenderer()->document()->printing()); +} + +int InlineTextBox::caretMinOffset() const +{ + return m_start; +} + +int InlineTextBox::caretMaxOffset() const +{ + return m_start + m_len; +} + +unsigned InlineTextBox::caretMaxRenderedOffset() const +{ + return m_start + m_len; +} + +int InlineTextBox::textPos() const +{ + // When computing the width of a text run, RenderBlock::computeInlineDirectionPositionsForLine() doesn't include the actual offset + // from the containing block edge in its measurement. textPos() should be consistent so the text are rendered in the same width. + if (logicalLeft() == 0) + return 0; + return logicalLeft() - root()->logicalLeft(); +} + +int InlineTextBox::offsetForPosition(int lineOffset, bool includePartialGlyphs) const +{ + if (isLineBreak()) + return 0; + + int leftOffset = isLeftToRightDirection() ? 0 : m_len; + int rightOffset = isLeftToRightDirection() ? m_len : 0; + bool blockIsInOppositeDirection = renderer()->containingBlock()->style()->isLeftToRightDirection() != isLeftToRightDirection(); + if (blockIsInOppositeDirection) + swap(leftOffset, rightOffset); + + if (lineOffset - logicalLeft() > logicalWidth()) + return rightOffset; + if (lineOffset - logicalLeft() < 0) + return leftOffset; + + RenderText* text = toRenderText(renderer()); + RenderStyle* style = text->style(m_firstLine); + const Font* f = &style->font(); +<<<<<<< HEAD:WebCore/rendering/InlineTextBox.cpp +#ifdef ANDROID_DISABLE_ROUNDING_HACKS + TextRun textRun = TextRun(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()); + if (m_disableRoundingHacks) + textRun.disableRoundingHacks(); + return f->offsetForPosition(textRun, lineOffset - logicalLeft(), includePartialGlyphs); +#else + return f->offsetForPosition(TextRun(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()), + lineOffset - logicalLeft(), includePartialGlyphs); +#endif +======= + int offset = f->offsetForPosition(TextRun(textRenderer()->text()->characters() + m_start, m_len, + textRenderer()->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()), + lineOffset - logicalLeft(), includePartialGlyphs); + if (blockIsInOppositeDirection && (!offset || offset == m_len)) + return !offset ? m_len : 0; + return offset; +>>>>>>> webkit.org at r75315:Source/WebCore/rendering/InlineTextBox.cpp +} + +int InlineTextBox::positionForOffset(int offset) const +{ + ASSERT(offset >= m_start); + ASSERT(offset <= m_start + m_len); + + if (isLineBreak()) + return logicalLeft(); + + RenderText* text = toRenderText(renderer()); + const Font& f = text->style(m_firstLine)->font(); + int from = !isLeftToRightDirection() ? offset - m_start : 0; + int to = !isLeftToRightDirection() ? m_len : offset - m_start; + // FIXME: Do we need to add rightBearing here? +#ifdef ANDROID_DISABLE_ROUNDING_HACKS + TextRun textRun = TextRun(text->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride); + if (m_disableRoundingHacks) + textRun.disableRoundingHacks(); + return enclosingIntRect(f.selectionRectForText(textRun, IntPoint(logicalLeft(), 0), 0, from, to)).right(); +#else + return enclosingIntRect(f.selectionRectForText(TextRun(text->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride), + IntPoint(logicalLeft(), 0), 0, from, to)).right(); +#endif +} + +bool InlineTextBox::containsCaretOffset(int offset) const +{ + // Offsets before the box are never "in". + if (offset < m_start) + return false; + + int pastEnd = m_start + m_len; + + // Offsets inside the box (not at either edge) are always "in". + if (offset < pastEnd) + return true; + + // Offsets outside the box are always "out". + if (offset > pastEnd) + return false; + + // Offsets at the end are "out" for line breaks (they are on the next line). + if (isLineBreak()) + return false; + + // Offsets at the end are "in" for normal boxes (but the caller has to check affinity). + return true; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/InlineTextBox.h b/Source/WebCore/rendering/InlineTextBox.h new file mode 100644 index 0000000..ee75f06 --- /dev/null +++ b/Source/WebCore/rendering/InlineTextBox.h @@ -0,0 +1,178 @@ +/* + * (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef InlineTextBox_h +#define InlineTextBox_h + +#include "InlineBox.h" +#include "RenderText.h" // so textRenderer() can be inline + +namespace WebCore { + +struct CompositionUnderline; +struct DocumentMarker; + +const unsigned short cNoTruncation = USHRT_MAX; +const unsigned short cFullTruncation = USHRT_MAX - 1; + +// Helper functions shared by InlineTextBox / SVGRootInlineBox +void updateGraphicsContext(GraphicsContext*, const Color& fillColor, const Color& strokeColor, float strokeThickness, ColorSpace); +Color correctedTextColor(Color textColor, Color backgroundColor); + +class InlineTextBox : public InlineBox { +public: +#ifdef ANDROID_DISABLE_ROUNDING_HACKS + InlineTextBox(RenderObject* obj, bool disableRoundingHacks = false) +#else + InlineTextBox(RenderObject* obj) +#endif + : InlineBox(obj) + , m_prevTextBox(0) + , m_nextTextBox(0) + , m_start(0) + , m_len(0) + , m_truncation(cNoTruncation) +#ifdef ANDROID_DISABLE_ROUNDING_HACKS + , m_disableRoundingHacks(disableRoundingHacks) +#endif + { + } + + InlineTextBox* prevTextBox() const { return m_prevTextBox; } + InlineTextBox* nextTextBox() const { return m_nextTextBox; } + void setNextTextBox(InlineTextBox* n) { m_nextTextBox = n; } + void setPreviousTextBox(InlineTextBox* p) { m_prevTextBox = p; } + + unsigned start() const { return m_start; } + unsigned end() const { return m_len ? m_start + m_len - 1 : m_start; } + unsigned len() const { return m_len; } + + void setStart(unsigned start) { m_start = start; } + void setLen(unsigned len) { m_len = len; } + + void offsetRun(int d) { m_start += d; } + + unsigned short truncation() { return m_truncation; } + + bool hasHyphen() const { return m_hasEllipsisBoxOrHyphen; } + void setHasHyphen(bool hasHyphen) { m_hasEllipsisBoxOrHyphen = hasHyphen; } + static inline bool compareByStart(const InlineTextBox* first, const InlineTextBox* second) { return first->start() < second->start(); } + + virtual int baselinePosition(FontBaseline) const; + virtual int lineHeight() const; + + bool getEmphasisMarkPosition(RenderStyle*, TextEmphasisPosition&) const; + +private: + int selectionTop(); + int selectionBottom(); + int selectionHeight(); + +public: + virtual IntRect calculateBoundaries() const { return IntRect(x(), y(), logicalWidth(), logicalHeight()); } + + virtual IntRect selectionRect(int absx, int absy, int startPos, int endPos); + bool isSelected(int startPos, int endPos) const; + void selectionStartEnd(int& sPos, int& ePos); + +protected: + virtual void paint(PaintInfo&, int tx, int ty); + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty); + +public: + RenderText* textRenderer() const; + +private: + virtual void deleteLine(RenderArena*); + virtual void extractLine(); + virtual void attachLine(); + +public: + virtual RenderObject::SelectionState selectionState(); + +private: + virtual void clearTruncation() { m_truncation = cNoTruncation; } + virtual int placeEllipsisBox(bool flowIsLTR, int visibleLeftEdge, int visibleRightEdge, int ellipsisWidth, bool& foundBox); + +public: + virtual bool isLineBreak() const; + + void setSpaceAdd(int add) { m_logicalWidth -= m_toAdd; m_toAdd = add; m_logicalWidth += m_toAdd; } + +private: + virtual bool isInlineTextBox() const { return true; } + +public: + virtual int caretMinOffset() const; + virtual int caretMaxOffset() const; + +private: + virtual unsigned caretMaxRenderedOffset() const; + + int textPos() const; // returns the x position relative to the left start of the text line. + +public: + virtual int offsetForPosition(int x, bool includePartialGlyphs = true) const; + virtual int positionForOffset(int offset) const; + + bool containsCaretOffset(int offset) const; // false for offset after line break + + // Needs to be public, so the static paintTextWithShadows() function can use it. + static FloatSize applyShadowToGraphicsContext(GraphicsContext*, const ShadowData*, const FloatRect& textRect, bool stroked, bool opaque, bool horizontal); + +private: + InlineTextBox* m_prevTextBox; // The previous box that also uses our RenderObject + InlineTextBox* m_nextTextBox; // The next box that also uses our RenderObject + + int m_start; + unsigned short m_len; + + unsigned short m_truncation; // Where to truncate when text overflow is applied. We use special constants to + // denote no truncation (the whole run paints) and full truncation (nothing paints at all). +#ifdef ANDROID_DISABLE_ROUNDING_HACKS + bool m_disableRoundingHacks; +#endif + +protected: + void paintCompositionBackground(GraphicsContext*, const IntPoint& boxOrigin, RenderStyle*, const Font&, int startPos, int endPos); + void paintDocumentMarkers(GraphicsContext*, const IntPoint& boxOrigin, RenderStyle*, const Font&, bool background); + void paintCompositionUnderline(GraphicsContext*, const IntPoint& boxOrigin, const CompositionUnderline&); +#if PLATFORM(MAC) + void paintCustomHighlight(int tx, int ty, const AtomicString& type); +#endif + +private: + void paintDecoration(GraphicsContext*, const IntPoint& boxOrigin, int decoration, const ShadowData*); + void paintSelection(GraphicsContext*, const IntPoint& boxOrigin, RenderStyle*, const Font&); + void paintSpellingOrGrammarMarker(GraphicsContext*, const IntPoint& boxOrigin, const DocumentMarker&, RenderStyle*, const Font&, bool grammar); + void paintTextMatchMarker(GraphicsContext*, const IntPoint& boxOrigin, const DocumentMarker&, RenderStyle*, const Font&); + void computeRectForReplacementMarker(const DocumentMarker&, RenderStyle*, const Font&); +}; + +inline RenderText* InlineTextBox::textRenderer() const +{ + return toRenderText(renderer()); +} + +} // namespace WebCore + +#endif // InlineTextBox_h diff --git a/Source/WebCore/rendering/LayoutState.cpp b/Source/WebCore/rendering/LayoutState.cpp new file mode 100644 index 0000000..aeba416 --- /dev/null +++ b/Source/WebCore/rendering/LayoutState.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "LayoutState.h" + +#include "ColumnInfo.h" +#include "RenderArena.h" +#include "RenderInline.h" +#include "RenderLayer.h" +#include "RenderView.h" + +namespace WebCore { + +LayoutState::LayoutState(LayoutState* prev, RenderBox* renderer, const IntSize& offset, int pageLogicalHeight, bool pageLogicalHeightChanged, ColumnInfo* columnInfo) + : m_columnInfo(columnInfo) + , m_next(prev) +#ifndef NDEBUG + , m_renderer(renderer) +#endif +{ + ASSERT(m_next); + + bool fixed = renderer->isPositioned() && renderer->style()->position() == FixedPosition; + if (fixed) { + // FIXME: This doesn't work correctly with transforms. + FloatPoint fixedOffset = renderer->view()->localToAbsolute(FloatPoint(), true); + m_paintOffset = IntSize(fixedOffset.x(), fixedOffset.y()) + offset; + } else + m_paintOffset = prev->m_paintOffset + offset; + + if (renderer->isPositioned() && !fixed) { + if (RenderObject* container = renderer->container()) { + if (container->isRelPositioned() && container->isRenderInline()) + m_paintOffset += toRenderInline(container)->relativePositionedInlineOffset(renderer); + } + } + + m_layoutOffset = m_paintOffset; + + if (renderer->isRelPositioned() && renderer->hasLayer()) + m_paintOffset += renderer->layer()->relativePositionOffset(); + + m_clipped = !fixed && prev->m_clipped; + if (m_clipped) + m_clipRect = prev->m_clipRect; + + if (renderer->hasOverflowClip()) { + RenderLayer* layer = renderer->layer(); + IntRect clipRect(toPoint(m_paintOffset) + renderer->view()->layoutDelta(), layer->size()); + if (m_clipped) + m_clipRect.intersect(clipRect); + else { + m_clipRect = clipRect; + m_clipped = true; + } + + m_paintOffset -= layer->scrolledContentOffset(); + } + + // If we establish a new page height, then cache the offset to the top of the first page. + // We can compare this later on to figure out what part of the page we're actually on, + if (pageLogicalHeight || m_columnInfo) { + m_pageLogicalHeight = pageLogicalHeight; + m_pageOffset = IntSize(m_layoutOffset.width() + renderer->borderLeft() + renderer->paddingLeft(), + m_layoutOffset.height() + renderer->borderTop() + renderer->paddingTop()); + m_pageLogicalHeightChanged = pageLogicalHeightChanged; + } else { + // If we don't establish a new page height, then propagate the old page height and offset down. + m_pageLogicalHeight = m_next->m_pageLogicalHeight; + m_pageLogicalHeightChanged = m_next->m_pageLogicalHeightChanged; + m_pageOffset = m_next->m_pageOffset; + + // Disable pagination for objects we don't support. For now this includes overflow:scroll/auto and inline blocks. + if (renderer->isReplaced() || renderer->scrollsOverflow()) + m_pageLogicalHeight = 0; + } + + if (!m_columnInfo) + m_columnInfo = m_next->m_columnInfo; + + m_layoutDelta = m_next->m_layoutDelta; + + // FIXME: <http://bugs.webkit.org/show_bug.cgi?id=13443> Apply control clip if present. +} + +LayoutState::LayoutState(RenderObject* root) + : m_clipped(false) + , m_pageLogicalHeight(0) + , m_pageLogicalHeightChanged(false) + , m_columnInfo(0) + , m_next(0) +#ifndef NDEBUG + , m_renderer(root) +#endif +{ + RenderObject* container = root->container(); + FloatPoint absContentPoint = container->localToAbsolute(FloatPoint(), false, true); + m_paintOffset = IntSize(absContentPoint.x(), absContentPoint.y()); + + if (container->hasOverflowClip()) { + RenderLayer* layer = toRenderBoxModelObject(container)->layer(); + m_clipped = true; + m_clipRect = IntRect(toPoint(m_paintOffset), layer->size()); + m_paintOffset -= layer->scrolledContentOffset(); + } +} + +#ifndef NDEBUG +static bool inLayoutStateDestroy; +#endif + +void LayoutState::destroy(RenderArena* renderArena) +{ +#ifndef NDEBUG + inLayoutStateDestroy = true; +#endif + delete this; +#ifndef NDEBUG + inLayoutStateDestroy = false; +#endif + renderArena->free(*(size_t*)this, this); +} + +void* LayoutState::operator new(size_t sz, RenderArena* renderArena) throw() +{ + return renderArena->allocate(sz); +} + +void LayoutState::operator delete(void* ptr, size_t sz) +{ + ASSERT(inLayoutStateDestroy); + *(size_t*)ptr = sz; +} + +void LayoutState::clearPaginationInformation() +{ + m_pageLogicalHeight = m_next->m_pageLogicalHeight; + m_pageOffset = m_next->m_pageOffset; + m_columnInfo = m_next->m_columnInfo; +} + +int LayoutState::pageLogicalOffset(int childLogicalOffset) const +{ + return m_layoutOffset.height() + childLogicalOffset - m_pageOffset.height(); +} + +void LayoutState::addForcedColumnBreak(int childY) +{ + if (!m_columnInfo || m_columnInfo->columnHeight()) + return; + m_columnInfo->addForcedBreak(pageLogicalOffset(childY)); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/LayoutState.h b/Source/WebCore/rendering/LayoutState.h new file mode 100644 index 0000000..0d06cb1 --- /dev/null +++ b/Source/WebCore/rendering/LayoutState.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LayoutState_h +#define LayoutState_h + +#include "IntRect.h" +#include "IntSize.h" +#include <wtf/Noncopyable.h> + +namespace WebCore { + +class ColumnInfo; +class RenderArena; +class RenderBox; +class RenderObject; + +class LayoutState : public Noncopyable { +public: + LayoutState() + : m_clipped(false) + , m_pageLogicalHeight(0) + , m_pageLogicalHeightChanged(false) + , m_columnInfo(0) + , m_next(0) +#ifndef NDEBUG + , m_renderer(0) +#endif + { + } + + LayoutState(LayoutState*, RenderBox*, const IntSize& offset, int pageHeight, bool pageHeightChanged, ColumnInfo*); + LayoutState(RenderObject*); + + void destroy(RenderArena*); + + // Overloaded new operator. + void* operator new(size_t, RenderArena*) throw(); + + // Overridden to prevent the normal delete from being called. + void operator delete(void*, size_t); + + void clearPaginationInformation(); + bool isPaginatingColumns() const { return m_columnInfo; } + bool isPaginated() const { return m_pageLogicalHeight || m_columnInfo; } + + // The page logical offset is the object's offset from the top of the page in the page progression + // direction (so an x-offset in vertical text and a y-offset for horizontal text). + int pageLogicalOffset(int childLogicalOffset) const; + + void addForcedColumnBreak(int childY); + + bool pageLogicalHeight() const { return m_pageLogicalHeight; } + bool pageLogicalHeightChanged() const { return m_pageLogicalHeightChanged; } + +private: + // The normal operator new is disallowed. + void* operator new(size_t) throw(); + +public: + bool m_clipped; + IntRect m_clipRect; + IntSize m_paintOffset; // x/y offset from container. Includes relative positioning and scroll offsets. + IntSize m_layoutOffset; // x/y offset from container. Does not include relative positioning or scroll offsets. + IntSize m_layoutDelta; // Transient offset from the final position of the object + // used to ensure that repaints happen in the correct place. + // This is a total delta accumulated from the root. + + int m_pageLogicalHeight; // The current page height for the pagination model that encloses us. + bool m_pageLogicalHeightChanged; // If our page height has changed, this will force all blocks to relayout. + IntSize m_pageOffset; // The offset of the start of the first page in the nearest enclosing pagination model. + ColumnInfo* m_columnInfo; // If the enclosing pagination model is a column model, then this will store column information for easy retrieval/manipulation. + + LayoutState* m_next; +#ifndef NDEBUG + RenderObject* m_renderer; +#endif +}; + +} // namespace WebCore + +#endif // LayoutState_h diff --git a/Source/WebCore/rendering/MediaControlElements.cpp b/Source/WebCore/rendering/MediaControlElements.cpp new file mode 100644 index 0000000..accbf4f --- /dev/null +++ b/Source/WebCore/rendering/MediaControlElements.cpp @@ -0,0 +1,876 @@ +/* + * Copyright (C) 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if ENABLE(VIDEO) + +#include "MediaControlElements.h" + +#include "EventNames.h" +#include "FloatConversion.h" +#include "Frame.h" +#include "HTMLNames.h" +#include "LocalizedStrings.h" +#include "MouseEvent.h" +#include "Page.h" +#include "RenderMedia.h" +#include "RenderSlider.h" +#include "RenderTheme.h" +#include "Settings.h" + +namespace WebCore { + +using namespace HTMLNames; + +HTMLMediaElement* toParentMediaElement(RenderObject* o) +{ + Node* node = o->node(); + Node* mediaNode = node ? node->shadowAncestorNode() : 0; + if (!mediaNode || (!mediaNode->hasTagName(HTMLNames::videoTag) && !mediaNode->hasTagName(HTMLNames::audioTag))) + return 0; + + return static_cast<HTMLMediaElement*>(mediaNode); +} + +// FIXME: These constants may need to be tweaked to better match the seeking in the QuickTime plug-in. +static const float cSeekRepeatDelay = 0.1f; +static const float cStepTime = 0.07f; +static const float cSeekTime = 0.2f; + +inline MediaControlShadowRootElement::MediaControlShadowRootElement(HTMLMediaElement* mediaElement) + : HTMLDivElement(divTag, mediaElement->document()) +{ + setShadowHost(mediaElement); +} + +PassRefPtr<MediaControlShadowRootElement> MediaControlShadowRootElement::create(HTMLMediaElement* mediaElement) +{ + RefPtr<MediaControlShadowRootElement> element = adoptRef(new MediaControlShadowRootElement(mediaElement)); + + RefPtr<RenderStyle> rootStyle = RenderStyle::create(); + rootStyle->inheritFrom(mediaElement->renderer()->style()); + rootStyle->setDisplay(BLOCK); + rootStyle->setPosition(RelativePosition); + + RenderMediaControlShadowRoot* renderer = new (mediaElement->renderer()->renderArena()) RenderMediaControlShadowRoot(element.get()); + renderer->setStyle(rootStyle.release()); + + element->setRenderer(renderer); + element->setAttached(); + element->setInDocument(); + + return element.release(); +} + +void MediaControlShadowRootElement::updateStyle() +{ + if (renderer()) { + RenderStyle* timelineContainerStyle = shadowHost()->renderer()->getCachedPseudoStyle(MEDIA_CONTROLS_TIMELINE_CONTAINER); + renderer()->setStyle(timelineContainerStyle); + } +} + +void MediaControlShadowRootElement::detach() +{ + HTMLDivElement::detach(); + // FIXME: Remove once shadow DOM uses Element::setShadowRoot(). + setShadowHost(0); +} + +// ---------------------------- + +MediaControlElement::MediaControlElement(HTMLMediaElement* mediaElement, PseudoId pseudo) + : HTMLDivElement(divTag, mediaElement->document()) + , m_mediaElement(mediaElement) + , m_pseudoStyleId(pseudo) +{ + setInDocument(); + switch (pseudo) { + case MEDIA_CONTROLS_CURRENT_TIME_DISPLAY: + m_displayType = MediaCurrentTimeDisplay; + break; + case MEDIA_CONTROLS_TIME_REMAINING_DISPLAY: + m_displayType = MediaTimeRemainingDisplay; + break; + case MEDIA_CONTROLS_TIMELINE_CONTAINER: + m_displayType = MediaTimelineContainer; + break; + case MEDIA_CONTROLS_STATUS_DISPLAY: + m_displayType = MediaStatusDisplay; + break; + case MEDIA_CONTROLS_PANEL: + m_displayType = MediaControlsPanel; + break; + case MEDIA_CONTROLS_VOLUME_SLIDER_CONTAINER: + m_displayType = MediaVolumeSliderContainer; + break; + default: + ASSERT_NOT_REACHED(); + break; + } +} + +PassRefPtr<MediaControlElement> MediaControlElement::create(HTMLMediaElement* mediaElement, PseudoId pseudoStyleId) +{ + return adoptRef(new MediaControlElement(mediaElement, pseudoStyleId)); +} + +void MediaControlElement::attachToParent(Element* parent) +{ + // FIXME: This code seems very wrong. Why are we magically adding |this| to the DOM here? + // We shouldn't be calling parser API methods outside of the parser! + parent->parserAddChild(this); +} + +void MediaControlElement::update() +{ + if (renderer()) + renderer()->updateFromElement(); + updateStyle(); +} + +PassRefPtr<RenderStyle> MediaControlElement::styleForElement() +{ + RenderStyle* style = m_mediaElement->renderer()->getCachedPseudoStyle(m_pseudoStyleId); + if (!style) + return 0; + + // text-decoration can't be overrided from CSS. So we do it here. + // See https://bugs.webkit.org/show_bug.cgi?id=27015 + style->setTextDecoration(TDNONE); + style->setTextDecorationsInEffect(TDNONE); + + return style; +} + +bool MediaControlElement::rendererIsNeeded(RenderStyle* style) +{ + ASSERT(document()->page()); + + return HTMLDivElement::rendererIsNeeded(style) && parentNode() && parentNode()->renderer() + && (!style->hasAppearance() || document()->page()->theme()->shouldRenderMediaControlPart(style->appearance(), m_mediaElement)); +} + +void MediaControlElement::attach() +{ + RefPtr<RenderStyle> style = styleForElement(); + if (!style) + return; + bool needsRenderer = rendererIsNeeded(style.get()); + if (!needsRenderer) + return; + RenderObject* renderer = createRenderer(m_mediaElement->renderer()->renderArena(), style.get()); + if (!renderer) + return; + renderer->setStyle(style.get()); + setRenderer(renderer); + if (parentNode() && parentNode()->renderer()) { + // Find next sibling with a renderer to determine where to insert. + Node* sibling = nextSibling(); + while (sibling && !sibling->renderer()) + sibling = sibling->nextSibling(); + parentNode()->renderer()->addChild(renderer, sibling ? sibling->renderer() : 0); + } + ContainerNode::attach(); +} + +void MediaControlElement::updateStyle() +{ + if (!m_mediaElement || !m_mediaElement->renderer()) + return; + + RefPtr<RenderStyle> style = styleForElement(); + if (!style) + return; + + bool needsRenderer = rendererIsNeeded(style.get()) && parentNode() && parentNode()->renderer(); + if (renderer() && !needsRenderer) + detach(); + else if (!renderer() && needsRenderer) + attach(); + else if (renderer()) { + renderer()->setStyle(style.get()); + + // Make sure that if there is any innerText renderer, it is updated as well. + if (firstChild() && firstChild()->renderer()) + firstChild()->renderer()->setStyle(style.get()); + } +} + +// ---------------------------- + +inline MediaControlTimelineContainerElement::MediaControlTimelineContainerElement(HTMLMediaElement* mediaElement) + : MediaControlElement(mediaElement, MEDIA_CONTROLS_TIMELINE_CONTAINER) +{ +} + +PassRefPtr<MediaControlTimelineContainerElement> MediaControlTimelineContainerElement::create(HTMLMediaElement* mediaElement) +{ + return adoptRef(new MediaControlTimelineContainerElement(mediaElement)); +} + +bool MediaControlTimelineContainerElement::rendererIsNeeded(RenderStyle* style) +{ + if (!MediaControlElement::rendererIsNeeded(style)) + return false; + + // This is for MediaControllerThemeClassic: + // If there is no style for MediaControlStatusDisplayElement style, don't hide + // the timeline. + if (!mediaElement()->renderer()->getCachedPseudoStyle(MEDIA_CONTROLS_STATUS_DISPLAY)) + return true; + + float duration = mediaElement()->duration(); + return !isnan(duration) && !isinf(duration); +} + +// ---------------------------- + +inline MediaControlVolumeSliderContainerElement::MediaControlVolumeSliderContainerElement(HTMLMediaElement* mediaElement) + : MediaControlElement(mediaElement, MEDIA_CONTROLS_VOLUME_SLIDER_CONTAINER) + , m_isVisible(false) + , m_x(0) + , m_y(0) +{ +} + +PassRefPtr<MediaControlVolumeSliderContainerElement> MediaControlVolumeSliderContainerElement::create(HTMLMediaElement* mediaElement) +{ + return adoptRef(new MediaControlVolumeSliderContainerElement(mediaElement)); +} + +PassRefPtr<RenderStyle> MediaControlVolumeSliderContainerElement::styleForElement() +{ + RefPtr<RenderStyle> style = MediaControlElement::styleForElement(); + style->setPosition(AbsolutePosition); + style->setLeft(Length(m_x, Fixed)); + style->setTop(Length(m_y, Fixed)); + style->setDisplay(m_isVisible ? BLOCK : NONE); + return style; +} + +void MediaControlVolumeSliderContainerElement::setVisible(bool visible) +{ + if (visible == m_isVisible) + return; + m_isVisible = visible; +} + +void MediaControlVolumeSliderContainerElement::setPosition(int x, int y) +{ + if (x == m_x && y == m_y) + return; + m_x = x; + m_y = y; +} + +bool MediaControlVolumeSliderContainerElement::hitTest(const IntPoint& absPoint) +{ + if (renderer() && renderer()->style()->hasAppearance()) + return renderer()->theme()->hitTestMediaControlPart(renderer(), absPoint); + + return false; +} + +// ---------------------------- + +inline MediaControlStatusDisplayElement::MediaControlStatusDisplayElement(HTMLMediaElement* mediaElement) + : MediaControlElement(mediaElement, MEDIA_CONTROLS_STATUS_DISPLAY) + , m_stateBeingDisplayed(Nothing) +{ +} + +PassRefPtr<MediaControlStatusDisplayElement> MediaControlStatusDisplayElement::create(HTMLMediaElement* mediaElement) +{ + return adoptRef(new MediaControlStatusDisplayElement(mediaElement)); +} + +void MediaControlStatusDisplayElement::update() +{ + MediaControlElement::update(); + + // Get the new state that we'll have to display. + StateBeingDisplayed newStateToDisplay = Nothing; + + if (mediaElement()->readyState() != HTMLMediaElement::HAVE_ENOUGH_DATA && !mediaElement()->currentSrc().isEmpty()) + newStateToDisplay = Loading; + else if (mediaElement()->movieLoadType() == MediaPlayer::LiveStream) + newStateToDisplay = LiveBroadcast; + + // Propagate only if needed. + if (newStateToDisplay == m_stateBeingDisplayed) + return; + m_stateBeingDisplayed = newStateToDisplay; + + ExceptionCode e; + switch (m_stateBeingDisplayed) { + case Nothing: + setInnerText("", e); + break; + case Loading: + setInnerText(mediaElementLoadingStateText(), e); + break; + case LiveBroadcast: + setInnerText(mediaElementLiveBroadcastStateText(), e); + break; + } +} + +bool MediaControlStatusDisplayElement::rendererIsNeeded(RenderStyle* style) +{ + if (!MediaControlElement::rendererIsNeeded(style)) + return false; + float duration = mediaElement()->duration(); + return (isnan(duration) || isinf(duration)); +} + +// ---------------------------- + +MediaControlInputElement::MediaControlInputElement(HTMLMediaElement* mediaElement, PseudoId pseudo) + : HTMLInputElement(inputTag, mediaElement->document()) + , m_mediaElement(mediaElement) + , m_pseudoStyleId(pseudo) +{ + setInDocument(); + + switch (pseudo) { + case MEDIA_CONTROLS_MUTE_BUTTON: + m_displayType = MediaMuteButton; + break; + case MEDIA_CONTROLS_PLAY_BUTTON: + m_displayType = MediaPlayButton; + break; + case MEDIA_CONTROLS_SEEK_FORWARD_BUTTON: + m_displayType = MediaSeekForwardButton; + break; + case MEDIA_CONTROLS_SEEK_BACK_BUTTON: + m_displayType = MediaSeekBackButton; + break; + case MEDIA_CONTROLS_FULLSCREEN_BUTTON: + m_displayType = MediaFullscreenButton; + break; + case MEDIA_CONTROLS_TIMELINE: + m_displayType = MediaSlider; + break; + case MEDIA_CONTROLS_RETURN_TO_REALTIME_BUTTON: + m_displayType = MediaReturnToRealtimeButton; + break; + case MEDIA_CONTROLS_REWIND_BUTTON: + m_displayType = MediaRewindButton; + break; + case MEDIA_CONTROLS_VOLUME_SLIDER: + m_displayType = MediaVolumeSlider; + break; + case MEDIA_CONTROLS_VOLUME_SLIDER_MUTE_BUTTON: + m_displayType = MediaVolumeSliderMuteButton; + break; + case MEDIA_CONTROLS_TOGGLE_CLOSED_CAPTIONS_BUTTON: + m_displayType = MediaShowClosedCaptionsButton; + break; + default: + ASSERT_NOT_REACHED(); + break; + } +} + +void MediaControlInputElement::attachToParent(Element* parent) +{ + // FIXME: This code seems very wrong. Why are we magically adding |this| to the DOM here? + // We shouldn't be calling parser API methods outside of the parser! + parent->parserAddChild(this); +} + +void MediaControlInputElement::update() +{ + updateDisplayType(); + if (renderer()) + renderer()->updateFromElement(); + updateStyle(); +} + +PassRefPtr<RenderStyle> MediaControlInputElement::styleForElement() +{ + return mediaElement()->renderer()->getCachedPseudoStyle(m_pseudoStyleId); +} + +bool MediaControlInputElement::rendererIsNeeded(RenderStyle* style) +{ + ASSERT(document()->page()); + + return HTMLInputElement::rendererIsNeeded(style) && parentNode() && parentNode()->renderer() + && (!style->hasAppearance() || document()->page()->theme()->shouldRenderMediaControlPart(style->appearance(), mediaElement())); +} + +void MediaControlInputElement::attach() +{ + RefPtr<RenderStyle> style = styleForElement(); + if (!style) + return; + + bool needsRenderer = rendererIsNeeded(style.get()); + if (!needsRenderer) + return; + RenderObject* renderer = createRenderer(mediaElement()->renderer()->renderArena(), style.get()); + if (!renderer) + return; + renderer->setStyle(style.get()); + setRenderer(renderer); + if (parentNode() && parentNode()->renderer()) { + // Find next sibling with a renderer to determine where to insert. + Node* sibling = nextSibling(); + while (sibling && !sibling->renderer()) + sibling = sibling->nextSibling(); + parentNode()->renderer()->addChild(renderer, sibling ? sibling->renderer() : 0); + } + ContainerNode::attach(); +} + +void MediaControlInputElement::updateStyle() +{ + if (!mediaElement() || !mediaElement()->renderer()) + return; + + RefPtr<RenderStyle> style = styleForElement(); + if (!style) + return; + + bool needsRenderer = rendererIsNeeded(style.get()) && parentNode() && parentNode()->renderer(); + if (renderer() && !needsRenderer) + detach(); + else if (!renderer() && needsRenderer) + attach(); + else if (renderer()) + renderer()->setStyle(style.get()); +} + +bool MediaControlInputElement::hitTest(const IntPoint& absPoint) +{ + if (renderer() && renderer()->style()->hasAppearance()) + return renderer()->theme()->hitTestMediaControlPart(renderer(), absPoint); + + return false; +} + +void MediaControlInputElement::setDisplayType(MediaControlElementType displayType) +{ + if (displayType == m_displayType) + return; + + m_displayType = displayType; + if (RenderObject* object = renderer()) + object->repaint(); +} + +// ---------------------------- + +inline MediaControlMuteButtonElement::MediaControlMuteButtonElement(HTMLMediaElement* mediaElement, ButtonLocation location) + : MediaControlInputElement(mediaElement, location == Controller ? MEDIA_CONTROLS_MUTE_BUTTON : MEDIA_CONTROLS_VOLUME_SLIDER_MUTE_BUTTON) +{ +} + +PassRefPtr<MediaControlMuteButtonElement> MediaControlMuteButtonElement::create(HTMLMediaElement* mediaElement, ButtonLocation location) +{ + RefPtr<MediaControlMuteButtonElement> button = adoptRef(new MediaControlMuteButtonElement(mediaElement, location)); + button->setType("button"); + return button.release(); +} + +void MediaControlMuteButtonElement::defaultEventHandler(Event* event) +{ + if (event->type() == eventNames().clickEvent) { + mediaElement()->setMuted(!mediaElement()->muted()); + event->setDefaultHandled(); + } + HTMLInputElement::defaultEventHandler(event); +} + +void MediaControlMuteButtonElement::updateDisplayType() +{ + setDisplayType(mediaElement()->muted() ? MediaUnMuteButton : MediaMuteButton); +} + +// ---------------------------- + +inline MediaControlPlayButtonElement::MediaControlPlayButtonElement(HTMLMediaElement* mediaElement) + : MediaControlInputElement(mediaElement, MEDIA_CONTROLS_PLAY_BUTTON) +{ +} + +PassRefPtr<MediaControlPlayButtonElement> MediaControlPlayButtonElement::create(HTMLMediaElement* mediaElement) +{ + RefPtr<MediaControlPlayButtonElement> button = adoptRef(new MediaControlPlayButtonElement(mediaElement)); + button->setType("button"); + return button.release(); +} + +void MediaControlPlayButtonElement::defaultEventHandler(Event* event) +{ + if (event->type() == eventNames().clickEvent) { + mediaElement()->togglePlayState(); + event->setDefaultHandled(); + } + HTMLInputElement::defaultEventHandler(event); +} + +void MediaControlPlayButtonElement::updateDisplayType() +{ + setDisplayType(mediaElement()->canPlay() ? MediaPlayButton : MediaPauseButton); +} + +// ---------------------------- + +inline MediaControlSeekButtonElement::MediaControlSeekButtonElement(HTMLMediaElement* mediaElement, PseudoId pseudoId) + : MediaControlInputElement(mediaElement, pseudoId) + , m_seeking(false) + , m_capturing(false) + , m_seekTimer(this, &MediaControlSeekButtonElement::seekTimerFired) +{ +} + +PassRefPtr<MediaControlSeekButtonElement> MediaControlSeekButtonElement::create(HTMLMediaElement* mediaElement, PseudoId pseudoStyleId) +{ + RefPtr<MediaControlSeekButtonElement> button = adoptRef(new MediaControlSeekButtonElement(mediaElement, pseudoStyleId)); + button->setType("button"); + return button.release(); +} + +inline bool MediaControlSeekButtonElement::isForwardButton() const +{ + return pseudoStyleId() == MEDIA_CONTROLS_SEEK_FORWARD_BUTTON; +} + +void MediaControlSeekButtonElement::defaultEventHandler(Event* event) +{ + if (event->type() == eventNames().mousedownEvent) { + if (Frame* frame = document()->frame()) { + m_capturing = true; + frame->eventHandler()->setCapturingMouseEventsNode(this); + } + mediaElement()->pause(event->fromUserGesture()); + m_seekTimer.startRepeating(cSeekRepeatDelay); + event->setDefaultHandled(); + } else if (event->type() == eventNames().mouseupEvent) { + if (m_capturing) + if (Frame* frame = document()->frame()) { + m_capturing = false; + frame->eventHandler()->setCapturingMouseEventsNode(0); + } + ExceptionCode ec; + if (m_seeking || m_seekTimer.isActive()) { + if (!m_seeking) { + float stepTime = isForwardButton() ? cStepTime : -cStepTime; + mediaElement()->setCurrentTime(mediaElement()->currentTime() + stepTime, ec); + } + m_seekTimer.stop(); + m_seeking = false; + event->setDefaultHandled(); + } + } + HTMLInputElement::defaultEventHandler(event); +} + +void MediaControlSeekButtonElement::seekTimerFired(Timer<MediaControlSeekButtonElement>*) +{ + ExceptionCode ec; + m_seeking = true; + float seekTime = isForwardButton() ? cSeekTime : -cSeekTime; + mediaElement()->setCurrentTime(mediaElement()->currentTime() + seekTime, ec); +} + +void MediaControlSeekButtonElement::detach() +{ + if (m_capturing) { + if (Frame* frame = document()->frame()) + frame->eventHandler()->setCapturingMouseEventsNode(0); + } + MediaControlInputElement::detach(); +} + +// ---------------------------- + +inline MediaControlRewindButtonElement::MediaControlRewindButtonElement(HTMLMediaElement* element) + : MediaControlInputElement(element, MEDIA_CONTROLS_REWIND_BUTTON) +{ +} + +PassRefPtr<MediaControlRewindButtonElement> MediaControlRewindButtonElement::create(HTMLMediaElement* mediaElement) +{ + RefPtr<MediaControlRewindButtonElement> button = adoptRef(new MediaControlRewindButtonElement(mediaElement)); + button->setType("button"); + return button.release(); +} + +void MediaControlRewindButtonElement::defaultEventHandler(Event* event) +{ + if (event->type() == eventNames().clickEvent) { + mediaElement()->rewind(30); + event->setDefaultHandled(); + } + HTMLInputElement::defaultEventHandler(event); +} + +// ---------------------------- + +inline MediaControlReturnToRealtimeButtonElement::MediaControlReturnToRealtimeButtonElement(HTMLMediaElement* mediaElement) + : MediaControlInputElement(mediaElement, MEDIA_CONTROLS_RETURN_TO_REALTIME_BUTTON) +{ +} + +PassRefPtr<MediaControlReturnToRealtimeButtonElement> MediaControlReturnToRealtimeButtonElement::create(HTMLMediaElement* mediaElement) +{ + RefPtr<MediaControlReturnToRealtimeButtonElement> button = adoptRef(new MediaControlReturnToRealtimeButtonElement(mediaElement)); + button->setType("button"); + return button.release(); +} + +void MediaControlReturnToRealtimeButtonElement::defaultEventHandler(Event* event) +{ + if (event->type() == eventNames().clickEvent) { + mediaElement()->returnToRealtime(); + event->setDefaultHandled(); + } + HTMLInputElement::defaultEventHandler(event); +} + + +// ---------------------------- + +inline MediaControlToggleClosedCaptionsButtonElement::MediaControlToggleClosedCaptionsButtonElement(HTMLMediaElement* mediaElement) + : MediaControlInputElement(mediaElement, MEDIA_CONTROLS_TOGGLE_CLOSED_CAPTIONS_BUTTON) +{ +} + +PassRefPtr<MediaControlToggleClosedCaptionsButtonElement> MediaControlToggleClosedCaptionsButtonElement::create(HTMLMediaElement* mediaElement) +{ + RefPtr<MediaControlToggleClosedCaptionsButtonElement> button = adoptRef(new MediaControlToggleClosedCaptionsButtonElement(mediaElement)); + button->setType("button"); + return button.release(); +} + +void MediaControlToggleClosedCaptionsButtonElement::defaultEventHandler(Event* event) +{ + if (event->type() == eventNames().clickEvent) { + mediaElement()->setClosedCaptionsVisible(!mediaElement()->closedCaptionsVisible()); + setChecked(mediaElement()->closedCaptionsVisible()); + event->setDefaultHandled(); + } + HTMLInputElement::defaultEventHandler(event); +} + +void MediaControlToggleClosedCaptionsButtonElement::updateDisplayType() +{ + setDisplayType(mediaElement()->closedCaptionsVisible() ? MediaHideClosedCaptionsButton : MediaShowClosedCaptionsButton); +} + +// ---------------------------- + +MediaControlTimelineElement::MediaControlTimelineElement(HTMLMediaElement* mediaElement) + : MediaControlInputElement(mediaElement, MEDIA_CONTROLS_TIMELINE) +{ +} + +PassRefPtr<MediaControlTimelineElement> MediaControlTimelineElement::create(HTMLMediaElement* mediaElement) +{ + RefPtr<MediaControlTimelineElement> timeline = adoptRef(new MediaControlTimelineElement(mediaElement)); + timeline->setType("range"); + return timeline.release(); +} + +void MediaControlTimelineElement::defaultEventHandler(Event* event) +{ + // Left button is 0. Rejects mouse events not from left button. + if (event->isMouseEvent() && static_cast<MouseEvent*>(event)->button()) + return; + + if (!attached()) + return; + + if (event->type() == eventNames().mousedownEvent) + mediaElement()->beginScrubbing(); + + MediaControlInputElement::defaultEventHandler(event); + + if (event->type() == eventNames().mouseoverEvent || event->type() == eventNames().mouseoutEvent || event->type() == eventNames().mousemoveEvent) + return; + + float time = narrowPrecisionToFloat(value().toDouble()); + if (time != mediaElement()->currentTime()) { + ExceptionCode ec; + mediaElement()->setCurrentTime(time, ec); + } + + RenderSlider* slider = toRenderSlider(renderer()); + if (slider && slider->inDragMode()) { + toRenderMedia(mediaElement()->renderer())->updateTimeDisplay(); +#if PLATFORM(ANDROID) + toRenderMedia(mediaElement()->renderer())->updateLastTouch(); +#endif + } + + if (event->type() == eventNames().mouseupEvent) + mediaElement()->endScrubbing(); +} + +void MediaControlTimelineElement::update(bool updateDuration) +{ + if (updateDuration) { + float duration = mediaElement()->duration(); + setAttribute(maxAttr, String::number(isfinite(duration) ? duration : 0)); + } + setValue(String::number(mediaElement()->currentTime())); + MediaControlInputElement::update(); +} + +// ---------------------------- + +inline MediaControlVolumeSliderElement::MediaControlVolumeSliderElement(HTMLMediaElement* mediaElement) + : MediaControlInputElement(mediaElement, MEDIA_CONTROLS_VOLUME_SLIDER) +{ +} + +PassRefPtr<MediaControlVolumeSliderElement> MediaControlVolumeSliderElement::create(HTMLMediaElement* mediaElement) +{ + RefPtr<MediaControlVolumeSliderElement> slider = adoptRef(new MediaControlVolumeSliderElement(mediaElement)); + slider->setType("range"); + return slider.release(); +} + +void MediaControlVolumeSliderElement::defaultEventHandler(Event* event) +{ + // Left button is 0. Rejects mouse events not from left button. + if (event->isMouseEvent() && static_cast<MouseEvent*>(event)->button()) + return; + + if (!attached()) + return; + + MediaControlInputElement::defaultEventHandler(event); + + if (event->type() == eventNames().mouseoverEvent || event->type() == eventNames().mouseoutEvent || event->type() == eventNames().mousemoveEvent) + return; + + float volume = narrowPrecisionToFloat(value().toDouble()); + if (volume != mediaElement()->volume()) { + ExceptionCode ec = 0; + mediaElement()->setVolume(volume, ec); + ASSERT(!ec); + } +} + +void MediaControlVolumeSliderElement::update() +{ + float volume = mediaElement()->volume(); + if (value().toFloat() != volume) + setValue(String::number(volume)); + MediaControlInputElement::update(); +} + +// ---------------------------- + +inline MediaControlFullscreenButtonElement::MediaControlFullscreenButtonElement(HTMLMediaElement* mediaElement) + : MediaControlInputElement(mediaElement, MEDIA_CONTROLS_FULLSCREEN_BUTTON) +{ +} + +PassRefPtr<MediaControlFullscreenButtonElement> MediaControlFullscreenButtonElement::create(HTMLMediaElement* mediaElement) +{ + RefPtr<MediaControlFullscreenButtonElement> button = adoptRef(new MediaControlFullscreenButtonElement(mediaElement)); + button->setType("button"); + return button.release(); +} + +void MediaControlFullscreenButtonElement::defaultEventHandler(Event* event) +{ + if (event->type() == eventNames().clickEvent) { +#if ENABLE(FULLSCREEN_API) + // Only use the new full screen API if the fullScreenEnabled setting has + // been explicitly enabled. Otherwise, use the old fullscreen API. This + // allows apps which embed a WebView to retain the existing full screen + // video implementation without requiring them to implement their own full + // screen behavior. + if (document()->settings() && document()->settings()->fullScreenEnabled()) { + if (document()->webkitIsFullScreen() && document()->webkitCurrentFullScreenElement() == mediaElement()) + document()->webkitCancelFullScreen(); + else + mediaElement()->webkitRequestFullScreen(0); + } else +#endif + mediaElement()->enterFullscreen(); + event->setDefaultHandled(); + } + HTMLInputElement::defaultEventHandler(event); +} + +// ---------------------------- + +inline MediaControlTimeDisplayElement::MediaControlTimeDisplayElement(HTMLMediaElement* mediaElement, PseudoId pseudo) + : MediaControlElement(mediaElement, pseudo) + , m_currentValue(0) + , m_isVisible(true) +{ +} + +PassRefPtr<MediaControlTimeDisplayElement> MediaControlTimeDisplayElement::create(HTMLMediaElement* mediaElement, PseudoId pseudoStyleId) +{ + return adoptRef(new MediaControlTimeDisplayElement(mediaElement, pseudoStyleId)); +} + +PassRefPtr<RenderStyle> MediaControlTimeDisplayElement::styleForElement() +{ + RefPtr<RenderStyle> style = MediaControlElement::styleForElement(); + if (!m_isVisible) { + style = RenderStyle::clone(style.get()); + style->setWidth(Length(0, Fixed)); + } + return style; +} + +void MediaControlTimeDisplayElement::setVisible(bool visible) +{ + if (visible == m_isVisible) + return; + m_isVisible = visible; + + // This function is used during the RenderMedia::layout() + // call, where we cannot change the renderer at this time. + if (!renderer() || !renderer()->style()) + return; + + RefPtr<RenderStyle> style = styleForElement(); + renderer()->setStyle(style.get()); +} + +void MediaControlTimeDisplayElement::setCurrentValue(float time) +{ + m_currentValue = time; +} + +} // namespace WebCore + +#endif // ENABLE(VIDEO) diff --git a/Source/WebCore/rendering/MediaControlElements.h b/Source/WebCore/rendering/MediaControlElements.h new file mode 100644 index 0000000..b2d063d --- /dev/null +++ b/Source/WebCore/rendering/MediaControlElements.h @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MediaControlElements_h +#define MediaControlElements_h + +#if ENABLE(VIDEO) + +#include "HTMLDivElement.h" +#include "HTMLInputElement.h" +#include "HTMLMediaElement.h" +#include "RenderBlock.h" + +// These are the shadow elements used in RenderMedia + +namespace WebCore { + +class Event; +class Frame; + +// Must match WebKitSystemInterface.h +enum MediaControlElementType { + MediaFullscreenButton = 0, + MediaMuteButton, + MediaPlayButton, + MediaSeekBackButton, + MediaSeekForwardButton, + MediaSlider, + MediaSliderThumb, + MediaRewindButton, + MediaReturnToRealtimeButton, + MediaShowClosedCaptionsButton, + MediaHideClosedCaptionsButton, + MediaUnMuteButton, + MediaPauseButton, + MediaTimelineContainer, + MediaCurrentTimeDisplay, + MediaTimeRemainingDisplay, + MediaStatusDisplay, + MediaControlsPanel, + MediaVolumeSliderContainer, + MediaVolumeSlider, + MediaVolumeSliderThumb, + MediaVolumeSliderMuteButton, +}; + +HTMLMediaElement* toParentMediaElement(RenderObject*); + +class MediaControlShadowRootElement : public HTMLDivElement { +public: + static PassRefPtr<MediaControlShadowRootElement> create(HTMLMediaElement*); + + void updateStyle(); + virtual void detach(); + +private: + MediaControlShadowRootElement(HTMLMediaElement*); +}; + +// ---------------------------- + +class MediaControlElement : public HTMLDivElement { +public: + static PassRefPtr<MediaControlElement> create(HTMLMediaElement*, PseudoId); + + virtual void attach(); + void attachToParent(Element*); + void update(); + void updateStyle(); + + MediaControlElementType displayType() const { return m_displayType; } + + HTMLMediaElement* mediaElement() const { return m_mediaElement; } + +protected: + MediaControlElement(HTMLMediaElement*, PseudoId); + + virtual bool rendererIsNeeded(RenderStyle*); + + virtual PassRefPtr<RenderStyle> styleForElement(); + +private: + virtual bool isMediaControlElement() const { return true; } + + HTMLMediaElement* m_mediaElement; + PseudoId m_pseudoStyleId; + MediaControlElementType m_displayType; // some elements can show multiple types (e.g. play/pause) +}; + +// ---------------------------- + +class MediaControlTimelineContainerElement : public MediaControlElement { +public: + static PassRefPtr<MediaControlTimelineContainerElement> create(HTMLMediaElement*); + +private: + MediaControlTimelineContainerElement(HTMLMediaElement*); + virtual bool rendererIsNeeded(RenderStyle*); +}; + +// ---------------------------- + +class MediaControlVolumeSliderContainerElement : public MediaControlElement { +public: + static PassRefPtr<MediaControlVolumeSliderContainerElement> create(HTMLMediaElement*); + + virtual PassRefPtr<RenderStyle> styleForElement(); + void setVisible(bool); + bool isVisible() { return m_isVisible; } + void setPosition(int x, int y); + bool hitTest(const IntPoint& absPoint); + +private: + MediaControlVolumeSliderContainerElement(HTMLMediaElement*); + + bool m_isVisible; + int m_x, m_y; +}; + +// ---------------------------- + +class MediaControlStatusDisplayElement : public MediaControlElement { +public: + static PassRefPtr<MediaControlStatusDisplayElement> create(HTMLMediaElement*); + + void update(); + +private: + MediaControlStatusDisplayElement(HTMLMediaElement*); + + virtual bool rendererIsNeeded(RenderStyle*); + + enum StateBeingDisplayed { Nothing, Loading, LiveBroadcast }; + StateBeingDisplayed m_stateBeingDisplayed; +}; + +// ---------------------------- + +class MediaControlInputElement : public HTMLInputElement { +public: + void attachToParent(Element*); + void update(); + void updateStyle(); + + bool hitTest(const IntPoint& absPoint); + + MediaControlElementType displayType() const { return m_displayType; } + + HTMLMediaElement* mediaElement() const { return m_mediaElement; } + +protected: + MediaControlInputElement(HTMLMediaElement*, PseudoId); + + void setDisplayType(MediaControlElementType); + + PseudoId pseudoStyleId() const { return m_pseudoStyleId; } + +private: + virtual void attach(); + virtual bool rendererIsNeeded(RenderStyle*); + + virtual PassRefPtr<RenderStyle> styleForElement(); + + virtual bool isMediaControlElement() const { return true; } + + virtual void updateDisplayType() { } + + HTMLMediaElement* m_mediaElement; + PseudoId m_pseudoStyleId; + MediaControlElementType m_displayType; +}; + +// ---------------------------- + +class MediaControlMuteButtonElement : public MediaControlInputElement { +public: + enum ButtonLocation { Controller, VolumeSlider }; + static PassRefPtr<MediaControlMuteButtonElement> create(HTMLMediaElement*, ButtonLocation); + + virtual void defaultEventHandler(Event*); + +private: + MediaControlMuteButtonElement(HTMLMediaElement*, ButtonLocation); + + virtual void updateDisplayType(); +}; + +// ---------------------------- + +class MediaControlPlayButtonElement : public MediaControlInputElement { +public: + static PassRefPtr<MediaControlPlayButtonElement> create(HTMLMediaElement*); + + virtual void defaultEventHandler(Event*); + +private: + MediaControlPlayButtonElement(HTMLMediaElement*); + + virtual void updateDisplayType(); +}; + +// ---------------------------- + +class MediaControlSeekButtonElement : public MediaControlInputElement { +public: + static PassRefPtr<MediaControlSeekButtonElement> create(HTMLMediaElement*, PseudoId); + + virtual void defaultEventHandler(Event*); + +private: + MediaControlSeekButtonElement(HTMLMediaElement*, PseudoId); + + bool isForwardButton() const; + + virtual void detach(); + void seekTimerFired(Timer<MediaControlSeekButtonElement>*); + + bool m_seeking; + bool m_capturing; + Timer<MediaControlSeekButtonElement> m_seekTimer; +}; + +// ---------------------------- + +class MediaControlRewindButtonElement : public MediaControlInputElement { +public: + static PassRefPtr<MediaControlRewindButtonElement> create(HTMLMediaElement*); + + virtual void defaultEventHandler(Event*); + +private: + MediaControlRewindButtonElement(HTMLMediaElement*); +}; + +// ---------------------------- + +class MediaControlReturnToRealtimeButtonElement : public MediaControlInputElement { +public: + static PassRefPtr<MediaControlReturnToRealtimeButtonElement> create(HTMLMediaElement*); + + virtual void defaultEventHandler(Event*); + +private: + MediaControlReturnToRealtimeButtonElement(HTMLMediaElement*); +}; + +// ---------------------------- + +class MediaControlToggleClosedCaptionsButtonElement : public MediaControlInputElement { +public: + static PassRefPtr<MediaControlToggleClosedCaptionsButtonElement> create(HTMLMediaElement*); + + virtual void defaultEventHandler(Event*); + +private: + MediaControlToggleClosedCaptionsButtonElement(HTMLMediaElement*); + + virtual void updateDisplayType(); +}; + +// ---------------------------- + +class MediaControlTimelineElement : public MediaControlInputElement { +public: + static PassRefPtr<MediaControlTimelineElement> create(HTMLMediaElement*); + + virtual void defaultEventHandler(Event*); + void update(bool updateDuration = true); + +private: + MediaControlTimelineElement(HTMLMediaElement*); +}; + +// ---------------------------- + +class MediaControlVolumeSliderElement : public MediaControlInputElement { +public: + static PassRefPtr<MediaControlVolumeSliderElement> create(HTMLMediaElement*); + + virtual void defaultEventHandler(Event*); + void update(); + +private: + MediaControlVolumeSliderElement(HTMLMediaElement*); +}; + +// ---------------------------- + +class MediaControlFullscreenButtonElement : public MediaControlInputElement { +public: + static PassRefPtr<MediaControlFullscreenButtonElement> create(HTMLMediaElement*); + + virtual void defaultEventHandler(Event*); + +private: + MediaControlFullscreenButtonElement(HTMLMediaElement*); +}; + +// ---------------------------- + +class MediaControlTimeDisplayElement : public MediaControlElement { +public: + static PassRefPtr<MediaControlTimeDisplayElement> create(HTMLMediaElement*, PseudoId); + + void setVisible(bool); + + void setCurrentValue(float); + float currentValue() const { return m_currentValue; } + +private: + MediaControlTimeDisplayElement(HTMLMediaElement*, PseudoId); + + virtual PassRefPtr<RenderStyle> styleForElement(); + float m_currentValue; + bool m_isVisible; +}; + +// ---------------------------- + +class RenderMediaControlShadowRoot : public RenderBlock { +public: + RenderMediaControlShadowRoot(Element* e) : RenderBlock(e) { } + void setParent(RenderObject* p) { RenderObject::setParent(p); } +}; + +// ---------------------------- + +} //namespace WebCore + +#endif // ENABLE(VIDEO) + +#endif // MediaControlElements_h diff --git a/Source/WebCore/rendering/OverlapTestRequestClient.h b/Source/WebCore/rendering/OverlapTestRequestClient.h new file mode 100644 index 0000000..71400ab --- /dev/null +++ b/Source/WebCore/rendering/OverlapTestRequestClient.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2009 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef OverlapTestRequestClient_h +#define OverlapTestRequestClient_h + +namespace WebCore { + +class OverlapTestRequestClient { +public: + virtual ~OverlapTestRequestClient() { } + virtual void setOverlapTestResult(bool) = 0; +}; + +} // namespace WebCore + +#endif // OverlapTestRequestClient_h diff --git a/Source/WebCore/rendering/PaintInfo.h b/Source/WebCore/rendering/PaintInfo.h new file mode 100644 index 0000000..3598807 --- /dev/null +++ b/Source/WebCore/rendering/PaintInfo.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * (C) 2004 Allan Sandfeld Jensen (kde@carewolf.com) + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef PaintInfo_h +#define PaintInfo_h + +#if ENABLE(SVG) +#include "AffineTransform.h" +#endif + +#include "GraphicsContext.h" +#include "IntRect.h" +#include "PaintPhase.h" +#include <wtf/HashMap.h> +#include <wtf/ListHashSet.h> + +namespace WebCore { + +class OverlapTestRequestClient; +class RenderInline; +class RenderObject; + +typedef HashMap<OverlapTestRequestClient*, IntRect> OverlapTestRequestMap; + +/* + * Paint the object and its children, clipped by (x|y|w|h). + * (tx|ty) is the calculated position of the parent + */ +struct PaintInfo { + PaintInfo(GraphicsContext* newContext, const IntRect& newRect, PaintPhase newPhase, bool newForceBlackText, + RenderObject* newPaintingRoot, ListHashSet<RenderInline*>* newOutlineObjects, + OverlapTestRequestMap* overlapTestRequests = 0) + : context(newContext) + , rect(newRect) + , phase(newPhase) + , forceBlackText(newForceBlackText) + , paintingRoot(newPaintingRoot) + , outlineObjects(newOutlineObjects) + , overlapTestRequests(overlapTestRequests) + { + } + + void updatePaintingRootForChildren(const RenderObject* renderer) + { + if (!paintingRoot) + return; + + // If we're the painting root, kids draw normally, and see root of 0. + if (paintingRoot == renderer) { + paintingRoot = 0; + return; + } + } + + bool shouldPaintWithinRoot(const RenderObject* renderer) const + { + return !paintingRoot || paintingRoot == renderer; + } + +#if ENABLE(SVG) + void applyTransform(const AffineTransform& localToAncestorTransform) + { + if (localToAncestorTransform.isIdentity()) + return; + + context->concatCTM(localToAncestorTransform); + + if (rect == infiniteRect()) + return; + + rect = localToAncestorTransform.inverse().mapRect(rect); + } +#endif + + static IntRect infiniteRect() { return IntRect(INT_MIN / 2, INT_MIN / 2, INT_MAX, INT_MAX); } + + // FIXME: Introduce setters/getters at some point. Requires a lot of changes throughout rendering/. + GraphicsContext* context; + IntRect rect; + PaintPhase phase; + bool forceBlackText; + RenderObject* paintingRoot; // used to draw just one element and its visual kids + ListHashSet<RenderInline*>* outlineObjects; // used to list outlines that should be painted by a block with inline children + OverlapTestRequestMap* overlapTestRequests; +}; + +} // namespace WebCore + +#endif // PaintInfo_h diff --git a/Source/WebCore/rendering/PaintPhase.h b/Source/WebCore/rendering/PaintPhase.h new file mode 100644 index 0000000..396131f --- /dev/null +++ b/Source/WebCore/rendering/PaintPhase.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * (C) 2004 Allan Sandfeld Jensen (kde@carewolf.com) + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef PaintPhase_h +#define PaintPhase_h + +namespace WebCore { + +/* + * The painting of a layer occurs in three distinct phases. Each phase involves + * a recursive descent into the layer's render objects. The first phase is the background phase. + * The backgrounds and borders of all blocks are painted. Inlines are not painted at all. + * Floats must paint above block backgrounds but entirely below inline content that can overlap them. + * In the foreground phase, all inlines are fully painted. Inline replaced elements will get all + * three phases invoked on them during this phase. + */ + +enum PaintPhase { + PaintPhaseBlockBackground, + PaintPhaseChildBlockBackground, + PaintPhaseChildBlockBackgrounds, + PaintPhaseFloat, + PaintPhaseForeground, + PaintPhaseOutline, + PaintPhaseChildOutlines, + PaintPhaseSelfOutline, + PaintPhaseSelection, + PaintPhaseCollapsedTableBorders, + PaintPhaseTextClip, + PaintPhaseMask +}; + +enum PaintBehaviorFlags { + PaintBehaviorNormal = 0, + PaintBehaviorSelectionOnly = 1 << 0, + PaintBehaviorForceBlackText = 1 << 1, + PaintBehaviorFlattenCompositingLayers = 1 << 2 +}; + +typedef unsigned PaintBehavior; + +} // namespace WebCore + +#endif // PaintPhase_h diff --git a/Source/WebCore/rendering/PointerEventsHitRules.cpp b/Source/WebCore/rendering/PointerEventsHitRules.cpp new file mode 100644 index 0000000..cc5aae4 --- /dev/null +++ b/Source/WebCore/rendering/PointerEventsHitRules.cpp @@ -0,0 +1,111 @@ +/* + Copyright (C) 2007 Rob Buis <buis@kde.org> + + 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 + aint 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 "PointerEventsHitRules.h" + +namespace WebCore { + +PointerEventsHitRules::PointerEventsHitRules(EHitTesting hitTesting, const HitTestRequest& request, EPointerEvents pointerEvents) + : requireVisible(false) + , requireFill(false) + , requireStroke(false) + , canHitStroke(false) + , canHitFill(false) +{ + if (request.svgClipContent()) + pointerEvents = PE_FILL; + + if (hitTesting == SVG_PATH_HITTESTING) { + switch (pointerEvents) + { + case PE_VISIBLE_PAINTED: + case PE_AUTO: // "auto" is like "visiblePainted" when in SVG content + requireFill = true; + requireStroke = true; + case PE_VISIBLE: + requireVisible = true; + canHitFill = true; + canHitStroke = true; + break; + case PE_VISIBLE_FILL: + requireVisible = true; + canHitFill = true; + break; + case PE_VISIBLE_STROKE: + requireVisible = true; + canHitStroke = true; + break; + case PE_PAINTED: + requireFill = true; + requireStroke = true; + case PE_ALL: + canHitFill = true; + canHitStroke = true; + break; + case PE_FILL: + canHitFill = true; + break; + case PE_STROKE: + canHitStroke = true; + break; + case PE_NONE: + // nothing to do here, defaults are all false. + break; + } + } else { + switch (pointerEvents) + { + case PE_VISIBLE_PAINTED: + case PE_AUTO: // "auto" is like "visiblePainted" when in SVG content + requireVisible = true; + requireFill = true; + requireStroke = true; + canHitFill = true; + canHitStroke = true; + break; + case PE_VISIBLE_FILL: + case PE_VISIBLE_STROKE: + case PE_VISIBLE: + requireVisible = true; + canHitFill = true; + canHitStroke = true; + break; + case PE_PAINTED: + requireFill = true; + requireStroke = true; + canHitFill = true; + canHitStroke = true; + break; + case PE_FILL: + case PE_STROKE: + case PE_ALL: + canHitFill = true; + canHitStroke = true; + break; + case PE_NONE: + // nothing to do here, defaults are all false. + break; + } + } +} + +} + +// vim:ts=4:noet diff --git a/Source/WebCore/rendering/PointerEventsHitRules.h b/Source/WebCore/rendering/PointerEventsHitRules.h new file mode 100644 index 0000000..72fbc45 --- /dev/null +++ b/Source/WebCore/rendering/PointerEventsHitRules.h @@ -0,0 +1,49 @@ +/* + Copyright (C) 2007 Rob Buis <buis@kde.org> + + 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 + aint 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. +*/ + +#ifndef PointerEventsHitRules_h +#define PointerEventsHitRules_h + +#include "HitTestRequest.h" +#include "RenderStyleConstants.h" + +namespace WebCore { + +class PointerEventsHitRules { +public: + enum EHitTesting { + SVG_IMAGE_HITTESTING, + SVG_PATH_HITTESTING, + SVG_TEXT_HITTESTING + }; + + PointerEventsHitRules(EHitTesting, const HitTestRequest&, EPointerEvents); + + bool requireVisible; + bool requireFill; + bool requireStroke; + bool canHitStroke; + bool canHitFill; +}; + +} + +#endif + +// vim:ts=4:noet diff --git a/Source/WebCore/rendering/RenderApplet.cpp b/Source/WebCore/rendering/RenderApplet.cpp new file mode 100644 index 0000000..eaa3535 --- /dev/null +++ b/Source/WebCore/rendering/RenderApplet.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * Copyright (C) 2003, 2006, 2009 Apple Inc. All rights reserved. + * + * 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 "RenderApplet.h" + +#include "Frame.h" +#include "HTMLAppletElement.h" +#include "HTMLNames.h" +#include "HTMLParamElement.h" +#include "PluginViewBase.h" +#include "Widget.h" + +namespace WebCore { + +using namespace HTMLNames; + +RenderApplet::RenderApplet(HTMLAppletElement* applet, const HashMap<String, String>& args) + : RenderWidget(applet) + , m_args(args) +{ + setInline(true); +} + +RenderApplet::~RenderApplet() +{ +} + +IntSize RenderApplet::intrinsicSize() const +{ + // FIXME: This doesn't make sense. We can't just start returning + // a different size once we've created the widget and expect + // layout and sizing to be correct. We should remove this and + // pass the appropriate intrinsic size in the constructor. + return widget() ? IntSize(50, 50) : IntSize(150, 150); +} + +void RenderApplet::createWidgetIfNecessary() +{ + HTMLAppletElement* element = static_cast<HTMLAppletElement*>(node()); + if (widget() || !element->isFinishedParsingChildren()) + return; + + // FIXME: Java applets can't be resized (this is a bug in Apple's Java implementation). + // In order to work around this problem and have a correct size from the start, we will + // use fixed widths/heights from the style system when we can, since the widget might + // not have an accurate m_width/m_height. + int contentWidth = style()->width().isFixed() ? style()->width().value() : + width() - borderAndPaddingWidth(); + int contentHeight = style()->height().isFixed() ? style()->height().value() : + height() - borderAndPaddingHeight(); + for (Node* child = element->firstChild(); child; child = child->nextSibling()) { + if (child->hasTagName(paramTag)) { + HTMLParamElement* p = static_cast<HTMLParamElement*>(child); + if (!p->name().isEmpty()) + m_args.set(p->name(), p->value()); + } + } + + Frame* frame = this->frame(); + ASSERT(frame); + setWidget(frame->loader()->subframeLoader()->createJavaAppletWidget(IntSize(contentWidth, contentHeight), element, m_args)); +} + +void RenderApplet::layout() +{ + ASSERT(needsLayout()); + + computeLogicalWidth(); + computeLogicalHeight(); + + // The applet's widget gets created lazily upon first layout. + createWidgetIfNecessary(); + setNeedsLayout(false); +} + +#if USE(ACCELERATED_COMPOSITING) +bool RenderApplet::requiresLayer() const +{ + if (RenderWidget::requiresLayer()) + return true; + + return allowsAcceleratedCompositing(); +} + +bool RenderApplet::allowsAcceleratedCompositing() const +{ + return widget() && widget()->isPluginViewBase() && static_cast<PluginViewBase*>(widget())->platformLayer(); +} +#endif + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderApplet.h b/Source/WebCore/rendering/RenderApplet.h new file mode 100644 index 0000000..007902d --- /dev/null +++ b/Source/WebCore/rendering/RenderApplet.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * Copyright (C) 2006, 2007, 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderApplet_h +#define RenderApplet_h + +#include "RenderWidget.h" +#include <wtf/text/StringHash.h> + +namespace WebCore { + +class HTMLAppletElement; + +class RenderApplet : public RenderWidget { +public: + RenderApplet(HTMLAppletElement*, const HashMap<String, String>& args); + virtual ~RenderApplet(); + + void createWidgetIfNecessary(); + +#if USE(ACCELERATED_COMPOSITING) + virtual bool allowsAcceleratedCompositing() const; +#endif + +private: + virtual const char* renderName() const { return "RenderApplet"; } + + virtual bool isApplet() const { return true; } + + virtual void layout(); + virtual IntSize intrinsicSize() const; + +#if USE(ACCELERATED_COMPOSITING) + virtual bool requiresLayer() const; +#endif + + HashMap<String, String> m_args; +}; + +inline RenderApplet* toRenderApplet(RenderObject* object) +{ + ASSERT(!object || object->isApplet()); + return static_cast<RenderApplet*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderApplet(const RenderApplet*); + +} // namespace WebCore + +#endif // RenderApplet_h diff --git a/Source/WebCore/rendering/RenderArena.cpp b/Source/WebCore/rendering/RenderArena.cpp new file mode 100644 index 0000000..57ed978 --- /dev/null +++ b/Source/WebCore/rendering/RenderArena.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2003 Apple Computer, Inc. + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * Portions are Copyright (C) 1998 Netscape Communications Corporation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Alternatively, the contents of this file may be used under the terms + * of either the Mozilla Public License Version 1.1, found at + * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public + * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html + * (the "GPL"), in which case the provisions of the MPL or the GPL are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of one of those two + * licenses (the MPL or the GPL) and not to allow others to use your + * version of this file under the LGPL, indicate your decision by + * deletingthe provisions above and replace them with the notice and + * other provisions required by the MPL or the GPL, as the case may be. + * If you do not delete the provisions above, a recipient may use your + * version of this file under any of the LGPL, the MPL or the GPL. + */ + +#include "config.h" +#include "RenderArena.h" + +#include <stdlib.h> +#include <string.h> +#include <wtf/Assertions.h> + +#define ROUNDUP(x, y) ((((x)+((y)-1))/(y))*(y)) + +namespace WebCore { + +#ifndef NDEBUG + +const int signature = 0xDBA00AEA; +const int signatureDead = 0xDBA00AED; + +typedef struct { + RenderArena* arena; + size_t size; + int signature; +} RenderArenaDebugHeader; + +static const size_t debugHeaderSize = ARENA_ALIGN(sizeof(RenderArenaDebugHeader)); + +#endif + +RenderArena::RenderArena(unsigned arenaSize) +{ + // Initialize the arena pool + INIT_ARENA_POOL(&m_pool, "RenderArena", arenaSize); + + // Zero out the recyclers array + memset(m_recyclers, 0, sizeof(m_recyclers)); +} + +RenderArena::~RenderArena() +{ + FinishArenaPool(&m_pool); +} + +void* RenderArena::allocate(size_t size) +{ +#ifndef NDEBUG + // Use standard malloc so that memory debugging tools work. + ASSERT(this); + void* block = ::malloc(debugHeaderSize + size); + RenderArenaDebugHeader* header = static_cast<RenderArenaDebugHeader*>(block); + header->arena = this; + header->size = size; + header->signature = signature; + return static_cast<char*>(block) + debugHeaderSize; +#else + void* result = 0; + + // Ensure we have correct alignment for pointers. Important for Tru64 + size = ROUNDUP(size, sizeof(void*)); + + // Check recyclers first + if (size < gMaxRecycledSize) { + const int index = size >> 2; + + result = m_recyclers[index]; + if (result) { + // Need to move to the next object + void* next = *((void**)result); + m_recyclers[index] = next; + } + } + + if (!result) { + // Allocate a new chunk from the arena + ARENA_ALLOCATE(result, &m_pool, size); + } + + return result; +#endif +} + +void RenderArena::free(size_t size, void* ptr) +{ +#ifndef NDEBUG + // Use standard free so that memory debugging tools work. + void* block = static_cast<char*>(ptr) - debugHeaderSize; + RenderArenaDebugHeader* header = static_cast<RenderArenaDebugHeader*>(block); + ASSERT(header->signature == signature); + ASSERT_UNUSED(size, header->size == size); + ASSERT(header->arena == this); + header->signature = signatureDead; + ::free(block); +#else + // Ensure we have correct alignment for pointers. Important for Tru64 + size = ROUNDUP(size, sizeof(void*)); + + // See if it's a size that we recycle + if (size < gMaxRecycledSize) { + const int index = size >> 2; + void* currentTop = m_recyclers[index]; + m_recyclers[index] = ptr; + *((void**)ptr) = currentTop; + } +#endif +} + +#ifdef ANDROID_INSTRUMENT +size_t RenderArena::reportPoolSize() const +{ + return ReportPoolSize(&m_pool); +} +#endif + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderArena.h b/Source/WebCore/rendering/RenderArena.h new file mode 100644 index 0000000..edf052a --- /dev/null +++ b/Source/WebCore/rendering/RenderArena.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2003 Apple Computer, Inc. + * + * Portions are Copyright (C) 1998 Netscape Communications Corporation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Alternatively, the contents of this file may be used under the terms + * of either the Mozilla Public License Version 1.1, found at + * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public + * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html + * (the "GPL"), in which case the provisions of the MPL or the GPL are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of one of those two + * licenses (the MPL or the GPL) and not to allow others to use your + * version of this file under the LGPL, indicate your decision by + * deletingthe provisions above and replace them with the notice and + * other provisions required by the MPL or the GPL, as the case may be. + * If you do not delete the provisions above, a recipient may use your + * version of this file under any of the LGPL, the MPL or the GPL. + */ + +#ifndef RenderArena_h +#define RenderArena_h + +#include "Arena.h" +#include <wtf/Noncopyable.h> + +namespace WebCore { + +static const size_t gMaxRecycledSize = 400; + +class RenderArena : public Noncopyable { +public: + RenderArena(unsigned arenaSize = 4096); + ~RenderArena(); + + // Memory management functions + void* allocate(size_t); + void free(size_t, void*); + +#ifdef ANDROID_INSTRUMENT + size_t reportPoolSize() const; +#endif + +private: + // Underlying arena pool + ArenaPool m_pool; + + // The recycler array is sparse with the indices being multiples of 4, + // i.e., 0, 4, 8, 12, 16, 20, ... + void* m_recyclers[gMaxRecycledSize >> 2]; +}; + +} // namespace WebCore + +#endif // RenderArena_h diff --git a/Source/WebCore/rendering/RenderBR.cpp b/Source/WebCore/rendering/RenderBR.cpp new file mode 100644 index 0000000..fb136a4 --- /dev/null +++ b/Source/WebCore/rendering/RenderBR.cpp @@ -0,0 +1,81 @@ +/** + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * Copyright (C) 2006 Apple Computer, Inc. + * + * 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 "RenderBR.h" + +#include "Document.h" +#include "InlineTextBox.h" +#include "VisiblePosition.h" + +namespace WebCore { + +RenderBR::RenderBR(Node* node) + : RenderText(node, StringImpl::create("\n")) + , m_lineHeight(-1) +{ +} + +RenderBR::~RenderBR() +{ +} + +int RenderBR::lineHeight(bool firstLine) const +{ + if (firstLine && document()->usesFirstLineRules()) { + RenderStyle* s = style(firstLine); + if (s != style()) + return s->computedLineHeight(); + } + + if (m_lineHeight == -1) + m_lineHeight = style()->computedLineHeight(); + + return m_lineHeight; +} + +void RenderBR::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderText::styleDidChange(diff, oldStyle); + m_lineHeight = -1; +} + +int RenderBR::caretMinOffset() const +{ + return 0; +} + +int RenderBR::caretMaxOffset() const +{ + return 1; +} + +unsigned RenderBR::caretMaxRenderedOffset() const +{ + return 1; +} + +VisiblePosition RenderBR::positionForPoint(const IntPoint&) +{ + return createVisiblePosition(0, DOWNSTREAM); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderBR.h b/Source/WebCore/rendering/RenderBR.h new file mode 100644 index 0000000..7216b5a --- /dev/null +++ b/Source/WebCore/rendering/RenderBR.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * + * 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. + * + */ + +#ifndef RenderBR_h +#define RenderBR_h + +#include "RenderText.h" + +/* + * The whole class here is a hack to get <br> working, as long as we don't have support for + * CSS2 :before and :after pseudo elements + */ +namespace WebCore { + +class Position; + +class RenderBR : public RenderText { +public: + RenderBR(Node*); + virtual ~RenderBR(); + + virtual const char* renderName() const { return "RenderBR"; } + + virtual IntRect selectionRectForRepaint(RenderBoxModelObject* /*repaintContainer*/, bool /*clipToVisibleContent*/) { return IntRect(); } + + virtual unsigned width(unsigned /*from*/, unsigned /*len*/, const Font&, int /*xpos*/) const { return 0; } + virtual unsigned width(unsigned /*from*/, unsigned /*len*/, int /*xpos*/, bool /*firstLine = false*/) const { return 0; } + + int lineHeight(bool firstLine) const; + + // overrides + virtual bool isBR() const { return true; } + + virtual int caretMinOffset() const; + virtual int caretMaxOffset() const; + virtual unsigned caretMaxRenderedOffset() const; + + virtual VisiblePosition positionForPoint(const IntPoint&); + +protected: + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + +private: + mutable int m_lineHeight; +}; + + +inline RenderBR* toRenderBR(RenderObject* object) +{ + ASSERT(!object || object->isBR()); + return static_cast<RenderBR*>(object); +} + +inline const RenderBR* toRenderBR(const RenderObject* object) +{ + ASSERT(!object || object->isBR()); + return static_cast<const RenderBR*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderBR(const RenderBR*); + +} // namespace WebCore + +#endif // RenderBR_h diff --git a/Source/WebCore/rendering/RenderBlock.cpp b/Source/WebCore/rendering/RenderBlock.cpp new file mode 100644 index 0000000..e0fe6a0 --- /dev/null +++ b/Source/WebCore/rendering/RenderBlock.cpp @@ -0,0 +1,6111 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2007 David Smith (catfish.man@gmail.com) + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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 "RenderBlock.h" + +#include "ColumnInfo.h" +#include "Document.h" +#include "Element.h" +#include "FloatQuad.h" +#include "Frame.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "HTMLFormElement.h" +#include "HTMLNames.h" +#include "HitTestResult.h" +#include "InlineTextBox.h" +#include "RenderFlexibleBox.h" +#include "RenderImage.h" +#include "RenderInline.h" +#include "RenderLayer.h" +#include "RenderMarquee.h" +#include "RenderReplica.h" +#include "RenderTableCell.h" +#include "RenderTextFragment.h" +#include "RenderTheme.h" +#include "RenderView.h" +#include "SelectionController.h" +#include "Settings.h" +#include "TransformState.h" +#include <wtf/StdLibExtras.h> + +#ifdef ANDROID_LAYOUT +#include "Settings.h" +#endif + +using namespace std; +using namespace WTF; +using namespace Unicode; + +namespace WebCore { + +using namespace HTMLNames; + +typedef WTF::HashMap<const RenderBox*, ColumnInfo*> ColumnInfoMap; +static ColumnInfoMap* gColumnInfoMap = 0; + +typedef WTF::HashMap<const RenderBlock*, HashSet<RenderBox*>*> PercentHeightDescendantsMap; +static PercentHeightDescendantsMap* gPercentHeightDescendantsMap = 0; + +typedef WTF::HashMap<const RenderBox*, HashSet<RenderBlock*>*> PercentHeightContainerMap; +static PercentHeightContainerMap* gPercentHeightContainerMap = 0; + +typedef WTF::HashMap<RenderBlock*, ListHashSet<RenderInline*>*> ContinuationOutlineTableMap; + +typedef WTF::HashSet<RenderBlock*> DelayedUpdateScrollInfoSet; +static int gDelayUpdateScrollInfo = 0; +static DelayedUpdateScrollInfoSet* gDelayedUpdateScrollInfoSet = 0; + +// Our MarginInfo state used when laying out block children. +RenderBlock::MarginInfo::MarginInfo(RenderBlock* block, int beforeBorderPadding, int afterBorderPadding) + : m_atBeforeSideOfBlock(true) + , m_atAfterSideOfBlock(false) + , m_marginBeforeQuirk(false) + , m_marginAfterQuirk(false) + , m_determinedMarginBeforeQuirk(false) +{ + // Whether or not we can collapse our own margins with our children. We don't do this + // if we had any border/padding (obviously), if we're the root or HTML elements, or if + // we're positioned, floating, a table cell. + m_canCollapseWithChildren = !block->isRenderView() && !block->isRoot() && !block->isPositioned() + && !block->isFloating() && !block->isTableCell() && !block->hasOverflowClip() && !block->isInlineBlockOrInlineTable() + && !block->isWritingModeRoot(); + + m_canCollapseMarginBeforeWithChildren = m_canCollapseWithChildren && (beforeBorderPadding == 0) && block->style()->marginBeforeCollapse() != MSEPARATE; + + // If any height other than auto is specified in CSS, then we don't collapse our bottom + // margins with our children's margins. To do otherwise would be to risk odd visual + // effects when the children overflow out of the parent block and yet still collapse + // with it. We also don't collapse if we have any bottom border/padding. + m_canCollapseMarginAfterWithChildren = m_canCollapseWithChildren && (afterBorderPadding == 0) && + (block->style()->logicalHeight().isAuto() && block->style()->logicalHeight().value() == 0) && block->style()->marginAfterCollapse() != MSEPARATE; + + m_quirkContainer = block->isTableCell() || block->isBody() || block->style()->marginBeforeCollapse() == MDISCARD || + block->style()->marginAfterCollapse() == MDISCARD; + + m_positiveMargin = m_canCollapseMarginBeforeWithChildren ? block->maxPositiveMarginBefore() : 0; + m_negativeMargin = m_canCollapseMarginBeforeWithChildren ? block->maxNegativeMarginBefore() : 0; +} + +// ------------------------------------------------------------------------------------------------------- + +RenderBlock::RenderBlock(Node* node) + : RenderBox(node) + , m_floatingObjects(0) + , m_positionedObjects(0) + , m_rareData(0) + , m_lineHeight(-1) +{ + setChildrenInline(true); +} + +RenderBlock::~RenderBlock() +{ + delete m_floatingObjects; + delete m_positionedObjects; + + if (hasColumns()) + delete gColumnInfoMap->take(this); + + if (gPercentHeightDescendantsMap) { + if (HashSet<RenderBox*>* descendantSet = gPercentHeightDescendantsMap->take(this)) { + HashSet<RenderBox*>::iterator end = descendantSet->end(); + for (HashSet<RenderBox*>::iterator descendant = descendantSet->begin(); descendant != end; ++descendant) { + HashSet<RenderBlock*>* containerSet = gPercentHeightContainerMap->get(*descendant); + ASSERT(containerSet); + if (!containerSet) + continue; + ASSERT(containerSet->contains(this)); + containerSet->remove(this); + if (containerSet->isEmpty()) { + gPercentHeightContainerMap->remove(*descendant); + delete containerSet; + } + } + delete descendantSet; + } + } +} + +void RenderBlock::destroy() +{ + // Make sure to destroy anonymous children first while they are still connected to the rest of the tree, so that they will + // properly dirty line boxes that they are removed from. Effects that do :before/:after only on hover could crash otherwise. + children()->destroyLeftoverChildren(); + + // Destroy our continuation before anything other than anonymous children. + // The reason we don't destroy it before anonymous children is that they may + // have continuations of their own that are anonymous children of our continuation. + RenderBoxModelObject* continuation = this->continuation(); + if (continuation) { + continuation->destroy(); + setContinuation(0); + } + + if (!documentBeingDestroyed()) { + if (firstLineBox()) { + // We can't wait for RenderBox::destroy to clear the selection, + // because by then we will have nuked the line boxes. + // FIXME: The SelectionController should be responsible for this when it + // is notified of DOM mutations. + if (isSelectionBorder()) + view()->clearSelection(); + + // If we are an anonymous block, then our line boxes might have children + // that will outlast this block. In the non-anonymous block case those + // children will be destroyed by the time we return from this function. + if (isAnonymousBlock()) { + for (InlineFlowBox* box = firstLineBox(); box; box = box->nextLineBox()) { + while (InlineBox* childBox = box->firstChild()) + childBox->remove(); + } + } + } else if (isInline() && parent()) + parent()->dirtyLinesFromChangedChild(this); + } + + m_lineBoxes.deleteLineBoxes(renderArena()); + + RenderBox::destroy(); +} + +void RenderBlock::styleWillChange(StyleDifference diff, const RenderStyle* newStyle) +{ + setReplaced(newStyle->isDisplayReplacedType()); + + if (style() && parent() && diff == StyleDifferenceLayout && style()->position() != newStyle->position()) { + if (newStyle->position() == StaticPosition) + // Clear our positioned objects list. Our absolutely positioned descendants will be + // inserted into our containing block's positioned objects list during layout. + removePositionedObjects(0); + else if (style()->position() == StaticPosition) { + // Remove our absolutely positioned descendants from their current containing block. + // They will be inserted into our positioned objects list during layout. + RenderObject* cb = parent(); + while (cb && (cb->style()->position() == StaticPosition || (cb->isInline() && !cb->isReplaced())) && !cb->isRenderView()) { + if (cb->style()->position() == RelativePosition && cb->isInline() && !cb->isReplaced()) { + cb = cb->containingBlock(); + break; + } + cb = cb->parent(); + } + + if (cb->isRenderBlock()) + toRenderBlock(cb)->removePositionedObjects(this); + } + } + + RenderBox::styleWillChange(diff, newStyle); +} + +void RenderBlock::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBox::styleDidChange(diff, oldStyle); + + if (!isAnonymousBlock()) { + // Ensure that all of our continuation blocks pick up the new style. + for (RenderBlock* currCont = blockElementContinuation(); currCont; currCont = currCont->blockElementContinuation()) { + RenderBoxModelObject* nextCont = currCont->continuation(); + currCont->setContinuation(0); + currCont->setStyle(style()); + currCont->setContinuation(nextCont); + } + } + + // FIXME: We could save this call when the change only affected non-inherited properties + for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { + if (child->isAnonymousBlock()) { + RefPtr<RenderStyle> newStyle = RenderStyle::create(); + newStyle->inheritFrom(style()); + if (style()->specifiesColumns()) { + if (child->style()->specifiesColumns()) + newStyle->inheritColumnPropertiesFrom(style()); + if (child->style()->columnSpan()) + newStyle->setColumnSpan(true); + } + newStyle->setDisplay(BLOCK); + child->setStyle(newStyle.release()); + } + } + + m_lineHeight = -1; + + // Update pseudos for :before and :after now. + if (!isAnonymous() && document()->usesBeforeAfterRules() && canHaveChildren()) { + updateBeforeAfterContent(BEFORE); + updateBeforeAfterContent(AFTER); + } +} + +void RenderBlock::updateBeforeAfterContent(PseudoId pseudoId) +{ + // If this is an anonymous wrapper, then the parent applies its own pseudo-element style to it. + if (parent() && parent()->createsAnonymousWrapper()) + return; + return children()->updateBeforeAfterContent(this, pseudoId); +} + +RenderBlock* RenderBlock::continuationBefore(RenderObject* beforeChild) +{ + if (beforeChild && beforeChild->parent() == this) + return this; + + RenderBlock* curr = toRenderBlock(continuation()); + RenderBlock* nextToLast = this; + RenderBlock* last = this; + while (curr) { + if (beforeChild && beforeChild->parent() == curr) { + if (curr->firstChild() == beforeChild) + return last; + return curr; + } + + nextToLast = last; + last = curr; + curr = toRenderBlock(curr->continuation()); + } + + if (!beforeChild && !last->firstChild()) + return nextToLast; + return last; +} + +void RenderBlock::addChildToContinuation(RenderObject* newChild, RenderObject* beforeChild) +{ + RenderBlock* flow = continuationBefore(beforeChild); + ASSERT(!beforeChild || beforeChild->parent()->isAnonymousColumnSpanBlock() || beforeChild->parent()->isRenderBlock()); + RenderBoxModelObject* beforeChildParent = 0; + if (beforeChild) + beforeChildParent = toRenderBoxModelObject(beforeChild->parent()); + else { + RenderBoxModelObject* cont = flow->continuation(); + if (cont) + beforeChildParent = cont; + else + beforeChildParent = flow; + } + + if (newChild->isFloatingOrPositioned()) + return beforeChildParent->addChildIgnoringContinuation(newChild, beforeChild); + + // A continuation always consists of two potential candidates: a block or an anonymous + // column span box holding column span children. + bool childIsNormal = newChild->isInline() || !newChild->style()->columnSpan(); + bool bcpIsNormal = beforeChildParent->isInline() || !beforeChildParent->style()->columnSpan(); + bool flowIsNormal = flow->isInline() || !flow->style()->columnSpan(); + + if (flow == beforeChildParent) + return flow->addChildIgnoringContinuation(newChild, beforeChild); + + // The goal here is to match up if we can, so that we can coalesce and create the + // minimal # of continuations needed for the inline. + if (childIsNormal == bcpIsNormal) + return beforeChildParent->addChildIgnoringContinuation(newChild, beforeChild); + if (flowIsNormal == childIsNormal) + return flow->addChildIgnoringContinuation(newChild, 0); // Just treat like an append. + return beforeChildParent->addChildIgnoringContinuation(newChild, beforeChild); +} + + +void RenderBlock::addChildToAnonymousColumnBlocks(RenderObject* newChild, RenderObject* beforeChild) +{ + ASSERT(!continuation()); // We don't yet support column spans that aren't immediate children of the multi-column block. + + // The goal is to locate a suitable box in which to place our child. + RenderBlock* beforeChildParent = toRenderBlock(beforeChild && beforeChild->parent()->isRenderBlock() ? beforeChild->parent() : lastChild()); + + // If the new child is floating or positioned it can just go in that block. + if (newChild->isFloatingOrPositioned()) + return beforeChildParent->addChildIgnoringAnonymousColumnBlocks(newChild, beforeChild); + + // See if the child can be placed in the box. + bool newChildHasColumnSpan = newChild->style()->columnSpan() && !newChild->isInline(); + bool beforeChildParentHoldsColumnSpans = beforeChildParent->isAnonymousColumnSpanBlock(); + + if (newChildHasColumnSpan == beforeChildParentHoldsColumnSpans) + return beforeChildParent->addChildIgnoringAnonymousColumnBlocks(newChild, beforeChild); + + if (!beforeChild) { + // Create a new block of the correct type. + RenderBlock* newBox = newChildHasColumnSpan ? createAnonymousColumnSpanBlock() : createAnonymousColumnsBlock(); + children()->appendChildNode(this, newBox); + newBox->addChildIgnoringAnonymousColumnBlocks(newChild, 0); + return; + } + + RenderObject* immediateChild = beforeChild; + bool isPreviousBlockViable = true; + while (immediateChild->parent() != this) { + if (isPreviousBlockViable) + isPreviousBlockViable = !immediateChild->previousSibling(); + immediateChild = immediateChild->parent(); + } + if (isPreviousBlockViable && immediateChild->previousSibling()) + return toRenderBlock(immediateChild->previousSibling())->addChildIgnoringAnonymousColumnBlocks(newChild, 0); // Treat like an append. + + // Split our anonymous blocks. + RenderObject* newBeforeChild = splitAnonymousBlocksAroundChild(beforeChild); + + // Create a new anonymous box of the appropriate type. + RenderBlock* newBox = newChildHasColumnSpan ? createAnonymousColumnSpanBlock() : createAnonymousColumnsBlock(); + children()->insertChildNode(this, newBox, newBeforeChild); + newBox->addChildIgnoringAnonymousColumnBlocks(newChild, 0); + return; +} + +RenderBlock* RenderBlock::containingColumnsBlock(bool allowAnonymousColumnBlock) +{ + for (RenderObject* curr = this; curr; curr = curr->parent()) { + if (!curr->isRenderBlock() || curr->isFloatingOrPositioned() || curr->isTableCell() || curr->isRoot() || curr->isRenderView() || curr->hasOverflowClip() + || curr->isInlineBlockOrInlineTable()) + return 0; + + RenderBlock* currBlock = toRenderBlock(curr); + if (currBlock->style()->specifiesColumns() && (allowAnonymousColumnBlock || !currBlock->isAnonymousColumnsBlock())) + return currBlock; + + if (currBlock->isAnonymousColumnSpanBlock()) + return 0; + } + return 0; +} + +RenderBlock* RenderBlock::clone() const +{ + RenderBlock* cloneBlock; + if (isAnonymousBlock()) + cloneBlock = createAnonymousBlock(); + else { + cloneBlock = new (renderArena()) RenderBlock(node()); + cloneBlock->setStyle(style()); + } + cloneBlock->setChildrenInline(childrenInline()); + return cloneBlock; +} + +void RenderBlock::splitBlocks(RenderBlock* fromBlock, RenderBlock* toBlock, + RenderBlock* middleBlock, + RenderObject* beforeChild, RenderBoxModelObject* oldCont) +{ + // Create a clone of this inline. + RenderBlock* cloneBlock = clone(); + if (!isAnonymousBlock()) + cloneBlock->setContinuation(oldCont); + + // Now take all of the children from beforeChild to the end and remove + // them from |this| and place them in the clone. + if (!beforeChild && isAfterContent(lastChild())) + beforeChild = lastChild(); + moveChildrenTo(cloneBlock, beforeChild, 0); + + // Hook |clone| up as the continuation of the middle block. + if (!cloneBlock->isAnonymousBlock()) + middleBlock->setContinuation(cloneBlock); + + // We have been reparented and are now under the fromBlock. We need + // to walk up our block parent chain until we hit the containing anonymous columns block. + // Once we hit the anonymous columns block we're done. + RenderBoxModelObject* curr = toRenderBoxModelObject(parent()); + RenderBoxModelObject* currChild = this; + + while (curr && curr != fromBlock) { + ASSERT(curr->isRenderBlock()); + + RenderBlock* blockCurr = toRenderBlock(curr); + + // Create a new clone. + RenderBlock* cloneChild = cloneBlock; + cloneBlock = blockCurr->clone(); + + // Insert our child clone as the first child. + cloneBlock->children()->appendChildNode(cloneBlock, cloneChild); + + // Hook the clone up as a continuation of |curr|. Note we do encounter + // anonymous blocks possibly as we walk up the block chain. When we split an + // anonymous block, there's no need to do any continuation hookup, since we haven't + // actually split a real element. + if (!blockCurr->isAnonymousBlock()) { + oldCont = blockCurr->continuation(); + blockCurr->setContinuation(cloneBlock); + cloneBlock->setContinuation(oldCont); + } + + // Someone may have indirectly caused a <q> to split. When this happens, the :after content + // has to move into the inline continuation. Call updateBeforeAfterContent to ensure that the inline's :after + // content gets properly destroyed. + if (document()->usesBeforeAfterRules()) + blockCurr->children()->updateBeforeAfterContent(blockCurr, AFTER); + + // Now we need to take all of the children starting from the first child + // *after* currChild and append them all to the clone. + RenderObject* afterContent = isAfterContent(cloneBlock->lastChild()) ? cloneBlock->lastChild() : 0; + blockCurr->moveChildrenTo(cloneBlock, currChild->nextSibling(), 0, afterContent); + + // Keep walking up the chain. + currChild = curr; + curr = toRenderBoxModelObject(curr->parent()); + } + + // Now we are at the columns block level. We need to put the clone into the toBlock. + toBlock->children()->appendChildNode(toBlock, cloneBlock); + + // Now take all the children after currChild and remove them from the fromBlock + // and put them in the toBlock. + fromBlock->moveChildrenTo(toBlock, currChild->nextSibling(), 0); +} + +void RenderBlock::splitFlow(RenderObject* beforeChild, RenderBlock* newBlockBox, + RenderObject* newChild, RenderBoxModelObject* oldCont) +{ + RenderBlock* pre = 0; + RenderBlock* block = containingColumnsBlock(); + + // Delete our line boxes before we do the inline split into continuations. + block->deleteLineBoxTree(); + + bool madeNewBeforeBlock = false; + if (block->isAnonymousColumnsBlock()) { + // We can reuse this block and make it the preBlock of the next continuation. + pre = block; + pre->removePositionedObjects(0); + block = toRenderBlock(block->parent()); + } else { + // No anonymous block available for use. Make one. + pre = block->createAnonymousColumnsBlock(); + pre->setChildrenInline(false); + madeNewBeforeBlock = true; + } + + RenderBlock* post = block->createAnonymousColumnsBlock(); + post->setChildrenInline(false); + + RenderObject* boxFirst = madeNewBeforeBlock ? block->firstChild() : pre->nextSibling(); + if (madeNewBeforeBlock) + block->children()->insertChildNode(block, pre, boxFirst); + block->children()->insertChildNode(block, newBlockBox, boxFirst); + block->children()->insertChildNode(block, post, boxFirst); + block->setChildrenInline(false); + + if (madeNewBeforeBlock) + block->moveChildrenTo(pre, boxFirst, 0); + + splitBlocks(pre, post, newBlockBox, beforeChild, oldCont); + + // We already know the newBlockBox isn't going to contain inline kids, so avoid wasting + // time in makeChildrenNonInline by just setting this explicitly up front. + newBlockBox->setChildrenInline(false); + + // We delayed adding the newChild until now so that the |newBlockBox| would be fully + // connected, thus allowing newChild access to a renderArena should it need + // to wrap itself in additional boxes (e.g., table construction). + newBlockBox->addChild(newChild); + + // Always just do a full layout in order to ensure that line boxes (especially wrappers for images) + // get deleted properly. Because objects moves from the pre block into the post block, we want to + // make new line boxes instead of leaving the old line boxes around. + pre->setNeedsLayoutAndPrefWidthsRecalc(); + block->setNeedsLayoutAndPrefWidthsRecalc(); + post->setNeedsLayoutAndPrefWidthsRecalc(); +} + +RenderObject* RenderBlock::splitAnonymousBlocksAroundChild(RenderObject* beforeChild) +{ + while (beforeChild->parent() != this) { + RenderBlock* blockToSplit = toRenderBlock(beforeChild->parent()); + if (blockToSplit->firstChild() != beforeChild) { + // We have to split the parentBlock into two blocks. + RenderBlock* post = createAnonymousBlockWithSameTypeAs(blockToSplit); + post->setChildrenInline(blockToSplit->childrenInline()); + RenderBlock* parentBlock = toRenderBlock(blockToSplit->parent()); + parentBlock->children()->insertChildNode(parentBlock, post, blockToSplit->nextSibling()); + blockToSplit->moveChildrenTo(post, beforeChild, 0, blockToSplit->hasLayer()); + post->setNeedsLayoutAndPrefWidthsRecalc(); + blockToSplit->setNeedsLayoutAndPrefWidthsRecalc(); + beforeChild = post; + } else + beforeChild = blockToSplit; + } + return beforeChild; +} + +void RenderBlock::makeChildrenAnonymousColumnBlocks(RenderObject* beforeChild, RenderBlock* newBlockBox, RenderObject* newChild) +{ + RenderBlock* pre = 0; + RenderBlock* post = 0; + RenderBlock* block = this; // Eventually block will not just be |this|, but will also be a block nested inside |this|. Assign to a variable + // so that we don't have to patch all of the rest of the code later on. + + // Delete the block's line boxes before we do the split. + block->deleteLineBoxTree(); + + if (beforeChild && beforeChild->parent() != this) + beforeChild = splitAnonymousBlocksAroundChild(beforeChild); + + if (beforeChild != firstChild()) { + pre = block->createAnonymousColumnsBlock(); + pre->setChildrenInline(block->childrenInline()); + } + + if (beforeChild) { + post = block->createAnonymousColumnsBlock(); + post->setChildrenInline(block->childrenInline()); + } + + RenderObject* boxFirst = block->firstChild(); + if (pre) + block->children()->insertChildNode(block, pre, boxFirst); + block->children()->insertChildNode(block, newBlockBox, boxFirst); + if (post) + block->children()->insertChildNode(block, post, boxFirst); + block->setChildrenInline(false); + + // The pre/post blocks always have layers, so we know to always do a full insert/remove (so we pass true as the last argument). + block->moveChildrenTo(pre, boxFirst, beforeChild, true); + block->moveChildrenTo(post, beforeChild, 0, true); + + // We already know the newBlockBox isn't going to contain inline kids, so avoid wasting + // time in makeChildrenNonInline by just setting this explicitly up front. + newBlockBox->setChildrenInline(false); + + // We delayed adding the newChild until now so that the |newBlockBox| would be fully + // connected, thus allowing newChild access to a renderArena should it need + // to wrap itself in additional boxes (e.g., table construction). + newBlockBox->addChild(newChild); + + // Always just do a full layout in order to ensure that line boxes (especially wrappers for images) + // get deleted properly. Because objects moved from the pre block into the post block, we want to + // make new line boxes instead of leaving the old line boxes around. + if (pre) + pre->setNeedsLayoutAndPrefWidthsRecalc(); + block->setNeedsLayoutAndPrefWidthsRecalc(); + if (post) + post->setNeedsLayoutAndPrefWidthsRecalc(); +} + +RenderBlock* RenderBlock::columnsBlockForSpanningElement(RenderObject* newChild) +{ + // FIXME: This function is the gateway for the addition of column-span support. It will + // be added to in three stages: + // (1) Immediate children of a multi-column block can span. + // (2) Nested block-level children with only block-level ancestors between them and the multi-column block can span. + // (3) Nested children with block or inline ancestors between them and the multi-column block can span (this is when we + // cross the streams and have to cope with both types of continuations mixed together). + // This function currently supports (1) and (2). + RenderBlock* columnsBlockAncestor = 0; + if (!newChild->isText() && newChild->style()->columnSpan() && !newChild->isFloatingOrPositioned() + && !newChild->isInline() && !isAnonymousColumnSpanBlock()) { + if (style()->specifiesColumns()) + columnsBlockAncestor = this; + else if (parent() && parent()->isRenderBlock()) + columnsBlockAncestor = toRenderBlock(parent())->containingColumnsBlock(false); + } + return columnsBlockAncestor; +} + +void RenderBlock::addChildIgnoringAnonymousColumnBlocks(RenderObject* newChild, RenderObject* beforeChild) +{ + // Make sure we don't append things after :after-generated content if we have it. + if (!beforeChild) { + RenderObject* lastRenderer = lastChild(); + if (isAfterContent(lastRenderer)) + beforeChild = lastRenderer; + else if (lastRenderer && lastRenderer->isAnonymousBlock() && isAfterContent(lastRenderer->lastChild())) + beforeChild = lastRenderer->lastChild(); + } + + // If the requested beforeChild is not one of our children, then this is because + // there is an anonymous container within this object that contains the beforeChild. + if (beforeChild && beforeChild->parent() != this) { + RenderObject* anonymousChild = beforeChild->parent(); + ASSERT(anonymousChild); + + while (anonymousChild->parent() != this) + anonymousChild = anonymousChild->parent(); + + ASSERT(anonymousChild->isAnonymous()); + + if (anonymousChild->isAnonymousBlock()) { + // Insert the child into the anonymous block box instead of here. + if (newChild->isInline() || beforeChild->parent()->firstChild() != beforeChild) + beforeChild->parent()->addChild(newChild, beforeChild); + else + addChild(newChild, beforeChild->parent()); + return; + } + + ASSERT(anonymousChild->isTable()); + if ((newChild->isTableCol() && newChild->style()->display() == TABLE_COLUMN_GROUP) + || (newChild->isRenderBlock() && newChild->style()->display() == TABLE_CAPTION) + || newChild->isTableSection() + || newChild->isTableRow() + || newChild->isTableCell()) { + // Insert into the anonymous table. + anonymousChild->addChild(newChild, beforeChild); + return; + } + + // Go on to insert before the anonymous table. + beforeChild = anonymousChild; + } + + // Check for a spanning element in columns. + RenderBlock* columnsBlockAncestor = columnsBlockForSpanningElement(newChild); + if (columnsBlockAncestor) { + // We are placing a column-span element inside a block. + RenderBlock* newBox = createAnonymousColumnSpanBlock(); + + if (columnsBlockAncestor != this) { + // We are nested inside a multi-column element and are being split by the span. We have to break up + // our block into continuations. + RenderBoxModelObject* oldContinuation = continuation(); + setContinuation(newBox); + + // Someone may have put a <p> inside a <q>, causing a split. When this happens, the :after content + // has to move into the inline continuation. Call updateBeforeAfterContent to ensure that our :after + // content gets properly destroyed. + bool isLastChild = (beforeChild == lastChild()); + if (document()->usesBeforeAfterRules()) + children()->updateBeforeAfterContent(this, AFTER); + if (isLastChild && beforeChild != lastChild()) + beforeChild = 0; // We destroyed the last child, so now we need to update our insertion + // point to be 0. It's just a straight append now. + + splitFlow(beforeChild, newBox, newChild, oldContinuation); + return; + } + + // We have to perform a split of this block's children. This involves creating an anonymous block box to hold + // the column-spanning |newChild|. We take all of the children from before |newChild| and put them into + // one anonymous columns block, and all of the children after |newChild| go into another anonymous block. + makeChildrenAnonymousColumnBlocks(beforeChild, newBox, newChild); + return; + } + + bool madeBoxesNonInline = false; + + // A block has to either have all of its children inline, or all of its children as blocks. + // So, if our children are currently inline and a block child has to be inserted, we move all our + // inline children into anonymous block boxes. + if (childrenInline() && !newChild->isInline() && !newChild->isFloatingOrPositioned()) { + // This is a block with inline content. Wrap the inline content in anonymous blocks. + makeChildrenNonInline(beforeChild); + madeBoxesNonInline = true; + + if (beforeChild && beforeChild->parent() != this) { + beforeChild = beforeChild->parent(); + ASSERT(beforeChild->isAnonymousBlock()); + ASSERT(beforeChild->parent() == this); + } + } else if (!childrenInline() && (newChild->isFloatingOrPositioned() || newChild->isInline())) { + // If we're inserting an inline child but all of our children are blocks, then we have to make sure + // it is put into an anomyous block box. We try to use an existing anonymous box if possible, otherwise + // a new one is created and inserted into our list of children in the appropriate position. + RenderObject* afterChild = beforeChild ? beforeChild->previousSibling() : lastChild(); + + if (afterChild && afterChild->isAnonymousBlock()) { + afterChild->addChild(newChild); + return; + } + + if (newChild->isInline()) { + // No suitable existing anonymous box - create a new one. + RenderBlock* newBox = createAnonymousBlock(); + RenderBox::addChild(newBox, beforeChild); + newBox->addChild(newChild); + return; + } + } + + RenderBox::addChild(newChild, beforeChild); + + if (madeBoxesNonInline && parent() && isAnonymousBlock() && parent()->isRenderBlock()) + toRenderBlock(parent())->removeLeftoverAnonymousBlock(this); + // this object may be dead here +} + +void RenderBlock::addChild(RenderObject* newChild, RenderObject* beforeChild) +{ + if (continuation() && !isAnonymousBlock()) + return addChildToContinuation(newChild, beforeChild); + return addChildIgnoringContinuation(newChild, beforeChild); +} + +void RenderBlock::addChildIgnoringContinuation(RenderObject* newChild, RenderObject* beforeChild) +{ + if (!isAnonymousBlock() && firstChild() && (firstChild()->isAnonymousColumnsBlock() || firstChild()->isAnonymousColumnSpanBlock())) + return addChildToAnonymousColumnBlocks(newChild, beforeChild); + return addChildIgnoringAnonymousColumnBlocks(newChild, beforeChild); +} + +static void getInlineRun(RenderObject* start, RenderObject* boundary, + RenderObject*& inlineRunStart, + RenderObject*& inlineRunEnd) +{ + // Beginning at |start| we find the largest contiguous run of inlines that + // we can. We denote the run with start and end points, |inlineRunStart| + // and |inlineRunEnd|. Note that these two values may be the same if + // we encounter only one inline. + // + // We skip any non-inlines we encounter as long as we haven't found any + // inlines yet. + // + // |boundary| indicates a non-inclusive boundary point. Regardless of whether |boundary| + // is inline or not, we will not include it in a run with inlines before it. It's as though we encountered + // a non-inline. + + // Start by skipping as many non-inlines as we can. + RenderObject * curr = start; + bool sawInline; + do { + while (curr && !(curr->isInline() || curr->isFloatingOrPositioned())) + curr = curr->nextSibling(); + + inlineRunStart = inlineRunEnd = curr; + + if (!curr) + return; // No more inline children to be found. + + sawInline = curr->isInline(); + + curr = curr->nextSibling(); + while (curr && (curr->isInline() || curr->isFloatingOrPositioned()) && (curr != boundary)) { + inlineRunEnd = curr; + if (curr->isInline()) + sawInline = true; + curr = curr->nextSibling(); + } + } while (!sawInline); +} + +void RenderBlock::deleteLineBoxTree() +{ + m_lineBoxes.deleteLineBoxTree(renderArena()); +} + +RootInlineBox* RenderBlock::createRootInlineBox() +{ + return new (renderArena()) RootInlineBox(this); +} + +RootInlineBox* RenderBlock::createAndAppendRootInlineBox() +{ + RootInlineBox* rootBox = createRootInlineBox(); + m_lineBoxes.appendLineBox(rootBox); + return rootBox; +} + +void RenderBlock::moveChildTo(RenderBlock* to, RenderObject* child, RenderObject* beforeChild, bool fullRemoveInsert) +{ + ASSERT(this == child->parent()); + ASSERT(!beforeChild || to == beforeChild->parent()); + to->children()->insertChildNode(to, children()->removeChildNode(this, child, fullRemoveInsert), beforeChild, fullRemoveInsert); +} + +void RenderBlock::moveChildrenTo(RenderBlock* to, RenderObject* startChild, RenderObject* endChild, RenderObject* beforeChild, bool fullRemoveInsert) +{ + ASSERT(!beforeChild || to == beforeChild->parent()); + RenderObject* nextChild = startChild; + while (nextChild && nextChild != endChild) { + RenderObject* child = nextChild; + nextChild = child->nextSibling(); + to->children()->insertChildNode(to, children()->removeChildNode(this, child, fullRemoveInsert), beforeChild, fullRemoveInsert); + if (child == endChild) + return; + } +} + +void RenderBlock::makeChildrenNonInline(RenderObject *insertionPoint) +{ + // makeChildrenNonInline takes a block whose children are *all* inline and it + // makes sure that inline children are coalesced under anonymous + // blocks. If |insertionPoint| is defined, then it represents the insertion point for + // the new block child that is causing us to have to wrap all the inlines. This + // means that we cannot coalesce inlines before |insertionPoint| with inlines following + // |insertionPoint|, because the new child is going to be inserted in between the inlines, + // splitting them. + ASSERT(isInlineBlockOrInlineTable() || !isInline()); + ASSERT(!insertionPoint || insertionPoint->parent() == this); + + setChildrenInline(false); + + RenderObject *child = firstChild(); + if (!child) + return; + + deleteLineBoxTree(); + + while (child) { + RenderObject *inlineRunStart, *inlineRunEnd; + getInlineRun(child, insertionPoint, inlineRunStart, inlineRunEnd); + + if (!inlineRunStart) + break; + + child = inlineRunEnd->nextSibling(); + + RenderBlock* block = createAnonymousBlock(); + children()->insertChildNode(this, block, inlineRunStart); + moveChildrenTo(block, inlineRunStart, child); + } + +#ifndef NDEBUG + for (RenderObject *c = firstChild(); c; c = c->nextSibling()) + ASSERT(!c->isInline()); +#endif + + repaint(); +} + +void RenderBlock::removeLeftoverAnonymousBlock(RenderBlock* child) +{ + ASSERT(child->isAnonymousBlock()); + ASSERT(!child->childrenInline()); + + if (child->continuation() || (child->firstChild() && (child->isAnonymousColumnSpanBlock() || child->isAnonymousColumnsBlock()))) + return; + + RenderObject* firstAnChild = child->m_children.firstChild(); + RenderObject* lastAnChild = child->m_children.lastChild(); + if (firstAnChild) { + RenderObject* o = firstAnChild; + while (o) { + o->setParent(this); + o = o->nextSibling(); + } + firstAnChild->setPreviousSibling(child->previousSibling()); + lastAnChild->setNextSibling(child->nextSibling()); + if (child->previousSibling()) + child->previousSibling()->setNextSibling(firstAnChild); + if (child->nextSibling()) + child->nextSibling()->setPreviousSibling(lastAnChild); + + if (child == m_children.firstChild()) + m_children.setFirstChild(firstAnChild); + if (child == m_children.lastChild()) + m_children.setLastChild(lastAnChild); + } else { + if (child == m_children.firstChild()) + m_children.setFirstChild(child->nextSibling()); + if (child == m_children.lastChild()) + m_children.setLastChild(child->previousSibling()); + + if (child->previousSibling()) + child->previousSibling()->setNextSibling(child->nextSibling()); + if (child->nextSibling()) + child->nextSibling()->setPreviousSibling(child->previousSibling()); + } + child->setParent(0); + child->setPreviousSibling(0); + child->setNextSibling(0); + + child->children()->setFirstChild(0); + child->m_next = 0; + + child->destroy(); +} + +static bool canMergeContiguousAnonymousBlocks(RenderObject* oldChild, RenderObject* prev, RenderObject* next) +{ + if (oldChild->documentBeingDestroyed() || oldChild->isInline() || oldChild->virtualContinuation()) + return false; + + if ((prev && (!prev->isAnonymousBlock() || toRenderBlock(prev)->continuation())) + || (next && (!next->isAnonymousBlock() || toRenderBlock(next)->continuation()))) + return false; + + // FIXME: This check isn't required when inline run-ins can't be split into continuations. + if (prev && prev->firstChild() && prev->firstChild()->isInline() && prev->firstChild()->isRunIn()) + return false; + + if ((prev && (prev->isRubyRun() || prev->isRubyBase())) + || (next && (next->isRubyRun() || next->isRubyBase()))) + return false; + + if (!prev || !next) + return true; + + // Make sure the types of the anonymous blocks match up. + return prev->isAnonymousColumnsBlock() == next->isAnonymousColumnsBlock() + && prev->isAnonymousColumnSpanBlock() == next->isAnonymousColumnSpanBlock(); +} + +void RenderBlock::removeChild(RenderObject* oldChild) +{ + // If this child is a block, and if our previous and next siblings are + // both anonymous blocks with inline content, then we can go ahead and + // fold the inline content back together. + RenderObject* prev = oldChild->previousSibling(); + RenderObject* next = oldChild->nextSibling(); + bool canMergeAnonymousBlocks = canMergeContiguousAnonymousBlocks(oldChild, prev, next); + if (canMergeAnonymousBlocks && prev && next) { + prev->setNeedsLayoutAndPrefWidthsRecalc(); + RenderBlock* nextBlock = toRenderBlock(next); + RenderBlock* prevBlock = toRenderBlock(prev); + + if (prev->childrenInline() != next->childrenInline()) { + RenderBlock* inlineChildrenBlock = prev->childrenInline() ? prevBlock : nextBlock; + RenderBlock* blockChildrenBlock = prev->childrenInline() ? nextBlock : prevBlock; + + // Place the inline children block inside of the block children block instead of deleting it. + // In order to reuse it, we have to reset it to just be a generic anonymous block. Make sure + // to clear out inherited column properties by just making a new style, and to also clear the + // column span flag if it is set. + ASSERT(!inlineChildrenBlock->continuation()); + RefPtr<RenderStyle> newStyle = RenderStyle::create(); + newStyle->inheritFrom(style()); + children()->removeChildNode(this, inlineChildrenBlock, inlineChildrenBlock->hasLayer()); + inlineChildrenBlock->setStyle(newStyle); + + // Now just put the inlineChildrenBlock inside the blockChildrenBlock. + blockChildrenBlock->children()->insertChildNode(blockChildrenBlock, inlineChildrenBlock, prev == inlineChildrenBlock ? blockChildrenBlock->firstChild() : 0, + inlineChildrenBlock->hasLayer() || blockChildrenBlock->hasLayer()); + next->setNeedsLayoutAndPrefWidthsRecalc(); + + // inlineChildrenBlock got reparented to blockChildrenBlock, so it is no longer a child + // of "this". we null out prev or next so that is not used later in the function. + if (inlineChildrenBlock == prevBlock) + prev = 0; + else + next = 0; + } else { + // Take all the children out of the |next| block and put them in + // the |prev| block. + nextBlock->moveAllChildrenTo(prevBlock, nextBlock->hasLayer() || prevBlock->hasLayer()); + + // FIXME: When we destroy nextBlock, it might happen that nextBlock's next sibling block and + // oldChild can get merged. Since oldChild is getting removed, we do not want to move + // nextBlock's next sibling block's children into it. By setting a fake continuation, + // we prevent this from happening. This is not the best approach, we should replace this + // something better later to automatically detect that oldChild is getting removed. + RenderBlock* oldChildBlock = 0; + if (oldChild->isAnonymous() && oldChild->isRenderBlock() && !toRenderBlock(oldChild)->continuation()) { + oldChildBlock = toRenderBlock(oldChild); + oldChildBlock->setContinuation(oldChildBlock); + } + + // Delete the now-empty block's lines and nuke it. + nextBlock->deleteLineBoxTree(); + nextBlock->destroy(); + next = 0; + + // FIXME: Revert the continuation change done above. + if (oldChildBlock) + oldChildBlock->setContinuation(0); + } + } + + RenderBox::removeChild(oldChild); + + RenderObject* child = prev ? prev : next; + if (canMergeAnonymousBlocks && child && !child->previousSibling() && !child->nextSibling() && !isFlexibleBox()) { + // The removal has knocked us down to containing only a single anonymous + // box. We can go ahead and pull the content right back up into our + // box. + setNeedsLayoutAndPrefWidthsRecalc(); + setChildrenInline(child->childrenInline()); + RenderBlock* anonBlock = toRenderBlock(children()->removeChildNode(this, child, child->hasLayer())); + anonBlock->moveAllChildrenTo(this, child->hasLayer()); + // Delete the now-empty block's lines and nuke it. + anonBlock->deleteLineBoxTree(); + anonBlock->destroy(); + } + + if (!firstChild() && !documentBeingDestroyed()) { + // If this was our last child be sure to clear out our line boxes. + if (childrenInline()) + lineBoxes()->deleteLineBoxes(renderArena()); + } +} + +bool RenderBlock::isSelfCollapsingBlock() const +{ + // We are not self-collapsing if we + // (a) have a non-zero height according to layout (an optimization to avoid wasting time) + // (b) are a table, + // (c) have border/padding, + // (d) have a min-height + // (e) have specified that one of our margins can't collapse using a CSS extension + if (logicalHeight() > 0 + || isTable() || borderAndPaddingLogicalHeight() + || style()->logicalMinHeight().isPositive() + || style()->marginBeforeCollapse() == MSEPARATE || style()->marginAfterCollapse() == MSEPARATE) + return false; + + Length logicalHeightLength = style()->logicalHeight(); + bool hasAutoHeight = logicalHeightLength.isAuto(); + if (logicalHeightLength.isPercent() && !document()->inQuirksMode()) { + hasAutoHeight = true; + for (RenderBlock* cb = containingBlock(); !cb->isRenderView(); cb = cb->containingBlock()) { + if (cb->style()->logicalHeight().isFixed() || cb->isTableCell()) + hasAutoHeight = false; + } + } + + // If the height is 0 or auto, then whether or not we are a self-collapsing block depends + // on whether we have content that is all self-collapsing or not. + if (hasAutoHeight || ((logicalHeightLength.isFixed() || logicalHeightLength.isPercent()) && logicalHeightLength.isZero())) { + // If the block has inline children, see if we generated any line boxes. If we have any + // line boxes, then we can't be self-collapsing, since we have content. + if (childrenInline()) + return !firstLineBox(); + + // Whether or not we collapse is dependent on whether all our normal flow children + // are also self-collapsing. + for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { + if (child->isFloatingOrPositioned()) + continue; + if (!child->isSelfCollapsingBlock()) + return false; + } + return true; + } + return false; +} + +void RenderBlock::startDelayUpdateScrollInfo() +{ + if (gDelayUpdateScrollInfo == 0) { + ASSERT(!gDelayedUpdateScrollInfoSet); + gDelayedUpdateScrollInfoSet = new DelayedUpdateScrollInfoSet; + } + ASSERT(gDelayedUpdateScrollInfoSet); + ++gDelayUpdateScrollInfo; +} + +void RenderBlock::finishDelayUpdateScrollInfo() +{ + --gDelayUpdateScrollInfo; + ASSERT(gDelayUpdateScrollInfo >= 0); + if (gDelayUpdateScrollInfo == 0) { + ASSERT(gDelayedUpdateScrollInfoSet); + + OwnPtr<DelayedUpdateScrollInfoSet> infoSet(gDelayedUpdateScrollInfoSet); + gDelayedUpdateScrollInfoSet = 0; + + for (DelayedUpdateScrollInfoSet::iterator it = infoSet->begin(); it != infoSet->end(); ++it) { + RenderBlock* block = *it; + if (block->hasOverflowClip()) { + block->layer()->updateScrollInfoAfterLayout(); + } + } + } +} + +void RenderBlock::updateScrollInfoAfterLayout() +{ + if (hasOverflowClip()) { + if (gDelayUpdateScrollInfo) + gDelayedUpdateScrollInfoSet->add(this); + else + layer()->updateScrollInfoAfterLayout(); + } +} + +void RenderBlock::layout() +{ + // Update our first letter info now. + updateFirstLetter(); + + // Table cells call layoutBlock directly, so don't add any logic here. Put code into + // layoutBlock(). + layoutBlock(false); + + // It's safe to check for control clip here, since controls can never be table cells. + // If we have a lightweight clip, there can never be any overflow from children. + if (hasControlClip() && m_overflow) + clearLayoutOverflow(); +} + +void RenderBlock::layoutBlock(bool relayoutChildren, int pageLogicalHeight) +{ + ASSERT(needsLayout()); + + if (isInline() && !isInlineBlockOrInlineTable()) // Inline <form>s inside various table elements can + return; // cause us to come in here. Just bail. + + if (!relayoutChildren && layoutOnlyPositionedObjects()) + return; + + LayoutRepainter repainter(*this, m_everHadLayout && checkForRepaintDuringLayout()); + + int oldWidth = logicalWidth(); + int oldColumnWidth = desiredColumnWidth(); + + computeLogicalWidth(); + calcColumnWidth(); + + m_overflow.clear(); + + if (oldWidth != logicalWidth() || oldColumnWidth != desiredColumnWidth()) + relayoutChildren = true; + +#ifdef ANDROID_LAYOUT + checkAndSetRelayoutChildren(&relayoutChildren); +#endif + + clearFloats(); + + int previousHeight = logicalHeight(); + setLogicalHeight(0); + bool hasSpecifiedPageLogicalHeight = false; + bool pageLogicalHeightChanged = false; + ColumnInfo* colInfo = columnInfo(); + if (hasColumns()) { + if (!pageLogicalHeight) { + // We need to go ahead and set our explicit page height if one exists, so that we can + // avoid doing two layout passes. + computeLogicalHeight(); + int columnHeight = contentLogicalHeight(); + if (columnHeight > 0) { + pageLogicalHeight = columnHeight; + hasSpecifiedPageLogicalHeight = true; + } + setLogicalHeight(0); + } + if (colInfo->columnHeight() != pageLogicalHeight && m_everHadLayout) { + colInfo->setColumnHeight(pageLogicalHeight); + pageLogicalHeightChanged = true; + } + + if (!hasSpecifiedPageLogicalHeight && !pageLogicalHeight) + colInfo->clearForcedBreaks(); + } + + LayoutStateMaintainer statePusher(view(), this, IntSize(x(), y()), hasColumns() || hasTransform() || hasReflection() || style()->isFlippedBlocksWritingMode(), pageLogicalHeight, pageLogicalHeightChanged, colInfo); + + // We use four values, maxTopPos, maxTopNeg, maxBottomPos, and maxBottomNeg, to track + // our current maximal positive and negative margins. These values are used when we + // are collapsed with adjacent blocks, so for example, if you have block A and B + // collapsing together, then you'd take the maximal positive margin from both A and B + // and subtract it from the maximal negative margin from both A and B to get the + // true collapsed margin. This algorithm is recursive, so when we finish layout() + // our block knows its current maximal positive/negative values. + // + // Start out by setting our margin values to our current margins. Table cells have + // no margins, so we don't fill in the values for table cells. + bool isCell = isTableCell(); + if (!isCell) { + initMaxMarginValues(); + + setMarginBeforeQuirk(style()->marginBefore().quirk()); + setMarginAfterQuirk(style()->marginAfter().quirk()); + + Node* n = node(); + if (n && n->hasTagName(formTag) && static_cast<HTMLFormElement*>(n)->isMalformed()) { + // See if this form is malformed (i.e., unclosed). If so, don't give the form + // a bottom margin. + setMaxMarginAfterValues(0, 0); + } + + setPaginationStrut(0); + } + + // For overflow:scroll blocks, ensure we have both scrollbars in place always. + if (scrollsOverflow()) { + if (style()->overflowX() == OSCROLL) + layer()->setHasHorizontalScrollbar(true); + if (style()->overflowY() == OSCROLL) + layer()->setHasVerticalScrollbar(true); + } + + int repaintLogicalTop = 0; + int repaintLogicalBottom = 0; + int maxFloatLogicalBottom = 0; + if (!firstChild() && !isAnonymousBlock()) + setChildrenInline(true); + if (childrenInline()) + layoutInlineChildren(relayoutChildren, repaintLogicalTop, repaintLogicalBottom); + else + layoutBlockChildren(relayoutChildren, maxFloatLogicalBottom); + + // Expand our intrinsic height to encompass floats. + int toAdd = borderAfter() + paddingAfter() + scrollbarLogicalHeight(); + if (lowestFloatLogicalBottom() > (logicalHeight() - toAdd) && expandsToEncloseOverhangingFloats()) + setLogicalHeight(lowestFloatLogicalBottom() + toAdd); + + if (layoutColumns(hasSpecifiedPageLogicalHeight, pageLogicalHeight, statePusher)) + return; + + // Calculate our new height. + int oldHeight = logicalHeight(); + int oldClientAfterEdge = clientLogicalBottom(); + computeLogicalHeight(); + int newHeight = logicalHeight(); + if (oldHeight != newHeight) { + if (oldHeight > newHeight && maxFloatLogicalBottom > newHeight && !childrenInline()) { + // One of our children's floats may have become an overhanging float for us. We need to look for it. + for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { + if (child->isBlockFlow() && !child->isFloatingOrPositioned()) { + RenderBlock* block = toRenderBlock(child); + if (block->lowestFloatLogicalBottom() + block->logicalTop() > newHeight) + addOverhangingFloats(block, -block->logicalLeft(), -block->logicalTop(), false); + } + } + } + } + + if (previousHeight != newHeight) + relayoutChildren = true; + + layoutPositionedObjects(relayoutChildren || isRoot()); + + // Add overflow from children (unless we're multi-column, since in that case all our child overflow is clipped anyway). + computeOverflow(oldClientAfterEdge); + + statePusher.pop(); + + if (view()->layoutState()->m_pageLogicalHeight) + setPageLogicalOffset(view()->layoutState()->pageLogicalOffset(y())); + + updateLayerTransform(); + + // Update our scroll information if we're overflow:auto/scroll/hidden now that we know if + // we overflow or not. + updateScrollInfoAfterLayout(); + + // Repaint with our new bounds if they are different from our old bounds. + bool didFullRepaint = repainter.repaintAfterLayout(); + if (!didFullRepaint && repaintLogicalTop != repaintLogicalBottom && (style()->visibility() == VISIBLE || enclosingLayer()->hasVisibleContent())) { + // FIXME: We could tighten up the left and right invalidation points if we let layoutInlineChildren fill them in based off the particular lines + // it had to lay out. We wouldn't need the hasOverflowClip() hack in that case either. + int repaintLogicalLeft = logicalLeftVisualOverflow(); + int repaintLogicalRight = logicalRightVisualOverflow(); + if (hasOverflowClip()) { + // If we have clipped overflow, we should use layout overflow as well, since visual overflow from lines didn't propagate to our block's overflow. + // Note the old code did this as well but even for overflow:visible. The addition of hasOverflowClip() at least tightens up the hack a bit. + // layoutInlineChildren should be patched to compute the entire repaint rect. + repaintLogicalLeft = min(repaintLogicalLeft, logicalLeftLayoutOverflow()); + repaintLogicalRight = max(repaintLogicalRight, logicalRightLayoutOverflow()); + } + + IntRect repaintRect; + if (style()->isHorizontalWritingMode()) + repaintRect = IntRect(repaintLogicalLeft, repaintLogicalTop, repaintLogicalRight - repaintLogicalLeft, repaintLogicalBottom - repaintLogicalTop); + else + repaintRect = IntRect(repaintLogicalTop, repaintLogicalLeft, repaintLogicalBottom - repaintLogicalTop, repaintLogicalRight - repaintLogicalLeft); + + // The repaint rect may be split across columns, in which case adjustRectForColumns() will return the union. + adjustRectForColumns(repaintRect); + + repaintRect.inflate(maximalOutlineSize(PaintPhaseOutline)); + + if (hasOverflowClip()) { + // Adjust repaint rect for scroll offset + repaintRect.move(-layer()->scrolledContentOffset()); + + // Don't allow this rect to spill out of our overflow box. + repaintRect.intersect(IntRect(0, 0, width(), height())); + } + + // Make sure the rect is still non-empty after intersecting for overflow above + if (!repaintRect.isEmpty()) { + repaintRectangle(repaintRect); // We need to do a partial repaint of our content. + if (hasReflection()) + repaintRectangle(reflectedRect(repaintRect)); + } + } + setNeedsLayout(false); +} + +void RenderBlock::addOverflowFromChildren() +{ + if (!hasColumns()) { + if (childrenInline()) + addOverflowFromInlineChildren(); + else + addOverflowFromBlockChildren(); + } else { + ColumnInfo* colInfo = columnInfo(); + if (columnCount(colInfo)) { + IntRect lastRect = columnRectAt(colInfo, columnCount(colInfo) - 1); + int overflowLeft = !style()->isLeftToRightDirection() ? min(0, lastRect.x()) : 0; + int overflowRight = style()->isLeftToRightDirection() ? max(width(), lastRect.x() + lastRect.width()) : 0; + int overflowHeight = borderTop() + paddingTop() + colInfo->columnHeight(); + addLayoutOverflow(IntRect(overflowLeft, 0, overflowRight - overflowLeft, overflowHeight)); + } + } +} + +void RenderBlock::computeOverflow(int oldClientAfterEdge, bool recomputeFloats) +{ + // Add overflow from children. + addOverflowFromChildren(); + + if (!hasColumns() && (recomputeFloats || isRoot() || expandsToEncloseOverhangingFloats() || hasSelfPaintingLayer())) + addOverflowFromFloats(); + + // Add in the overflow from positioned objects. + addOverflowFromPositionedObjects(); + + if (hasOverflowClip()) { + // When we have overflow clip, propagate the original spillout since it will include collapsed bottom margins + // and bottom padding. Set the axis we don't care about to be 1, since we want this overflow to always + // be considered reachable. + IntRect clientRect(clientBoxRect()); + IntRect rectToApply; + if (style()->isHorizontalWritingMode()) + rectToApply = IntRect(clientRect.x(), clientRect.y(), 1, max(0, oldClientAfterEdge - clientRect.y())); + else + rectToApply = IntRect(clientRect.x(), clientRect.y(), max(0, oldClientAfterEdge - clientRect.x()), 1); + addLayoutOverflow(rectToApply); + } + + // Add visual overflow from box-shadow and reflections. + addShadowOverflow(); +} + +void RenderBlock::addOverflowFromBlockChildren() +{ + for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { + if (!child->isFloatingOrPositioned()) + addOverflowFromChild(child); + } +} + +void RenderBlock::addOverflowFromFloats() +{ + IntRect result; + if (!m_floatingObjects) + return; + FloatingObject* r; + DeprecatedPtrListIterator<FloatingObject> it(*m_floatingObjects); + for (; (r = it.current()); ++it) { + if (r->m_isDescendant) + addOverflowFromChild(r->m_renderer, IntSize(r->left() + r->m_renderer->marginLeft(), r->top() + r->m_renderer->marginTop())); + } + return; +} + +void RenderBlock::addOverflowFromPositionedObjects() +{ + if (!m_positionedObjects) + return; + + RenderBox* positionedObject; + Iterator end = m_positionedObjects->end(); + for (Iterator it = m_positionedObjects->begin(); it != end; ++it) { + positionedObject = *it; + + // Fixed positioned elements don't contribute to layout overflow, since they don't scroll with the content. + if (positionedObject->style()->position() != FixedPosition) + addOverflowFromChild(positionedObject); + } +} + +bool RenderBlock::expandsToEncloseOverhangingFloats() const +{ + return isInlineBlockOrInlineTable() || isFloatingOrPositioned() || hasOverflowClip() || (parent() && parent()->isFlexibleBox()) + || hasColumns() || isTableCell() || isFieldset() || isWritingModeRoot(); +} + +void RenderBlock::adjustPositionedBlock(RenderBox* child, const MarginInfo& marginInfo) +{ + if (child->style()->hasStaticX()) { + if (style()->isLeftToRightDirection()) + child->layer()->setStaticX(borderLeft() + paddingLeft()); + else + child->layer()->setStaticX(borderRight() + paddingRight()); + } + + if (child->style()->hasStaticY()) { + int y = height(); + if (!marginInfo.canCollapseWithMarginBefore()) { + child->computeBlockDirectionMargins(this); + int marginTop = child->marginTop(); + int collapsedTopPos = marginInfo.positiveMargin(); + int collapsedTopNeg = marginInfo.negativeMargin(); + if (marginTop > 0) { + if (marginTop > collapsedTopPos) + collapsedTopPos = marginTop; + } else { + if (-marginTop > collapsedTopNeg) + collapsedTopNeg = -marginTop; + } + y += (collapsedTopPos - collapsedTopNeg) - marginTop; + } + RenderLayer* childLayer = child->layer(); + if (childLayer->staticY() != y) { + child->layer()->setStaticY(y); + child->setChildNeedsLayout(true, false); + } + } +} + +void RenderBlock::adjustFloatingBlock(const MarginInfo& marginInfo) +{ + // The float should be positioned taking into account the bottom margin + // of the previous flow. We add that margin into the height, get the + // float positioned properly, and then subtract the margin out of the + // height again. In the case of self-collapsing blocks, we always just + // use the top margins, since the self-collapsing block collapsed its + // own bottom margin into its top margin. + // + // Note also that the previous flow may collapse its margin into the top of + // our block. If this is the case, then we do not add the margin in to our + // height when computing the position of the float. This condition can be tested + // for by simply calling canCollapseWithMarginBefore. See + // http://www.hixie.ch/tests/adhoc/css/box/block/margin-collapse/046.html for + // an example of this scenario. + int marginOffset = marginInfo.canCollapseWithMarginBefore() ? 0 : marginInfo.margin(); + setLogicalHeight(logicalHeight() + marginOffset); + positionNewFloats(); + setLogicalHeight(logicalHeight() - marginOffset); +} + +bool RenderBlock::handleSpecialChild(RenderBox* child, const MarginInfo& marginInfo) +{ + // Handle in the given order + return handlePositionedChild(child, marginInfo) + || handleFloatingChild(child, marginInfo) + || handleRunInChild(child); +} + + +bool RenderBlock::handlePositionedChild(RenderBox* child, const MarginInfo& marginInfo) +{ + if (child->isPositioned()) { + child->containingBlock()->insertPositionedObject(child); + adjustPositionedBlock(child, marginInfo); + return true; + } + return false; +} + +bool RenderBlock::handleFloatingChild(RenderBox* child, const MarginInfo& marginInfo) +{ + if (child->isFloating()) { + insertFloatingObject(child); + adjustFloatingBlock(marginInfo); + return true; + } + return false; +} + +bool RenderBlock::handleRunInChild(RenderBox* child) +{ + // See if we have a run-in element with inline children. If the + // children aren't inline, then just treat the run-in as a normal + // block. + if (!child->isRunIn() || !child->childrenInline()) + return false; + // FIXME: We don't handle non-block elements with run-in for now. + if (!child->isRenderBlock()) + return false; + + // Get the next non-positioned/non-floating RenderBlock. + RenderBlock* blockRunIn = toRenderBlock(child); + RenderObject* curr = blockRunIn->nextSibling(); + while (curr && curr->isFloatingOrPositioned()) + curr = curr->nextSibling(); + + if (!curr || !curr->isRenderBlock() || !curr->childrenInline() || curr->isRunIn() || curr->isAnonymous()) + return false; + + RenderBlock* currBlock = toRenderBlock(curr); + + // Remove the old child. + children()->removeChildNode(this, blockRunIn); + + // Create an inline. + Node* runInNode = blockRunIn->node(); + RenderInline* inlineRunIn = new (renderArena()) RenderInline(runInNode ? runInNode : document()); + inlineRunIn->setStyle(blockRunIn->style()); + + bool runInIsGenerated = child->style()->styleType() == BEFORE || child->style()->styleType() == AFTER; + + // Move the nodes from the old child to the new child, but skip any :before/:after content. It has already + // been regenerated by the new inline. + for (RenderObject* runInChild = blockRunIn->firstChild(); runInChild;) { + RenderObject* nextSibling = runInChild->nextSibling(); + if (runInIsGenerated || (runInChild->style()->styleType() != BEFORE && runInChild->style()->styleType() != AFTER)) { + blockRunIn->children()->removeChildNode(blockRunIn, runInChild, false); + inlineRunIn->addChild(runInChild); // Use addChild instead of appendChildNode since it handles correct placement of the children relative to :after-generated content. + } + runInChild = nextSibling; + } + + // Now insert the new child under |currBlock|. + currBlock->children()->insertChildNode(currBlock, inlineRunIn, currBlock->firstChild()); + + // If the run-in had an element, we need to set the new renderer. + if (runInNode) + runInNode->setRenderer(inlineRunIn); + + // Destroy the block run-in, which includes deleting its line box tree. + blockRunIn->deleteLineBoxTree(); + blockRunIn->destroy(); + + // The block acts like an inline, so just null out its + // position. + + return true; +} + +int RenderBlock::collapseMargins(RenderBox* child, MarginInfo& marginInfo) +{ + // Get the four margin values for the child and cache them. + const MarginValues childMargins = marginValuesForChild(child); + + // Get our max pos and neg top margins. + int posTop = childMargins.positiveMarginBefore(); + int negTop = childMargins.negativeMarginBefore(); + + // For self-collapsing blocks, collapse our bottom margins into our + // top to get new posTop and negTop values. + if (child->isSelfCollapsingBlock()) { + posTop = max(posTop, childMargins.positiveMarginAfter()); + negTop = max(negTop, childMargins.negativeMarginAfter()); + } + + // See if the top margin is quirky. We only care if this child has + // margins that will collapse with us. + bool topQuirk = child->isMarginBeforeQuirk() || style()->marginBeforeCollapse() == MDISCARD; + + if (marginInfo.canCollapseWithMarginBefore()) { + // This child is collapsing with the top of the + // block. If it has larger margin values, then we need to update + // our own maximal values. + if (!document()->inQuirksMode() || !marginInfo.quirkContainer() || !topQuirk) + setMaxMarginBeforeValues(max(posTop, maxPositiveMarginBefore()), max(negTop, maxNegativeMarginBefore())); + + // The minute any of the margins involved isn't a quirk, don't + // collapse it away, even if the margin is smaller (www.webreference.com + // has an example of this, a <dt> with 0.8em author-specified inside + // a <dl> inside a <td>. + if (!marginInfo.determinedMarginBeforeQuirk() && !topQuirk && (posTop - negTop)) { + setMarginBeforeQuirk(false); + marginInfo.setDeterminedMarginBeforeQuirk(true); + } + + if (!marginInfo.determinedMarginBeforeQuirk() && topQuirk && !marginBefore()) + // We have no top margin and our top child has a quirky margin. + // We will pick up this quirky margin and pass it through. + // This deals with the <td><div><p> case. + // Don't do this for a block that split two inlines though. You do + // still apply margins in this case. + setMarginBeforeQuirk(true); + } + + if (marginInfo.quirkContainer() && marginInfo.atBeforeSideOfBlock() && (posTop - negTop)) + marginInfo.setMarginBeforeQuirk(topQuirk); + + int beforeCollapseLogicalTop = logicalHeight(); + int logicalTop = beforeCollapseLogicalTop; + if (child->isSelfCollapsingBlock()) { + // This child has no height. We need to compute our + // position before we collapse the child's margins together, + // so that we can get an accurate position for the zero-height block. + int collapsedBeforePos = max(marginInfo.positiveMargin(), childMargins.positiveMarginBefore()); + int collapsedBeforeNeg = max(marginInfo.negativeMargin(), childMargins.negativeMarginBefore()); + marginInfo.setMargin(collapsedBeforePos, collapsedBeforeNeg); + + // Now collapse the child's margins together, which means examining our + // bottom margin values as well. + marginInfo.setPositiveMarginIfLarger(childMargins.positiveMarginAfter()); + marginInfo.setNegativeMarginIfLarger(childMargins.negativeMarginAfter()); + + if (!marginInfo.canCollapseWithMarginBefore()) + // We need to make sure that the position of the self-collapsing block + // is correct, since it could have overflowing content + // that needs to be positioned correctly (e.g., a block that + // had a specified height of 0 but that actually had subcontent). + logicalTop = logicalHeight() + collapsedBeforePos - collapsedBeforeNeg; + } + else { + if (child->style()->marginBeforeCollapse() == MSEPARATE) { + setLogicalHeight(logicalHeight() + marginInfo.margin() + marginBeforeForChild(child)); + logicalTop = logicalHeight(); + } + else if (!marginInfo.atBeforeSideOfBlock() || + (!marginInfo.canCollapseMarginBeforeWithChildren() + && (!document()->inQuirksMode() || !marginInfo.quirkContainer() || !marginInfo.marginBeforeQuirk()))) { + // We're collapsing with a previous sibling's margins and not + // with the top of the block. + setLogicalHeight(logicalHeight() + max(marginInfo.positiveMargin(), posTop) - max(marginInfo.negativeMargin(), negTop)); + logicalTop = logicalHeight(); + } + + marginInfo.setPositiveMargin(childMargins.positiveMarginAfter()); + marginInfo.setNegativeMargin(childMargins.negativeMarginAfter()); + + if (marginInfo.margin()) + marginInfo.setMarginAfterQuirk(child->isMarginAfterQuirk() || style()->marginAfterCollapse() == MDISCARD); + } + + // If margins would pull us past the top of the next page, then we need to pull back and pretend like the margins + // collapsed into the page edge. + bool paginated = view()->layoutState()->isPaginated(); + if (paginated && logicalTop > beforeCollapseLogicalTop) { + int oldLogicalTop = logicalTop; + logicalTop = min(logicalTop, nextPageTop(beforeCollapseLogicalTop)); + setLogicalHeight(logicalHeight() + (logicalTop - oldLogicalTop)); + } + return logicalTop; +} + +int RenderBlock::clearFloatsIfNeeded(RenderBox* child, MarginInfo& marginInfo, int oldTopPosMargin, int oldTopNegMargin, int yPos) +{ + int heightIncrease = getClearDelta(child, yPos); + if (!heightIncrease) + return yPos; + + if (child->isSelfCollapsingBlock()) { + // For self-collapsing blocks that clear, they can still collapse their + // margins with following siblings. Reset the current margins to represent + // the self-collapsing block's margins only. + // CSS2.1 states: + // "An element that has had clearance applied to it never collapses its top margin with its parent block's bottom margin. + // Therefore if we are at the bottom of the block, let's go ahead and reset margins to only include the + // self-collapsing block's bottom margin. + bool atBottomOfBlock = true; + for (RenderBox* curr = child->nextSiblingBox(); curr && atBottomOfBlock; curr = curr->nextSiblingBox()) { + if (!curr->isFloatingOrPositioned()) + atBottomOfBlock = false; + } + + MarginValues childMargins = marginValuesForChild(child); + if (atBottomOfBlock) { + marginInfo.setPositiveMargin(childMargins.positiveMarginAfter()); + marginInfo.setNegativeMargin(childMargins.negativeMarginAfter()); + } else { + marginInfo.setPositiveMargin(max(childMargins.positiveMarginBefore(), childMargins.positiveMarginAfter())); + marginInfo.setNegativeMargin(max(childMargins.negativeMarginBefore(), childMargins.negativeMarginAfter())); + } + + // Adjust our height such that we are ready to be collapsed with subsequent siblings (or the bottom + // of the parent block). + setLogicalHeight(child->y() - max(0, marginInfo.margin())); + } else + // Increase our height by the amount we had to clear. + setLogicalHeight(height() + heightIncrease); + + if (marginInfo.canCollapseWithMarginBefore()) { + // We can no longer collapse with the top of the block since a clear + // occurred. The empty blocks collapse into the cleared block. + // FIXME: This isn't quite correct. Need clarification for what to do + // if the height the cleared block is offset by is smaller than the + // margins involved. + setMaxMarginBeforeValues(oldTopPosMargin, oldTopNegMargin); + marginInfo.setAtBeforeSideOfBlock(false); + } + + return yPos + heightIncrease; +} + +int RenderBlock::estimateLogicalTopPosition(RenderBox* child, const MarginInfo& marginInfo) +{ + // FIXME: We need to eliminate the estimation of vertical position, because when it's wrong we sometimes trigger a pathological + // relayout if there are intruding floats. + int logicalTopEstimate = logicalHeight(); + if (!marginInfo.canCollapseWithMarginBefore()) { + int childMarginBefore = child->selfNeedsLayout() ? marginBeforeForChild(child) : collapsedMarginBeforeForChild(child); + logicalTopEstimate += max(marginInfo.margin(), childMarginBefore); + } + + bool paginated = view()->layoutState()->isPaginated(); + + // Adjust logicalTopEstimate down to the next page if the margins are so large that we don't fit on the current + // page. + if (paginated && logicalTopEstimate > logicalHeight()) + logicalTopEstimate = min(logicalTopEstimate, nextPageTop(logicalHeight())); + + logicalTopEstimate += getClearDelta(child, logicalTopEstimate); + + if (paginated) { + // If the object has a page or column break value of "before", then we should shift to the top of the next page. + logicalTopEstimate = applyBeforeBreak(child, logicalTopEstimate); + + // For replaced elements and scrolled elements, we want to shift them to the next page if they don't fit on the current one. + logicalTopEstimate = adjustForUnsplittableChild(child, logicalTopEstimate); + + if (!child->selfNeedsLayout() && child->isRenderBlock()) + logicalTopEstimate += toRenderBlock(child)->paginationStrut(); + } + + return logicalTopEstimate; +} + +void RenderBlock::determineLogicalLeftPositionForChild(RenderBox* child) +{ + int startPosition = borderStart() + paddingStart(); + int totalAvailableLogicalWidth = borderAndPaddingLogicalWidth() + availableLogicalWidth(); + + // Add in our start margin. + int childMarginStart = marginStartForChild(child); + int newPosition = startPosition + childMarginStart; + + // Some objects (e.g., tables, horizontal rules, overflow:auto blocks) avoid floats. They need + // to shift over as necessary to dodge any floats that might get in the way. + if (child->avoidsFloats()) { + int startOff = style()->isLeftToRightDirection() ? logicalLeftOffsetForLine(logicalHeight(), false) : totalAvailableLogicalWidth - logicalRightOffsetForLine(logicalHeight(), false); + if (style()->textAlign() != WEBKIT_CENTER && !child->style()->marginStartUsing(style()).isAuto()) { + if (childMarginStart < 0) + startOff += childMarginStart; + newPosition = max(newPosition, startOff); // Let the float sit in the child's margin if it can fit. + } else if (startOff != startPosition) { + // The object is shifting to the "end" side of the block. The object might be centered, so we need to + // recalculate our inline direction margins. Note that the containing block content + // width computation will take into account the delta between |startOff| and |startPosition| + // so that we can just pass the content width in directly to the |computeMarginsInContainingBlockInlineDirection| + // function. + child->computeInlineDirectionMargins(this, availableLogicalWidthForLine(logicalTopForChild(child), false), logicalWidthForChild(child)); + newPosition = startOff + marginStartForChild(child); + } + } + + setLogicalLeftForChild(child, style()->isLeftToRightDirection() ? newPosition : totalAvailableLogicalWidth - newPosition - logicalWidthForChild(child), ApplyLayoutDelta); +} + +void RenderBlock::setCollapsedBottomMargin(const MarginInfo& marginInfo) +{ + if (marginInfo.canCollapseWithMarginAfter() && !marginInfo.canCollapseWithMarginBefore()) { + // Update our max pos/neg bottom margins, since we collapsed our bottom margins + // with our children. + setMaxMarginAfterValues(max(maxPositiveMarginAfter(), marginInfo.positiveMargin()), max(maxNegativeMarginAfter(), marginInfo.negativeMargin())); + + if (!marginInfo.marginAfterQuirk()) + setMarginAfterQuirk(false); + + if (marginInfo.marginAfterQuirk() && marginAfter() == 0) + // We have no bottom margin and our last child has a quirky margin. + // We will pick up this quirky margin and pass it through. + // This deals with the <td><div><p> case. + setMarginAfterQuirk(true); + } +} + +void RenderBlock::handleAfterSideOfBlock(int beforeSide, int afterSide, MarginInfo& marginInfo) +{ + marginInfo.setAtAfterSideOfBlock(true); + + // If we can't collapse with children then go ahead and add in the bottom margin. + if (!marginInfo.canCollapseWithMarginAfter() && !marginInfo.canCollapseWithMarginBefore() + && (!document()->inQuirksMode() || !marginInfo.quirkContainer() || !marginInfo.marginAfterQuirk())) + setLogicalHeight(logicalHeight() + marginInfo.margin()); + + // Now add in our bottom border/padding. + setLogicalHeight(logicalHeight() + afterSide); + + // Negative margins can cause our height to shrink below our minimal height (border/padding). + // If this happens, ensure that the computed height is increased to the minimal height. + setLogicalHeight(max(logicalHeight(), beforeSide + afterSide)); + + // Update our bottom collapsed margin info. + setCollapsedBottomMargin(marginInfo); +} + +void RenderBlock::setLogicalLeftForChild(RenderBox* child, int logicalLeft, ApplyLayoutDeltaMode applyDelta) +{ + if (style()->isHorizontalWritingMode()) { + if (applyDelta == ApplyLayoutDelta) + view()->addLayoutDelta(IntSize(child->x() - logicalLeft, 0)); + child->setX(logicalLeft); + } else { + if (applyDelta == ApplyLayoutDelta) + view()->addLayoutDelta(IntSize(0, child->y() - logicalLeft)); + child->setY(logicalLeft); + } +} + +void RenderBlock::setLogicalTopForChild(RenderBox* child, int logicalTop, ApplyLayoutDeltaMode applyDelta) +{ + if (style()->isHorizontalWritingMode()) { + if (applyDelta == ApplyLayoutDelta) + view()->addLayoutDelta(IntSize(0, child->y() - logicalTop)); + child->setY(logicalTop); + } else { + if (applyDelta == ApplyLayoutDelta) + view()->addLayoutDelta(IntSize(child->x() - logicalTop, 0)); + child->setX(logicalTop); + } +} + +void RenderBlock::layoutBlockChildren(bool relayoutChildren, int& maxFloatLogicalBottom) +{ + if (gPercentHeightDescendantsMap) { + if (HashSet<RenderBox*>* descendants = gPercentHeightDescendantsMap->get(this)) { + HashSet<RenderBox*>::iterator end = descendants->end(); + for (HashSet<RenderBox*>::iterator it = descendants->begin(); it != end; ++it) { + RenderBox* box = *it; + while (box != this) { + if (box->normalChildNeedsLayout()) + break; + box->setChildNeedsLayout(true, false); + box = box->containingBlock(); + ASSERT(box); + if (!box) + break; + } + } + } + } + + int beforeEdge = borderBefore() + paddingBefore(); + int afterEdge = borderAfter() + paddingAfter() + scrollbarLogicalHeight(); + + setLogicalHeight(beforeEdge); + + // The margin struct caches all our current margin collapsing state. The compact struct caches state when we encounter compacts, + MarginInfo marginInfo(this, beforeEdge, afterEdge); + + // Fieldsets need to find their legend and position it inside the border of the object. + // The legend then gets skipped during normal layout. The same is true for ruby text. + // It doesn't get included in the normal layout process but is instead skipped. + RenderObject* childToExclude = layoutSpecialExcludedChild(relayoutChildren); + + int previousFloatLogicalBottom = 0; + maxFloatLogicalBottom = 0; + + RenderBox* next = firstChildBox(); + + while (next) { + RenderBox* child = next; + next = child->nextSiblingBox(); + + if (childToExclude == child) + continue; // Skip this child, since it will be positioned by the specialized subclass (fieldsets and ruby runs). + + // Make sure we layout children if they need it. + // FIXME: Technically percentage height objects only need a relayout if their percentage isn't going to be turned into + // an auto value. Add a method to determine this, so that we can avoid the relayout. + if (relayoutChildren || ((child->style()->logicalHeight().isPercent() || child->style()->logicalMinHeight().isPercent() || child->style()->logicalMaxHeight().isPercent()) && !isRenderView())) + child->setChildNeedsLayout(true, false); + + // If relayoutChildren is set and the child has percentage padding, we also need to invalidate the child's pref widths. + if (relayoutChildren && (child->style()->paddingStart().isPercent() || child->style()->paddingEnd().isPercent())) + child->setPreferredLogicalWidthsDirty(true, false); + + // Handle the four types of special elements first. These include positioned content, floating content, compacts and + // run-ins. When we encounter these four types of objects, we don't actually lay them out as normal flow blocks. + if (handleSpecialChild(child, marginInfo)) + continue; + + // Lay out the child. + layoutBlockChild(child, marginInfo, previousFloatLogicalBottom, maxFloatLogicalBottom); + } + + // Now do the handling of the bottom of the block, adding in our bottom border/padding and + // determining the correct collapsed bottom margin information. + handleAfterSideOfBlock(beforeEdge, afterEdge, marginInfo); +} + +void RenderBlock::layoutBlockChild(RenderBox* child, MarginInfo& marginInfo, int& previousFloatLogicalBottom, int& maxFloatLogicalBottom) +{ + int oldPosMarginBefore = maxPositiveMarginBefore(); + int oldNegMarginBefore = maxNegativeMarginBefore(); + + // The child is a normal flow object. Compute the margins we will use for collapsing now. + child->computeBlockDirectionMargins(this); + + // Do not allow a collapse if the margin-before-collapse style is set to SEPARATE. + if (child->style()->marginBeforeCollapse() == MSEPARATE) { + marginInfo.setAtBeforeSideOfBlock(false); + marginInfo.clearMargin(); + } + + // Try to guess our correct logical top position. In most cases this guess will + // be correct. Only if we're wrong (when we compute the real logical top position) + // will we have to potentially relayout. + int logicalTopEstimate = estimateLogicalTopPosition(child, marginInfo); + + // Cache our old rect so that we can dirty the proper repaint rects if the child moves. + IntRect oldRect(child->x(), child->y() , child->width(), child->height()); + int oldLogicalTop = logicalTopForChild(child); + +#ifndef NDEBUG + IntSize oldLayoutDelta = view()->layoutDelta(); +#endif + // Go ahead and position the child as though it didn't collapse with the top. + setLogicalTopForChild(child, logicalTopEstimate, ApplyLayoutDelta); + + RenderBlock* childRenderBlock = child->isRenderBlock() ? toRenderBlock(child) : 0; + bool markDescendantsWithFloats = false; + if (logicalTopEstimate != oldLogicalTop && !child->avoidsFloats() && childRenderBlock && childRenderBlock->containsFloats()) + markDescendantsWithFloats = true; + else if (!child->avoidsFloats() || child->shrinkToAvoidFloats()) { + // If an element might be affected by the presence of floats, then always mark it for + // layout. + int fb = max(previousFloatLogicalBottom, lowestFloatLogicalBottom()); + if (fb > logicalTopEstimate) + markDescendantsWithFloats = true; + } + + if (childRenderBlock) { + if (markDescendantsWithFloats) + childRenderBlock->markAllDescendantsWithFloatsForLayout(); + if (!child->isWritingModeRoot()) + previousFloatLogicalBottom = max(previousFloatLogicalBottom, oldLogicalTop + childRenderBlock->lowestFloatLogicalBottom()); + } + + if (!child->needsLayout()) + child->markForPaginationRelayoutIfNeeded(); + + bool childHadLayout = child->m_everHadLayout; + bool childNeededLayout = child->needsLayout(); + if (childNeededLayout) + child->layout(); + + // Cache if we are at the top of the block right now. + bool atBeforeSideOfBlock = marginInfo.atBeforeSideOfBlock(); + + // Now determine the correct ypos based off examination of collapsing margin + // values. + int logicalTopBeforeClear = collapseMargins(child, marginInfo); + + // Now check for clear. + int logicalTopAfterClear = clearFloatsIfNeeded(child, marginInfo, oldPosMarginBefore, oldNegMarginBefore, logicalTopBeforeClear); + + bool paginated = view()->layoutState()->isPaginated(); + if (paginated) { + int oldTop = logicalTopAfterClear; + + // If the object has a page or column break value of "before", then we should shift to the top of the next page. + logicalTopAfterClear = applyBeforeBreak(child, logicalTopAfterClear); + + // For replaced elements and scrolled elements, we want to shift them to the next page if they don't fit on the current one. + int logicalTopBeforeUnsplittableAdjustment = logicalTopAfterClear; + int logicalTopAfterUnsplittableAdjustment = adjustForUnsplittableChild(child, logicalTopAfterClear); + + int paginationStrut = 0; + int unsplittableAdjustmentDelta = logicalTopAfterUnsplittableAdjustment - logicalTopBeforeUnsplittableAdjustment; + if (unsplittableAdjustmentDelta) + paginationStrut = unsplittableAdjustmentDelta; + else if (childRenderBlock && childRenderBlock->paginationStrut()) + paginationStrut = childRenderBlock->paginationStrut(); + + if (paginationStrut) { + // We are willing to propagate out to our parent block as long as we were at the top of the block prior + // to collapsing our margins, and as long as we didn't clear or move as a result of other pagination. + if (atBeforeSideOfBlock && oldTop == logicalTopBeforeClear && !isPositioned() && !isTableCell()) { + // FIXME: Should really check if we're exceeding the page height before propagating the strut, but we don't + // have all the information to do so (the strut only has the remaining amount to push). Gecko gets this wrong too + // and pushes to the next page anyway, so not too concerned about it. + setPaginationStrut(logicalTopAfterClear + paginationStrut); + if (childRenderBlock) + childRenderBlock->setPaginationStrut(0); + } else + logicalTopAfterClear += paginationStrut; + } + + // Similar to how we apply clearance. Go ahead and boost height() to be the place where we're going to position the child. + setLogicalHeight(logicalHeight() + (logicalTopAfterClear - oldTop)); + } + + setLogicalTopForChild(child, logicalTopAfterClear, ApplyLayoutDelta); + + // Now we have a final top position. See if it really does end up being different from our estimate. + if (logicalTopAfterClear != logicalTopEstimate) { + if (child->shrinkToAvoidFloats()) { + // The child's width depends on the line width. + // When the child shifts to clear an item, its width can + // change (because it has more available line width). + // So go ahead and mark the item as dirty. + child->setChildNeedsLayout(true, false); + } + if (childRenderBlock) { + if (!child->avoidsFloats() && childRenderBlock->containsFloats()) + childRenderBlock->markAllDescendantsWithFloatsForLayout(); + if (!child->needsLayout()) + child->markForPaginationRelayoutIfNeeded(); + } + + // Our guess was wrong. Make the child lay itself out again. + child->layoutIfNeeded(); + } + + // We are no longer at the top of the block if we encounter a non-empty child. + // This has to be done after checking for clear, so that margins can be reset if a clear occurred. + if (marginInfo.atBeforeSideOfBlock() && !child->isSelfCollapsingBlock()) + marginInfo.setAtBeforeSideOfBlock(false); + + // Now place the child in the correct left position + determineLogicalLeftPositionForChild(child); + + // Update our height now that the child has been placed in the correct position. + setLogicalHeight(logicalHeight() + logicalHeightForChild(child)); + if (child->style()->marginAfterCollapse() == MSEPARATE) { + setLogicalHeight(logicalHeight() + marginAfterForChild(child)); + marginInfo.clearMargin(); + } + // If the child has overhanging floats that intrude into following siblings (or possibly out + // of this block), then the parent gets notified of the floats now. + if (childRenderBlock && childRenderBlock->containsFloats()) + maxFloatLogicalBottom = max(maxFloatLogicalBottom, addOverhangingFloats(toRenderBlock(child), -child->logicalLeft(), -child->logicalTop(), !childNeededLayout)); + + IntSize childOffset(child->x() - oldRect.x(), child->y() - oldRect.y()); + if (childOffset.width() || childOffset.height()) { + view()->addLayoutDelta(childOffset); + + // If the child moved, we have to repaint it as well as any floating/positioned + // descendants. An exception is if we need a layout. In this case, we know we're going to + // repaint ourselves (and the child) anyway. + if (childHadLayout && !selfNeedsLayout() && child->checkForRepaintDuringLayout()) + child->repaintDuringLayoutIfMoved(oldRect); + } + + if (!childHadLayout && child->checkForRepaintDuringLayout()) { + child->repaint(); + child->repaintOverhangingFloats(true); + } + + if (paginated) { + // Check for an after page/column break. + int newHeight = applyAfterBreak(child, height(), marginInfo); + if (newHeight != height()) + setLogicalHeight(newHeight); + } + + ASSERT(oldLayoutDelta == view()->layoutDelta()); +} + +bool RenderBlock::layoutOnlyPositionedObjects() +{ + if (!posChildNeedsLayout() || normalChildNeedsLayout() || selfNeedsLayout()) + return false; + + LayoutStateMaintainer statePusher(view(), this, IntSize(x(), y()), hasColumns() || hasTransform() || hasReflection() || style()->isFlippedBlocksWritingMode()); + + if (needsPositionedMovementLayout()) { + tryLayoutDoingPositionedMovementOnly(); + if (needsLayout()) + return false; + } + + // All we have to is lay out our positioned objects. + layoutPositionedObjects(false); + + // Recompute our overflow information. + // FIXME: We could do better here by computing a temporary overflow object from layoutPositionedObjects and only + // updating our overflow if we either used to have overflow or if the new temporary object has overflow. + // For now just always recompute overflow. This is no worse performance-wise than the old code that called rightmostPosition and + // lowestPosition on every relayout so it's not a regression. + m_overflow.clear(); + computeOverflow(clientLogicalBottom(), true); + + statePusher.pop(); + + updateLayerTransform(); + + updateScrollInfoAfterLayout(); + +#ifdef ANDROID_FIX + // iframe flatten will call FrameView::layout() which calls performPostLayoutTasks, + // which may make us need to layout again + if (!posChildNeedsLayout() || normalChildNeedsLayout() || selfNeedsLayout()) + return false; +#endif + + setNeedsLayout(false); + return true; +} + +void RenderBlock::layoutPositionedObjects(bool relayoutChildren) +{ + if (!m_positionedObjects) + return; + + if (hasColumns()) + view()->layoutState()->clearPaginationInformation(); // Positioned objects are not part of the column flow, so they don't paginate with the columns. + + RenderBox* r; + Iterator end = m_positionedObjects->end(); + for (Iterator it = m_positionedObjects->begin(); it != end; ++it) { + r = *it; + // When a non-positioned block element moves, it may have positioned children that are implicitly positioned relative to the + // non-positioned block. Rather than trying to detect all of these movement cases, we just always lay out positioned + // objects that are positioned implicitly like this. Such objects are rare, and so in typical DHTML menu usage (where everything is + // positioned explicitly) this should not incur a performance penalty. + if (relayoutChildren || (r->style()->hasStaticY() && r->parent() != this && r->parent()->isBlockFlow())) + r->setChildNeedsLayout(true, false); + + // If relayoutChildren is set and we have percentage padding, we also need to invalidate the child's pref widths. + if (relayoutChildren && (r->style()->paddingStart().isPercent() || r->style()->paddingEnd().isPercent())) + r->setPreferredLogicalWidthsDirty(true, false); + + if (!r->needsLayout()) + r->markForPaginationRelayoutIfNeeded(); + + // We don't have to do a full layout. We just have to update our position. Try that first. If we have shrink-to-fit width + // and we hit the available width constraint, the layoutIfNeeded() will catch it and do a full layout. + if (r->needsPositionedMovementLayoutOnly()) + r->tryLayoutDoingPositionedMovementOnly(); + r->layoutIfNeeded(); + } + + if (hasColumns()) + view()->layoutState()->m_columnInfo = columnInfo(); // FIXME: Kind of gross. We just put this back into the layout state so that pop() will work. +} + +void RenderBlock::markPositionedObjectsForLayout() +{ + if (m_positionedObjects) { + RenderBox* r; + Iterator end = m_positionedObjects->end(); + for (Iterator it = m_positionedObjects->begin(); it != end; ++it) { + r = *it; + r->setChildNeedsLayout(true); + } + } +} + +void RenderBlock::markForPaginationRelayoutIfNeeded() +{ + ASSERT(!needsLayout()); + if (needsLayout()) + return; + + if (view()->layoutState()->pageLogicalHeightChanged() || (view()->layoutState()->pageLogicalHeight() && view()->layoutState()->pageLogicalOffset(y()) != pageLogicalOffset())) + setChildNeedsLayout(true, false); +} + +void RenderBlock::repaintOverhangingFloats(bool paintAllDescendants) +{ + // Repaint any overhanging floats (if we know we're the one to paint them). + if (hasOverhangingFloats()) { + // We think that we must be in a bad state if m_floatingObjects is nil at this point, so + // we assert on Debug builds and nil-check Release builds. + ASSERT(m_floatingObjects); + if (!m_floatingObjects) + return; + + FloatingObject* r; + DeprecatedPtrListIterator<FloatingObject> it(*m_floatingObjects); + + // FIXME: Avoid disabling LayoutState. At the very least, don't disable it for floats originating + // in this block. Better yet would be to push extra state for the containers of other floats. + view()->disableLayoutState(); + for ( ; (r = it.current()); ++it) { + // Only repaint the object if it is overhanging, is not in its own layer, and + // is our responsibility to paint (m_shouldPaint is set). When paintAllDescendants is true, the latter + // condition is replaced with being a descendant of us. + if (logicalBottomForFloat(r) > logicalHeight() && ((paintAllDescendants && r->m_renderer->isDescendantOf(this)) || r->m_shouldPaint) && !r->m_renderer->hasSelfPaintingLayer()) { + r->m_renderer->repaint(); + r->m_renderer->repaintOverhangingFloats(); + } + } + view()->enableLayoutState(); + } +} + +void RenderBlock::paint(PaintInfo& paintInfo, int tx, int ty) +{ + tx += x(); + ty += y(); + + PaintPhase phase = paintInfo.phase; + + // Check if we need to do anything at all. + // FIXME: Could eliminate the isRoot() check if we fix background painting so that the RenderView + // paints the root's background. + if (!isRoot()) { + IntRect overflowBox = visualOverflowRect(); + 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, phase, tx, ty); + + // Our scrollbar widgets paint exactly when we tell them to, so that they work properly with + // z-index. We paint after we painted the background/border, so that the scrollbars will + // sit above the background/border. + if (hasOverflowClip() && style()->visibility() == VISIBLE && (phase == PaintPhaseBlockBackground || phase == PaintPhaseChildBlockBackground) && paintInfo.shouldPaintWithinRoot(this)) + layer()->paintOverflowControls(paintInfo.context, tx, ty, paintInfo.rect); +} + +void RenderBlock::paintColumnRules(PaintInfo& paintInfo, int tx, int ty) +{ + const Color& ruleColor = style()->visitedDependentColor(CSSPropertyWebkitColumnRuleColor); + bool ruleTransparent = style()->columnRuleIsTransparent(); + EBorderStyle ruleStyle = style()->columnRuleStyle(); + int ruleWidth = style()->columnRuleWidth(); + int colGap = columnGap(); + bool renderRule = ruleStyle > BHIDDEN && !ruleTransparent && ruleWidth <= colGap; + if (!renderRule) + return; + + // We need to do multiple passes, breaking up our child painting into strips. + ColumnInfo* colInfo = columnInfo(); + unsigned colCount = columnCount(colInfo); + int currXOffset = style()->isLeftToRightDirection() ? 0 : contentWidth(); + int ruleAdd = borderLeft() + paddingLeft(); + int ruleX = style()->isLeftToRightDirection() ? 0 : contentWidth(); + for (unsigned i = 0; i < colCount; i++) { + IntRect colRect = columnRectAt(colInfo, i); + + // Move to the next position. + if (style()->isLeftToRightDirection()) { + ruleX += colRect.width() + colGap / 2; + currXOffset += colRect.width() + colGap; + } else { + ruleX -= (colRect.width() + colGap / 2); + currXOffset -= (colRect.width() + colGap); + } + + // Now paint the column rule. + if (i < colCount - 1) { + int ruleStart = tx + ruleX - ruleWidth / 2 + ruleAdd; + int ruleEnd = ruleStart + ruleWidth; + int ruleTop = ty + borderTop() + paddingTop(); + int ruleBottom = ruleTop + contentHeight(); + drawLineForBoxSide(paintInfo.context, ruleStart, ruleTop, ruleEnd, ruleBottom, + style()->isLeftToRightDirection() ? BSLeft : BSRight, ruleColor, ruleStyle, 0, 0); + } + + ruleX = currXOffset; + } +} + +void RenderBlock::paintColumnContents(PaintInfo& paintInfo, int tx, int ty, bool paintingFloats) +{ + // We need to do multiple passes, breaking up our child painting into strips. + GraphicsContext* context = paintInfo.context; + int colGap = columnGap(); + ColumnInfo* colInfo = columnInfo(); + unsigned colCount = columnCount(colInfo); + if (!colCount) + return; + int currXOffset = style()->isLeftToRightDirection() ? 0 : contentWidth() - columnRectAt(colInfo, 0).width(); + int currYOffset = 0; + for (unsigned i = 0; i < colCount; i++) { + // For each rect, we clip to the rect, and then we adjust our coords. + IntRect colRect = columnRectAt(colInfo, i); + colRect.move(tx, ty); + PaintInfo info(paintInfo); + info.rect.intersect(colRect); + + if (!info.rect.isEmpty()) { + context->save(); + + // Each strip pushes a clip, since column boxes are specified as being + // like overflow:hidden. + context->clip(colRect); + + // Adjust our x and y when painting. + int finalX = tx + currXOffset; + int finalY = ty + currYOffset; + if (paintingFloats) + paintFloats(info, finalX, finalY, paintInfo.phase == PaintPhaseSelection || paintInfo.phase == PaintPhaseTextClip); + else + paintContents(info, finalX, finalY); + + context->restore(); + } + + // Move to the next position. + if (style()->isLeftToRightDirection()) + currXOffset += colRect.width() + colGap; + else + currXOffset -= (colRect.width() + colGap); + + currYOffset -= colRect.height(); + } +} + +void RenderBlock::paintContents(PaintInfo& paintInfo, int tx, int ty) +{ + // Avoid painting descendants of the root element when stylesheets haven't loaded. This eliminates FOUC. + // It's ok not to draw, because later on, when all the stylesheets do load, updateStyleSelector on the Document + // will do a full repaint(). + if (document()->mayCauseFlashOfUnstyledContent() && !isRenderView()) + return; + + if (childrenInline()) + m_lineBoxes.paint(this, paintInfo, tx, ty); + else + paintChildren(paintInfo, tx, ty); +} + +void RenderBlock::paintChildren(PaintInfo& paintInfo, int tx, int ty) +{ + PaintPhase newPhase = (paintInfo.phase == PaintPhaseChildOutlines) ? PaintPhaseOutline : paintInfo.phase; + newPhase = (newPhase == PaintPhaseChildBlockBackgrounds) ? PaintPhaseChildBlockBackground : newPhase; + + // We don't paint our own background, but we do let the kids paint their backgrounds. + PaintInfo info(paintInfo); + info.phase = newPhase; + info.updatePaintingRootForChildren(this); + + // FIXME: Paint-time pagination is obsolete and is now only used by embedded WebViews inside AppKit + // NSViews. Do not add any more code for this. + RenderView* renderView = view(); + bool usePrintRect = !renderView->printRect().isEmpty(); + + for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { + // Check for page-break-before: always, and if it's set, break and bail. + bool checkBeforeAlways = !childrenInline() && (usePrintRect && child->style()->pageBreakBefore() == PBALWAYS); + if (checkBeforeAlways + && (ty + child->y()) > paintInfo.rect.y() + && (ty + child->y()) < paintInfo.rect.bottom()) { + view()->setBestTruncatedAt(ty + child->y(), this, true); + return; + } + + if (!child->isFloating() && child->isReplaced() && usePrintRect && child->height() <= renderView->printRect().height()) { + // Paginate block-level replaced elements. + if (ty + child->y() + child->height() > renderView->printRect().bottom()) { + if (ty + child->y() < renderView->truncatedAt()) + renderView->setBestTruncatedAt(ty + child->y(), child); + // If we were able to truncate, don't paint. + if (ty + child->y() >= renderView->truncatedAt()) + break; + } + } + + IntPoint childPoint = flipForWritingMode(child, IntPoint(tx, ty), ParentToChildFlippingAdjustment); + if (!child->hasSelfPaintingLayer() && !child->isFloating()) + child->paint(info, childPoint.x(), childPoint.y()); + + // Check for page-break-after: always, and if it's set, break and bail. + bool checkAfterAlways = !childrenInline() && (usePrintRect && child->style()->pageBreakAfter() == PBALWAYS); + if (checkAfterAlways + && (ty + child->y() + child->height()) > paintInfo.rect.y() + && (ty + child->y() + child->height()) < paintInfo.rect.bottom()) { + view()->setBestTruncatedAt(ty + child->y() + child->height() + max(0, child->collapsedMarginAfter()), this, true); + return; + } + } +} + +void RenderBlock::paintCaret(PaintInfo& paintInfo, int tx, int ty, CaretType type) +{ + SelectionController* selection = type == CursorCaret ? frame()->selection() : frame()->page()->dragCaretController(); + + // Paint the caret if the SelectionController says so or if caret browsing is enabled + bool caretBrowsing = frame()->settings() && frame()->settings()->caretBrowsingEnabled(); + RenderObject* caretPainter = selection->caretRenderer(); + if (caretPainter == this && (selection->isContentEditable() || caretBrowsing)) { + // Convert the painting offset into the local coordinate system of this renderer, + // to match the localCaretRect computed by the SelectionController + offsetForContents(tx, ty); + + if (type == CursorCaret) + frame()->selection()->paintCaret(paintInfo.context, tx, ty, paintInfo.rect); + else + frame()->selection()->paintDragCaret(paintInfo.context, tx, ty, paintInfo.rect); + } +} + +void RenderBlock::paintObject(PaintInfo& paintInfo, int tx, int ty) +{ + PaintPhase paintPhase = paintInfo.phase; + + // 1. paint background, borders etc + if ((paintPhase == PaintPhaseBlockBackground || paintPhase == PaintPhaseChildBlockBackground) && style()->visibility() == VISIBLE) { + if (hasBoxDecorations()) + paintBoxDecorations(paintInfo, tx, ty); + if (hasColumns()) + paintColumnRules(paintInfo, tx, ty); + } + + if (paintPhase == PaintPhaseMask && style()->visibility() == VISIBLE) { + paintMask(paintInfo, tx, ty); + return; + } + + // We're done. We don't bother painting any children. + if (paintPhase == PaintPhaseBlockBackground) + return; + + // Adjust our painting position if we're inside a scrolled layer (e.g., an overflow:auto div). + int scrolledX = tx; + int scrolledY = ty; + if (hasOverflowClip()) { + IntSize offset = layer()->scrolledContentOffset(); + scrolledX -= offset.width(); + scrolledY -= offset.height(); + } + + // 2. paint contents + if (paintPhase != PaintPhaseSelfOutline) { + if (hasColumns()) + paintColumnContents(paintInfo, scrolledX, scrolledY); + else + paintContents(paintInfo, scrolledX, scrolledY); + } + + // 3. paint selection + // FIXME: Make this work with multi column layouts. For now don't fill gaps. + bool isPrinting = document()->printing(); + if (!isPrinting && !hasColumns()) + paintSelection(paintInfo, scrolledX, scrolledY); // Fill in gaps in selection on lines and between blocks. + + // 4. paint floats. + if (paintPhase == PaintPhaseFloat || paintPhase == PaintPhaseSelection || paintPhase == PaintPhaseTextClip) { + if (hasColumns()) + paintColumnContents(paintInfo, scrolledX, scrolledY, true); + else + paintFloats(paintInfo, scrolledX, scrolledY, paintPhase == PaintPhaseSelection || paintPhase == PaintPhaseTextClip); + } + + // 5. paint outline. + if ((paintPhase == PaintPhaseOutline || paintPhase == PaintPhaseSelfOutline) && hasOutline() && style()->visibility() == VISIBLE) + paintOutline(paintInfo.context, tx, ty, width(), height()); + + // 6. paint continuation outlines. + if ((paintPhase == PaintPhaseOutline || paintPhase == PaintPhaseChildOutlines)) { + RenderInline* inlineCont = inlineElementContinuation(); + if (inlineCont && inlineCont->hasOutline() && inlineCont->style()->visibility() == VISIBLE) { + RenderInline* inlineRenderer = toRenderInline(inlineCont->node()->renderer()); + RenderBlock* cb = containingBlock(); + + bool inlineEnclosedInSelfPaintingLayer = false; + for (RenderBoxModelObject* box = inlineRenderer; box != cb; box = box->parent()->enclosingBoxModelObject()) { + if (box->hasSelfPaintingLayer()) { + inlineEnclosedInSelfPaintingLayer = true; + break; + } + } + + if (!inlineEnclosedInSelfPaintingLayer) + cb->addContinuationWithOutline(inlineRenderer); + else if (!inlineRenderer->firstLineBox()) + inlineRenderer->paintOutline(paintInfo.context, tx - x() + inlineRenderer->containingBlock()->x(), + ty - y() + inlineRenderer->containingBlock()->y()); + } + paintContinuationOutlines(paintInfo, tx, ty); + } + + // 7. paint caret. + // If the caret's node's render object's containing block is this block, and the paint action is PaintPhaseForeground, + // then paint the caret. + if (paintPhase == PaintPhaseForeground) { + paintCaret(paintInfo, scrolledX, scrolledY, CursorCaret); + paintCaret(paintInfo, scrolledX, scrolledY, DragCaret); + } +} + +void RenderBlock::paintFloats(PaintInfo& paintInfo, int tx, int ty, bool preservePhase) +{ + if (!m_floatingObjects) + return; + + FloatingObject* r; + DeprecatedPtrListIterator<FloatingObject> it(*m_floatingObjects); + for (; (r = it.current()); ++it) { + // Only paint the object if our m_shouldPaint flag is set. + if (r->m_shouldPaint && !r->m_renderer->hasSelfPaintingLayer()) { + PaintInfo currentPaintInfo(paintInfo); + currentPaintInfo.phase = preservePhase ? paintInfo.phase : PaintPhaseBlockBackground; + IntPoint childPoint = flipForWritingMode(r->m_renderer, IntPoint(tx + r->left() + r->m_renderer->marginLeft() - r->m_renderer->x(), ty + r->top() + r->m_renderer->marginTop() - r->m_renderer->y()), ParentToChildFlippingAdjustment); + r->m_renderer->paint(currentPaintInfo, childPoint.x(), childPoint.y()); + if (!preservePhase) { + currentPaintInfo.phase = PaintPhaseChildBlockBackgrounds; + r->m_renderer->paint(currentPaintInfo, childPoint.x(), childPoint.y()); + currentPaintInfo.phase = PaintPhaseFloat; + r->m_renderer->paint(currentPaintInfo, childPoint.x(), childPoint.y()); + currentPaintInfo.phase = PaintPhaseForeground; + r->m_renderer->paint(currentPaintInfo, childPoint.x(), childPoint.y()); + currentPaintInfo.phase = PaintPhaseOutline; + r->m_renderer->paint(currentPaintInfo, childPoint.x(), childPoint.y()); + } + } + } +} + +void RenderBlock::paintEllipsisBoxes(PaintInfo& paintInfo, int tx, int ty) +{ + if (!paintInfo.shouldPaintWithinRoot(this) || !firstLineBox()) + return; + + if (style()->visibility() == VISIBLE && paintInfo.phase == PaintPhaseForeground) { + // We can check the first box and last box and avoid painting if we don't + // intersect. + int yPos = ty + firstLineBox()->y(); + int h = lastLineBox()->y() + lastLineBox()->logicalHeight() - firstLineBox()->y(); + if (yPos >= paintInfo.rect.bottom() || yPos + h <= paintInfo.rect.y()) + return; + + // See if our boxes intersect with the dirty rect. If so, then we paint + // them. Note that boxes can easily overlap, so we can't make any assumptions + // based off positions of our first line box or our last line box. + for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) { + yPos = ty + curr->y(); + h = curr->logicalHeight(); + if (curr->ellipsisBox() && yPos < paintInfo.rect.bottom() && yPos + h > paintInfo.rect.y()) + curr->paintEllipsisBox(paintInfo, tx, ty); + } + } +} + +RenderInline* RenderBlock::inlineElementContinuation() const +{ + RenderBoxModelObject* continuation = this->continuation(); + return continuation && continuation->isInline() ? toRenderInline(continuation) : 0; +} + +RenderBlock* RenderBlock::blockElementContinuation() const +{ + RenderBoxModelObject* currentContinuation = continuation(); + if (!currentContinuation || currentContinuation->isInline()) + return 0; + RenderBlock* nextContinuation = toRenderBlock(currentContinuation); + if (nextContinuation->isAnonymousBlock()) + return nextContinuation->blockElementContinuation(); + return nextContinuation; +} + +static ContinuationOutlineTableMap* continuationOutlineTable() +{ + DEFINE_STATIC_LOCAL(ContinuationOutlineTableMap, table, ()); + return &table; +} + +void RenderBlock::addContinuationWithOutline(RenderInline* flow) +{ + // We can't make this work if the inline is in a layer. We'll just rely on the broken + // way of painting. + ASSERT(!flow->layer() && !flow->isInlineElementContinuation()); + + ContinuationOutlineTableMap* table = continuationOutlineTable(); + ListHashSet<RenderInline*>* continuations = table->get(this); + if (!continuations) { + continuations = new ListHashSet<RenderInline*>; + table->set(this, continuations); + } + + continuations->add(flow); +} + +void RenderBlock::paintContinuationOutlines(PaintInfo& info, int tx, int ty) +{ + ContinuationOutlineTableMap* table = continuationOutlineTable(); + if (table->isEmpty()) + return; + + ListHashSet<RenderInline*>* continuations = table->get(this); + if (!continuations) + return; + + // Paint each continuation outline. + ListHashSet<RenderInline*>::iterator end = continuations->end(); + for (ListHashSet<RenderInline*>::iterator it = continuations->begin(); it != end; ++it) { + // Need to add in the coordinates of the intervening blocks. + RenderInline* flow = *it; + RenderBlock* block = flow->containingBlock(); + for ( ; block && block != this; block = block->containingBlock()) { + tx += block->x(); + ty += block->y(); + } + ASSERT(block); + flow->paintOutline(info.context, tx, ty); + } + + // Delete + delete continuations; + table->remove(this); +} + +bool RenderBlock::shouldPaintSelectionGaps() const +{ + return selectionState() != SelectionNone && style()->visibility() == VISIBLE && isSelectionRoot(); +} + +bool RenderBlock::isSelectionRoot() const +{ + if (!node()) + return false; + + // FIXME: Eventually tables should have to learn how to fill gaps between cells, at least in simple non-spanning cases. + if (isTable()) + return false; + + if (isBody() || isRoot() || hasOverflowClip() || isRelPositioned() || + isFloatingOrPositioned() || isTableCell() || isInlineBlockOrInlineTable() || hasTransform() || + hasReflection() || hasMask() || isWritingModeRoot()) + return true; + + if (view() && view()->selectionStart()) { + Node* startElement = view()->selectionStart()->node(); + if (startElement && startElement->rootEditableElement() == node()) + return true; + } + + return false; +} + +GapRects RenderBlock::selectionGapRectsForRepaint(RenderBoxModelObject* repaintContainer) +{ + ASSERT(!needsLayout()); + + if (!shouldPaintSelectionGaps()) + return GapRects(); + + // FIXME: this is broken with transforms + TransformState transformState(TransformState::ApplyTransformDirection, FloatPoint()); + mapLocalToContainer(repaintContainer, false, false, transformState); + IntPoint offsetFromRepaintContainer = roundedIntPoint(transformState.mappedPoint()); + + if (hasOverflowClip()) + offsetFromRepaintContainer -= layer()->scrolledContentOffset(); + + int lastTop = 0; + int lastLeft = logicalLeftSelectionOffset(this, lastTop); + int lastRight = logicalRightSelectionOffset(this, lastTop); + + return selectionGaps(this, offsetFromRepaintContainer, IntSize(), lastTop, lastLeft, lastRight); +} + +void RenderBlock::paintSelection(PaintInfo& paintInfo, int tx, int ty) +{ + if (shouldPaintSelectionGaps() && paintInfo.phase == PaintPhaseForeground) { + int lastTop = 0; + int lastLeft = logicalLeftSelectionOffset(this, lastTop); + int lastRight = logicalRightSelectionOffset(this, lastTop); + paintInfo.context->save(); + IntRect gapRectsBounds = selectionGaps(this, IntPoint(tx, ty), IntSize(), lastTop, lastLeft, lastRight, &paintInfo); + if (!gapRectsBounds.isEmpty()) { + if (RenderLayer* layer = enclosingLayer()) { + gapRectsBounds.move(IntSize(-tx, -ty)); + if (!hasLayer()) { + IntRect localBounds(gapRectsBounds); + flipForWritingMode(localBounds); + gapRectsBounds = localToContainerQuad(FloatRect(localBounds), layer->renderer()).enclosingBoundingBox(); + gapRectsBounds.move(layer->scrolledContentOffset()); + } + layer->addBlockSelectionGapsBounds(gapRectsBounds); + } + } + paintInfo.context->restore(); + } +} + +static void clipOutPositionedObjects(const PaintInfo* paintInfo, const IntPoint& offset, RenderBlock::PositionedObjectsListHashSet* positionedObjects) +{ + if (!positionedObjects) + return; + + RenderBlock::PositionedObjectsListHashSet::const_iterator end = positionedObjects->end(); + for (RenderBlock::PositionedObjectsListHashSet::const_iterator it = positionedObjects->begin(); it != end; ++it) { + RenderBox* r = *it; + paintInfo->context->clipOut(IntRect(offset.x() + r->x(), offset.y() + r->y(), r->width(), r->height())); + } +} + +static int blockDirectionOffset(RenderBlock* rootBlock, const IntSize& offsetFromRootBlock) +{ + return rootBlock->style()->isHorizontalWritingMode() ? offsetFromRootBlock.height() : offsetFromRootBlock.width(); +} + +static int inlineDirectionOffset(RenderBlock* rootBlock, const IntSize& offsetFromRootBlock) +{ + return rootBlock->style()->isHorizontalWritingMode() ? offsetFromRootBlock.width() : offsetFromRootBlock.height(); +} + +IntRect RenderBlock::logicalRectToPhysicalRect(const IntPoint& rootBlockPhysicalPosition, const IntRect& logicalRect) +{ + IntRect result; + if (style()->isHorizontalWritingMode()) + result = logicalRect; + else + result = IntRect(logicalRect.y(), logicalRect.x(), logicalRect.height(), logicalRect.width()); + flipForWritingMode(result); + result.move(rootBlockPhysicalPosition.x(), rootBlockPhysicalPosition.y()); + return result; +} + +GapRects RenderBlock::selectionGaps(RenderBlock* rootBlock, const IntPoint& rootBlockPhysicalPosition, const IntSize& offsetFromRootBlock, + int& lastLogicalTop, int& lastLogicalLeft, int& lastLogicalRight, const PaintInfo* paintInfo) +{ + // IMPORTANT: Callers of this method that intend for painting to happen need to do a save/restore. + // Clip out floating and positioned objects when painting selection gaps. + if (paintInfo) { + // Note that we don't clip out overflow for positioned objects. We just stick to the border box. + IntRect flippedBlockRect = IntRect(offsetFromRootBlock.width(), offsetFromRootBlock.height(), width(), height()); + rootBlock->flipForWritingMode(flippedBlockRect); + flippedBlockRect.move(rootBlockPhysicalPosition.x(), rootBlockPhysicalPosition.y()); + clipOutPositionedObjects(paintInfo, flippedBlockRect.location(), m_positionedObjects); + if (isBody() || isRoot()) // The <body> must make sure to examine its containingBlock's positioned objects. + for (RenderBlock* cb = containingBlock(); cb && !cb->isRenderView(); cb = cb->containingBlock()) + clipOutPositionedObjects(paintInfo, IntPoint(cb->x(), cb->y()), cb->m_positionedObjects); // FIXME: Not right for flipped writing modes. + if (m_floatingObjects) { + for (DeprecatedPtrListIterator<FloatingObject> it(*m_floatingObjects); it.current(); ++it) { + FloatingObject* r = it.current(); + IntRect floatBox = IntRect(offsetFromRootBlock.width() + r->left() + r->m_renderer->marginLeft(), + offsetFromRootBlock.height() + r->top() + r->m_renderer->marginTop(), + r->m_renderer->width(), r->m_renderer->height()); + rootBlock->flipForWritingMode(floatBox); + floatBox.move(rootBlockPhysicalPosition.x(), rootBlockPhysicalPosition.y()); + paintInfo->context->clipOut(floatBox); + } + } + } + + // FIXME: overflow: auto/scroll regions need more math here, since painting in the border box is different from painting in the padding box (one is scrolled, the other is + // fixed). + GapRects result; + if (!isBlockFlow()) // FIXME: Make multi-column selection gap filling work someday. + return result; + + if (hasColumns() || hasTransform() || style()->columnSpan()) { + // FIXME: We should learn how to gap fill multiple columns and transforms eventually. + lastLogicalTop = blockDirectionOffset(rootBlock, offsetFromRootBlock) + logicalHeight(); + lastLogicalLeft = logicalLeftSelectionOffset(rootBlock, logicalHeight()); + lastLogicalRight = logicalRightSelectionOffset(rootBlock, logicalHeight()); + return result; + } + + if (childrenInline()) + result = inlineSelectionGaps(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, lastLogicalTop, lastLogicalLeft, lastLogicalRight, paintInfo); + else + result = blockSelectionGaps(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, lastLogicalTop, lastLogicalLeft, lastLogicalRight, paintInfo); + + // Go ahead and fill the vertical gap all the way to the bottom of our block if the selection extends past our block. + if (rootBlock == this && (selectionState() != SelectionBoth && selectionState() != SelectionEnd)) + result.uniteCenter(blockSelectionGap(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, lastLogicalTop, lastLogicalLeft, lastLogicalRight, + logicalHeight(), paintInfo)); + return result; +} + +GapRects RenderBlock::inlineSelectionGaps(RenderBlock* rootBlock, const IntPoint& rootBlockPhysicalPosition, const IntSize& offsetFromRootBlock, + int& lastLogicalTop, int& lastLogicalLeft, int& lastLogicalRight, const PaintInfo* paintInfo) +{ + GapRects result; + + bool containsStart = selectionState() == SelectionStart || selectionState() == SelectionBoth; + + if (!firstLineBox()) { + if (containsStart) { + // Go ahead and update our lastLogicalTop to be the bottom of the block. <hr>s or empty blocks with height can trip this + // case. + lastLogicalTop = blockDirectionOffset(rootBlock, offsetFromRootBlock) + logicalHeight(); + lastLogicalLeft = logicalLeftSelectionOffset(rootBlock, logicalHeight()); + lastLogicalRight = logicalRightSelectionOffset(rootBlock, logicalHeight()); + } + return result; + } + + RootInlineBox* lastSelectedLine = 0; + RootInlineBox* curr; + for (curr = firstRootBox(); curr && !curr->hasSelectedChildren(); curr = curr->nextRootBox()) { } + + // Now paint the gaps for the lines. + for (; curr && curr->hasSelectedChildren(); curr = curr->nextRootBox()) { + int selTop = curr->selectionTop(); + int selHeight = curr->selectionHeight(); + + if (!containsStart && !lastSelectedLine && + selectionState() != SelectionStart && selectionState() != SelectionBoth) + result.uniteCenter(blockSelectionGap(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, lastLogicalTop, lastLogicalLeft, lastLogicalRight, + selTop, paintInfo)); + + IntRect logicalRect(curr->logicalLeft(), selTop, curr->logicalWidth(), selTop + selHeight); + logicalRect.move(style()->isHorizontalWritingMode() ? offsetFromRootBlock : IntSize(offsetFromRootBlock.height(), offsetFromRootBlock.width())); + IntRect physicalRect = rootBlock->logicalRectToPhysicalRect(rootBlockPhysicalPosition, logicalRect); + if (!paintInfo || (style()->isHorizontalWritingMode() && physicalRect.y() < paintInfo->rect.bottom() && physicalRect.bottom() > paintInfo->rect.y()) + || (!style()->isHorizontalWritingMode() && physicalRect.x() < paintInfo->rect.right() && physicalRect.right() > paintInfo->rect.x())) + result.unite(curr->lineSelectionGap(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, selTop, selHeight, paintInfo)); + + lastSelectedLine = curr; + } + + if (containsStart && !lastSelectedLine) + // VisibleSelection must start just after our last line. + lastSelectedLine = lastRootBox(); + + if (lastSelectedLine && selectionState() != SelectionEnd && selectionState() != SelectionBoth) { + // Go ahead and update our lastY to be the bottom of the last selected line. + lastLogicalTop = blockDirectionOffset(rootBlock, offsetFromRootBlock) + lastSelectedLine->selectionBottom(); + lastLogicalLeft = logicalLeftSelectionOffset(rootBlock, lastSelectedLine->selectionBottom()); + lastLogicalRight = logicalRightSelectionOffset(rootBlock, lastSelectedLine->selectionBottom()); + } + return result; +} + +GapRects RenderBlock::blockSelectionGaps(RenderBlock* rootBlock, const IntPoint& rootBlockPhysicalPosition, const IntSize& offsetFromRootBlock, + int& lastLogicalTop, int& lastLogicalLeft, int& lastLogicalRight, const PaintInfo* paintInfo) +{ + GapRects result; + + // Go ahead and jump right to the first block child that contains some selected objects. + RenderBox* curr; + for (curr = firstChildBox(); curr && curr->selectionState() == SelectionNone; curr = curr->nextSiblingBox()) { } + + for (bool sawSelectionEnd = false; curr && !sawSelectionEnd; curr = curr->nextSiblingBox()) { + SelectionState childState = curr->selectionState(); + if (childState == SelectionBoth || childState == SelectionEnd) + sawSelectionEnd = true; + + if (curr->isFloatingOrPositioned()) + continue; // We must be a normal flow object in order to even be considered. + + if (curr->isRelPositioned() && curr->hasLayer()) { + // If the relposition offset is anything other than 0, then treat this just like an absolute positioned element. + // Just disregard it completely. + IntSize relOffset = curr->layer()->relativePositionOffset(); + if (relOffset.width() || relOffset.height()) + continue; + } + + bool paintsOwnSelection = curr->shouldPaintSelectionGaps() || curr->isTable(); // FIXME: Eventually we won't special-case table like this. + bool fillBlockGaps = paintsOwnSelection || (curr->canBeSelectionLeaf() && childState != SelectionNone); + if (fillBlockGaps) { + // We need to fill the vertical gap above this object. + if (childState == SelectionEnd || childState == SelectionInside) + // Fill the gap above the object. + result.uniteCenter(blockSelectionGap(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, lastLogicalTop, lastLogicalLeft, lastLogicalRight, + curr->logicalTop(), paintInfo)); + + // Only fill side gaps for objects that paint their own selection if we know for sure the selection is going to extend all the way *past* + // our object. We know this if the selection did not end inside our object. + if (paintsOwnSelection && (childState == SelectionStart || sawSelectionEnd)) + childState = SelectionNone; + + // Fill side gaps on this object based off its state. + bool leftGap, rightGap; + getSelectionGapInfo(childState, leftGap, rightGap); + + if (leftGap) + result.uniteLeft(logicalLeftSelectionGap(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, this, curr->logicalLeft(), curr->logicalTop(), curr->logicalHeight(), paintInfo)); + if (rightGap) + result.uniteRight(logicalRightSelectionGap(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, this, curr->logicalRight(), curr->logicalTop(), curr->logicalHeight(), paintInfo)); + + // Update lastLogicalTop to be just underneath the object. lastLogicalLeft and lastLogicalRight extend as far as + // they can without bumping into floating or positioned objects. Ideally they will go right up + // to the border of the root selection block. + lastLogicalTop = blockDirectionOffset(rootBlock, offsetFromRootBlock) + curr->logicalBottom(); + lastLogicalLeft = logicalLeftSelectionOffset(rootBlock, curr->logicalBottom()); + lastLogicalRight = logicalRightSelectionOffset(rootBlock, curr->logicalBottom()); + } else if (childState != SelectionNone) + // We must be a block that has some selected object inside it. Go ahead and recur. + result.unite(toRenderBlock(curr)->selectionGaps(rootBlock, rootBlockPhysicalPosition, IntSize(offsetFromRootBlock.width() + curr->x(), offsetFromRootBlock.height() + curr->y()), + lastLogicalTop, lastLogicalLeft, lastLogicalRight, paintInfo)); + } + return result; +} + +IntRect RenderBlock::blockSelectionGap(RenderBlock* rootBlock, const IntPoint& rootBlockPhysicalPosition, const IntSize& offsetFromRootBlock, + int lastLogicalTop, int lastLogicalLeft, int lastLogicalRight, int logicalBottom, const PaintInfo* paintInfo) +{ + int logicalTop = lastLogicalTop; + int logicalHeight = blockDirectionOffset(rootBlock, offsetFromRootBlock) + logicalBottom - logicalTop; + if (logicalHeight <= 0) + return IntRect(); + + // Get the selection offsets for the bottom of the gap + int logicalLeft = max(lastLogicalLeft, logicalLeftSelectionOffset(rootBlock, logicalBottom)); + int logicalRight = min(lastLogicalRight, logicalRightSelectionOffset(rootBlock, logicalBottom)); + int logicalWidth = logicalRight - logicalLeft; + if (logicalWidth <= 0) + return IntRect(); + + IntRect gapRect = rootBlock->logicalRectToPhysicalRect(rootBlockPhysicalPosition, IntRect(logicalLeft, logicalTop, logicalWidth, logicalHeight)); + if (paintInfo) + paintInfo->context->fillRect(gapRect, selectionBackgroundColor(), style()->colorSpace()); + return gapRect; +} + +IntRect RenderBlock::logicalLeftSelectionGap(RenderBlock* rootBlock, const IntPoint& rootBlockPhysicalPosition, const IntSize& offsetFromRootBlock, + RenderObject* selObj, int logicalLeft, int logicalTop, int logicalHeight, const PaintInfo* paintInfo) +{ + int rootBlockLogicalTop = blockDirectionOffset(rootBlock, offsetFromRootBlock) + logicalTop; + int rootBlockLogicalLeft = max(logicalLeftSelectionOffset(rootBlock, logicalTop), logicalLeftSelectionOffset(rootBlock, logicalTop + logicalHeight)); + int rootBlockLogicalRight = min(inlineDirectionOffset(rootBlock, offsetFromRootBlock) + logicalLeft, min(logicalRightSelectionOffset(rootBlock, logicalTop), logicalRightSelectionOffset(rootBlock, logicalTop + logicalHeight))); + int rootBlockLogicalWidth = rootBlockLogicalRight - rootBlockLogicalLeft; + if (rootBlockLogicalWidth <= 0) + return IntRect(); + + IntRect gapRect = rootBlock->logicalRectToPhysicalRect(rootBlockPhysicalPosition, IntRect(rootBlockLogicalLeft, rootBlockLogicalTop, rootBlockLogicalWidth, logicalHeight)); + if (paintInfo) + paintInfo->context->fillRect(gapRect, selObj->selectionBackgroundColor(), selObj->style()->colorSpace()); + return gapRect; +} + +IntRect RenderBlock::logicalRightSelectionGap(RenderBlock* rootBlock, const IntPoint& rootBlockPhysicalPosition, const IntSize& offsetFromRootBlock, + RenderObject* selObj, int logicalRight, int logicalTop, int logicalHeight, const PaintInfo* paintInfo) +{ + int rootBlockLogicalTop = blockDirectionOffset(rootBlock, offsetFromRootBlock) + logicalTop; + int rootBlockLogicalLeft = max(inlineDirectionOffset(rootBlock, offsetFromRootBlock) + logicalRight, max(logicalLeftSelectionOffset(rootBlock, logicalTop), logicalLeftSelectionOffset(rootBlock, logicalTop + logicalHeight))); + int rootBlockLogicalRight = min(logicalRightSelectionOffset(rootBlock, logicalTop), logicalRightSelectionOffset(rootBlock, logicalTop + logicalHeight)); + int rootBlockLogicalWidth = rootBlockLogicalRight - rootBlockLogicalLeft; + if (rootBlockLogicalWidth <= 0) + return IntRect(); + + IntRect gapRect = rootBlock->logicalRectToPhysicalRect(rootBlockPhysicalPosition, IntRect(rootBlockLogicalLeft, rootBlockLogicalTop, rootBlockLogicalWidth, logicalHeight)); + if (paintInfo) + paintInfo->context->fillRect(gapRect, selObj->selectionBackgroundColor(), selObj->style()->colorSpace()); + return gapRect; +} + +void RenderBlock::getSelectionGapInfo(SelectionState state, bool& leftGap, bool& rightGap) +{ + bool ltr = style()->isLeftToRightDirection(); + leftGap = (state == RenderObject::SelectionInside) || + (state == RenderObject::SelectionEnd && ltr) || + (state == RenderObject::SelectionStart && !ltr); + rightGap = (state == RenderObject::SelectionInside) || + (state == RenderObject::SelectionStart && ltr) || + (state == RenderObject::SelectionEnd && !ltr); +} + +int RenderBlock::logicalLeftSelectionOffset(RenderBlock* rootBlock, int position) +{ + int logicalLeft = logicalLeftOffsetForLine(position, false); + if (logicalLeft == logicalLeftOffsetForContent()) { + if (rootBlock != this) + // The border can potentially be further extended by our containingBlock(). + return containingBlock()->logicalLeftSelectionOffset(rootBlock, position + logicalTop()); + return logicalLeft; + } else { + RenderBlock* cb = this; + while (cb != rootBlock) { + logicalLeft += cb->logicalLeft(); + cb = cb->containingBlock(); + } + } + return logicalLeft; +} + +int RenderBlock::logicalRightSelectionOffset(RenderBlock* rootBlock, int position) +{ + int logicalRight = logicalRightOffsetForLine(position, false); + if (logicalRight == logicalRightOffsetForContent()) { + if (rootBlock != this) + // The border can potentially be further extended by our containingBlock(). + return containingBlock()->logicalRightSelectionOffset(rootBlock, position + logicalTop()); + return logicalRight; + } else { + RenderBlock* cb = this; + while (cb != rootBlock) { + logicalRight += cb->logicalLeft(); + cb = cb->containingBlock(); + } + } + return logicalRight; +} + +void RenderBlock::insertPositionedObject(RenderBox* o) +{ + // Create the list of special objects if we don't aleady have one + if (!m_positionedObjects) + m_positionedObjects = new PositionedObjectsListHashSet; + + m_positionedObjects->add(o); +} + +void RenderBlock::removePositionedObject(RenderBox* o) +{ + if (m_positionedObjects) + m_positionedObjects->remove(o); +} + +void RenderBlock::removePositionedObjects(RenderBlock* o) +{ + if (!m_positionedObjects) + return; + + RenderBox* r; + + Iterator end = m_positionedObjects->end(); + + Vector<RenderBox*, 16> deadObjects; + + for (Iterator it = m_positionedObjects->begin(); it != end; ++it) { + r = *it; + if (!o || r->isDescendantOf(o)) { + if (o) + r->setChildNeedsLayout(true, false); + + // It is parent blocks job to add positioned child to positioned objects list of its containing block + // Parent layout needs to be invalidated to ensure this happens. + RenderObject* p = r->parent(); + while (p && !p->isRenderBlock()) + p = p->parent(); + if (p) + p->setChildNeedsLayout(true); + + deadObjects.append(r); + } + } + + for (unsigned i = 0; i < deadObjects.size(); i++) + m_positionedObjects->remove(deadObjects.at(i)); +} + +RenderBlock::FloatingObject* RenderBlock::insertFloatingObject(RenderBox* o) +{ + ASSERT(o->isFloating()); + + // Create the list of special objects if we don't aleady have one + if (!m_floatingObjects) { + m_floatingObjects = new DeprecatedPtrList<FloatingObject>; + m_floatingObjects->setAutoDelete(true); + } else { + // Don't insert the object again if it's already in the list + DeprecatedPtrListIterator<FloatingObject> it(*m_floatingObjects); + FloatingObject* f; + while ( (f = it.current()) ) { + if (f->m_renderer == o) + return f; + ++it; + } + } + + // Create the special object entry & append it to the list + + FloatingObject* newObj = new FloatingObject(o->style()->floating() == FLEFT ? FloatingObject::FloatLeft : FloatingObject::FloatRight); + + // Our location is irrelevant if we're unsplittable or no pagination is in effect. + // Just go ahead and lay out the float. + bool isChildRenderBlock = o->isRenderBlock(); + if (isChildRenderBlock && !o->needsLayout() && view()->layoutState()->pageLogicalHeightChanged()) + o->setChildNeedsLayout(true, false); + + bool affectedByPagination = isChildRenderBlock && view()->layoutState()->m_pageLogicalHeight; + if (!affectedByPagination || isWritingModeRoot()) // We are unsplittable if we're a block flow root. + o->layoutIfNeeded(); + else { + o->computeLogicalWidth(); + o->computeBlockDirectionMargins(this); + } + setLogicalWidthForFloat(newObj, logicalWidthForChild(o) + marginStartForChild(o) + marginEndForChild(o)); + + newObj->m_shouldPaint = !o->hasSelfPaintingLayer(); // If a layer exists, the float will paint itself. Otherwise someone else will. + newObj->m_isDescendant = true; + newObj->m_renderer = o; + + m_floatingObjects->append(newObj); + + return newObj; +} + +void RenderBlock::removeFloatingObject(RenderBox* o) +{ + if (m_floatingObjects) { + DeprecatedPtrListIterator<FloatingObject> it(*m_floatingObjects); + while (it.current()) { + if (it.current()->m_renderer == o) { + if (childrenInline()) { + int logicalTop = logicalTopForFloat(it.current()); + int logicalBottom = logicalBottomForFloat(it.current()); + + // Special-case zero- and less-than-zero-height floats: those don't touch + // the line that they're on, but it still needs to be dirtied. This is + // accomplished by pretending they have a height of 1. + logicalBottom = max(logicalBottom, logicalTop + 1); + markLinesDirtyInBlockRange(0, logicalBottom); + } + m_floatingObjects->removeRef(it.current()); + } + ++it; + } + } +} + +void RenderBlock::removeFloatingObjectsBelow(FloatingObject* lastFloat, int y) +{ + if (!m_floatingObjects) + return; + + FloatingObject* curr = m_floatingObjects->last(); + while (curr != lastFloat && (!curr->isPlaced() || curr->top() >= y)) { + m_floatingObjects->removeLast(); + curr = m_floatingObjects->last(); + } +} + +bool RenderBlock::positionNewFloats() +{ + if (!m_floatingObjects) + return false; + + FloatingObject* floatingObject = m_floatingObjects->last(); + + // If all floats have already been positioned, then we have no work to do. + if (!floatingObject || floatingObject->isPlaced()) + return false; + + // Move backwards through our floating object list until we find a float that has + // already been positioned. Then we'll be able to move forward, positioning all of + // the new floats that need it. + FloatingObject* lastFloat = m_floatingObjects->getPrev(); + while (lastFloat && !lastFloat->isPlaced()) { + floatingObject = m_floatingObjects->prev(); + lastFloat = m_floatingObjects->getPrev(); + } + + int logicalTop = logicalHeight(); + + // The float cannot start above the top position of the last positioned float. + if (lastFloat) + logicalTop = max(logicalTopForFloat(lastFloat), logicalTop); + + // Now walk through the set of unpositioned floats and place them. + while (floatingObject) { + // The containing block is responsible for positioning floats, so if we have floats in our + // list that come from somewhere else, do not attempt to position them. + if (floatingObject->renderer()->containingBlock() != this) { + floatingObject = m_floatingObjects->next(); + continue; + } + + RenderBox* childBox = floatingObject->renderer(); + int childLogicalLeftMargin = style()->isLeftToRightDirection() ? marginStartForChild(childBox) : marginEndForChild(childBox); + + int rightOffset = logicalRightOffsetForContent(); // Constant part of right offset. + int leftOffset = logicalLeftOffsetForContent(); // Constant part of left offset. + int floatLogicalWidth = logicalWidthForFloat(floatingObject); // The width we look for. + if (rightOffset - leftOffset < floatLogicalWidth) + floatLogicalWidth = rightOffset - leftOffset; // Never look for more than what will be available. + + IntRect oldRect(childBox->x(), childBox->y() , childBox->width(), childBox->height()); + + if (childBox->style()->clear() & CLEFT) + logicalTop = max(lowestFloatLogicalBottom(FloatingObject::FloatLeft), logicalTop); + if (childBox->style()->clear() & CRIGHT) + logicalTop = max(lowestFloatLogicalBottom(FloatingObject::FloatRight), logicalTop); + + int floatLogicalLeft; + if (childBox->style()->floating() == FLEFT) { + int heightRemainingLeft = 1; + int heightRemainingRight = 1; + floatLogicalLeft = logicalLeftOffsetForLine(logicalTop, leftOffset, false, &heightRemainingLeft); + while (logicalRightOffsetForLine(logicalTop, rightOffset, false, &heightRemainingRight) - floatLogicalLeft < floatLogicalWidth) { + logicalTop += min(heightRemainingLeft, heightRemainingRight); + floatLogicalLeft = logicalLeftOffsetForLine(logicalTop, leftOffset, false, &heightRemainingLeft); + } + floatLogicalLeft = max(0, floatLogicalLeft); + } else { + int heightRemainingLeft = 1; + int heightRemainingRight = 1; + floatLogicalLeft = logicalRightOffsetForLine(logicalTop, rightOffset, false, &heightRemainingRight); + while (floatLogicalLeft - logicalLeftOffsetForLine(logicalTop, leftOffset, false, &heightRemainingLeft) < floatLogicalWidth) { + logicalTop += min(heightRemainingLeft, heightRemainingRight); + floatLogicalLeft = logicalRightOffsetForLine(logicalTop, rightOffset, false, &heightRemainingRight); + } + floatLogicalLeft -= logicalWidthForFloat(floatingObject); // Use the original width of the float here, since the local variable + // |floatLogicalWidth| was capped to the available line width. + // See fast/block/float/clamped-right-float.html. + } + + setLogicalLeftForFloat(floatingObject, floatLogicalLeft); + setLogicalLeftForChild(childBox, floatLogicalLeft + childLogicalLeftMargin); + setLogicalTopForChild(childBox, logicalTop + marginBeforeForChild(childBox)); + + if (view()->layoutState()->isPaginated()) { + RenderBlock* childBlock = childBox->isRenderBlock() ? toRenderBlock(childBox) : 0; + + if (!childBox->needsLayout()) + childBox->markForPaginationRelayoutIfNeeded();; + childBox->layoutIfNeeded(); + + // If we are unsplittable and don't fit, then we need to move down. + // We include our margins as part of the unsplittable area. + int newLogicalTop = adjustForUnsplittableChild(childBox, logicalTop, true); + + // See if we have a pagination strut that is making us move down further. + // Note that an unsplittable child can't also have a pagination strut, so this is + // exclusive with the case above. + if (childBlock && childBlock->paginationStrut()) { + newLogicalTop += childBlock->paginationStrut(); + childBlock->setPaginationStrut(0); + } + + if (newLogicalTop != logicalTop) { + floatingObject->m_paginationStrut = newLogicalTop - logicalTop; + logicalTop = newLogicalTop; + setLogicalTopForChild(childBox, logicalTop + marginBeforeForChild(childBox)); + if (childBlock) + childBlock->setChildNeedsLayout(true, false); + childBox->layoutIfNeeded(); + } + } + + setLogicalTopForFloat(floatingObject, logicalTop); + setLogicalHeightForFloat(floatingObject, logicalHeightForChild(childBox) + marginBeforeForChild(childBox) + marginAfterForChild(childBox)); + + floatingObject->setIsPlaced(); + + // If the child moved, we have to repaint it. + if (childBox->checkForRepaintDuringLayout()) + childBox->repaintDuringLayoutIfMoved(oldRect); + + floatingObject = m_floatingObjects->next(); + } + return true; +} + +bool RenderBlock::positionNewFloatOnLine(FloatingObject* newFloat, FloatingObject* lastFloatFromPreviousLine) +{ + bool didPosition = positionNewFloats(); + if (!didPosition || !newFloat->m_paginationStrut) + return didPosition; + + int floatLogicalTop = logicalTopForFloat(newFloat); + int paginationStrut = newFloat->m_paginationStrut; + FloatingObject* f = m_floatingObjects->last(); + + ASSERT(f == newFloat); + + if (floatLogicalTop - paginationStrut != logicalHeight()) + return didPosition; + + for (f = m_floatingObjects->prev(); f && f != lastFloatFromPreviousLine; f = m_floatingObjects->prev()) { + if (logicalTopForFloat(f) == logicalHeight()) { + ASSERT(!f->m_paginationStrut); + f->m_paginationStrut = paginationStrut; + RenderBox* o = f->m_renderer; + setLogicalTopForChild(o, logicalTopForChild(o) + marginBeforeForChild(o) + paginationStrut); + if (o->isRenderBlock()) + toRenderBlock(o)->setChildNeedsLayout(true, false); + o->layoutIfNeeded(); + setLogicalTopForFloat(f, logicalTopForFloat(f) + f->m_paginationStrut); + } + } + + setLogicalHeight(logicalHeight() + paginationStrut); + + return didPosition; +} + +void RenderBlock::newLine(EClear clear) +{ + positionNewFloats(); + // set y position + int newY = 0; + switch (clear) + { + case CLEFT: + newY = lowestFloatLogicalBottom(FloatingObject::FloatLeft); + break; + case CRIGHT: + newY = lowestFloatLogicalBottom(FloatingObject::FloatRight); + break; + case CBOTH: + newY = lowestFloatLogicalBottom(); + default: + break; + } + if (height() < newY) + setLogicalHeight(newY); +} + +void RenderBlock::addPercentHeightDescendant(RenderBox* descendant) +{ + if (!gPercentHeightDescendantsMap) { + gPercentHeightDescendantsMap = new PercentHeightDescendantsMap; + gPercentHeightContainerMap = new PercentHeightContainerMap; + } + + HashSet<RenderBox*>* descendantSet = gPercentHeightDescendantsMap->get(this); + if (!descendantSet) { + descendantSet = new HashSet<RenderBox*>; + gPercentHeightDescendantsMap->set(this, descendantSet); + } + bool added = descendantSet->add(descendant).second; + if (!added) { + ASSERT(gPercentHeightContainerMap->get(descendant)); + ASSERT(gPercentHeightContainerMap->get(descendant)->contains(this)); + return; + } + + HashSet<RenderBlock*>* containerSet = gPercentHeightContainerMap->get(descendant); + if (!containerSet) { + containerSet = new HashSet<RenderBlock*>; + gPercentHeightContainerMap->set(descendant, containerSet); + } + ASSERT(!containerSet->contains(this)); + containerSet->add(this); +} + +void RenderBlock::removePercentHeightDescendant(RenderBox* descendant) +{ + if (!gPercentHeightContainerMap) + return; + + HashSet<RenderBlock*>* containerSet = gPercentHeightContainerMap->take(descendant); + if (!containerSet) + return; + + HashSet<RenderBlock*>::iterator end = containerSet->end(); + for (HashSet<RenderBlock*>::iterator it = containerSet->begin(); it != end; ++it) { + RenderBlock* container = *it; + HashSet<RenderBox*>* descendantSet = gPercentHeightDescendantsMap->get(container); + ASSERT(descendantSet); + if (!descendantSet) + continue; + ASSERT(descendantSet->contains(descendant)); + descendantSet->remove(descendant); + if (descendantSet->isEmpty()) { + gPercentHeightDescendantsMap->remove(container); + delete descendantSet; + } + } + + delete containerSet; +} + +HashSet<RenderBox*>* RenderBlock::percentHeightDescendants() const +{ + return gPercentHeightDescendantsMap ? gPercentHeightDescendantsMap->get(this) : 0; +} + +int RenderBlock::logicalLeftOffsetForLine(int logicalTop, int fixedOffset, bool applyTextIndent, int* heightRemaining) const +{ + int left = fixedOffset; + if (m_floatingObjects) { + if (heightRemaining) + *heightRemaining = 1; + FloatingObject* r; + DeprecatedPtrListIterator<FloatingObject> it(*m_floatingObjects); + for ( ; (r = it.current()); ++it) { + if (r->isPlaced() && logicalTopForFloat(r) <= logicalTop && logicalBottomForFloat(r) > logicalTop + && r->type() == FloatingObject::FloatLeft + && logicalRightForFloat(r) > left) { + left = logicalRightForFloat(r); + if (heightRemaining) + *heightRemaining = logicalBottomForFloat(r) - logicalTop; + } + } + } + + if (applyTextIndent && style()->isLeftToRightDirection()) { + int cw = 0; + if (style()->textIndent().isPercent()) + cw = containingBlock()->availableLogicalWidth(); + left += style()->textIndent().calcMinValue(cw); + } + + return left; +} + +int RenderBlock::logicalRightOffsetForLine(int logicalTop, int fixedOffset, bool applyTextIndent, int* heightRemaining) const +{ + int right = fixedOffset; + + if (m_floatingObjects) { + if (heightRemaining) + *heightRemaining = 1; + FloatingObject* r; + DeprecatedPtrListIterator<FloatingObject> it(*m_floatingObjects); + for ( ; (r = it.current()); ++it) { + if (r->isPlaced() && logicalTopForFloat(r) <= logicalTop && logicalBottomForFloat(r) > logicalTop + && r->type() == FloatingObject::FloatRight + && logicalLeftForFloat(r) < right) { + right = logicalLeftForFloat(r); + if (heightRemaining) + *heightRemaining = logicalBottomForFloat(r) - logicalTop; + } + } + } + + if (applyTextIndent && !style()->isLeftToRightDirection()) { + int cw = 0; + if (style()->textIndent().isPercent()) + cw = containingBlock()->availableLogicalWidth(); + right -= style()->textIndent().calcMinValue(cw); + } + + return right; +} + +int +RenderBlock::availableLogicalWidthForLine(int position, bool firstLine) const +{ + int result = logicalRightOffsetForLine(position, firstLine) - logicalLeftOffsetForLine(position, firstLine); + return (result < 0) ? 0 : result; +} + +int RenderBlock::nextFloatLogicalBottomBelow(int logicalHeight) const +{ + if (!m_floatingObjects) + return 0; + + int bottom = INT_MAX; + FloatingObject* r; + DeprecatedPtrListIterator<FloatingObject> it(*m_floatingObjects); + for ( ; (r = it.current()); ++it) { + int floatBottom = logicalBottomForFloat(r); + if (floatBottom > logicalHeight) + bottom = min(floatBottom, bottom); + } + + return bottom == INT_MAX ? 0 : bottom; +} + +int RenderBlock::lowestFloatLogicalBottom(FloatingObject::Type floatType) const +{ + if (!m_floatingObjects) + return 0; + int lowestFloatBottom = 0; + FloatingObject* r; + DeprecatedPtrListIterator<FloatingObject> it(*m_floatingObjects); + for ( ; (r = it.current()); ++it) { + if (r->isPlaced() && r->type() & floatType) + lowestFloatBottom = max(lowestFloatBottom, logicalBottomForFloat(r)); + } + return lowestFloatBottom; +} + +void RenderBlock::markLinesDirtyInBlockRange(int logicalTop, int logicalBottom, RootInlineBox* highest) +{ + if (logicalTop >= logicalBottom) + return; + + RootInlineBox* lowestDirtyLine = lastRootBox(); + RootInlineBox* afterLowest = lowestDirtyLine; + while (lowestDirtyLine && lowestDirtyLine->blockLogicalHeight() >= logicalBottom) { + afterLowest = lowestDirtyLine; + lowestDirtyLine = lowestDirtyLine->prevRootBox(); + } + + while (afterLowest && afterLowest != highest && afterLowest->blockLogicalHeight() >= logicalTop) { + afterLowest->markDirty(); + afterLowest = afterLowest->prevRootBox(); + } +} + +void RenderBlock::clearFloats() +{ + // Inline blocks are covered by the isReplaced() check in the avoidFloats method. + if (avoidsFloats() || isRoot() || isRenderView() || isFloatingOrPositioned() || isTableCell()) { + if (m_floatingObjects) + m_floatingObjects->clear(); + return; + } + + typedef HashMap<RenderObject*, FloatingObject*> RendererToFloatInfoMap; + RendererToFloatInfoMap floatMap; + + if (m_floatingObjects) { + if (childrenInline()) { + m_floatingObjects->first(); + while (FloatingObject* f = m_floatingObjects->take()) + floatMap.add(f->m_renderer, f); + } else + m_floatingObjects->clear(); + } + + // We should not process floats if the parent node is not a RenderBlock. Otherwise, we will add + // floats in an invalid context. This will cause a crash arising from a bad cast on the parent. + // See <rdar://problem/8049753>, where float property is applied on a text node in a SVG. + if (!parent() || !parent()->isRenderBlock()) + return; + + // Attempt to locate a previous sibling with overhanging floats. We skip any elements that are + // out of flow (like floating/positioned elements), and we also skip over any objects that may have shifted + // to avoid floats. + bool parentHasFloats = false; + RenderBlock* parentBlock = toRenderBlock(parent()); + RenderObject* prev = previousSibling(); + while (prev && (prev->isFloatingOrPositioned() || !prev->isBox() || !prev->isRenderBlock() || toRenderBlock(prev)->avoidsFloats())) { + if (prev->isFloating()) + parentHasFloats = true; + prev = prev->previousSibling(); + } + + // First add in floats from the parent. + int logicalTopOffset = logicalTop(); + if (parentHasFloats) + addIntrudingFloats(parentBlock, parentBlock->logicalLeftOffsetForContent(), logicalTopOffset); + + int logicalLeftOffset = 0; + if (prev) + logicalTopOffset -= toRenderBox(prev)->logicalTop(); + else { + prev = parentBlock; + logicalLeftOffset += parentBlock->logicalLeftOffsetForContent(); + } + + // Add overhanging floats from the previous RenderBlock, but only if it has a float that intrudes into our space. + if (!prev || !prev->isRenderBlock()) + return; + + RenderBlock* block = toRenderBlock(prev); + if (block->m_floatingObjects && block->lowestFloatLogicalBottom() > logicalTopOffset) + addIntrudingFloats(block, logicalLeftOffset, logicalTopOffset); + + if (childrenInline()) { + int changeLogicalTop = numeric_limits<int>::max(); + int changeLogicalBottom = numeric_limits<int>::min(); + if (m_floatingObjects) { + for (FloatingObject* f = m_floatingObjects->first(); f; f = m_floatingObjects->next()) { + FloatingObject* oldFloatingObject = floatMap.get(f->m_renderer); + int logicalBottom = logicalBottomForFloat(f); + if (oldFloatingObject) { + int oldLogicalBottom = logicalBottomForFloat(oldFloatingObject); + if (logicalWidthForFloat(f) != logicalWidthForFloat(oldFloatingObject) || logicalLeftForFloat(f) != logicalLeftForFloat(oldFloatingObject)) { + changeLogicalTop = 0; + changeLogicalBottom = max(changeLogicalBottom, max(logicalBottom, oldLogicalBottom)); + } else if (logicalBottom != oldLogicalBottom) { + changeLogicalTop = min(changeLogicalTop, min(logicalBottom, oldLogicalBottom)); + changeLogicalBottom = max(changeLogicalBottom, max(logicalBottom, oldLogicalBottom)); + } + + floatMap.remove(f->m_renderer); + delete oldFloatingObject; + } else { + changeLogicalTop = 0; + changeLogicalBottom = max(changeLogicalBottom, logicalBottom); + } + } + } + + RendererToFloatInfoMap::iterator end = floatMap.end(); + for (RendererToFloatInfoMap::iterator it = floatMap.begin(); it != end; ++it) { + FloatingObject* floatingObject = (*it).second; + if (!floatingObject->m_isDescendant) { + changeLogicalTop = 0; + changeLogicalBottom = max(changeLogicalBottom, logicalBottomForFloat(floatingObject)); + } + } + deleteAllValues(floatMap); + + markLinesDirtyInBlockRange(changeLogicalTop, changeLogicalBottom); + } +} + +int RenderBlock::addOverhangingFloats(RenderBlock* child, int logicalLeftOffset, int logicalTopOffset, bool makeChildPaintOtherFloats) +{ + // Prevent floats from being added to the canvas by the root element, e.g., <html>. + if (child->hasOverflowClip() || !child->containsFloats() || child->isRoot() || child->hasColumns() || child->isWritingModeRoot()) + return 0; + + int lowestFloatLogicalBottom = 0; + + // Floats that will remain the child's responsibility to paint should factor into its + // overflow. + DeprecatedPtrListIterator<FloatingObject> it(*child->m_floatingObjects); + for (FloatingObject* r; (r = it.current()); ++it) { + int logicalBottom = child->logicalTop() + logicalBottomForFloat(r); + lowestFloatLogicalBottom = max(lowestFloatLogicalBottom, logicalBottom); + + if (logicalBottom > logicalHeight()) { + // If the object is not in the list, we add it now. + if (!containsFloat(r->m_renderer)) { + int leftOffset = style()->isHorizontalWritingMode() ? logicalLeftOffset : logicalTopOffset; + int topOffset = style()->isHorizontalWritingMode() ? logicalTopOffset : logicalLeftOffset; + FloatingObject* floatingObj = new FloatingObject(r->type(), IntRect(r->left() - leftOffset, r->top() - topOffset, r->width(), r->height())); + floatingObj->m_renderer = r->m_renderer; + + // The nearest enclosing layer always paints the float (so that zindex and stacking + // behaves properly). We always want to propagate the desire to paint the float as + // far out as we can, to the outermost block that overlaps the float, stopping only + // if we hit a self-painting layer boundary. + if (r->m_renderer->enclosingFloatPaintingLayer() == enclosingFloatPaintingLayer()) + r->m_shouldPaint = false; + else + floatingObj->m_shouldPaint = false; + + floatingObj->m_isDescendant = true; + + // We create the floating object list lazily. + if (!m_floatingObjects) { + m_floatingObjects = new DeprecatedPtrList<FloatingObject>; + m_floatingObjects->setAutoDelete(true); + } + m_floatingObjects->append(floatingObj); + } + } else { + if (makeChildPaintOtherFloats && !r->m_shouldPaint && !r->m_renderer->hasSelfPaintingLayer() && + r->m_renderer->isDescendantOf(child) && r->m_renderer->enclosingFloatPaintingLayer() == child->enclosingFloatPaintingLayer()) { + // The float is not overhanging from this block, so if it is a descendant of the child, the child should + // paint it (the other case is that it is intruding into the child), unless it has its own layer or enclosing + // layer. + // If makeChildPaintOtherFloats is false, it means that the child must already know about all the floats + // it should paint. + r->m_shouldPaint = true; + } + + // Since the float doesn't overhang, it didn't get put into our list. We need to go ahead and add its overflow in to the + // child now. + if (r->m_isDescendant) + child->addOverflowFromChild(r->m_renderer, IntSize(r->left() + r->m_renderer->marginLeft(), r->top() + r->m_renderer->marginTop())); + } + } + return lowestFloatLogicalBottom; +} + +void RenderBlock::addIntrudingFloats(RenderBlock* prev, int logicalLeftOffset, int logicalTopOffset) +{ + // If the parent or previous sibling doesn't have any floats to add, don't bother. + if (!prev->m_floatingObjects) + return; + + logicalLeftOffset += (style()->isHorizontalWritingMode() ? marginLeft() : marginTop()); + + DeprecatedPtrListIterator<FloatingObject> it(*prev->m_floatingObjects); + for (FloatingObject *r; (r = it.current()); ++it) { + if (logicalBottomForFloat(r) > logicalTopOffset) { + // The object may already be in our list. Check for it up front to avoid + // creating duplicate entries. + FloatingObject* f = 0; + if (m_floatingObjects) { + DeprecatedPtrListIterator<FloatingObject> it(*m_floatingObjects); + while ((f = it.current())) { + if (f->m_renderer == r->m_renderer) + break; + ++it; + } + } + if (!f) { + int leftOffset = style()->isHorizontalWritingMode() ? logicalLeftOffset : logicalTopOffset; + int topOffset = style()->isHorizontalWritingMode() ? logicalTopOffset : logicalLeftOffset; + + FloatingObject* floatingObj = new FloatingObject(r->type(), IntRect(r->left() - leftOffset, r->top() - topOffset, r->width(), r->height())); + + // Applying the child's margin makes no sense in the case where the child was passed in. + // since this margin was added already through the modification of the |logicalLeftOffset| variable + // above. |logicalLeftOffset| will equal the margin in this case, so it's already been taken + // into account. Only apply this code if prev is the parent, since otherwise the left margin + // will get applied twice. + if (prev != parent()) { + if (style()->isHorizontalWritingMode()) + floatingObj->setLeft(floatingObj->left() + prev->marginLeft()); + else + floatingObj->setTop(floatingObj->top() + prev->marginTop()); + } + + floatingObj->m_shouldPaint = false; // We are not in the direct inheritance chain for this float. We will never paint it. + floatingObj->m_renderer = r->m_renderer; + + // We create the floating object list lazily. + if (!m_floatingObjects) { + m_floatingObjects = new DeprecatedPtrList<FloatingObject>; + m_floatingObjects->setAutoDelete(true); + } + m_floatingObjects->append(floatingObj); + } + } + } +} + +bool RenderBlock::avoidsFloats() const +{ + // Floats can't intrude into our box if we have a non-auto column count or width. + return RenderBox::avoidsFloats() || !style()->hasAutoColumnCount() || !style()->hasAutoColumnWidth(); +} + +bool RenderBlock::containsFloat(RenderObject* o) +{ + if (m_floatingObjects) { + DeprecatedPtrListIterator<FloatingObject> it(*m_floatingObjects); + while (it.current()) { + if (it.current()->m_renderer == o) + return true; + ++it; + } + } + return false; +} + +void RenderBlock::markAllDescendantsWithFloatsForLayout(RenderBox* floatToRemove, bool inLayout) +{ + if (!m_everHadLayout) + return; + + setChildNeedsLayout(true, !inLayout); + + if (floatToRemove) + removeFloatingObject(floatToRemove); + + // Iterate over our children and mark them as needed. + if (!childrenInline()) { + for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { + if ((!floatToRemove && child->isFloatingOrPositioned()) || !child->isRenderBlock()) + continue; + RenderBlock* childBlock = toRenderBlock(child); + if ((floatToRemove ? childBlock->containsFloat(floatToRemove) : childBlock->containsFloats()) || childBlock->shrinkToAvoidFloats()) + childBlock->markAllDescendantsWithFloatsForLayout(floatToRemove, inLayout); + } + } +} + +int RenderBlock::getClearDelta(RenderBox* child, int yPos) +{ + // There is no need to compute clearance if we have no floats. + if (!containsFloats()) + return 0; + + // At least one float is present. We need to perform the clearance computation. + bool clearSet = child->style()->clear() != CNONE; + int bottom = 0; + switch (child->style()->clear()) { + case CNONE: + break; + case CLEFT: + bottom = lowestFloatLogicalBottom(FloatingObject::FloatLeft); + break; + case CRIGHT: + bottom = lowestFloatLogicalBottom(FloatingObject::FloatRight); + break; + case CBOTH: + bottom = lowestFloatLogicalBottom(); + break; + } + + // We also clear floats if we are too big to sit on the same line as a float (and wish to avoid floats by default). + int result = clearSet ? max(0, bottom - yPos) : 0; + if (!result && child->avoidsFloats()) { + int y = yPos; + while (true) { + int widthAtY = availableLogicalWidthForLine(y, false); + if (widthAtY == availableLogicalWidth()) + return y - yPos; + + int oldChildY = child->y(); + int oldChildWidth = child->width(); + child->setY(y); + child->computeLogicalWidth(); + int childWidthAtY = child->width(); + child->setY(oldChildY); + child->setWidth(oldChildWidth); + + if (childWidthAtY <= widthAtY) + return y - yPos; + + y = nextFloatLogicalBottomBelow(y); + ASSERT(y >= yPos); + if (y < yPos) + break; + } + ASSERT_NOT_REACHED(); + } + return result; +} + +bool RenderBlock::isPointInOverflowControl(HitTestResult& result, int _x, int _y, int _tx, int _ty) +{ + if (!scrollsOverflow()) + return false; + + return layer()->hitTestOverflowControls(result, IntPoint(_x - _tx, _y - _ty)); +} + +bool RenderBlock::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction) +{ + int tx = _tx + x(); + int ty = _ty + y(); + + if (!isRenderView()) { + // Check if we need to do anything at all. + IntRect overflowBox = visualOverflowRect(); + overflowBox.move(tx, ty); + if (!overflowBox.intersects(result.rectForPoint(_x, _y))) + return false; + } + + if ((hitTestAction == HitTestBlockBackground || hitTestAction == HitTestChildBlockBackground) && isPointInOverflowControl(result, _x, _y, tx, ty)) { + updateHitTestResult(result, IntPoint(_x - tx, _y - ty)); + // FIXME: isPointInOverflowControl() doesn't handle rect-based tests yet. + if (!result.addNodeToRectBasedTestResult(node(), _x, _y)) + return true; + } + + // If we have clipping, then we can't have any spillout. + bool useOverflowClip = hasOverflowClip() && !hasSelfPaintingLayer(); + bool useClip = (hasControlClip() || useOverflowClip); + IntRect hitTestArea(result.rectForPoint(_x, _y)); + bool checkChildren = !useClip || (hasControlClip() ? controlClipRect(tx, ty).intersects(hitTestArea) : overflowClipRect(tx, ty).intersects(hitTestArea)); + if (checkChildren) { + // Hit test descendants first. + int scrolledX = tx; + int scrolledY = ty; + if (hasOverflowClip()) { + IntSize offset = layer()->scrolledContentOffset(); + scrolledX -= offset.width(); + scrolledY -= offset.height(); + } + + // Hit test contents if we don't have columns. + if (!hasColumns()) { + if (hitTestContents(request, result, _x, _y, scrolledX, scrolledY, hitTestAction)) { + updateHitTestResult(result, IntPoint(_x - tx, _y - ty)); + return true; + } + if (hitTestAction == HitTestFloat && hitTestFloats(request, result, _x, _y, scrolledX, scrolledY)) + return true; + } else if (hitTestColumns(request, result, _x, _y, scrolledX, scrolledY, hitTestAction)) { + updateHitTestResult(result, IntPoint(_x - tx, _y - ty)); + return true; + } + } + + // Now hit test our background + if (hitTestAction == HitTestBlockBackground || hitTestAction == HitTestChildBlockBackground) { + IntRect boundsRect(tx, ty, width(), height()); + if (visibleToHitTesting() && boundsRect.intersects(result.rectForPoint(_x, _y))) { + updateHitTestResult(result, flipForWritingMode(IntPoint(_x - tx, _y - ty))); + if (!result.addNodeToRectBasedTestResult(node(), _x, _y, boundsRect)) + return true; + } + } + + return false; +} + +bool RenderBlock::hitTestFloats(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty) +{ + if (!m_floatingObjects) + return false; + + if (isRenderView()) { + tx += toRenderView(this)->frameView()->scrollX(); + ty += toRenderView(this)->frameView()->scrollY(); + } + + FloatingObject* floatingObject; + DeprecatedPtrListIterator<FloatingObject> it(*m_floatingObjects); + for (it.toLast(); (floatingObject = it.current()); --it) { + if (floatingObject->m_shouldPaint && !floatingObject->m_renderer->hasSelfPaintingLayer()) { + int xOffset = floatingObject->left() + floatingObject->m_renderer->marginLeft() - floatingObject->m_renderer->x(); + int yOffset = floatingObject->top() + floatingObject->m_renderer->marginTop() - floatingObject->m_renderer->y(); + IntPoint childPoint = flipForWritingMode(floatingObject->m_renderer, IntPoint(tx + xOffset, ty + yOffset), ParentToChildFlippingAdjustment); + if (floatingObject->m_renderer->hitTest(request, result, IntPoint(x, y), childPoint.x(), childPoint.y())) { + updateHitTestResult(result, IntPoint(x - childPoint.x(), y - childPoint.y())); + return true; + } + } + } + + return false; +} + +bool RenderBlock::hitTestColumns(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction hitTestAction) +{ + // We need to do multiple passes, breaking up our hit testing into strips. + ColumnInfo* colInfo = columnInfo(); + int colCount = columnCount(colInfo); + if (!colCount) + return false; + int left = borderLeft() + paddingLeft(); + int currYOffset = 0; + int i; + for (i = 0; i < colCount; i++) + currYOffset -= columnRectAt(colInfo, i).height(); + for (i = colCount - 1; i >= 0; i--) { + IntRect colRect = columnRectAt(colInfo, i); + int currXOffset = colRect.x() - left; + currYOffset += colRect.height(); + colRect.move(tx, ty); + + if (colRect.intersects(result.rectForPoint(x, y))) { + // The point is inside this column. + // Adjust tx and ty to change where we hit test. + + int finalX = tx + currXOffset; + int finalY = ty + currYOffset; + if (result.isRectBasedTest() && !colRect.contains(result.rectForPoint(x, y))) + hitTestContents(request, result, x, y, finalX, finalY, hitTestAction); + else + return hitTestContents(request, result, x, y, finalX, finalY, hitTestAction) || (hitTestAction == HitTestFloat && hitTestFloats(request, result, x, y, finalX, finalY)); + } + } + + return false; +} + +bool RenderBlock::hitTestContents(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction hitTestAction) +{ + if (childrenInline() && !isTable()) { + // We have to hit-test our line boxes. + if (m_lineBoxes.hitTest(this, request, result, x, y, tx, ty, hitTestAction)) + return true; + } else { + // Hit test our children. + HitTestAction childHitTest = hitTestAction; + if (hitTestAction == HitTestChildBlockBackgrounds) + childHitTest = HitTestChildBlockBackground; + for (RenderBox* child = lastChildBox(); child; child = child->previousSiblingBox()) { + IntPoint childPoint = flipForWritingMode(child, IntPoint(tx, ty), ParentToChildFlippingAdjustment); + if (!child->hasSelfPaintingLayer() && !child->isFloating() && child->nodeAtPoint(request, result, x, y, childPoint.x(), childPoint.y(), childHitTest)) + return true; + } + } + + return false; +} + +Position RenderBlock::positionForBox(InlineBox *box, bool start) const +{ + if (!box) + return Position(); + + if (!box->renderer()->node()) + return Position(node(), start ? caretMinOffset() : caretMaxOffset()); + + if (!box->isInlineTextBox()) + return Position(box->renderer()->node(), start ? box->renderer()->caretMinOffset() : box->renderer()->caretMaxOffset()); + + InlineTextBox *textBox = static_cast<InlineTextBox *>(box); + return Position(box->renderer()->node(), start ? textBox->start() : textBox->start() + textBox->len()); +} + +Position RenderBlock::positionForRenderer(RenderObject* renderer, bool start) const +{ + if (!renderer) + return Position(node(), 0); + + Node* n = renderer->node() ? renderer->node() : node(); + if (!n) + return Position(); + + ASSERT(renderer == n->renderer()); + + int offset = start ? renderer->caretMinOffset() : renderer->caretMaxOffset(); + + // FIXME: This was a runtime check that seemingly couldn't fail; changed it to an assertion for now. + ASSERT(!n->isCharacterDataNode() || renderer->isText()); + + return Position(n, offset); +} + +// FIXME: This function should go on RenderObject as an instance method. Then +// all cases in which positionForPoint recurs could call this instead to +// prevent crossing editable boundaries. This would require many tests. +static VisiblePosition positionForPointRespectingEditingBoundaries(RenderBlock* parent, RenderBox* child, const IntPoint& pointInParentCoordinates) +{ + // FIXME: This is wrong if the child's writing-mode is different from the parent's. + IntPoint pointInChildCoordinates(pointInParentCoordinates - child->location()); + + // If this is an anonymous renderer, we just recur normally + Node* childNode = child->node(); + if (!childNode) + return child->positionForPoint(pointInChildCoordinates); + + // Otherwise, first make sure that the editability of the parent and child agree. + // If they don't agree, then we return a visible position just before or after the child + RenderObject* ancestor = parent; + while (ancestor && !ancestor->node()) + ancestor = ancestor->parent(); + + // If we can't find an ancestor to check editability on, or editability is unchanged, we recur like normal + if (!ancestor || ancestor->node()->isContentEditable() == childNode->isContentEditable()) + return child->positionForPoint(pointInChildCoordinates); + + // Otherwise return before or after the child, depending on if the click was to the logical left or logical right of the child + int childMiddle = parent->logicalWidthForChild(child) / 2; + int logicalLeft = parent->style()->isHorizontalWritingMode() ? pointInChildCoordinates.x() : pointInChildCoordinates.y(); + if (logicalLeft < childMiddle) + return ancestor->createVisiblePosition(childNode->nodeIndex(), DOWNSTREAM); + return ancestor->createVisiblePosition(childNode->nodeIndex() + 1, UPSTREAM); +} + +VisiblePosition RenderBlock::positionForPointWithInlineChildren(const IntPoint& pointInLogicalContents) +{ + ASSERT(childrenInline()); + + if (!firstRootBox()) + return createVisiblePosition(0, DOWNSTREAM); + + // look for the closest line box in the root box which is at the passed-in y coordinate + InlineBox* closestBox = 0; + RootInlineBox* firstRootBoxWithChildren = 0; + RootInlineBox* lastRootBoxWithChildren = 0; + for (RootInlineBox* root = firstRootBox(); root; root = root->nextRootBox()) { + if (!root->firstLeafChild()) + continue; + if (!firstRootBoxWithChildren) + firstRootBoxWithChildren = root; + lastRootBoxWithChildren = root; + + // check if this root line box is located at this y coordinate + if (pointInLogicalContents.y() < root->selectionBottom()) { + closestBox = root->closestLeafChildForLogicalLeftPosition(pointInLogicalContents.x()); + if (closestBox) + break; + } + } + + bool moveCaretToBoundary = document()->frame()->editor()->behavior().shouldMoveCaretToHorizontalBoundaryWhenPastTopOrBottom(); + + if (!moveCaretToBoundary && !closestBox && lastRootBoxWithChildren) { + // y coordinate is below last root line box, pretend we hit it + closestBox = lastRootBoxWithChildren->closestLeafChildForLogicalLeftPosition(pointInLogicalContents.x()); + } + + if (closestBox) { + if (moveCaretToBoundary && pointInLogicalContents.y() < firstRootBoxWithChildren->selectionTop()) { + // y coordinate is above first root line box, so return the start of the first + return VisiblePosition(positionForBox(firstRootBoxWithChildren->firstLeafChild(), true), DOWNSTREAM); + } + + // pass the box a top position that is inside it + IntPoint point(pointInLogicalContents.x(), closestBox->logicalTop()); + if (!style()->isHorizontalWritingMode()) + point = point.transposedPoint(); + if (closestBox->renderer()->isReplaced()) + return positionForPointRespectingEditingBoundaries(this, toRenderBox(closestBox->renderer()), point); + return closestBox->renderer()->positionForPoint(point); + } + + if (lastRootBoxWithChildren) { + // We hit this case for Mac behavior when the Y coordinate is below the last box. + ASSERT(moveCaretToBoundary); + return VisiblePosition(positionForBox(lastRootBoxWithChildren->lastLeafChild(), false), DOWNSTREAM); + } + + // Can't reach this. We have a root line box, but it has no kids. + // FIXME: This should ASSERT_NOT_REACHED(), but clicking on placeholder text + // seems to hit this code path. + return createVisiblePosition(0, DOWNSTREAM); +} + +static inline bool isChildHitTestCandidate(RenderBox* box) +{ + return box->height() && box->style()->visibility() == VISIBLE && !box->isFloatingOrPositioned(); +} + +VisiblePosition RenderBlock::positionForPoint(const IntPoint& point) +{ + if (isTable()) + return RenderBox::positionForPoint(point); + + if (isReplaced()) { + // FIXME: This seems wrong when the object's writing-mode doesn't match the line's writing-mode. + int pointLogicalLeft = style()->isHorizontalWritingMode() ? point.x() : point.y(); + int pointLogicalTop = style()->isHorizontalWritingMode() ? point.y() : point.x(); + + if (pointLogicalTop < 0 || (pointLogicalTop < logicalHeight() && pointLogicalLeft < 0)) + return createVisiblePosition(caretMinOffset(), DOWNSTREAM); + if (pointLogicalTop >= logicalHeight() || (pointLogicalTop >= 0 && pointLogicalLeft >= logicalWidth())) + return createVisiblePosition(caretMaxOffset(), DOWNSTREAM); + } + + int contentsX = point.x(); + int contentsY = point.y(); + offsetForContents(contentsX, contentsY); + IntPoint pointInContents(contentsX, contentsY); + IntPoint pointInLogicalContents(pointInContents); + if (!style()->isHorizontalWritingMode()) + pointInLogicalContents = pointInLogicalContents.transposedPoint(); + + if (childrenInline()) + return positionForPointWithInlineChildren(pointInLogicalContents); + + if (lastChildBox() && pointInContents.y() > lastChildBox()->logicalTop()) { + for (RenderBox* childBox = lastChildBox(); childBox; childBox = childBox->previousSiblingBox()) { + if (isChildHitTestCandidate(childBox)) + return positionForPointRespectingEditingBoundaries(this, childBox, pointInContents); + } + } else { + for (RenderBox* childBox = firstChildBox(); childBox; childBox = childBox->nextSiblingBox()) { + // We hit child if our click is above the bottom of its padding box (like IE6/7 and FF3). + if (isChildHitTestCandidate(childBox) && pointInContents.y() < childBox->logicalBottom()) + return positionForPointRespectingEditingBoundaries(this, childBox, pointInContents); + } + } + + // We only get here if there are no hit test candidate children below the click. + return RenderBox::positionForPoint(point); +} + +void RenderBlock::offsetForContents(int& tx, int& ty) const +{ + IntPoint contentsPoint(tx, ty); + + if (hasOverflowClip()) + contentsPoint += layer()->scrolledContentOffset(); + + if (hasColumns()) + adjustPointToColumnContents(contentsPoint); + + tx = contentsPoint.x(); + ty = contentsPoint.y(); +} + +int RenderBlock::availableLogicalWidth() const +{ + // If we have multiple columns, then the available logical width is reduced to our column width. + if (hasColumns()) + return desiredColumnWidth(); + return RenderBox::availableLogicalWidth(); +} + +int RenderBlock::columnGap() const +{ + if (style()->hasNormalColumnGap()) + return style()->fontDescription().computedPixelSize(); // "1em" is recommended as the normal gap setting. Matches <p> margins. + return static_cast<int>(style()->columnGap()); +} + +void RenderBlock::calcColumnWidth() +{ + // Calculate our column width and column count. + unsigned desiredColumnCount = 1; + int desiredColumnWidth = contentWidth(); + + // For now, we don't support multi-column layouts when printing, since we have to do a lot of work for proper pagination. + if (document()->paginated() || (style()->hasAutoColumnCount() && style()->hasAutoColumnWidth())) { + setDesiredColumnCountAndWidth(desiredColumnCount, desiredColumnWidth); + return; + } + + int availWidth = desiredColumnWidth; + int colGap = columnGap(); + int colWidth = max(1, static_cast<int>(style()->columnWidth())); + int colCount = max(1, static_cast<int>(style()->columnCount())); + + if (style()->hasAutoColumnWidth()) { + if ((colCount - 1) * colGap < availWidth) { + desiredColumnCount = colCount; + desiredColumnWidth = (availWidth - (desiredColumnCount - 1) * colGap) / desiredColumnCount; + } else if (colGap < availWidth) { + desiredColumnCount = availWidth / colGap; + if (desiredColumnCount < 1) + desiredColumnCount = 1; + desiredColumnWidth = (availWidth - (desiredColumnCount - 1) * colGap) / desiredColumnCount; + } + } else if (style()->hasAutoColumnCount()) { + if (colWidth < availWidth) { + desiredColumnCount = (availWidth + colGap) / (colWidth + colGap); + if (desiredColumnCount < 1) + desiredColumnCount = 1; + desiredColumnWidth = (availWidth - (desiredColumnCount - 1) * colGap) / desiredColumnCount; + } + } else { + // Both are set. + if (colCount * colWidth + (colCount - 1) * colGap <= availWidth) { + desiredColumnCount = colCount; + desiredColumnWidth = colWidth; + } else if (colWidth < availWidth) { + desiredColumnCount = (availWidth + colGap) / (colWidth + colGap); + if (desiredColumnCount < 1) + desiredColumnCount = 1; + desiredColumnWidth = (availWidth - (desiredColumnCount - 1) * colGap) / desiredColumnCount; + } + } + setDesiredColumnCountAndWidth(desiredColumnCount, desiredColumnWidth); +} + +void RenderBlock::setDesiredColumnCountAndWidth(int count, int width) +{ + bool destroyColumns = !firstChild() + || (count == 1 && style()->hasAutoColumnWidth()) + || firstChild()->isAnonymousColumnsBlock() + || firstChild()->isAnonymousColumnSpanBlock(); + if (destroyColumns) { + if (hasColumns()) { + delete gColumnInfoMap->take(this); + setHasColumns(false); + } + } else { + ColumnInfo* info; + if (hasColumns()) + info = gColumnInfoMap->get(this); + else { + if (!gColumnInfoMap) + gColumnInfoMap = new ColumnInfoMap; + info = new ColumnInfo; + gColumnInfoMap->add(this, info); + setHasColumns(true); + } + info->setDesiredColumnCount(count); + info->setDesiredColumnWidth(width); + } +} + +int RenderBlock::desiredColumnWidth() const +{ + if (!hasColumns()) + return contentWidth(); + return gColumnInfoMap->get(this)->desiredColumnWidth(); +} + +unsigned RenderBlock::desiredColumnCount() const +{ + if (!hasColumns()) + return 1; + return gColumnInfoMap->get(this)->desiredColumnCount(); +} + +ColumnInfo* RenderBlock::columnInfo() const +{ + if (!hasColumns()) + return 0; + return gColumnInfoMap->get(this); +} + +unsigned RenderBlock::columnCount(ColumnInfo* colInfo) const +{ + ASSERT(hasColumns() && gColumnInfoMap->get(this) == colInfo); + return colInfo->columnCount(); +} + +IntRect RenderBlock::columnRectAt(ColumnInfo* colInfo, unsigned index) const +{ + ASSERT(hasColumns() && gColumnInfoMap->get(this) == colInfo); + + // Compute the appropriate rect based off our information. + int colWidth = colInfo->desiredColumnWidth(); + int colHeight = colInfo->columnHeight(); + int colTop = borderTop() + paddingTop(); + int colGap = columnGap(); + int colLeft = style()->isLeftToRightDirection() ? + borderLeft() + paddingLeft() + (index * (colWidth + colGap)) + : borderLeft() + paddingLeft() + contentWidth() - colWidth - (index * (colWidth + colGap)); + return IntRect(colLeft, colTop, colWidth, colHeight); +} + +bool RenderBlock::layoutColumns(bool hasSpecifiedPageLogicalHeight, int pageLogicalHeight, LayoutStateMaintainer& statePusher) +{ + if (!hasColumns()) + return false; + + // FIXME: We don't balance properly at all in the presence of forced page breaks. We need to understand what + // the distance between forced page breaks is so that we can avoid making the minimum column height too tall. + ColumnInfo* colInfo = columnInfo(); + int desiredColumnCount = colInfo->desiredColumnCount(); + if (!hasSpecifiedPageLogicalHeight) { + int columnHeight = pageLogicalHeight; + int minColumnCount = colInfo->forcedBreaks() + 1; + if (minColumnCount >= desiredColumnCount) { + // The forced page breaks are in control of the balancing. Just set the column height to the + // maximum page break distance. + if (!pageLogicalHeight) { + int distanceBetweenBreaks = max(colInfo->maximumDistanceBetweenForcedBreaks(), + view()->layoutState()->pageLogicalOffset(borderTop() + paddingTop() + contentHeight()) - colInfo->forcedBreakOffset()); + columnHeight = max(colInfo->minimumColumnHeight(), distanceBetweenBreaks); + } + } else if (contentHeight() > pageLogicalHeight * desiredColumnCount) { + // Now that we know the intrinsic height of the columns, we have to rebalance them. + columnHeight = max(colInfo->minimumColumnHeight(), (int)ceilf((float)contentHeight() / desiredColumnCount)); + } + + if (columnHeight && columnHeight != pageLogicalHeight) { + statePusher.pop(); + m_everHadLayout = true; + layoutBlock(false, columnHeight); + return true; + } + } + + if (pageLogicalHeight) + colInfo->setColumnCountAndHeight(ceilf((float)contentHeight() / pageLogicalHeight), pageLogicalHeight); + + if (columnCount(colInfo)) { + setLogicalHeight(borderTop() + paddingTop() + colInfo->columnHeight() + borderBottom() + paddingBottom() + horizontalScrollbarHeight()); + m_overflow.clear(); + } + + return false; +} + +void RenderBlock::adjustPointToColumnContents(IntPoint& point) const +{ + // Just bail if we have no columns. + if (!hasColumns()) + return; + + ColumnInfo* colInfo = columnInfo(); + if (!columnCount(colInfo)) + return; + + // Determine which columns we intersect. + int colGap = columnGap(); + int leftGap = colGap / 2; + IntPoint columnPoint(columnRectAt(colInfo, 0).location()); + int yOffset = 0; + for (unsigned i = 0; i < colInfo->columnCount(); i++) { + // Add in half the column gap to the left and right of the rect. + IntRect colRect = columnRectAt(colInfo, i); + IntRect gapAndColumnRect(colRect.x() - leftGap, colRect.y(), colRect.width() + colGap, colRect.height()); + + if (point.x() >= gapAndColumnRect.x() && point.x() < gapAndColumnRect.right()) { + // FIXME: The clamping that follows is not completely right for right-to-left + // content. + // Clamp everything above the column to its top left. + if (point.y() < gapAndColumnRect.y()) + point = gapAndColumnRect.location(); + // Clamp everything below the column to the next column's top left. If there is + // no next column, this still maps to just after this column. + else if (point.y() >= gapAndColumnRect.bottom()) { + point = gapAndColumnRect.location(); + point.move(0, gapAndColumnRect.height()); + } + + // We're inside the column. Translate the x and y into our column coordinate space. + point.move(columnPoint.x() - colRect.x(), yOffset); + return; + } + + // Move to the next position. + yOffset += colRect.height(); + } +} + +void RenderBlock::adjustRectForColumns(IntRect& r) const +{ + // Just bail if we have no columns. + if (!hasColumns()) + return; + + ColumnInfo* colInfo = columnInfo(); + + // Begin with a result rect that is empty. + IntRect result; + + // Determine which columns we intersect. + unsigned colCount = columnCount(colInfo); + if (!colCount) + return; + + int left = borderLeft() + paddingLeft(); + + int currYOffset = 0; + for (unsigned i = 0; i < colCount; i++) { + IntRect colRect = columnRectAt(colInfo, i); + int currXOffset = colRect.x() - left; + + IntRect repaintRect = r; + repaintRect.move(currXOffset, currYOffset); + + repaintRect.intersect(colRect); + + result.unite(repaintRect); + + // Move to the next position. + currYOffset -= colRect.height(); + } + + r = result; +} + +void RenderBlock::adjustForColumns(IntSize& offset, const IntPoint& point) const +{ + if (!hasColumns()) + return; + + ColumnInfo* colInfo = columnInfo(); + + int left = borderLeft() + paddingLeft(); + int yOffset = 0; + size_t colCount = columnCount(colInfo); + for (size_t i = 0; i < colCount; ++i) { + IntRect columnRect = columnRectAt(colInfo, i); + int xOffset = columnRect.x() - left; + if (point.y() < columnRect.bottom() + yOffset) { + offset.expand(xOffset, -yOffset); + return; + } + + yOffset += columnRect.height(); + } +} + +void RenderBlock::computePreferredLogicalWidths() +{ + ASSERT(preferredLogicalWidthsDirty()); + + updateFirstLetter(); + + if (!isTableCell() && style()->logicalWidth().isFixed() && style()->logicalWidth().value() > 0) + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->logicalWidth().value()); + else { + m_minPreferredLogicalWidth = 0; + m_maxPreferredLogicalWidth = 0; + + if (childrenInline()) + computeInlinePreferredLogicalWidths(); + else + computeBlockPreferredLogicalWidths(); + + m_maxPreferredLogicalWidth = max(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth); + + if (!style()->autoWrap() && childrenInline()) { + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth; + + // A horizontal marquee with inline children has no minimum width. + if (layer() && layer()->marquee() && layer()->marquee()->isHorizontal()) + m_minPreferredLogicalWidth = 0; + } + + int scrollbarWidth = 0; + if (hasOverflowClip() && style()->overflowY() == OSCROLL) { + layer()->setHasVerticalScrollbar(true); + scrollbarWidth = verticalScrollbarWidth(); + m_maxPreferredLogicalWidth += scrollbarWidth; + } + + if (isTableCell()) { + Length w = toRenderTableCell(this)->styleOrColLogicalWidth(); + if (w.isFixed() && w.value() > 0) { + m_maxPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(w.value())); + scrollbarWidth = 0; + } + } + + m_minPreferredLogicalWidth += scrollbarWidth; + } + + if (style()->logicalMinWidth().isFixed() && style()->logicalMinWidth().value() > 0) { + m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->logicalMinWidth().value())); + m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->logicalMinWidth().value())); + } + + if (style()->logicalMaxWidth().isFixed() && style()->logicalMaxWidth().value() != undefinedLength) { + m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->logicalMaxWidth().value())); + m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->logicalMaxWidth().value())); + } + + int borderAndPadding = borderAndPaddingLogicalWidth(); + m_minPreferredLogicalWidth += borderAndPadding; + m_maxPreferredLogicalWidth += borderAndPadding; + + setPreferredLogicalWidthsDirty(false); +} + +struct InlineMinMaxIterator { +/* InlineMinMaxIterator is a class that will iterate over all render objects that contribute to + inline min/max width calculations. Note the following about the way it walks: + (1) Positioned content is skipped (since it does not contribute to min/max width of a block) + (2) We do not drill into the children of floats or replaced elements, since you can't break + in the middle of such an element. + (3) Inline flows (e.g., <a>, <span>, <i>) are walked twice, since each side can have + distinct borders/margin/padding that contribute to the min/max width. +*/ + RenderObject* parent; + RenderObject* current; + bool endOfInline; + + InlineMinMaxIterator(RenderObject* p, bool end = false) + :parent(p), current(p), endOfInline(end) {} + + RenderObject* next(); +}; + +RenderObject* InlineMinMaxIterator::next() +{ + RenderObject* result = 0; + bool oldEndOfInline = endOfInline; + endOfInline = false; + while (current || current == parent) { + if (!oldEndOfInline && + (current == parent || + (!current->isFloating() && !current->isReplaced() && !current->isPositioned()))) + result = current->firstChild(); + if (!result) { + // We hit the end of our inline. (It was empty, e.g., <span></span>.) + if (!oldEndOfInline && current->isRenderInline()) { + result = current; + endOfInline = true; + break; + } + + while (current && current != parent) { + result = current->nextSibling(); + if (result) break; + current = current->parent(); + if (current && current != parent && current->isRenderInline()) { + result = current; + endOfInline = true; + break; + } + } + } + + if (!result) + break; + + if (!result->isPositioned() && (result->isText() || result->isFloating() || result->isReplaced() || result->isRenderInline())) + break; + + current = result; + result = 0; + } + + // Update our position. + current = result; + return current; +} + +static int getBPMWidth(int childValue, Length cssUnit) +{ + if (cssUnit.type() != Auto) + return (cssUnit.isFixed() ? cssUnit.value() : childValue); + return 0; +} + +static int getBorderPaddingMargin(const RenderBoxModelObject* child, bool endOfInline) +{ + RenderStyle* cstyle = child->style(); + int result = 0; + bool leftSide = (cstyle->isLeftToRightDirection()) ? !endOfInline : endOfInline; + result += getBPMWidth((leftSide ? child->marginLeft() : child->marginRight()), + (leftSide ? cstyle->marginLeft() : + cstyle->marginRight())); + result += getBPMWidth((leftSide ? child->paddingLeft() : child->paddingRight()), + (leftSide ? cstyle->paddingLeft() : + cstyle->paddingRight())); + result += leftSide ? child->borderLeft() : child->borderRight(); + return result; +} + +static inline void stripTrailingSpace(int& inlineMax, int& inlineMin, + RenderObject* trailingSpaceChild) +{ + if (trailingSpaceChild && trailingSpaceChild->isText()) { + // Collapse away the trailing space at the end of a block. + RenderText* t = toRenderText(trailingSpaceChild); + const UChar space = ' '; + const Font& font = t->style()->font(); // FIXME: This ignores first-line. + int spaceWidth = font.width(TextRun(&space, 1)); + inlineMax -= spaceWidth + font.wordSpacing(); + if (inlineMin > inlineMax) + inlineMin = inlineMax; + } +} + +void RenderBlock::computeInlinePreferredLogicalWidths() +{ + int inlineMax = 0; + int inlineMin = 0; + + int cw = containingBlock()->contentWidth(); + + // If we are at the start of a line, we want to ignore all white-space. + // Also strip spaces if we previously had text that ended in a trailing space. + bool stripFrontSpaces = true; + RenderObject* trailingSpaceChild = 0; + + // Firefox and Opera will allow a table cell to grow to fit an image inside it under + // very specific cirucumstances (in order to match common WinIE renderings). + // Not supporting the quirk has caused us to mis-render some real sites. (See Bugzilla 10517.) + bool allowImagesToBreak = !document()->inQuirksMode() || !isTableCell() || !style()->width().isIntrinsicOrAuto(); + + bool autoWrap, oldAutoWrap; + autoWrap = oldAutoWrap = style()->autoWrap(); + + InlineMinMaxIterator childIterator(this); + bool addedTextIndent = false; // Only gets added in once. + RenderObject* prevFloat = 0; + while (RenderObject* child = childIterator.next()) { + autoWrap = child->isReplaced() ? child->parent()->style()->autoWrap() : + child->style()->autoWrap(); + + if (!child->isBR()) { + // Step One: determine whether or not we need to go ahead and + // terminate our current line. Each discrete chunk can become + // the new min-width, if it is the widest chunk seen so far, and + // it can also become the max-width. + + // Children fall into three categories: + // (1) An inline flow object. These objects always have a min/max of 0, + // and are included in the iteration solely so that their margins can + // be added in. + // + // (2) An inline non-text non-flow object, e.g., an inline replaced element. + // These objects can always be on a line by themselves, so in this situation + // we need to go ahead and break the current line, and then add in our own + // margins and min/max width on its own line, and then terminate the line. + // + // (3) A text object. Text runs can have breakable characters at the start, + // the middle or the end. They may also lose whitespace off the front if + // we're already ignoring whitespace. In order to compute accurate min-width + // information, we need three pieces of information. + // (a) the min-width of the first non-breakable run. Should be 0 if the text string + // starts with whitespace. + // (b) the min-width of the last non-breakable run. Should be 0 if the text string + // ends with whitespace. + // (c) the min/max width of the string (trimmed for whitespace). + // + // If the text string starts with whitespace, then we need to go ahead and + // terminate our current line (unless we're already in a whitespace stripping + // mode. + // + // If the text string has a breakable character in the middle, but didn't start + // with whitespace, then we add the width of the first non-breakable run and + // then end the current line. We then need to use the intermediate min/max width + // values (if any of them are larger than our current min/max). We then look at + // the width of the last non-breakable run and use that to start a new line + // (unless we end in whitespace). + RenderStyle* cstyle = child->style(); + int childMin = 0; + int childMax = 0; + + if (!child->isText()) { + // Case (1) and (2). Inline replaced and inline flow elements. + if (child->isRenderInline()) { + // Add in padding/border/margin from the appropriate side of + // the element. + int bpm = getBorderPaddingMargin(toRenderInline(child), childIterator.endOfInline); + childMin += bpm; + childMax += bpm; + + inlineMin += childMin; + inlineMax += childMax; + + child->setPreferredLogicalWidthsDirty(false); + } else { + // Inline replaced elts add in their margins to their min/max values. + int margins = 0; + Length leftMargin = cstyle->marginLeft(); + Length rightMargin = cstyle->marginRight(); + if (leftMargin.isFixed()) + margins += leftMargin.value(); + if (rightMargin.isFixed()) + margins += rightMargin.value(); + childMin += margins; + childMax += margins; + } + } + + if (!child->isRenderInline() && !child->isText()) { + // Case (2). Inline replaced elements and floats. + // Go ahead and terminate the current line as far as + // minwidth is concerned. + childMin += child->minPreferredLogicalWidth(); + childMax += child->maxPreferredLogicalWidth(); + + bool clearPreviousFloat; + if (child->isFloating()) { + clearPreviousFloat = (prevFloat + && ((prevFloat->style()->floating() == FLEFT && (child->style()->clear() & CLEFT)) + || (prevFloat->style()->floating() == FRIGHT && (child->style()->clear() & CRIGHT)))); + prevFloat = child; + } else + clearPreviousFloat = false; + + bool canBreakReplacedElement = !child->isImage() || allowImagesToBreak; + if ((canBreakReplacedElement && (autoWrap || oldAutoWrap)) || clearPreviousFloat) { + m_minPreferredLogicalWidth = max(inlineMin, m_minPreferredLogicalWidth); + inlineMin = 0; + } + + // If we're supposed to clear the previous float, then terminate maxwidth as well. + if (clearPreviousFloat) { + m_maxPreferredLogicalWidth = max(inlineMax, m_maxPreferredLogicalWidth); + inlineMax = 0; + } + + // Add in text-indent. This is added in only once. + int ti = 0; + if (!addedTextIndent) { + addedTextIndent = true; + ti = style()->textIndent().calcMinValue(cw); + childMin+=ti; + childMax+=ti; + } + + // Add our width to the max. + inlineMax += childMax; + + if (!autoWrap || !canBreakReplacedElement) { + if (child->isFloating()) + m_minPreferredLogicalWidth = max(childMin, m_minPreferredLogicalWidth); + else + inlineMin += childMin; + } else { + // Now check our line. + m_minPreferredLogicalWidth = max(childMin, m_minPreferredLogicalWidth); + + // Now start a new line. + inlineMin = 0; + } + + // We are no longer stripping whitespace at the start of + // a line. + if (!child->isFloating()) { + stripFrontSpaces = false; + trailingSpaceChild = 0; + } + } else if (child->isText()) { + // Case (3). Text. + RenderText* t = toRenderText(child); + + if (t->isWordBreak()) { + m_minPreferredLogicalWidth = max(inlineMin, m_minPreferredLogicalWidth); + inlineMin = 0; + continue; + } + + // Determine if we have a breakable character. Pass in + // whether or not we should ignore any spaces at the front + // of the string. If those are going to be stripped out, + // then they shouldn't be considered in the breakable char + // check. + bool hasBreakableChar, hasBreak; + int beginMin, endMin; + bool beginWS, endWS; + int beginMax, endMax; + t->trimmedPrefWidths(inlineMax, beginMin, beginWS, endMin, endWS, + hasBreakableChar, hasBreak, beginMax, endMax, + childMin, childMax, stripFrontSpaces); + + // This text object will not be rendered, but it may still provide a breaking opportunity. + if (!hasBreak && childMax == 0) { + if (autoWrap && (beginWS || endWS)) { + m_minPreferredLogicalWidth = max(inlineMin, m_minPreferredLogicalWidth); + inlineMin = 0; + } + continue; + } + + if (stripFrontSpaces) + trailingSpaceChild = child; + else + trailingSpaceChild = 0; + + // Add in text-indent. This is added in only once. + int ti = 0; + if (!addedTextIndent) { + addedTextIndent = true; + ti = style()->textIndent().calcMinValue(cw); + childMin+=ti; beginMin += ti; + childMax+=ti; beginMax += ti; + } + + // If we have no breakable characters at all, + // then this is the easy case. We add ourselves to the current + // min and max and continue. + if (!hasBreakableChar) { + inlineMin += childMin; + } else { + // We have a breakable character. Now we need to know if + // we start and end with whitespace. + if (beginWS) + // Go ahead and end the current line. + m_minPreferredLogicalWidth = max(inlineMin, m_minPreferredLogicalWidth); + else { + inlineMin += beginMin; + m_minPreferredLogicalWidth = max(inlineMin, m_minPreferredLogicalWidth); + childMin -= ti; + } + + inlineMin = childMin; + + if (endWS) { + // We end in whitespace, which means we can go ahead + // and end our current line. + m_minPreferredLogicalWidth = max(inlineMin, m_minPreferredLogicalWidth); + inlineMin = 0; + } else { + m_minPreferredLogicalWidth = max(inlineMin, m_minPreferredLogicalWidth); + inlineMin = endMin; + } + } + + if (hasBreak) { + inlineMax += beginMax; + m_maxPreferredLogicalWidth = max(inlineMax, m_maxPreferredLogicalWidth); + m_maxPreferredLogicalWidth = max(childMax, m_maxPreferredLogicalWidth); + inlineMax = endMax; + } else + inlineMax += childMax; + } + + // Ignore spaces after a list marker. + if (child->isListMarker()) + stripFrontSpaces = true; + } else { + m_minPreferredLogicalWidth = max(inlineMin, m_minPreferredLogicalWidth); + m_maxPreferredLogicalWidth = max(inlineMax, m_maxPreferredLogicalWidth); + inlineMin = inlineMax = 0; + stripFrontSpaces = true; + trailingSpaceChild = 0; + } + + oldAutoWrap = autoWrap; + } + + if (style()->collapseWhiteSpace()) + stripTrailingSpace(inlineMax, inlineMin, trailingSpaceChild); + + m_minPreferredLogicalWidth = max(inlineMin, m_minPreferredLogicalWidth); + m_maxPreferredLogicalWidth = max(inlineMax, m_maxPreferredLogicalWidth); +} + +// Use a very large value (in effect infinite). +#define BLOCK_MAX_WIDTH 15000 + +void RenderBlock::computeBlockPreferredLogicalWidths() +{ + bool nowrap = style()->whiteSpace() == NOWRAP; + + RenderObject *child = firstChild(); + int floatLeftWidth = 0, floatRightWidth = 0; + while (child) { + // Positioned children don't affect the min/max width + if (child->isPositioned()) { + child = child->nextSibling(); + continue; + } + + if (child->isFloating() || (child->isBox() && toRenderBox(child)->avoidsFloats())) { + int floatTotalWidth = floatLeftWidth + floatRightWidth; + if (child->style()->clear() & CLEFT) { + m_maxPreferredLogicalWidth = max(floatTotalWidth, m_maxPreferredLogicalWidth); + floatLeftWidth = 0; + } + if (child->style()->clear() & CRIGHT) { + m_maxPreferredLogicalWidth = max(floatTotalWidth, m_maxPreferredLogicalWidth); + floatRightWidth = 0; + } + } + + // A margin basically has three types: fixed, percentage, and auto (variable). + // Auto and percentage margins simply become 0 when computing min/max width. + // Fixed margins can be added in as is. + Length ml = child->style()->marginLeft(); + Length mr = child->style()->marginRight(); + int margin = 0, marginLeft = 0, marginRight = 0; + if (ml.isFixed()) + marginLeft += ml.value(); + if (mr.isFixed()) + marginRight += mr.value(); + margin = marginLeft + marginRight; + + int w = child->minPreferredLogicalWidth() + margin; + m_minPreferredLogicalWidth = max(w, m_minPreferredLogicalWidth); + + // IE ignores tables for calculation of nowrap. Makes some sense. + if (nowrap && !child->isTable()) + m_maxPreferredLogicalWidth = max(w, m_maxPreferredLogicalWidth); + + w = child->maxPreferredLogicalWidth() + margin; + + if (!child->isFloating()) { + if (child->isBox() && toRenderBox(child)->avoidsFloats()) { + // Determine a left and right max value based off whether or not the floats can fit in the + // margins of the object. For negative margins, we will attempt to overlap the float if the negative margin + // is smaller than the float width. + int maxLeft = marginLeft > 0 ? max(floatLeftWidth, marginLeft) : floatLeftWidth + marginLeft; + int maxRight = marginRight > 0 ? max(floatRightWidth, marginRight) : floatRightWidth + marginRight; + w = child->maxPreferredLogicalWidth() + maxLeft + maxRight; + w = max(w, floatLeftWidth + floatRightWidth); + } + else + m_maxPreferredLogicalWidth = max(floatLeftWidth + floatRightWidth, m_maxPreferredLogicalWidth); + floatLeftWidth = floatRightWidth = 0; + } + + if (child->isFloating()) { + if (style()->floating() == FLEFT) + floatLeftWidth += w; + else + floatRightWidth += w; + } else + m_maxPreferredLogicalWidth = max(w, m_maxPreferredLogicalWidth); + + // A very specific WinIE quirk. + // Example: + /* + <div style="position:absolute; width:100px; top:50px;"> + <div style="position:absolute;left:0px;top:50px;height:50px;background-color:green"> + <table style="width:100%"><tr><td></table> + </div> + </div> + */ + // In the above example, the inner absolute positioned block should have a computed width + // of 100px because of the table. + // We can achieve this effect by making the maxwidth of blocks that contain tables + // with percentage widths be infinite (as long as they are not inside a table cell). + if (document()->inQuirksMode() && child->style()->width().isPercent() && + !isTableCell() && child->isTable() && m_maxPreferredLogicalWidth < BLOCK_MAX_WIDTH) { + RenderBlock* cb = containingBlock(); + while (!cb->isRenderView() && !cb->isTableCell()) + cb = cb->containingBlock(); + if (!cb->isTableCell()) + m_maxPreferredLogicalWidth = BLOCK_MAX_WIDTH; + } + + child = child->nextSibling(); + } + + // Always make sure these values are non-negative. + m_minPreferredLogicalWidth = max(0, m_minPreferredLogicalWidth); + m_maxPreferredLogicalWidth = max(0, m_maxPreferredLogicalWidth); + + m_maxPreferredLogicalWidth = max(floatLeftWidth + floatRightWidth, m_maxPreferredLogicalWidth); +} + +bool RenderBlock::hasLineIfEmpty() const +{ + if (!node()) + return false; + + if (node()->isContentEditable() && node()->rootEditableElement() == node()) + return true; + + if (node()->isShadowRoot() && (node()->shadowHost()->hasTagName(inputTag))) + return true; + + return false; +} + +int RenderBlock::lineHeight(bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const +{ + // Inline blocks are replaced elements. Otherwise, just pass off to + // the base class. If we're being queried as though we're the root line + // box, then the fact that we're an inline-block is irrelevant, and we behave + // just like a block. + if (isReplaced() && linePositionMode == PositionOnContainingLine) + return RenderBox::lineHeight(firstLine, direction, linePositionMode); + + if (firstLine && document()->usesFirstLineRules()) { + RenderStyle* s = style(firstLine); + if (s != style()) + return s->computedLineHeight(); + } + + if (m_lineHeight == -1) + m_lineHeight = style()->computedLineHeight(); + + return m_lineHeight; +} + +int RenderBlock::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const +{ + // Inline blocks are replaced elements. Otherwise, just pass off to + // the base class. If we're being queried as though we're the root line + // box, then the fact that we're an inline-block is irrelevant, and we behave + // just like a block. + if (isReplaced() && linePositionMode == PositionOnContainingLine) { + // For "leaf" theme objects, let the theme decide what the baseline position is. + // FIXME: Might be better to have a custom CSS property instead, so that if the theme + // is turned off, checkboxes/radios will still have decent baselines. + // FIXME: Need to patch form controls to deal with vertical lines. + if (style()->hasAppearance() && !theme()->isControlContainer(style()->appearance())) + return theme()->baselinePosition(this); + + // CSS2.1 states that the baseline of an inline block is the baseline of the last line box in + // the normal flow. We make an exception for marquees, since their baselines are meaningless + // (the content inside them moves). This matches WinIE as well, which just bottom-aligns them. + // We also give up on finding a baseline if we have a vertical scrollbar, or if we are scrolled + // vertically (e.g., an overflow:hidden block that has had scrollTop moved) or if the baseline is outside + // of our content box. + bool ignoreBaseline = (layer() && (layer()->marquee() || (direction == HorizontalLine ? (layer()->verticalScrollbar() || layer()->scrollYOffset() != 0) + : (layer()->horizontalScrollbar() || layer()->scrollXOffset() != 0)))) || (isWritingModeRoot() && !isRubyRun()); + + int baselinePos = ignoreBaseline ? -1 : lastLineBoxBaseline(); + + int bottomOfContent = direction == HorizontalLine ? borderTop() + paddingTop() + contentHeight() : borderRight() + paddingRight() + contentWidth(); + if (baselinePos != -1 && baselinePos <= bottomOfContent) + return direction == HorizontalLine ? marginTop() + baselinePos : marginRight() + baselinePos; + + return RenderBox::baselinePosition(baselineType, firstLine, direction, linePositionMode); + } + + const Font& f = style(firstLine)->font(); + return f.ascent(baselineType) + (lineHeight(firstLine, direction, linePositionMode) - f.height()) / 2; +} + +int RenderBlock::firstLineBoxBaseline() const +{ + if (!isBlockFlow() || (isWritingModeRoot() && !isRubyRun())) + return -1; + + if (childrenInline()) { + if (firstLineBox()) + return firstLineBox()->logicalTop() + style(true)->font().ascent(firstRootBox()->baselineType()); + else + return -1; + } + else { + for (RenderBox* curr = firstChildBox(); curr; curr = curr->nextSiblingBox()) { + if (!curr->isFloatingOrPositioned()) { + int result = curr->firstLineBoxBaseline(); + if (result != -1) + return curr->logicalTop() + result; // Translate to our coordinate space. + } + } + } + + return -1; +} + +int RenderBlock::lastLineBoxBaseline() const +{ + if (!isBlockFlow() || (isWritingModeRoot() && !isRubyRun())) + return -1; + + LineDirectionMode lineDirection = style()->isHorizontalWritingMode() ? HorizontalLine : VerticalLine; + + if (childrenInline()) { + if (!firstLineBox() && hasLineIfEmpty()) { + const Font& f = firstLineStyle()->font(); + return f.ascent() + (lineHeight(true, lineDirection, PositionOfInteriorLineBoxes) - f.height()) / 2 + (lineDirection == HorizontalLine ? borderTop() + paddingTop() : borderRight() + paddingRight()); + } + if (lastLineBox()) + return lastLineBox()->logicalTop() + style(lastLineBox() == firstLineBox())->font().ascent(lastRootBox()->baselineType()); + return -1; + } else { + bool haveNormalFlowChild = false; + for (RenderBox* curr = lastChildBox(); curr; curr = curr->previousSiblingBox()) { + if (!curr->isFloatingOrPositioned()) { + haveNormalFlowChild = true; + int result = curr->lastLineBoxBaseline(); + if (result != -1) + return curr->logicalTop() + result; // Translate to our coordinate space. + } + } + if (!haveNormalFlowChild && hasLineIfEmpty()) { + const Font& f = firstLineStyle()->font(); + return f.ascent() + (lineHeight(true, lineDirection, PositionOfInteriorLineBoxes) - f.height()) / 2 + (lineDirection == HorizontalLine ? borderTop() + paddingTop() : borderRight() + paddingRight()); + } + } + + return -1; +} + +bool RenderBlock::containsNonZeroBidiLevel() const +{ + for (RootInlineBox* root = firstRootBox(); root; root = root->nextRootBox()) { + for (InlineBox* box = root->firstLeafChild(); box; box = box->nextLeafChild()) { + if (box->bidiLevel()) + return true; + } + } + return false; +} + +RenderBlock* RenderBlock::firstLineBlock() const +{ + RenderBlock* firstLineBlock = const_cast<RenderBlock*>(this); + bool hasPseudo = false; + while (true) { + hasPseudo = firstLineBlock->style()->hasPseudoStyle(FIRST_LINE); + if (hasPseudo) + break; + RenderObject* parentBlock = firstLineBlock->parent(); + if (firstLineBlock->isReplaced() || firstLineBlock->isFloating() || + !parentBlock || parentBlock->firstChild() != firstLineBlock || !parentBlock->isBlockFlow()) + break; + ASSERT(parentBlock->isRenderBlock()); + firstLineBlock = toRenderBlock(parentBlock); + } + + if (!hasPseudo) + return 0; + + return firstLineBlock; +} + +static RenderStyle* styleForFirstLetter(RenderObject* firstLetterBlock, RenderObject* firstLetterContainer) +{ + RenderStyle* pseudoStyle = firstLetterBlock->getCachedPseudoStyle(FIRST_LETTER, firstLetterContainer->firstLineStyle()); + // Force inline display (except for floating first-letters). + pseudoStyle->setDisplay(pseudoStyle->isFloating() ? BLOCK : INLINE); + // CSS2 says first-letter can't be positioned. + pseudoStyle->setPosition(StaticPosition); + return pseudoStyle; +} + +// CSS 2.1 http://www.w3.org/TR/CSS21/selector.html#first-letter +// "Punctuation (i.e, characters defined in Unicode [UNICODE] in the "open" (Ps), "close" (Pe), +// "initial" (Pi). "final" (Pf) and "other" (Po) punctuation classes), that precedes or follows the first letter should be included" +static inline bool isPunctuationForFirstLetter(UChar c) +{ + CharCategory charCategory = category(c); + return charCategory == Punctuation_Open + || charCategory == Punctuation_Close + || charCategory == Punctuation_InitialQuote + || charCategory == Punctuation_FinalQuote + || charCategory == Punctuation_Other; +} + +static inline bool shouldSkipForFirstLetter(UChar c) +{ + return isSpaceOrNewline(c) || c == noBreakSpace || isPunctuationForFirstLetter(c); +} + +void RenderBlock::updateFirstLetter() +{ + if (!document()->usesFirstLetterRules()) + return; + // Don't recur + if (style()->styleType() == FIRST_LETTER) + return; + + // FIXME: We need to destroy the first-letter object if it is no longer the first child. Need to find + // an efficient way to check for that situation though before implementing anything. + RenderObject* firstLetterBlock = this; + bool hasPseudoStyle = false; + while (true) { + // We only honor first-letter if the firstLetterBlock can have children in the DOM. This correctly + // prevents form controls from honoring first-letter. + hasPseudoStyle = firstLetterBlock->style()->hasPseudoStyle(FIRST_LETTER) + && firstLetterBlock->canHaveChildren(); + if (hasPseudoStyle) + break; + RenderObject* parentBlock = firstLetterBlock->parent(); + if (firstLetterBlock->isReplaced() || !parentBlock || parentBlock->firstChild() != firstLetterBlock || + !parentBlock->isBlockFlow()) + break; + firstLetterBlock = parentBlock; + } + + if (!hasPseudoStyle) + return; + + // Drill into inlines looking for our first text child. + RenderObject* currChild = firstLetterBlock->firstChild(); + while (currChild && ((!currChild->isReplaced() && !currChild->isRenderButton() && !currChild->isMenuList()) || currChild->isFloatingOrPositioned()) && !currChild->isText()) { + if (currChild->isFloatingOrPositioned()) { + if (currChild->style()->styleType() == FIRST_LETTER) { + currChild = currChild->firstChild(); + break; + } + currChild = currChild->nextSibling(); + } else + currChild = currChild->firstChild(); + } + + // Get list markers out of the way. + while (currChild && currChild->isListMarker()) + currChild = currChild->nextSibling(); + + if (!currChild) + return; + + // If the child already has style, then it has already been created, so we just want + // to update it. + if (currChild->parent()->style()->styleType() == FIRST_LETTER) { + RenderObject* firstLetter = currChild->parent(); + RenderObject* firstLetterContainer = firstLetter->parent(); + RenderStyle* pseudoStyle = styleForFirstLetter(firstLetterBlock, firstLetterContainer); + + if (Node::diff(firstLetter->style(), pseudoStyle) == Node::Detach) { + // The first-letter renderer needs to be replaced. Create a new renderer of the right type. + RenderObject* newFirstLetter; + if (pseudoStyle->display() == INLINE) + newFirstLetter = new (renderArena()) RenderInline(document()); + else + newFirstLetter = new (renderArena()) RenderBlock(document()); + newFirstLetter->setStyle(pseudoStyle); + + // Move the first letter into the new renderer. + view()->disableLayoutState(); + while (RenderObject* child = firstLetter->firstChild()) { + if (child->isText()) + toRenderText(child)->dirtyLineBoxes(true); + firstLetter->removeChild(child); + newFirstLetter->addChild(child, 0); + } + RenderTextFragment* remainingText = toRenderTextFragment(firstLetter->nextSibling()); + ASSERT(remainingText->node()->renderer() == remainingText); + // Replace the old renderer with the new one. + remainingText->setFirstLetter(newFirstLetter); + firstLetter->destroy(); + firstLetter = newFirstLetter; + firstLetterContainer->addChild(firstLetter, remainingText); + view()->enableLayoutState(); + } else + firstLetter->setStyle(pseudoStyle); + + for (RenderObject* genChild = firstLetter->firstChild(); genChild; genChild = genChild->nextSibling()) { + if (genChild->isText()) + genChild->setStyle(pseudoStyle); + } + + return; + } + + if (!currChild->isText() || currChild->isBR()) + return; + + // If the child does not already have style, we create it here. + RenderObject* firstLetterContainer = currChild->parent(); + + // Our layout state is not valid for the repaints we are going to trigger by + // adding and removing children of firstLetterContainer. + view()->disableLayoutState(); + + RenderText* textObj = toRenderText(currChild); + + // Create our pseudo style now that we have our firstLetterContainer determined. + RenderStyle* pseudoStyle = styleForFirstLetter(firstLetterBlock, firstLetterContainer); + + RenderObject* firstLetter = 0; + if (pseudoStyle->display() == INLINE) + firstLetter = new (renderArena()) RenderInline(document()); + else + firstLetter = new (renderArena()) RenderBlock(document()); + firstLetter->setStyle(pseudoStyle); + firstLetterContainer->addChild(firstLetter, currChild); + + // The original string is going to be either a generated content string or a DOM node's + // string. We want the original string before it got transformed in case first-letter has + // no text-transform or a different text-transform applied to it. + RefPtr<StringImpl> oldText = textObj->originalText(); + ASSERT(oldText); + + if (oldText && oldText->length() > 0) { + unsigned length = 0; + + // Account for leading spaces and punctuation. + while (length < oldText->length() && shouldSkipForFirstLetter((*oldText)[length])) + length++; + + // Account for first letter. + length++; + + // Keep looking for whitespace and allowed punctuation, but avoid + // accumulating just whitespace into the :first-letter. + for (unsigned scanLength = length; scanLength < oldText->length(); ++scanLength) { + UChar c = (*oldText)[scanLength]; + + if (!shouldSkipForFirstLetter(c)) + break; + + if (isPunctuationForFirstLetter(c)) + length = scanLength + 1; + } + + // Construct a text fragment for the text after the first letter. + // This text fragment might be empty. + RenderTextFragment* remainingText = + new (renderArena()) RenderTextFragment(textObj->node() ? textObj->node() : textObj->document(), oldText.get(), length, oldText->length() - length); + remainingText->setStyle(textObj->style()); + if (remainingText->node()) + remainingText->node()->setRenderer(remainingText); + + RenderObject* nextObj = textObj->nextSibling(); + firstLetterContainer->removeChild(textObj); + firstLetterContainer->addChild(remainingText, nextObj); + remainingText->setFirstLetter(firstLetter); + + // construct text fragment for the first letter + RenderTextFragment* letter = + new (renderArena()) RenderTextFragment(remainingText->node() ? remainingText->node() : remainingText->document(), oldText.get(), 0, length); + letter->setStyle(pseudoStyle); + firstLetter->addChild(letter); + + textObj->destroy(); + } + view()->enableLayoutState(); +} + +// Helper methods for obtaining the last line, computing line counts and heights for line counts +// (crawling into blocks). +static bool shouldCheckLines(RenderObject* obj) +{ + return !obj->isFloatingOrPositioned() && !obj->isRunIn() && + obj->isBlockFlow() && obj->style()->height().isAuto() && + (!obj->isFlexibleBox() || obj->style()->boxOrient() == VERTICAL); +} + +static RootInlineBox* getLineAtIndex(RenderBlock* block, int i, int& count) +{ + if (block->style()->visibility() == VISIBLE) { + if (block->childrenInline()) { + for (RootInlineBox* box = block->firstRootBox(); box; box = box->nextRootBox()) { + if (count++ == i) + return box; + } + } + else { + for (RenderObject* obj = block->firstChild(); obj; obj = obj->nextSibling()) { + if (shouldCheckLines(obj)) { + RootInlineBox *box = getLineAtIndex(toRenderBlock(obj), i, count); + if (box) + return box; + } + } + } + } + return 0; +} + +static int getHeightForLineCount(RenderBlock* block, int l, bool includeBottom, int& count) +{ + if (block->style()->visibility() == VISIBLE) { + if (block->childrenInline()) { + for (RootInlineBox* box = block->firstRootBox(); box; box = box->nextRootBox()) { + if (++count == l) + return box->lineBottom() + (includeBottom ? (block->borderBottom() + block->paddingBottom()) : 0); + } + } + else { + RenderBox* normalFlowChildWithoutLines = 0; + for (RenderBox* obj = block->firstChildBox(); obj; obj = obj->nextSiblingBox()) { + if (shouldCheckLines(obj)) { + int result = getHeightForLineCount(toRenderBlock(obj), l, false, count); + if (result != -1) + return result + obj->y() + (includeBottom ? (block->borderBottom() + block->paddingBottom()) : 0); + } + else if (!obj->isFloatingOrPositioned() && !obj->isRunIn()) + normalFlowChildWithoutLines = obj; + } + if (normalFlowChildWithoutLines && l == 0) + return normalFlowChildWithoutLines->y() + normalFlowChildWithoutLines->height(); + } + } + + return -1; +} + +RootInlineBox* RenderBlock::lineAtIndex(int i) +{ + int count = 0; + return getLineAtIndex(this, i, count); +} + +int RenderBlock::lineCount() +{ + int count = 0; + if (style()->visibility() == VISIBLE) { + if (childrenInline()) + for (RootInlineBox* box = firstRootBox(); box; box = box->nextRootBox()) + count++; + else + for (RenderObject* obj = firstChild(); obj; obj = obj->nextSibling()) + if (shouldCheckLines(obj)) + count += toRenderBlock(obj)->lineCount(); + } + return count; +} + +int RenderBlock::heightForLineCount(int l) +{ + int count = 0; + return getHeightForLineCount(this, l, true, count); +} + +void RenderBlock::adjustForBorderFit(int x, int& left, int& right) const +{ + // We don't deal with relative positioning. Our assumption is that you shrink to fit the lines without accounting + // for either overflow or translations via relative positioning. + if (style()->visibility() == VISIBLE) { + if (childrenInline()) { + for (RootInlineBox* box = firstRootBox(); box; box = box->nextRootBox()) { + if (box->firstChild()) + left = min(left, x + box->firstChild()->x()); + if (box->lastChild()) + right = max(right, x + box->lastChild()->x() + box->lastChild()->logicalWidth()); + } + } + else { + for (RenderBox* obj = firstChildBox(); obj; obj = obj->nextSiblingBox()) { + if (!obj->isFloatingOrPositioned()) { + if (obj->isBlockFlow() && !obj->hasOverflowClip()) + toRenderBlock(obj)->adjustForBorderFit(x + obj->x(), left, right); + else if (obj->style()->visibility() == VISIBLE) { + // We are a replaced element or some kind of non-block-flow object. + left = min(left, x + obj->x()); + right = max(right, x + obj->x() + obj->width()); + } + } + } + } + + if (m_floatingObjects) { + FloatingObject* r; + DeprecatedPtrListIterator<FloatingObject> it(*m_floatingObjects); + for (; (r = it.current()); ++it) { + // Only examine the object if our m_shouldPaint flag is set. + if (r->m_shouldPaint) { + int floatLeft = r->left() - r->m_renderer->x() + r->m_renderer->marginLeft(); + int floatRight = floatLeft + r->m_renderer->width(); + left = min(left, floatLeft); + right = max(right, floatRight); + } + } + } + } +} + +void RenderBlock::borderFitAdjust(int& x, int& w) const +{ + if (style()->borderFit() == BorderFitBorder) + return; + + // Walk any normal flow lines to snugly fit. + int left = INT_MAX; + int right = INT_MIN; + int oldWidth = w; + adjustForBorderFit(0, left, right); + if (left != INT_MAX) { + left -= (borderLeft() + paddingLeft()); + if (left > 0) { + x += left; + w -= left; + } + } + if (right != INT_MIN) { + right += (borderRight() + paddingRight()); + if (right < oldWidth) + w -= (oldWidth - right); + } +} + +void RenderBlock::clearTruncation() +{ + if (style()->visibility() == VISIBLE) { + if (childrenInline() && hasMarkupTruncation()) { + setHasMarkupTruncation(false); + for (RootInlineBox* box = firstRootBox(); box; box = box->nextRootBox()) + box->clearTruncation(); + } + else + for (RenderObject* obj = firstChild(); obj; obj = obj->nextSibling()) + if (shouldCheckLines(obj)) + toRenderBlock(obj)->clearTruncation(); + } +} + +void RenderBlock::setMaxMarginBeforeValues(int pos, int neg) +{ + if (!m_rareData) { + if (pos == RenderBlockRareData::positiveMarginBeforeDefault(this) && neg == RenderBlockRareData::negativeMarginBeforeDefault(this)) + return; + m_rareData = new RenderBlockRareData(this); + } + m_rareData->m_margins.setPositiveMarginBefore(pos); + m_rareData->m_margins.setNegativeMarginBefore(neg); +} + +void RenderBlock::setMaxMarginAfterValues(int pos, int neg) +{ + if (!m_rareData) { + if (pos == RenderBlockRareData::positiveMarginAfterDefault(this) && neg == RenderBlockRareData::negativeMarginAfterDefault(this)) + return; + m_rareData = new RenderBlockRareData(this); + } + m_rareData->m_margins.setPositiveMarginAfter(pos); + m_rareData->m_margins.setNegativeMarginAfter(neg); +} + +void RenderBlock::setPaginationStrut(int strut) +{ + if (!m_rareData) { + if (!strut) + return; + m_rareData = new RenderBlockRareData(this); + } + m_rareData->m_paginationStrut = strut; +} + +void RenderBlock::setPageLogicalOffset(int logicalOffset) +{ + if (!m_rareData) { + if (!logicalOffset) + return; + m_rareData = new RenderBlockRareData(this); + } + m_rareData->m_pageLogicalOffset = logicalOffset; +} + +void RenderBlock::absoluteRects(Vector<IntRect>& rects, int tx, int ty) +{ + // For blocks inside inlines, we go ahead and include margins so that we run right up to the + // inline boxes above and below us (thus getting merged with them to form a single irregular + // shape). + if (isAnonymousBlockContinuation()) { + // FIXME: This is wrong for block-flows that are horizontal. + // https://bugs.webkit.org/show_bug.cgi?id=46781 + rects.append(IntRect(tx, ty - collapsedMarginBefore(), + width(), height() + collapsedMarginBefore() + collapsedMarginAfter())); + continuation()->absoluteRects(rects, + tx - x() + inlineElementContinuation()->containingBlock()->x(), + ty - y() + inlineElementContinuation()->containingBlock()->y()); + } else + rects.append(IntRect(tx, ty, width(), height())); +} + +void RenderBlock::absoluteQuads(Vector<FloatQuad>& quads) +{ + // For blocks inside inlines, we go ahead and include margins so that we run right up to the + // inline boxes above and below us (thus getting merged with them to form a single irregular + // shape). + if (isAnonymousBlockContinuation()) { + // FIXME: This is wrong for block-flows that are horizontal. + // https://bugs.webkit.org/show_bug.cgi?id=46781 + FloatRect localRect(0, -collapsedMarginBefore(), + width(), height() + collapsedMarginBefore() + collapsedMarginAfter()); + quads.append(localToAbsoluteQuad(localRect)); + continuation()->absoluteQuads(quads); + } else + quads.append(RenderBox::localToAbsoluteQuad(FloatRect(0, 0, width(), height()))); +} + +IntRect RenderBlock::rectWithOutlineForRepaint(RenderBoxModelObject* repaintContainer, int outlineWidth) +{ + IntRect r(RenderBox::rectWithOutlineForRepaint(repaintContainer, outlineWidth)); + if (isAnonymousBlockContinuation()) + r.inflateY(collapsedMarginBefore()); // FIXME: This is wrong for block-flows that are horizontal. + return r; +} + +RenderObject* RenderBlock::hoverAncestor() const +{ + return isAnonymousBlockContinuation() ? continuation() : RenderBox::hoverAncestor(); +} + +void RenderBlock::updateDragState(bool dragOn) +{ + RenderBox::updateDragState(dragOn); + if (continuation()) + continuation()->updateDragState(dragOn); +} + +RenderStyle* RenderBlock::outlineStyleForRepaint() const +{ + return isAnonymousBlockContinuation() ? continuation()->style() : style(); +} + +void RenderBlock::childBecameNonInline(RenderObject*) +{ + makeChildrenNonInline(); + if (isAnonymousBlock() && parent() && parent()->isRenderBlock()) + toRenderBlock(parent())->removeLeftoverAnonymousBlock(this); + // |this| may be dead here +} + +void RenderBlock::updateHitTestResult(HitTestResult& result, const IntPoint& point) +{ + if (result.innerNode()) + return; + + Node* n = node(); + if (isAnonymousBlockContinuation()) + // We are in the margins of block elements that are part of a continuation. In + // this case we're actually still inside the enclosing element that was + // split. Go ahead and set our inner node accordingly. + n = continuation()->node(); + + if (n) { + result.setInnerNode(n); + if (!result.innerNonSharedNode()) + result.setInnerNonSharedNode(n); + result.setLocalPoint(point); + } +} + +IntRect RenderBlock::localCaretRect(InlineBox* inlineBox, int caretOffset, int* extraWidthToEndOfLine) +{ + // Do the normal calculation in most cases. + if (firstChild()) + return RenderBox::localCaretRect(inlineBox, caretOffset, extraWidthToEndOfLine); + + // This is a special case: + // The element is not an inline element, and it's empty. So we have to + // calculate a fake position to indicate where objects are to be inserted. + + // FIXME: This does not take into account either :first-line or :first-letter + // However, as soon as some content is entered, the line boxes will be + // constructed and this kludge is not called any more. So only the caret size + // of an empty :first-line'd block is wrong. I think we can live with that. + RenderStyle* currentStyle = firstLineStyle(); + int height = lineHeight(true, currentStyle->isHorizontalWritingMode() ? HorizontalLine : VerticalLine); + + enum CaretAlignment { alignLeft, alignRight, alignCenter }; + + CaretAlignment alignment = alignLeft; + + switch (currentStyle->textAlign()) { + case TAAUTO: + case JUSTIFY: + if (!currentStyle->isLeftToRightDirection()) + alignment = alignRight; + break; + case LEFT: + case WEBKIT_LEFT: + break; + case CENTER: + case WEBKIT_CENTER: + alignment = alignCenter; + break; + case RIGHT: + case WEBKIT_RIGHT: + alignment = alignRight; + break; + } + + int x = borderLeft() + paddingLeft(); + int w = width(); + + switch (alignment) { + case alignLeft: + break; + case alignCenter: + x = (x + w - (borderRight() + paddingRight())) / 2; + break; + case alignRight: + x = w - (borderRight() + paddingRight()) - caretWidth; + break; + } + + if (extraWidthToEndOfLine) { + if (isRenderBlock()) { + *extraWidthToEndOfLine = w - (x + caretWidth); + } else { + // FIXME: This code looks wrong. + // myRight and containerRight are set up, but then clobbered. + // So *extraWidthToEndOfLine will always be 0 here. + + int myRight = x + caretWidth; + // FIXME: why call localToAbsoluteForContent() twice here, too? + FloatPoint absRightPoint = localToAbsolute(FloatPoint(myRight, 0)); + + int containerRight = containingBlock()->x() + containingBlockLogicalWidthForContent(); + FloatPoint absContainerPoint = localToAbsolute(FloatPoint(containerRight, 0)); + + *extraWidthToEndOfLine = absContainerPoint.x() - absRightPoint.x(); + } + } + + int y = paddingTop() + borderTop(); + + return IntRect(x, y, caretWidth, height); +} + +void RenderBlock::addFocusRingRects(Vector<IntRect>& rects, int tx, int ty) +{ + // For blocks inside inlines, we go ahead and include margins so that we run right up to the + // inline boxes above and below us (thus getting merged with them to form a single irregular + // shape). + if (inlineElementContinuation()) { + // FIXME: This check really isn't accurate. + bool nextInlineHasLineBox = inlineElementContinuation()->firstLineBox(); + // FIXME: This is wrong. The principal renderer may not be the continuation preceding this block. + // FIXME: This is wrong for block-flows that are horizontal. + // https://bugs.webkit.org/show_bug.cgi?id=46781 + bool prevInlineHasLineBox = toRenderInline(inlineElementContinuation()->node()->renderer())->firstLineBox(); + int topMargin = prevInlineHasLineBox ? collapsedMarginBefore() : 0; + int bottomMargin = nextInlineHasLineBox ? collapsedMarginAfter() : 0; + IntRect rect(tx, ty - topMargin, width(), height() + topMargin + bottomMargin); + if (!rect.isEmpty()) + rects.append(rect); + } else if (width() && height()) + rects.append(IntRect(tx, ty, width(), height())); + + if (!hasOverflowClip() && !hasControlClip()) { + for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) { + int top = max(curr->lineTop(), curr->y()); + int bottom = min(curr->lineBottom(), curr->y() + curr->logicalHeight()); + IntRect rect(tx + curr->x(), ty + top, curr->logicalWidth(), bottom - top); + if (!rect.isEmpty()) + rects.append(rect); + } + + for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) { + if (!curr->isText() && !curr->isListMarker() && curr->isBox()) { + RenderBox* box = toRenderBox(curr); + FloatPoint pos; + // FIXME: This doesn't work correctly with transforms. + if (box->layer()) + pos = curr->localToAbsolute(); + else + pos = FloatPoint(tx + box->x(), ty + box->y()); + box->addFocusRingRects(rects, pos.x(), pos.y()); + } + } + } + + if (inlineElementContinuation()) + inlineElementContinuation()->addFocusRingRects(rects, + tx - x() + inlineElementContinuation()->containingBlock()->x(), + ty - y() + inlineElementContinuation()->containingBlock()->y()); +} + +RenderBlock* RenderBlock::createAnonymousBlock(bool isFlexibleBox) const +{ + RefPtr<RenderStyle> newStyle = RenderStyle::create(); + newStyle->inheritFrom(style()); + + RenderBlock* newBox = 0; + if (isFlexibleBox) { + newStyle->setDisplay(BOX); + newBox = new (renderArena()) RenderFlexibleBox(document() /* anonymous box */); + } else { + newStyle->setDisplay(BLOCK); + newBox = new (renderArena()) RenderBlock(document() /* anonymous box */); + } + + newBox->setStyle(newStyle.release()); + return newBox; +} + +RenderBlock* RenderBlock::createAnonymousBlockWithSameTypeAs(RenderBlock* otherAnonymousBlock) const +{ + if (otherAnonymousBlock->isAnonymousColumnsBlock()) + return createAnonymousColumnsBlock(); + if (otherAnonymousBlock->isAnonymousColumnSpanBlock()) + return createAnonymousColumnSpanBlock(); + return createAnonymousBlock(otherAnonymousBlock->style()->display() == BOX); +} + +RenderBlock* RenderBlock::createAnonymousColumnsBlock() const +{ + RefPtr<RenderStyle> newStyle = RenderStyle::create(); + newStyle->inheritFrom(style()); + newStyle->inheritColumnPropertiesFrom(style()); + newStyle->setDisplay(BLOCK); + + RenderBlock* newBox = new (renderArena()) RenderBlock(document() /* anonymous box */); + newBox->setStyle(newStyle.release()); + return newBox; +} + +RenderBlock* RenderBlock::createAnonymousColumnSpanBlock() const +{ + RefPtr<RenderStyle> newStyle = RenderStyle::create(); + newStyle->inheritFrom(style()); + newStyle->setColumnSpan(true); + newStyle->setDisplay(BLOCK); + + RenderBlock* newBox = new (renderArena()) RenderBlock(document() /* anonymous box */); + newBox->setStyle(newStyle.release()); + return newBox; +} + +int RenderBlock::nextPageTop(int yPos) const +{ + LayoutState* layoutState = view()->layoutState(); + if (!layoutState->m_pageLogicalHeight) + return yPos; + + // The yPos is in our coordinate space. We can add in our pushed offset. + int pageLogicalHeight = layoutState->m_pageLogicalHeight; + int remainingHeight = (pageLogicalHeight - ((layoutState->m_layoutOffset - layoutState->m_pageOffset).height() + yPos) % pageLogicalHeight) % pageLogicalHeight; + return yPos + remainingHeight; +} + +static bool inNormalFlow(RenderBox* child) +{ + RenderBlock* curr = child->containingBlock(); + RenderBlock* initialBlock = child->view(); + while (curr && curr != initialBlock) { + if (curr->hasColumns()) + return true; + if (curr->isFloatingOrPositioned()) + return false; + curr = curr->containingBlock(); + } + return true; +} + +int RenderBlock::applyBeforeBreak(RenderBox* child, int yPos) +{ + // FIXME: Add page break checking here when we support printing. + bool checkColumnBreaks = view()->layoutState()->isPaginatingColumns(); + bool checkPageBreaks = !checkColumnBreaks && view()->layoutState()->m_pageLogicalHeight; // FIXME: Once columns can print we have to check this. + bool checkBeforeAlways = (checkColumnBreaks && child->style()->columnBreakBefore() == PBALWAYS) || (checkPageBreaks && child->style()->pageBreakBefore() == PBALWAYS); + if (checkBeforeAlways && inNormalFlow(child)) { + if (checkColumnBreaks) + view()->layoutState()->addForcedColumnBreak(yPos); + return nextPageTop(yPos); + } + return yPos; +} + +int RenderBlock::applyAfterBreak(RenderBox* child, int yPos, MarginInfo& marginInfo) +{ + // FIXME: Add page break checking here when we support printing. + bool checkColumnBreaks = view()->layoutState()->isPaginatingColumns(); + bool checkPageBreaks = !checkColumnBreaks && view()->layoutState()->m_pageLogicalHeight; // FIXME: Once columns can print we have to check this. + bool checkAfterAlways = (checkColumnBreaks && child->style()->columnBreakAfter() == PBALWAYS) || (checkPageBreaks && child->style()->pageBreakAfter() == PBALWAYS); + if (checkAfterAlways && inNormalFlow(child)) { + marginInfo.setMarginAfterQuirk(true); // Cause margins to be discarded for any following content. + if (checkColumnBreaks) + view()->layoutState()->addForcedColumnBreak(yPos); + return nextPageTop(yPos); + } + return yPos; +} + +int RenderBlock::adjustForUnsplittableChild(RenderBox* child, int yPos, bool includeMargins) +{ + bool isUnsplittable = child->isReplaced() || child->scrollsOverflow(); + if (!isUnsplittable) + return yPos; + int childHeight = child->height() + (includeMargins ? child->marginTop() + child->marginBottom() : 0); + LayoutState* layoutState = view()->layoutState(); + if (layoutState->m_columnInfo) + layoutState->m_columnInfo->updateMinimumColumnHeight(childHeight); + int pageLogicalHeight = layoutState->m_pageLogicalHeight; + if (!pageLogicalHeight || childHeight > pageLogicalHeight) + return yPos; + int remainingHeight = (pageLogicalHeight - ((layoutState->m_layoutOffset - layoutState->m_pageOffset).height() + yPos) % pageLogicalHeight) % pageLogicalHeight; + if (remainingHeight < childHeight) + return yPos + remainingHeight; + return yPos; +} + +void RenderBlock::adjustLinePositionForPagination(RootInlineBox* lineBox, int& delta) +{ + // FIXME: For now we paginate using line overflow. This ensures that lines don't overlap at all when we + // put a strut between them for pagination purposes. However, this really isn't the desired rendering, since + // the line on the top of the next page will appear too far down relative to the same kind of line at the top + // of the first column. + // + // The rendering we would like to see is one where the lineTop is at the top of the column, and any line overflow + // simply spills out above the top of the column. This effect would match what happens at the top of the first column. + // We can't achieve this rendering, however, until we stop columns from clipping to the column bounds (thus allowing + // for overflow to occur), and then cache visible overflow for each column rect. + // + // Furthermore, the paint we have to do when a column has overflow has to be special. We need to exclude + // content that paints in a previous column (and content that paints in the following column). + // + // FIXME: Another problem with simply moving lines is that the available line width may change (because of floats). + // Technically if the location we move the line to has a different line width than our old position, then we need to dirty the + // line and all following lines. + LayoutState* layoutState = view()->layoutState(); + int pageLogicalHeight = layoutState->m_pageLogicalHeight; + int yPos = lineBox->topVisualOverflow(); + int lineHeight = lineBox->bottomVisualOverflow() - yPos; + if (layoutState->m_columnInfo) + layoutState->m_columnInfo->updateMinimumColumnHeight(lineHeight); + yPos += delta; + lineBox->setPaginationStrut(0); + if (!pageLogicalHeight || lineHeight > pageLogicalHeight) + return; + int remainingHeight = pageLogicalHeight - ((layoutState->m_layoutOffset - layoutState->m_pageOffset).height() + yPos) % pageLogicalHeight; + if (remainingHeight < lineHeight) { + int totalHeight = lineHeight + max(0, yPos); + if (lineBox == firstRootBox() && totalHeight < pageLogicalHeight && !isPositioned() && !isTableCell()) + setPaginationStrut(remainingHeight + max(0, yPos)); + else { + delta += remainingHeight; + lineBox->setPaginationStrut(remainingHeight); + } + } +} + +int RenderBlock::collapsedMarginBeforeForChild(RenderBox* child) const +{ + // If the child has the same directionality as we do, then we can just return its + // collapsed margin. + if (!child->isWritingModeRoot()) + return child->collapsedMarginBefore(); + + // The child has a different directionality. If the child is parallel, then it's just + // flipped relative to us. We can use the collapsed margin for the opposite edge. + if (child->style()->isHorizontalWritingMode() == style()->isHorizontalWritingMode()) + return child->collapsedMarginAfter(); + + // The child is perpendicular to us, which means its margins don't collapse but are on the + // "logical left/right" sides of the child box. We can just return the raw margin in this case. + return marginBeforeForChild(child); +} + +int RenderBlock::collapsedMarginAfterForChild(RenderBox* child) const +{ + // If the child has the same directionality as we do, then we can just return its + // collapsed margin. + if (!child->isWritingModeRoot()) + return child->collapsedMarginAfter(); + + // The child has a different directionality. If the child is parallel, then it's just + // flipped relative to us. We can use the collapsed margin for the opposite edge. + if (child->style()->isHorizontalWritingMode() == style()->isHorizontalWritingMode()) + return child->collapsedMarginBefore(); + + // The child is perpendicular to us, which means its margins don't collapse but are on the + // "logical left/right" side of the child box. We can just return the raw margin in this case. + return marginAfterForChild(child); +} + +int RenderBlock::marginBeforeForChild(RenderBoxModelObject* child) const +{ + switch (style()->writingMode()) { + case TopToBottomWritingMode: + return child->marginTop(); + case BottomToTopWritingMode: + return child->marginBottom(); + case LeftToRightWritingMode: + return child->marginLeft(); + case RightToLeftWritingMode: + return child->marginRight(); + } + ASSERT_NOT_REACHED(); + return child->marginTop(); +} + +int RenderBlock::marginAfterForChild(RenderBoxModelObject* child) const +{ + switch (style()->writingMode()) { + case TopToBottomWritingMode: + return child->marginBottom(); + case BottomToTopWritingMode: + return child->marginTop(); + case LeftToRightWritingMode: + return child->marginRight(); + case RightToLeftWritingMode: + return child->marginLeft(); + } + ASSERT_NOT_REACHED(); + return child->marginBottom(); +} + +int RenderBlock::marginStartForChild(RenderBoxModelObject* child) const +{ + if (style()->isHorizontalWritingMode()) + return style()->isLeftToRightDirection() ? child->marginLeft() : child->marginRight(); + return style()->isLeftToRightDirection() ? child->marginTop() : child->marginBottom(); +} + +int RenderBlock::marginEndForChild(RenderBoxModelObject* child) const +{ + if (style()->isHorizontalWritingMode()) + return style()->isLeftToRightDirection() ? child->marginRight() : child->marginLeft(); + return style()->isLeftToRightDirection() ? child->marginBottom() : child->marginTop(); +} + +void RenderBlock::setMarginStartForChild(RenderBox* child, int margin) +{ + if (style()->isHorizontalWritingMode()) { + if (style()->isLeftToRightDirection()) + child->setMarginLeft(margin); + else + child->setMarginRight(margin); + } else { + if (style()->isLeftToRightDirection()) + child->setMarginTop(margin); + else + child->setMarginBottom(margin); + } +} + +void RenderBlock::setMarginEndForChild(RenderBox* child, int margin) +{ + if (style()->isHorizontalWritingMode()) { + if (style()->isLeftToRightDirection()) + child->setMarginRight(margin); + else + child->setMarginLeft(margin); + } else { + if (style()->isLeftToRightDirection()) + child->setMarginBottom(margin); + else + child->setMarginTop(margin); + } +} + +void RenderBlock::setMarginBeforeForChild(RenderBox* child, int margin) +{ + switch (style()->writingMode()) { + case TopToBottomWritingMode: + child->setMarginTop(margin); + break; + case BottomToTopWritingMode: + child->setMarginBottom(margin); + break; + case LeftToRightWritingMode: + child->setMarginLeft(margin); + break; + case RightToLeftWritingMode: + child->setMarginRight(margin); + break; + } +} + +void RenderBlock::setMarginAfterForChild(RenderBox* child, int margin) +{ + switch (style()->writingMode()) { + case TopToBottomWritingMode: + child->setMarginBottom(margin); + break; + case BottomToTopWritingMode: + child->setMarginTop(margin); + break; + case LeftToRightWritingMode: + child->setMarginRight(margin); + break; + case RightToLeftWritingMode: + child->setMarginLeft(margin); + break; + } +} + +RenderBlock::MarginValues RenderBlock::marginValuesForChild(RenderBox* child) +{ + int childBeforePositive = 0; + int childBeforeNegative = 0; + int childAfterPositive = 0; + int childAfterNegative = 0; + + int beforeMargin = 0; + int afterMargin = 0; + + RenderBlock* childRenderBlock = child->isRenderBlock() ? toRenderBlock(child) : 0; + + // If the child has the same directionality as we do, then we can just return its + // margins in the same direction. + if (!child->isWritingModeRoot()) { + if (childRenderBlock) { + childBeforePositive = childRenderBlock->maxPositiveMarginBefore(); + childBeforeNegative = childRenderBlock->maxNegativeMarginBefore(); + childAfterPositive = childRenderBlock->maxPositiveMarginAfter(); + childAfterNegative = childRenderBlock->maxNegativeMarginAfter(); + } else { + beforeMargin = child->marginBefore(); + afterMargin = child->marginAfter(); + } + } else if (child->style()->isHorizontalWritingMode() == style()->isHorizontalWritingMode()) { + // The child has a different directionality. If the child is parallel, then it's just + // flipped relative to us. We can use the margins for the opposite edges. + if (childRenderBlock) { + childBeforePositive = childRenderBlock->maxPositiveMarginAfter(); + childBeforeNegative = childRenderBlock->maxNegativeMarginAfter(); + childAfterPositive = childRenderBlock->maxPositiveMarginBefore(); + childAfterNegative = childRenderBlock->maxNegativeMarginBefore(); + } else { + beforeMargin = child->marginAfter(); + afterMargin = child->marginBefore(); + } + } else { + // The child is perpendicular to us, which means its margins don't collapse but are on the + // "logical left/right" sides of the child box. We can just return the raw margin in this case. + beforeMargin = marginBeforeForChild(child); + afterMargin = marginAfterForChild(child); + } + + // Resolve uncollapsing margins into their positive/negative buckets. + if (beforeMargin) { + if (beforeMargin > 0) + childBeforePositive = beforeMargin; + else + childBeforeNegative = -beforeMargin; + } + if (afterMargin) { + if (afterMargin > 0) + childAfterPositive = afterMargin; + else + childAfterNegative = -afterMargin; + } + + return MarginValues(childBeforePositive, childBeforeNegative, childAfterPositive, childAfterNegative); +} + +const char* RenderBlock::renderName() const +{ + if (isBody()) + return "RenderBody"; // FIXME: Temporary hack until we know that the regression tests pass. + + if (isFloating()) + return "RenderBlock (floating)"; + if (isPositioned()) + return "RenderBlock (positioned)"; + if (isAnonymousColumnsBlock()) + return "RenderBlock (anonymous multi-column)"; + if (isAnonymousColumnSpanBlock()) + return "RenderBlock (anonymous multi-column span)"; + if (isAnonymousBlock()) + return "RenderBlock (anonymous)"; + else if (isAnonymous()) + return "RenderBlock (generated)"; + if (isRelPositioned()) + return "RenderBlock (relative positioned)"; + if (isRunIn()) + return "RenderBlock (run-in)"; + return "RenderBlock"; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderBlock.h b/Source/WebCore/rendering/RenderBlock.h new file mode 100644 index 0000000..bd8be2c --- /dev/null +++ b/Source/WebCore/rendering/RenderBlock.h @@ -0,0 +1,739 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2007 David Smith (catfish.man@gmail.com) + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * 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. + */ + +#ifndef RenderBlock_h +#define RenderBlock_h + +#include "DeprecatedPtrList.h" +#include "GapRects.h" +#include "RenderBox.h" +#include "RenderLineBoxList.h" +#include "RootInlineBox.h" +#include <wtf/ListHashSet.h> + +namespace WebCore { + +class ColumnInfo; +class InlineIterator; +class LayoutStateMaintainer; +class RenderInline; + +struct BidiRun; + +template <class Iterator, class Run> class BidiResolver; +template <class Iterator> struct MidpointState; +typedef BidiResolver<InlineIterator, BidiRun> InlineBidiResolver; +typedef MidpointState<InlineIterator> LineMidpointState; + +enum CaretType { CursorCaret, DragCaret }; + +class RenderBlock : public RenderBox { +public: + RenderBlock(Node*); + virtual ~RenderBlock(); + + const RenderObjectChildList* children() const { return &m_children; } + RenderObjectChildList* children() { return &m_children; } + + virtual void destroy(); + + // These two functions are overridden for inline-block. + virtual int lineHeight(bool firstLine, LineDirectionMode, LinePositionMode = PositionOnContainingLine) const; + virtual int baselinePosition(FontBaseline, bool firstLine, LineDirectionMode, LinePositionMode = PositionOnContainingLine) const; + + RenderLineBoxList* lineBoxes() { return &m_lineBoxes; } + const RenderLineBoxList* lineBoxes() const { return &m_lineBoxes; } + + InlineFlowBox* firstLineBox() const { return m_lineBoxes.firstLineBox(); } + InlineFlowBox* lastLineBox() const { return m_lineBoxes.lastLineBox(); } + + void deleteLineBoxTree(); + + virtual void addChild(RenderObject* newChild, RenderObject* beforeChild = 0); + virtual void removeChild(RenderObject*); + + virtual void layoutBlock(bool relayoutChildren, int pageLogicalHeight = 0); + + void insertPositionedObject(RenderBox*); + void removePositionedObject(RenderBox*); + void removePositionedObjects(RenderBlock*); + + typedef ListHashSet<RenderBox*, 4> PositionedObjectsListHashSet; + PositionedObjectsListHashSet* positionedObjects() const { return m_positionedObjects; } + + void addPercentHeightDescendant(RenderBox*); + static void removePercentHeightDescendant(RenderBox*); + HashSet<RenderBox*>* percentHeightDescendants() const; + + RootInlineBox* createAndAppendRootInlineBox(); + + bool generatesLineBoxesForInlineChild(RenderObject*, bool isLineEmpty = true, bool previousLineBrokeCleanly = true); + + void markAllDescendantsWithFloatsForLayout(RenderBox* floatToRemove = 0, bool inLayout = true); + void markPositionedObjectsForLayout(); + virtual void markForPaginationRelayoutIfNeeded(); + + bool containsFloats() { return m_floatingObjects && !m_floatingObjects->isEmpty(); } + bool containsFloat(RenderObject*); + + int availableLogicalWidthForLine(int position, bool firstLine) const; + int logicalRightOffsetForLine(int position, bool firstLine) const { return logicalRightOffsetForLine(position, logicalRightOffsetForContent(), firstLine); } + int logicalLeftOffsetForLine(int position, bool firstLine) const { return logicalLeftOffsetForLine(position, logicalLeftOffsetForContent(), firstLine); } + + virtual VisiblePosition positionForPoint(const IntPoint&); + + // Block flows subclass availableWidth to handle multi column layout (shrinking the width available to children when laying out.) + virtual int availableLogicalWidth() const; + + RootInlineBox* firstRootBox() const { return static_cast<RootInlineBox*>(firstLineBox()); } + RootInlineBox* lastRootBox() const { return static_cast<RootInlineBox*>(lastLineBox()); } + + bool containsNonZeroBidiLevel() const; + + GapRects selectionGapRectsForRepaint(RenderBoxModelObject* repaintContainer); + IntRect logicalLeftSelectionGap(RenderBlock* rootBlock, const IntPoint& rootBlockPhysicalPosition, const IntSize& offsetFromRootBlock, + RenderObject* selObj, int logicalLeft, int logicalTop, int logicalHeight, const PaintInfo*); + IntRect logicalRightSelectionGap(RenderBlock* rootBlock, const IntPoint& rootBlockPhysicalPosition, const IntSize& offsetFromRootBlock, + RenderObject* selObj, int logicalRight, int logicalTop, int logicalHeight, const PaintInfo*); + void getSelectionGapInfo(SelectionState, bool& leftGap, bool& rightGap); + IntRect logicalRectToPhysicalRect(const IntPoint& physicalPosition, const IntRect& logicalRect); + + // Helper methods for computing line counts and heights for line counts. + RootInlineBox* lineAtIndex(int); + int lineCount(); + int heightForLineCount(int); + void clearTruncation(); + + void adjustRectForColumns(IntRect&) const; + virtual void adjustForColumns(IntSize&, const IntPoint&) const; + + void addContinuationWithOutline(RenderInline*); + + virtual RenderBoxModelObject* virtualContinuation() const { return continuation(); } + bool isAnonymousBlockContinuation() const { return continuation() && isAnonymousBlock(); } + RenderInline* inlineElementContinuation() const; + RenderBlock* blockElementContinuation() const; + + using RenderBoxModelObject::continuation; + using RenderBoxModelObject::setContinuation; + + // This function is a convenience helper for creating an anonymous block that inherits its + // style from this RenderBlock. + RenderBlock* createAnonymousBlock(bool isFlexibleBox = false) const; + RenderBlock* createAnonymousColumnsBlock() const; + RenderBlock* createAnonymousColumnSpanBlock() const; + RenderBlock* createAnonymousBlockWithSameTypeAs(RenderBlock* otherAnonymousBlock) const; + + static void appendRunsForObject(int start, int end, RenderObject*, InlineBidiResolver&); + static bool requiresLineBox(const InlineIterator&, bool isLineEmpty = true, bool previousLineBrokeCleanly = true); + + ColumnInfo* columnInfo() const; + int columnGap() const; + + // These two functions take the ColumnInfo* to avoid repeated lookups of the info in the global HashMap. + unsigned columnCount(ColumnInfo*) const; + IntRect columnRectAt(ColumnInfo*, unsigned) const; + + int paginationStrut() const { return m_rareData ? m_rareData->m_paginationStrut : 0; } + void setPaginationStrut(int); + + // The page logical offset is the object's offset from the top of the page in the page progression + // direction (so an x-offset in vertical text and a y-offset for horizontal text). + int pageLogicalOffset() const { return m_rareData ? m_rareData->m_pageLogicalOffset : 0; } + void setPageLogicalOffset(int); + + // Accessors for logical width/height and margins in the containing block's block-flow direction. + enum ApplyLayoutDeltaMode { ApplyLayoutDelta, DoNotApplyLayoutDelta }; + int logicalWidthForChild(RenderBox* child) { return style()->isHorizontalWritingMode() ? child->width() : child->height(); } + int logicalHeightForChild(RenderBox* child) { return style()->isHorizontalWritingMode() ? child->height() : child->width(); } + int logicalTopForChild(RenderBox* child) { return style()->isHorizontalWritingMode() ? child->y() : child->x(); } + void setLogicalLeftForChild(RenderBox* child, int logicalLeft, ApplyLayoutDeltaMode = DoNotApplyLayoutDelta); + void setLogicalTopForChild(RenderBox* child, int logicalTop, ApplyLayoutDeltaMode = DoNotApplyLayoutDelta); + int marginBeforeForChild(RenderBoxModelObject* child) const; + int marginAfterForChild(RenderBoxModelObject* child) const; + int marginStartForChild(RenderBoxModelObject* child) const; + int marginEndForChild(RenderBoxModelObject* child) const; + void setMarginStartForChild(RenderBox* child, int); + void setMarginEndForChild(RenderBox* child, int); + void setMarginBeforeForChild(RenderBox* child, int); + void setMarginAfterForChild(RenderBox* child, int); + int collapsedMarginBeforeForChild(RenderBox* child) const; + int collapsedMarginAfterForChild(RenderBox* child) const; + + virtual void updateFirstLetter(); + + class MarginValues { + public: + MarginValues(int beforePos, int beforeNeg, int afterPos, int afterNeg) + : m_positiveMarginBefore(beforePos) + , m_negativeMarginBefore(beforeNeg) + , m_positiveMarginAfter(afterPos) + , m_negativeMarginAfter(afterNeg) + { } + + int positiveMarginBefore() const { return m_positiveMarginBefore; } + int negativeMarginBefore() const { return m_negativeMarginBefore; } + int positiveMarginAfter() const { return m_positiveMarginAfter; } + int negativeMarginAfter() const { return m_negativeMarginAfter; } + + void setPositiveMarginBefore(int pos) { m_positiveMarginBefore = pos; } + void setNegativeMarginBefore(int neg) { m_negativeMarginBefore = neg; } + void setPositiveMarginAfter(int pos) { m_positiveMarginAfter = pos; } + void setNegativeMarginAfter(int neg) { m_negativeMarginAfter = neg; } + + private: + int m_positiveMarginBefore; + int m_negativeMarginBefore; + int m_positiveMarginAfter; + int m_negativeMarginAfter; + }; + MarginValues marginValuesForChild(RenderBox* child); + + virtual void scrollbarsChanged(bool /*horizontalScrollbarChanged*/, bool /*verticalScrollbarChanged*/) { }; + +protected: + // These functions are only used internally to manipulate the render tree structure via remove/insert/appendChildNode. + // Since they are typically called only to move objects around within anonymous blocks (which only have layers in + // the case of column spans), the default for fullRemoveInsert is false rather than true. + void moveChildTo(RenderBlock* to, RenderObject* child, bool fullRemoveInsert = false) + { + return moveChildTo(to, child, 0, fullRemoveInsert); + } + void moveChildTo(RenderBlock* to, RenderObject* child, RenderObject* beforeChild, bool fullRemoveInsert = false); + void moveAllChildrenTo(RenderBlock* to, bool fullRemoveInsert = false) + { + return moveAllChildrenTo(to, 0, fullRemoveInsert); + } + void moveAllChildrenTo(RenderBlock* to, RenderObject* beforeChild, bool fullRemoveInsert = false) + { + return moveChildrenTo(to, firstChild(), 0, beforeChild, fullRemoveInsert); + } + // Move all of the kids from |startChild| up to but excluding |endChild|. 0 can be passed as the endChild to denote + // that all the kids from |startChild| onwards should be added. + void moveChildrenTo(RenderBlock* to, RenderObject* startChild, RenderObject* endChild, bool fullRemoveInsert = false) + { + return moveChildrenTo(to, startChild, endChild, 0, fullRemoveInsert); + } + void moveChildrenTo(RenderBlock* to, RenderObject* startChild, RenderObject* endChild, RenderObject* beforeChild, bool fullRemoveInsert = false); + + int maxPositiveMarginBefore() const { return m_rareData ? m_rareData->m_margins.positiveMarginBefore() : RenderBlockRareData::positiveMarginBeforeDefault(this); } + int maxNegativeMarginBefore() const { return m_rareData ? m_rareData->m_margins.negativeMarginBefore() : RenderBlockRareData::negativeMarginBeforeDefault(this); } + int maxPositiveMarginAfter() const { return m_rareData ? m_rareData->m_margins.positiveMarginAfter() : RenderBlockRareData::positiveMarginAfterDefault(this); } + int maxNegativeMarginAfter() const { return m_rareData ? m_rareData->m_margins.negativeMarginAfter() : RenderBlockRareData::negativeMarginAfterDefault(this); } + + void setMaxMarginBeforeValues(int pos, int neg); + void setMaxMarginAfterValues(int pos, int neg); + + void initMaxMarginValues() + { + if (m_rareData) { + m_rareData->m_margins = MarginValues(RenderBlockRareData::positiveMarginBeforeDefault(this) , RenderBlockRareData::negativeMarginBeforeDefault(this), + RenderBlockRareData::positiveMarginAfterDefault(this), RenderBlockRareData::negativeMarginAfterDefault(this)); + m_rareData->m_paginationStrut = 0; + } + } + + virtual void layout(); + + void layoutPositionedObjects(bool relayoutChildren); + + virtual void paint(PaintInfo&, int tx, int ty); + virtual void paintObject(PaintInfo&, int tx, int ty); + + int logicalRightOffsetForContent() const { return style()->isHorizontalWritingMode() ? borderLeft() + paddingLeft() + availableLogicalWidth() : borderTop() + paddingTop() + availableLogicalWidth(); } + int logicalLeftOffsetForContent() const { return style()->isHorizontalWritingMode() ? borderLeft() + paddingLeft() : borderTop() + paddingTop(); } + int logicalRightOffsetForLine(int position, int fixedOffset, bool applyTextIndent = true, int* logicalHeightRemaining = 0) const; + int logicalLeftOffsetForLine(int position, int fixedOffset, bool applyTextIndent = true, int* logicalHeightRemaining = 0) const; + + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + + virtual void computePreferredLogicalWidths(); + + virtual int firstLineBoxBaseline() const; + virtual int lastLineBoxBaseline() const; + + virtual void updateHitTestResult(HitTestResult&, const IntPoint&); + + // Delay update scrollbar until finishDelayRepaint() will be + // called. This function is used when a flexbox is laying out its + // descendant. If multiple calls are made to startDelayRepaint(), + // finishDelayRepaint() will do nothing until finishDelayRepaint() + // is called the same number of times. + static void startDelayUpdateScrollInfo(); + static void finishDelayUpdateScrollInfo(); + + virtual void styleWillChange(StyleDifference, const RenderStyle* newStyle); + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + virtual bool hasLineIfEmpty() const; + bool layoutOnlyPositionedObjects(); + + void computeOverflow(int oldClientAfterEdge, bool recomputeFloats = false); + virtual void addOverflowFromChildren(); + void addOverflowFromFloats(); + void addOverflowFromPositionedObjects(); + void addOverflowFromBlockChildren(); + void addOverflowFromInlineChildren(); + +#if ENABLE(SVG) +protected: + + // Only used by RenderSVGText, which explicitely overrides RenderBlock::layoutBlock(), do NOT use for anything else. + void forceLayoutInlineChildren() + { + int repaintLogicalTop = 0; + int repaintLogicalBottom = 0; + layoutInlineChildren(true, repaintLogicalTop, repaintLogicalBottom); + } +#endif + +private: + virtual RenderObjectChildList* virtualChildren() { return children(); } + virtual const RenderObjectChildList* virtualChildren() const { return children(); } + + virtual const char* renderName() const; + + virtual bool isRenderBlock() const { return true; } + virtual bool isBlockFlow() const { return (!isInline() || isReplaced()) && !isTable(); } + virtual bool isInlineBlockOrInlineTable() const { return isInline() && isReplaced(); } + + void makeChildrenNonInline(RenderObject* insertionPoint = 0); + virtual void removeLeftoverAnonymousBlock(RenderBlock* child); + + virtual void dirtyLinesFromChangedChild(RenderObject* child) { m_lineBoxes.dirtyLinesFromChangedChild(this, child); } + + void addChildToContinuation(RenderObject* newChild, RenderObject* beforeChild); + void addChildIgnoringContinuation(RenderObject* newChild, RenderObject* beforeChild); + void addChildToAnonymousColumnBlocks(RenderObject* newChild, RenderObject* beforeChild); + virtual void addChildIgnoringAnonymousColumnBlocks(RenderObject* newChild, RenderObject* beforeChild = 0); + + virtual bool isSelfCollapsingBlock() const; + + virtual int collapsedMarginBefore() const { return maxPositiveMarginBefore() - maxNegativeMarginBefore(); } + virtual int collapsedMarginAfter() const { return maxPositiveMarginAfter() - maxNegativeMarginAfter(); } + + virtual void repaintOverhangingFloats(bool paintAllDescendants); + + void layoutBlockChildren(bool relayoutChildren, int& maxFloatLogicalBottom); + void layoutInlineChildren(bool relayoutChildren, int& repaintLogicalTop, int& repaintLogicalBottom); + + virtual void borderFitAdjust(int& x, int& w) const; // Shrink the box in which the border paints if border-fit is set. + + virtual void updateBeforeAfterContent(PseudoId); + + virtual RootInlineBox* createRootInlineBox(); // Subclassed by SVG and Ruby. + + // Called to lay out the legend for a fieldset or the ruby text of a ruby run. + virtual RenderObject* layoutSpecialExcludedChild(bool /*relayoutChildren*/) { return 0; } + + struct FloatWithRect { + FloatWithRect(RenderBox* f) + : object(f) + , rect(IntRect(f->x() - f->marginLeft(), f->y() - f->marginTop(), f->width() + f->marginLeft() + f->marginRight(), f->height() + f->marginTop() + f->marginBottom())) + , everHadLayout(f->m_everHadLayout) + { + } + + RenderBox* object; + IntRect rect; + bool everHadLayout; + }; + + struct FloatingObject : Noncopyable { + // Note that Type uses bits so you can use FloatBoth as a mask to query for both left and right. + enum Type { FloatLeft = 1, FloatRight = 2, FloatBoth = 3 }; + + FloatingObject(Type type) + : m_renderer(0) + , m_paginationStrut(0) + , m_type(type) + , m_shouldPaint(true) + , m_isDescendant(false) + , m_isPlaced(false) + { + } + + FloatingObject(Type type, const IntRect& frameRect) + : m_renderer(0) + , m_frameRect(frameRect) + , m_paginationStrut(0) + , m_type(type) + , m_shouldPaint(true) + , m_isDescendant(false) + , m_isPlaced(true) + { + } + + Type type() const { return static_cast<Type>(m_type); } + RenderBox* renderer() const { return m_renderer; } + + bool isPlaced() const { return m_isPlaced; } + void setIsPlaced(bool placed = true) { m_isPlaced = placed; } + + int left() const { ASSERT(isPlaced()); return m_frameRect.x(); } + int right() const { ASSERT(isPlaced()); return m_frameRect.right(); } + int top() const { ASSERT(isPlaced()); return m_frameRect.y(); } + int bottom() const { ASSERT(isPlaced()); return m_frameRect.bottom(); } + int width() const { return m_frameRect.width(); } + int height() const { return m_frameRect.height(); } + + void setLeft(int left) { m_frameRect.setX(left); } + void setTop(int top) { m_frameRect.setY(top); } + void setWidth(int width) { m_frameRect.setWidth(width); } + void setHeight(int height) { m_frameRect.setHeight(height); } + + const IntRect& frameRect() const { ASSERT(isPlaced()); return m_frameRect; } + void setFrameRect(const IntRect& frameRect) { m_frameRect = frameRect; } + + RenderBox* m_renderer; + IntRect m_frameRect; + int m_paginationStrut; + unsigned m_type : 2; // Type (left or right aligned) + bool m_shouldPaint : 1; + bool m_isDescendant : 1; + bool m_isPlaced : 1; + }; + + int logicalTopForFloat(FloatingObject* child) const { return style()->isHorizontalWritingMode() ? child->top() : child->left(); } + int logicalBottomForFloat(FloatingObject* child) const { return style()->isHorizontalWritingMode() ? child->bottom() : child->right(); } + int logicalLeftForFloat(FloatingObject* child) const { return style()->isHorizontalWritingMode() ? child->left() : child->top(); } + int logicalRightForFloat(FloatingObject* child) const { return style()->isHorizontalWritingMode() ? child->right() : child->bottom(); } + int logicalWidthForFloat(FloatingObject* child) const { return style()->isHorizontalWritingMode() ? child->width() : child->height(); } + void setLogicalTopForFloat(FloatingObject* child, int logicalTop) + { + if (style()->isHorizontalWritingMode()) + child->setTop(logicalTop); + else + child->setLeft(logicalTop); + } + void setLogicalLeftForFloat(FloatingObject* child, int logicalLeft) + { + if (style()->isHorizontalWritingMode()) + child->setLeft(logicalLeft); + else + child->setTop(logicalLeft); + } + void setLogicalHeightForFloat(FloatingObject* child, int logicalHeight) + { + if (style()->isHorizontalWritingMode()) + child->setHeight(logicalHeight); + else + child->setWidth(logicalHeight); + } + void setLogicalWidthForFloat(FloatingObject* child, int logicalWidth) + { + if (style()->isHorizontalWritingMode()) + child->setWidth(logicalWidth); + else + child->setHeight(logicalWidth); + } + + // The following functions' implementations are in RenderBlockLineLayout.cpp. + RootInlineBox* determineStartPosition(bool& firstLine, bool& fullLayout, bool& previousLineBrokeCleanly, + InlineBidiResolver&, Vector<FloatWithRect>& floats, unsigned& numCleanFloats, + bool& useRepaintBounds, int& repaintTop, int& repaintBottom); + RootInlineBox* determineEndPosition(RootInlineBox* startBox, InlineIterator& cleanLineStart, + BidiStatus& cleanLineBidiStatus, + int& yPos); + bool matchedEndLine(const InlineBidiResolver&, const InlineIterator& endLineStart, const BidiStatus& endLineStatus, + RootInlineBox*& endLine, int& endYPos, int& repaintBottom, int& repaintTop); + + void skipTrailingWhitespace(InlineIterator&, bool isLineEmpty, bool previousLineBrokeCleanly); + int skipLeadingWhitespace(InlineBidiResolver&, bool firstLine, bool isLineEmpty, bool previousLineBrokeCleanly, FloatingObject* lastFloatFromPreviousLine); + void fitBelowFloats(int widthToFit, bool firstLine, int& availableWidth); + InlineIterator findNextLineBreak(InlineBidiResolver&, bool firstLine, bool& isLineEmpty, bool& previousLineBrokeCleanly, bool& hyphenated, EClear*, FloatingObject* lastFloatFromPreviousLine); + RootInlineBox* constructLine(unsigned runCount, BidiRun* firstRun, BidiRun* lastRun, bool firstLine, bool lastLine, RenderObject* endObject); + InlineFlowBox* createLineBoxes(RenderObject*, bool firstLine); + void computeInlineDirectionPositionsForLine(RootInlineBox*, bool firstLine, BidiRun* firstRun, BidiRun* trailingSpaceRun, bool reachedEnd, GlyphOverflowAndFallbackFontsMap&); + void computeBlockDirectionPositionsForLine(RootInlineBox*, BidiRun*, GlyphOverflowAndFallbackFontsMap&, VerticalPositionCache&); + void deleteEllipsisLineBoxes(); + void checkLinesForTextOverflow(); + int beforeSideVisualOverflowForLine(RootInlineBox*) const; + int afterSideVisualOverflowForLine(RootInlineBox*) const; + int beforeSideLayoutOverflowForLine(RootInlineBox*) const; + int afterSideLayoutOverflowForLine(RootInlineBox*) const; + // End of functions defined in RenderBlockLineLayout.cpp. + + void paintFloats(PaintInfo&, int tx, int ty, bool preservePhase = false); + void paintContents(PaintInfo&, int tx, int ty); + void paintColumnContents(PaintInfo&, int tx, int ty, bool paintFloats = false); + void paintColumnRules(PaintInfo&, int tx, int ty); + void paintChildren(PaintInfo&, int tx, int ty); + void paintEllipsisBoxes(PaintInfo&, int tx, int ty); + void paintSelection(PaintInfo&, int tx, int ty); + void paintCaret(PaintInfo&, int tx, int ty, CaretType); + + FloatingObject* insertFloatingObject(RenderBox*); + void removeFloatingObject(RenderBox*); + void removeFloatingObjectsBelow(FloatingObject*, int y); + + // Called from lineWidth, to position the floats added in the last line. + // Returns true if and only if it has positioned any floats. + bool positionNewFloats(); + + // Positions new floats and also adjust all floats encountered on the line if any of them + // have to move to the next page/column. + bool positionNewFloatOnLine(FloatingObject* newFloat, FloatingObject* lastFloatFromPreviousLine); + + void clearFloats(); + int getClearDelta(RenderBox* child, int yPos); + + virtual bool avoidsFloats() const; + + bool hasOverhangingFloats() { return parent() && !hasColumns() && lowestFloatLogicalBottom() > logicalHeight(); } + void addIntrudingFloats(RenderBlock* prev, int xoffset, int yoffset); + int addOverhangingFloats(RenderBlock* child, int xoffset, int yoffset, bool makeChildPaintOtherFloats); + + int lowestFloatLogicalBottom(FloatingObject::Type = FloatingObject::FloatBoth) const; + int nextFloatLogicalBottomBelow(int) const; + + virtual bool hitTestColumns(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + virtual bool hitTestContents(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + bool hitTestFloats(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty); + + virtual bool isPointInOverflowControl(HitTestResult&, int x, int y, int tx, int ty); + + void computeInlinePreferredLogicalWidths(); + void computeBlockPreferredLogicalWidths(); + + // Obtains the nearest enclosing block (including this block) that contributes a first-line style to our inline + // children. + virtual RenderBlock* firstLineBlock() const; + + virtual IntRect rectWithOutlineForRepaint(RenderBoxModelObject* repaintContainer, int outlineWidth); + virtual RenderStyle* outlineStyleForRepaint() const; + + virtual RenderObject* hoverAncestor() const; + virtual void updateDragState(bool dragOn); + virtual void childBecameNonInline(RenderObject* child); + + virtual IntRect selectionRectForRepaint(RenderBoxModelObject* repaintContainer, bool /*clipToVisibleContent*/) + { + return selectionGapRectsForRepaint(repaintContainer); + } + virtual bool shouldPaintSelectionGaps() const; + bool isSelectionRoot() const; + GapRects selectionGaps(RenderBlock* rootBlock, const IntPoint& rootBlockPhysicalPosition, const IntSize& offsetFromRootBlock, + int& lastLogicalTop, int& lastLogicalLeft, int& lastLogicalRight, const PaintInfo* = 0); + GapRects inlineSelectionGaps(RenderBlock* rootBlock, const IntPoint& rootBlockPhysicalPosition, const IntSize& offsetFromRootBlock, + int& lastLogicalTop, int& lastLogicalLeft, int& lastLogicalRight, const PaintInfo*); + GapRects blockSelectionGaps(RenderBlock* rootBlock, const IntPoint& rootBlockPhysicalPosition, const IntSize& offsetFromRootBlock, + int& lastLogicalTop, int& lastLogicalLeft, int& lastLogicalRight, const PaintInfo*); + IntRect blockSelectionGap(RenderBlock* rootBlock, const IntPoint& rootBlockPhysicalPosition, const IntSize& offsetFromRootBlock, + int lastLogicalTop, int lastLogicalLeft, int lastLogicalRight, int logicalBottom, const PaintInfo*); + int logicalLeftSelectionOffset(RenderBlock* rootBlock, int position); + int logicalRightSelectionOffset(RenderBlock* rootBlock, int position); + + virtual void absoluteRects(Vector<IntRect>&, int tx, int ty); + virtual void absoluteQuads(Vector<FloatQuad>&); + + int desiredColumnWidth() const; + unsigned desiredColumnCount() const; + void setDesiredColumnCountAndWidth(int count, int width); + + void paintContinuationOutlines(PaintInfo&, int tx, int ty); + + virtual IntRect localCaretRect(InlineBox*, int caretOffset, int* extraWidthToEndOfLine = 0); + + virtual void addFocusRingRects(Vector<IntRect>&, int tx, int ty); + + void adjustPointToColumnContents(IntPoint&) const; + void adjustForBorderFit(int x, int& left, int& right) const; // Helper function for borderFitAdjust + + void markLinesDirtyInBlockRange(int logicalTop, int logicalBottom, RootInlineBox* highest = 0); + + void newLine(EClear); + + Position positionForBox(InlineBox*, bool start = true) const; + Position positionForRenderer(RenderObject*, bool start = true) const; + VisiblePosition positionForPointWithInlineChildren(const IntPoint&); + + // Adjust tx and ty from painting offsets to the local coords of this renderer + void offsetForContents(int& tx, int& ty) const; + + void calcColumnWidth(); + bool layoutColumns(bool hasSpecifiedPageLogicalHeight, int pageLogicalHeight, LayoutStateMaintainer&); + void makeChildrenAnonymousColumnBlocks(RenderObject* beforeChild, RenderBlock* newBlockBox, RenderObject* newChild); + + bool expandsToEncloseOverhangingFloats() const; + + void updateScrollInfoAfterLayout(); + + RenderObject* splitAnonymousBlocksAroundChild(RenderObject* beforeChild); + void splitBlocks(RenderBlock* fromBlock, RenderBlock* toBlock, RenderBlock* middleBlock, + RenderObject* beforeChild, RenderBoxModelObject* oldCont); + void splitFlow(RenderObject* beforeChild, RenderBlock* newBlockBox, + RenderObject* newChild, RenderBoxModelObject* oldCont); + RenderBlock* clone() const; + RenderBlock* continuationBefore(RenderObject* beforeChild); + RenderBlock* containingColumnsBlock(bool allowAnonymousColumnBlock = true); + RenderBlock* columnsBlockForSpanningElement(RenderObject* newChild); + + class MarginInfo { + // Collapsing flags for whether we can collapse our margins with our children's margins. + bool m_canCollapseWithChildren : 1; + bool m_canCollapseMarginBeforeWithChildren : 1; + bool m_canCollapseMarginAfterWithChildren : 1; + + // Whether or not we are a quirky container, i.e., do we collapse away top and bottom + // margins in our container. Table cells and the body are the common examples. We + // also have a custom style property for Safari RSS to deal with TypePad blog articles. + bool m_quirkContainer : 1; + + // This flag tracks whether we are still looking at child margins that can all collapse together at the beginning of a block. + // They may or may not collapse with the top margin of the block (|m_canCollapseTopWithChildren| tells us that), but they will + // always be collapsing with one another. This variable can remain set to true through multiple iterations + // as long as we keep encountering self-collapsing blocks. + bool m_atBeforeSideOfBlock : 1; + + // This flag is set when we know we're examining bottom margins and we know we're at the bottom of the block. + bool m_atAfterSideOfBlock : 1; + + // These variables are used to detect quirky margins that we need to collapse away (in table cells + // and in the body element). + bool m_marginBeforeQuirk : 1; + bool m_marginAfterQuirk : 1; + bool m_determinedMarginBeforeQuirk : 1; + + // These flags track the previous maximal positive and negative margins. + int m_positiveMargin; + int m_negativeMargin; + + public: + MarginInfo(RenderBlock* b, int beforeBorderPadding, int afterBorderPadding); + + void setAtBeforeSideOfBlock(bool b) { m_atBeforeSideOfBlock = b; } + void setAtAfterSideOfBlock(bool b) { m_atAfterSideOfBlock = b; } + void clearMargin() { m_positiveMargin = m_negativeMargin = 0; } + void setMarginBeforeQuirk(bool b) { m_marginBeforeQuirk = b; } + void setMarginAfterQuirk(bool b) { m_marginAfterQuirk = b; } + void setDeterminedMarginBeforeQuirk(bool b) { m_determinedMarginBeforeQuirk = b; } + void setPositiveMargin(int p) { m_positiveMargin = p; } + void setNegativeMargin(int n) { m_negativeMargin = n; } + void setPositiveMarginIfLarger(int p) { if (p > m_positiveMargin) m_positiveMargin = p; } + void setNegativeMarginIfLarger(int n) { if (n > m_negativeMargin) m_negativeMargin = n; } + + void setMargin(int p, int n) { m_positiveMargin = p; m_negativeMargin = n; } + + bool atBeforeSideOfBlock() const { return m_atBeforeSideOfBlock; } + bool canCollapseWithMarginBefore() const { return m_atBeforeSideOfBlock && m_canCollapseMarginBeforeWithChildren; } + bool canCollapseWithMarginAfter() const { return m_atAfterSideOfBlock && m_canCollapseMarginAfterWithChildren; } + bool canCollapseMarginBeforeWithChildren() const { return m_canCollapseMarginBeforeWithChildren; } + bool canCollapseMarginAfterWithChildren() const { return m_canCollapseMarginAfterWithChildren; } + bool quirkContainer() const { return m_quirkContainer; } + bool determinedMarginBeforeQuirk() const { return m_determinedMarginBeforeQuirk; } + bool marginBeforeQuirk() const { return m_marginBeforeQuirk; } + bool marginAfterQuirk() const { return m_marginAfterQuirk; } + int positiveMargin() const { return m_positiveMargin; } + int negativeMargin() const { return m_negativeMargin; } + int margin() const { return m_positiveMargin - m_negativeMargin; } + }; + + void layoutBlockChild(RenderBox* child, MarginInfo&, int& previousFloatLogicalBottom, int& maxFloatLogicalBottom); + void adjustPositionedBlock(RenderBox* child, const MarginInfo&); + void adjustFloatingBlock(const MarginInfo&); + bool handleSpecialChild(RenderBox* child, const MarginInfo&); + bool handleFloatingChild(RenderBox* child, const MarginInfo&); + bool handlePositionedChild(RenderBox* child, const MarginInfo&); + bool handleRunInChild(RenderBox* child); + int collapseMargins(RenderBox* child, MarginInfo&); + int clearFloatsIfNeeded(RenderBox* child, MarginInfo&, int oldTopPosMargin, int oldTopNegMargin, int yPos); + int estimateLogicalTopPosition(RenderBox* child, const MarginInfo&); + void determineLogicalLeftPositionForChild(RenderBox* child); + void handleAfterSideOfBlock(int top, int bottom, MarginInfo&); + void setCollapsedBottomMargin(const MarginInfo&); + // End helper functions and structs used by layoutBlockChildren. + + // Pagination routines. + int nextPageTop(int yPos) const; // Returns the top of the next page following yPos. + int applyBeforeBreak(RenderBox* child, int yPos); // If the child has a before break, then return a new yPos that shifts to the top of the next page/column. + int applyAfterBreak(RenderBox* child, int yPos, MarginInfo& marginInfo); // If the child has an after break, then return a new yPos that shifts to the top of the next page/column. + int adjustForUnsplittableChild(RenderBox* child, int yPos, bool includeMargins = false); // If the child is unsplittable and can't fit on the current page, return the top of the next page/column. + void adjustLinePositionForPagination(RootInlineBox*, int& deltaY); // Computes a deltaY value that put a line at the top of the next page if it doesn't fit on the current page. + + typedef PositionedObjectsListHashSet::const_iterator Iterator; + DeprecatedPtrList<FloatingObject>* m_floatingObjects; + + PositionedObjectsListHashSet* m_positionedObjects; + + // Allocated only when some of these fields have non-default values + struct RenderBlockRareData : Noncopyable { + RenderBlockRareData(const RenderBlock* block) + : m_margins(positiveMarginBeforeDefault(block), negativeMarginBeforeDefault(block), positiveMarginAfterDefault(block), negativeMarginAfterDefault(block)) + , m_paginationStrut(0) + , m_pageLogicalOffset(0) + { + } + + static int positiveMarginBeforeDefault(const RenderBlock* block) + { + return std::max(block->marginBefore(), 0); + } + + static int negativeMarginBeforeDefault(const RenderBlock* block) + { + return std::max(-block->marginBefore(), 0); + } + static int positiveMarginAfterDefault(const RenderBlock* block) + { + return std::max(block->marginAfter(), 0); + } + static int negativeMarginAfterDefault(const RenderBlock* block) + { + return std::max(-block->marginAfter(), 0); + } + + MarginValues m_margins; + int m_paginationStrut; + int m_pageLogicalOffset; + }; + + OwnPtr<RenderBlockRareData> m_rareData; + + RenderObjectChildList m_children; + RenderLineBoxList m_lineBoxes; // All of the root line boxes created for this block flow. For example, <div>Hello<br>world.</div> will have two total lines for the <div>. + + mutable int m_lineHeight; + + // RenderRubyBase objects need to be able to split and merge, moving their children around + // (calling moveChildTo, moveAllChildrenTo, and makeChildrenNonInline). + friend class RenderRubyBase; +}; + +inline RenderBlock* toRenderBlock(RenderObject* object) +{ + ASSERT(!object || object->isRenderBlock()); + return static_cast<RenderBlock*>(object); +} + +inline const RenderBlock* toRenderBlock(const RenderObject* object) +{ + ASSERT(!object || object->isRenderBlock()); + return static_cast<const RenderBlock*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderBlock(const RenderBlock*); + +} // namespace WebCore + +#endif // RenderBlock_h diff --git a/Source/WebCore/rendering/RenderBlockLineLayout.cpp b/Source/WebCore/rendering/RenderBlockLineLayout.cpp new file mode 100644 index 0000000..1d909a3 --- /dev/null +++ b/Source/WebCore/rendering/RenderBlockLineLayout.cpp @@ -0,0 +1,2244 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * Copyright (C) 2003, 2004, 2006, 2007, 2008, 2009, 2010 Apple Inc. All right reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * 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 "BidiResolver.h" +#include "CharacterNames.h" +#include "Hyphenation.h" +#include "InlineIterator.h" +#include "InlineTextBox.h" +#include "Logging.h" +#include "RenderArena.h" +#include "RenderInline.h" +#include "RenderLayer.h" +#include "RenderListMarker.h" +#include "RenderView.h" +#include "Settings.h" +#include "TrailingFloatsRootInlineBox.h" +#include "VerticalPositionCache.h" +#include "break_lines.h" +#include <wtf/AlwaysInline.h> +#include <wtf/RefCountedLeakCounter.h> +#include <wtf/StdLibExtras.h> +#include <wtf/Vector.h> +#ifdef ANDROID_LAYOUT +#include "Frame.h" +#include "FrameTree.h" +#include "Settings.h" +#include "Text.h" +#include "HTMLNames.h" +#endif // ANDROID_LAYOUT + +#if ENABLE(SVG) +#include "RenderSVGInlineText.h" +#include "SVGRootInlineBox.h" +#endif + +using namespace std; +using namespace WTF; +using namespace Unicode; + +namespace WebCore { + +// We don't let our line box tree for a single line get any deeper than this. +const unsigned cMaxLineDepth = 200; + +static int getBorderPaddingMargin(RenderBoxModelObject* child, bool endOfInline) +{ + if (endOfInline) + return child->marginEnd() + child->paddingEnd() + child->borderEnd(); + return child->marginStart() + child->paddingStart() + child->borderStart(); +} + +static int inlineLogicalWidth(RenderObject* child, bool start = true, bool end = true) +{ + unsigned lineDepth = 1; + int extraWidth = 0; + RenderObject* parent = child->parent(); + while (parent->isInline() && !parent->isInlineBlockOrInlineTable() && lineDepth++ < cMaxLineDepth) { + if (start && !child->previousSibling()) + extraWidth += getBorderPaddingMargin(toRenderBoxModelObject(parent), false); + if (end && !child->nextSibling()) + extraWidth += getBorderPaddingMargin(toRenderBoxModelObject(parent), true); + child = parent; + parent = child->parent(); + } + return extraWidth; +} + +static void checkMidpoints(LineMidpointState& lineMidpointState, InlineIterator& lBreak) +{ + // Check to see if our last midpoint is a start point beyond the line break. If so, + // shave it off the list, and shave off a trailing space if the previous end point doesn't + // preserve whitespace. + if (lBreak.obj && lineMidpointState.numMidpoints && !(lineMidpointState.numMidpoints % 2)) { + InlineIterator* midpoints = lineMidpointState.midpoints.data(); + InlineIterator& endpoint = midpoints[lineMidpointState.numMidpoints - 2]; + const InlineIterator& startpoint = midpoints[lineMidpointState.numMidpoints - 1]; + InlineIterator currpoint = endpoint; + while (!currpoint.atEnd() && currpoint != startpoint && currpoint != lBreak) + currpoint.increment(); + if (currpoint == lBreak) { + // We hit the line break before the start point. Shave off the start point. + lineMidpointState.numMidpoints--; + if (endpoint.obj->style()->collapseWhiteSpace()) + endpoint.pos--; + } + } +} + +static void addMidpoint(LineMidpointState& lineMidpointState, const InlineIterator& midpoint) +{ + if (lineMidpointState.midpoints.size() <= lineMidpointState.numMidpoints) + lineMidpointState.midpoints.grow(lineMidpointState.numMidpoints + 10); + + InlineIterator* midpoints = lineMidpointState.midpoints.data(); + midpoints[lineMidpointState.numMidpoints++] = midpoint; +} + +void RenderBlock::appendRunsForObject(int start, int end, RenderObject* obj, InlineBidiResolver& resolver) +{ + if (start > end || obj->isFloating() || + (obj->isPositioned() && !obj->style()->hasStaticX() && !obj->style()->hasStaticY() && !obj->container()->isRenderInline())) + return; + + LineMidpointState& lineMidpointState = resolver.midpointState(); + bool haveNextMidpoint = (lineMidpointState.currentMidpoint < lineMidpointState.numMidpoints); + InlineIterator nextMidpoint; + if (haveNextMidpoint) + nextMidpoint = lineMidpointState.midpoints[lineMidpointState.currentMidpoint]; + if (lineMidpointState.betweenMidpoints) { + if (!(haveNextMidpoint && nextMidpoint.obj == obj)) + return; + // This is a new start point. Stop ignoring objects and + // adjust our start. + lineMidpointState.betweenMidpoints = false; + start = nextMidpoint.pos; + lineMidpointState.currentMidpoint++; + if (start < end) + return appendRunsForObject(start, end, obj, resolver); + } else { + if (!haveNextMidpoint || (obj != nextMidpoint.obj)) { + resolver.addRun(new (obj->renderArena()) BidiRun(start, end, obj, resolver.context(), resolver.dir())); + return; + } + + // An end midpoint has been encountered within our object. We + // need to go ahead and append a run with our endpoint. + if (static_cast<int>(nextMidpoint.pos + 1) <= end) { + lineMidpointState.betweenMidpoints = true; + lineMidpointState.currentMidpoint++; + if (nextMidpoint.pos != UINT_MAX) { // UINT_MAX means stop at the object and don't include any of it. + if (static_cast<int>(nextMidpoint.pos + 1) > start) + resolver.addRun(new (obj->renderArena()) + BidiRun(start, nextMidpoint.pos + 1, obj, resolver.context(), resolver.dir())); + return appendRunsForObject(nextMidpoint.pos + 1, end, obj, resolver); + } + } else + resolver.addRun(new (obj->renderArena()) BidiRun(start, end, obj, resolver.context(), resolver.dir())); + } +} + +static inline InlineBox* createInlineBoxForRenderer(RenderObject* obj, bool isRootLineBox, bool isOnlyRun = false) +{ + if (isRootLineBox) + return toRenderBlock(obj)->createAndAppendRootInlineBox(); + + if (obj->isText()) { + InlineTextBox* textBox = toRenderText(obj)->createInlineTextBox(); + // We only treat a box as text for a <br> if we are on a line by ourself or in strict mode + // (Note the use of strict mode. In "almost strict" mode, we don't treat the box for <br> as text.) + if (obj->isBR()) + textBox->setIsText(isOnlyRun || obj->document()->inNoQuirksMode()); + return textBox; + } + + if (obj->isBox()) + return toRenderBox(obj)->createInlineBox(); + + return toRenderInline(obj)->createAndAppendInlineFlowBox(); +} + +static inline void dirtyLineBoxesForRenderer(RenderObject* o, bool fullLayout) +{ + if (o->isText()) { + if (o->preferredLogicalWidthsDirty() && o->isCounter()) + toRenderText(o)->computePreferredLogicalWidths(0); // FIXME: Counters depend on this hack. No clue why. Should be investigated and removed. + toRenderText(o)->dirtyLineBoxes(fullLayout); + } else + toRenderInline(o)->dirtyLineBoxes(fullLayout); +} + +InlineFlowBox* RenderBlock::createLineBoxes(RenderObject* obj, bool firstLine) +{ + // See if we have an unconstructed line box for this object that is also + // the last item on the line. + unsigned lineDepth = 1; + InlineFlowBox* childBox = 0; + InlineFlowBox* parentBox = 0; + InlineFlowBox* result = 0; + do { + ASSERT(obj->isRenderInline() || obj == this); + + // Get the last box we made for this render object. + parentBox = obj->isRenderInline() ? toRenderInline(obj)->lastLineBox() : toRenderBlock(obj)->lastLineBox(); + + // If this box is constructed then it is from a previous line, and we need + // to make a new box for our line. If this box is unconstructed but it has + // something following it on the line, then we know we have to make a new box + // as well. In this situation our inline has actually been split in two on + // the same line (this can happen with very fancy language mixtures). + bool constructedNewBox = false; + if (!parentBox || parentBox->isConstructed() || parentBox->nextOnLine()) { + // We need to make a new box for this render object. Once + // made, we need to place it at the end of the current line. + InlineBox* newBox = createInlineBoxForRenderer(obj, obj == this); + ASSERT(newBox->isInlineFlowBox()); + parentBox = static_cast<InlineFlowBox*>(newBox); + parentBox->setFirstLineStyleBit(firstLine); + parentBox->setIsHorizontal(style()->isHorizontalWritingMode()); + constructedNewBox = true; + } + + if (!result) + result = parentBox; + + // If we have hit the block itself, then |box| represents the root + // inline box for the line, and it doesn't have to be appended to any parent + // inline. + if (childBox) + parentBox->addToLine(childBox); + + if (!constructedNewBox || obj == this) + break; + + childBox = parentBox; + + // If we've exceeded our line depth, then jump straight to the root and skip all the remaining + // intermediate inline flows. + obj = (++lineDepth >= cMaxLineDepth) ? this : obj->parent(); + + } while (true); + + return result; +} + +RootInlineBox* RenderBlock::constructLine(unsigned runCount, BidiRun* firstRun, BidiRun* lastRun, bool firstLine, bool lastLine, RenderObject* endObject) +{ + ASSERT(firstRun); + + bool rootHasSelectedChildren = false; + InlineFlowBox* parentBox = 0; + for (BidiRun* r = firstRun; r; r = r->next()) { + // Create a box for our object. + bool isOnlyRun = (runCount == 1); + if (runCount == 2 && !r->m_object->isListMarker()) + isOnlyRun = (!style()->isLeftToRightDirection() ? lastRun : firstRun)->m_object->isListMarker(); + + InlineBox* box = createInlineBoxForRenderer(r->m_object, false, isOnlyRun); + r->m_box = box; + + ASSERT(box); + if (!box) + continue; + + if (!rootHasSelectedChildren && box->renderer()->selectionState() != RenderObject::SelectionNone) + rootHasSelectedChildren = true; + + // If we have no parent box yet, or if the run is not simply a sibling, + // then we need to construct inline boxes as necessary to properly enclose the + // run's inline box. + if (!parentBox || parentBox->renderer() != r->m_object->parent()) + // Create new inline boxes all the way back to the appropriate insertion point. + parentBox = createLineBoxes(r->m_object->parent(), firstLine); + + // Append the inline box to this line. + parentBox->addToLine(box); + + bool visuallyOrdered = r->m_object->style()->visuallyOrdered(); + box->setBidiLevel(visuallyOrdered ? 0 : r->level()); + + if (box->isInlineTextBox()) { + InlineTextBox* text = static_cast<InlineTextBox*>(box); + text->setStart(r->m_start); + text->setLen(r->m_stop - r->m_start); + text->m_dirOverride = r->dirOverride(visuallyOrdered); + if (r->m_hasHyphen) + text->setHasHyphen(true); + } + } + + // We should have a root inline box. It should be unconstructed and + // be the last continuation of our line list. + ASSERT(lastLineBox() && !lastLineBox()->isConstructed()); + + // Set the m_selectedChildren flag on the root inline box if one of the leaf inline box + // from the bidi runs walk above has a selection state. + if (rootHasSelectedChildren) + lastLineBox()->root()->setHasSelectedChildren(true); + + // Set bits on our inline flow boxes that indicate which sides should + // paint borders/margins/padding. This knowledge will ultimately be used when + // we determine the horizontal positions and widths of all the inline boxes on + // the line. + lastLineBox()->determineSpacingForFlowBoxes(lastLine, endObject); + + // Now mark the line boxes as being constructed. + lastLineBox()->setConstructed(); + + // Return the last line. + return lastRootBox(); +} + +void RenderBlock::computeInlineDirectionPositionsForLine(RootInlineBox* lineBox, bool firstLine, BidiRun* firstRun, BidiRun* trailingSpaceRun, bool reachedEnd, GlyphOverflowAndFallbackFontsMap& textBoxDataMap) +{ + // First determine our total logical width. + int availableLogicalWidth = availableLogicalWidthForLine(logicalHeight(), firstLine); + int totalLogicalWidth = lineBox->getFlowSpacingLogicalWidth(); + bool needsWordSpacing = false; + unsigned numSpaces = 0; + ETextAlign textAlign = style()->textAlign(); + + for (BidiRun* r = firstRun; r; r = r->next()) { + if (!r->m_box || r->m_object->isPositioned() || r->m_box->isLineBreak()) + continue; // Positioned objects are only participating to figure out their + // correct static x position. They have no effect on the width. + // Similarly, line break boxes have no effect on the width. + if (r->m_object->isText()) { + RenderText* rt = toRenderText(r->m_object); + + if (textAlign == JUSTIFY && r != trailingSpaceRun) { + const UChar* characters = rt->characters(); + for (int i = r->m_start; i < r->m_stop; i++) { + UChar c = characters[i]; + if (c == ' ' || c == '\n' || c == '\t') + numSpaces++; + } + } + + if (int length = rt->textLength()) { + if (!r->m_start && needsWordSpacing && isSpaceOrNewline(rt->characters()[r->m_start])) + totalLogicalWidth += rt->style(firstLine)->font().wordSpacing(); + needsWordSpacing = !isSpaceOrNewline(rt->characters()[r->m_stop - 1]) && r->m_stop == length; + } + HashSet<const SimpleFontData*> fallbackFonts; + GlyphOverflow glyphOverflow; + int hyphenWidth = 0; + if (static_cast<InlineTextBox*>(r->m_box)->hasHyphen()) { + const AtomicString& hyphenString = rt->style()->hyphenString(); + hyphenWidth = rt->style(firstLine)->font().width(TextRun(hyphenString.characters(), hyphenString.length())); + } + r->m_box->setLogicalWidth(rt->width(r->m_start, r->m_stop - r->m_start, totalLogicalWidth, firstLine, &fallbackFonts, &glyphOverflow) + hyphenWidth); + if (!fallbackFonts.isEmpty()) { + ASSERT(r->m_box->isText()); + GlyphOverflowAndFallbackFontsMap::iterator it = textBoxDataMap.add(static_cast<InlineTextBox*>(r->m_box), make_pair(Vector<const SimpleFontData*>(), GlyphOverflow())).first; + ASSERT(it->second.first.isEmpty()); + copyToVector(fallbackFonts, it->second.first); + } + if ((glyphOverflow.top || glyphOverflow.bottom || glyphOverflow.left || glyphOverflow.right)) { + ASSERT(r->m_box->isText()); + GlyphOverflowAndFallbackFontsMap::iterator it = textBoxDataMap.add(static_cast<InlineTextBox*>(r->m_box), make_pair(Vector<const SimpleFontData*>(), GlyphOverflow())).first; + it->second.second = glyphOverflow; + } + } else if (!r->m_object->isRenderInline()) { + RenderBox* renderBox = toRenderBox(r->m_object); + renderBox->computeLogicalWidth(); + r->m_box->setLogicalWidth(logicalWidthForChild(renderBox)); + totalLogicalWidth += marginStartForChild(renderBox) + marginEndForChild(renderBox); + } + + totalLogicalWidth += r->m_box->logicalWidth(); + } + + // Armed with the total width of the line (without justification), + // we now examine our text-align property in order to determine where to position the + // objects horizontally. The total width of the line can be increased if we end up + // justifying text. + int logicalLeft = logicalLeftOffsetForLine(logicalHeight(), firstLine); + switch (textAlign) { + case LEFT: + case WEBKIT_LEFT: + // The direction of the block should determine what happens with wide lines. In + // particular with RTL blocks, wide lines should still spill out to the left. + if (style()->isLeftToRightDirection()) { + if (totalLogicalWidth > availableLogicalWidth && trailingSpaceRun) + trailingSpaceRun->m_box->setLogicalWidth(max(0, trailingSpaceRun->m_box->logicalWidth() - totalLogicalWidth + availableLogicalWidth)); + } else { + if (trailingSpaceRun) + trailingSpaceRun->m_box->setLogicalWidth(0); + else if (totalLogicalWidth > availableLogicalWidth) + logicalLeft -= (totalLogicalWidth - availableLogicalWidth); + } + break; + case JUSTIFY: + if (numSpaces && !reachedEnd && !lineBox->endsWithBreak()) { + if (trailingSpaceRun) { + totalLogicalWidth -= trailingSpaceRun->m_box->logicalWidth(); + trailingSpaceRun->m_box->setLogicalWidth(0); + } + break; + } + // fall through + case TAAUTO: + numSpaces = 0; + // for right to left fall through to right aligned + if (style()->isLeftToRightDirection()) { + if (totalLogicalWidth > availableLogicalWidth && trailingSpaceRun) + trailingSpaceRun->m_box->setLogicalWidth(max(0, trailingSpaceRun->m_box->logicalWidth() - totalLogicalWidth + availableLogicalWidth)); + break; + } + case RIGHT: + case WEBKIT_RIGHT: + // Wide lines spill out of the block based off direction. + // So even if text-align is right, if direction is LTR, wide lines should overflow out of the right + // side of the block. + if (style()->isLeftToRightDirection()) { + if (trailingSpaceRun) { + totalLogicalWidth -= trailingSpaceRun->m_box->logicalWidth(); + trailingSpaceRun->m_box->setLogicalWidth(0); + } + if (totalLogicalWidth < availableLogicalWidth) + logicalLeft += availableLogicalWidth - totalLogicalWidth; + } else { + if (totalLogicalWidth > availableLogicalWidth && trailingSpaceRun) { + trailingSpaceRun->m_box->setLogicalWidth(max(0, trailingSpaceRun->m_box->logicalWidth() - totalLogicalWidth + availableLogicalWidth)); + totalLogicalWidth -= trailingSpaceRun->m_box->logicalWidth(); + } else + logicalLeft += availableLogicalWidth - totalLogicalWidth; + } + break; + case CENTER: + case WEBKIT_CENTER: + int trailingSpaceWidth = 0; + if (trailingSpaceRun) { + totalLogicalWidth -= trailingSpaceRun->m_box->logicalWidth(); + trailingSpaceWidth = min(trailingSpaceRun->m_box->logicalWidth(), (availableLogicalWidth - totalLogicalWidth + 1) / 2); + trailingSpaceRun->m_box->setLogicalWidth(max(0, trailingSpaceWidth)); + } + if (style()->isLeftToRightDirection()) + logicalLeft += max((availableLogicalWidth - totalLogicalWidth) / 2, 0); + else + logicalLeft += totalLogicalWidth > availableLogicalWidth ? (availableLogicalWidth - totalLogicalWidth) : (availableLogicalWidth - totalLogicalWidth) / 2 - trailingSpaceWidth; + break; + } + + if (numSpaces) { + for (BidiRun* r = firstRun; r; r = r->next()) { + if (!r->m_box || r == trailingSpaceRun) + continue; + + int spaceAdd = 0; + if (r->m_object->isText()) { + unsigned spaces = 0; + const UChar* characters = toRenderText(r->m_object)->characters(); + for (int i = r->m_start; i < r->m_stop; i++) { + UChar c = characters[i]; + if (c == ' ' || c == '\n' || c == '\t') + spaces++; + } + + ASSERT(spaces <= numSpaces); + + // Only justify text if whitespace is collapsed. + if (r->m_object->style()->collapseWhiteSpace()) { + spaceAdd = (availableLogicalWidth - totalLogicalWidth) * spaces / numSpaces; + static_cast<InlineTextBox*>(r->m_box)->setSpaceAdd(spaceAdd); + totalLogicalWidth += spaceAdd; + } + numSpaces -= spaces; + if (!numSpaces) + break; + } + } + } + + // The widths of all runs are now known. We can now place every inline box (and + // compute accurate widths for the inline flow boxes). + needsWordSpacing = false; + lineBox->placeBoxesInInlineDirection(logicalLeft, needsWordSpacing, textBoxDataMap); +} + +void RenderBlock::computeBlockDirectionPositionsForLine(RootInlineBox* lineBox, BidiRun* firstRun, GlyphOverflowAndFallbackFontsMap& textBoxDataMap, + VerticalPositionCache& verticalPositionCache) +{ + setLogicalHeight(lineBox->alignBoxesInBlockDirection(logicalHeight(), textBoxDataMap, verticalPositionCache)); + lineBox->setBlockLogicalHeight(logicalHeight()); + + // Now make sure we place replaced render objects correctly. + for (BidiRun* r = firstRun; r; r = r->next()) { + ASSERT(r->m_box); + if (!r->m_box) + continue; // Skip runs with no line boxes. + + // Align positioned boxes with the top of the line box. This is + // a reasonable approximation of an appropriate y position. + if (r->m_object->isPositioned()) + r->m_box->setLogicalTop(logicalHeight()); + + // Position is used to properly position both replaced elements and + // to update the static normal flow x/y of positioned elements. + if (r->m_object->isText()) + toRenderText(r->m_object)->positionLineBox(r->m_box); + else if (r->m_object->isBox()) + toRenderBox(r->m_object)->positionLineBox(r->m_box); + } + // Positioned objects and zero-length text nodes destroy their boxes in + // position(), which unnecessarily dirties the line. + lineBox->markDirty(false); +} + +static inline bool isCollapsibleSpace(UChar character, RenderText* renderer) +{ + if (character == ' ' || character == '\t' || character == softHyphen) + return true; + if (character == '\n') + return !renderer->style()->preserveNewline(); + if (character == noBreakSpace) + return renderer->style()->nbspMode() == SPACE; + return false; +} + +void RenderBlock::layoutInlineChildren(bool relayoutChildren, int& repaintLogicalTop, int& repaintLogicalBottom) +{ + bool useRepaintBounds = false; + + m_overflow.clear(); + + setLogicalHeight(borderBefore() + paddingBefore()); + + // Figure out if we should clear out our line boxes. + // FIXME: Handle resize eventually! + bool fullLayout = !firstLineBox() || selfNeedsLayout() || relayoutChildren; + if (fullLayout) + lineBoxes()->deleteLineBoxes(renderArena()); + + // Text truncation only kicks in if your overflow isn't visible and your text-overflow-mode isn't + // clip. + // FIXME: CSS3 says that descendants that are clipped must also know how to truncate. This is insanely + // difficult to figure out (especially in the middle of doing layout), and is really an esoteric pile of nonsense + // anyway, so we won't worry about following the draft here. + bool hasTextOverflow = style()->textOverflow() && hasOverflowClip(); + + // Walk all the lines and delete our ellipsis line boxes if they exist. + if (hasTextOverflow) + deleteEllipsisLineBoxes(); + + if (firstChild()) { +#ifdef ANDROID_LAYOUT + // if we are in fitColumnToScreen mode + // and the current object is not float:right in LTR or not float:left in RTL, + // and text align is auto, or justify or left in LTR, or right in RTL, we + // will wrap text around screen width so that it doesn't need to scroll + // horizontally when reading a paragraph. + // In case the line height is less than the font size, we skip + // the text wrapping since this will cause text overlapping. + // If a text has background image, we ignore text wrapping, + // otherwise the background will be potentially messed up. + const Settings* settings = document()->settings(); + bool doTextWrap = settings && settings->layoutAlgorithm() == Settings::kLayoutFitColumnToScreen; + if (doTextWrap) { + int ta = style()->textAlign(); + int dir = style()->direction(); + bool autowrap = style()->autoWrap(); + // if the RenderBlock is positioned, don't wrap text around screen + // width as it may cause text to overlap. + bool positioned = isPositioned(); + EFloat cssfloat = style()->floating(); + const int lineHeight = style()->computedLineHeight(); + const int fontSize = style()->fontSize(); + doTextWrap = autowrap && !positioned && + (fontSize <= lineHeight) && !style()->hasBackground() && + (((dir == LTR && cssfloat != FRIGHT) || + (dir == RTL && cssfloat != FLEFT)) && + ((ta == TAAUTO) || (ta == JUSTIFY) || + ((ta == LEFT || ta == WEBKIT_LEFT) && (dir == LTR)) || + ((ta == RIGHT || ta == WEBKIT_RIGHT) && (dir == RTL)))); + } + bool hasTextToWrap = false; +#endif + // layout replaced elements + bool endOfInline = false; + RenderObject* o = bidiFirst(this, 0, false); + Vector<FloatWithRect> floats; + bool hasInlineChild = false; + while (o) { + if (!hasInlineChild && o->isInline()) + hasInlineChild = true; + + if (o->isReplaced() || o->isFloating() || o->isPositioned()) { + RenderBox* box = toRenderBox(o); + + if (relayoutChildren || o->style()->width().isPercent() || o->style()->height().isPercent()) + o->setChildNeedsLayout(true, false); + + // If relayoutChildren is set and we have percentage padding, we also need to invalidate the child's pref widths. + if (relayoutChildren && (o->style()->paddingStart().isPercent() || o->style()->paddingEnd().isPercent())) + o->setPreferredLogicalWidthsDirty(true, false); + + if (o->isPositioned()) + o->containingBlock()->insertPositionedObject(box); +#if PLATFORM(ANDROID) + else { +#ifdef ANDROID_LAYOUT + // ignore text wrap for textField or menuList + if (doTextWrap && (o->isTextField() || o->isMenuList())) + doTextWrap = false; +#endif + if (o->isFloating()) + floats.append(FloatWithRect(box)); + else if (fullLayout || o->needsLayout()) { + // Replaced elements + toRenderBox(o)->dirtyLineBoxes(fullLayout); + o->layoutIfNeeded(); + } + } +#else + else if (o->isFloating()) + floats.append(FloatWithRect(box)); + else if (fullLayout || o->needsLayout()) { + // Replaced elements + toRenderBox(o)->dirtyLineBoxes(fullLayout); + o->layoutIfNeeded(); + } +#endif // PLATFORM(ANDROID) + } else if (o->isText() || (o->isRenderInline() && !endOfInline)) { + if (fullLayout || o->selfNeedsLayout()) + dirtyLineBoxesForRenderer(o, fullLayout); + o->setNeedsLayout(false); +#ifdef ANDROID_LAYOUT + if (doTextWrap && !hasTextToWrap && o->isText()) { + Node* node = o->node(); + // as it is very common for sites to use a serial of <a> or + // <li> as tabs, we don't force text to wrap if all the text + // are short and within an <a> or <li> tag, and only separated + // by short word like "|" or ";". + if (node && node->isTextNode() && + !static_cast<Text*>(node)->containsOnlyWhitespace()) { + int length = static_cast<Text*>(node)->length(); + // FIXME, need a magic number to decide it is too long to + // be a tab. Pick 25 for now as it covers around 160px + // (half of 320px) with the default font. + if (length > 25 || (length > 3 && + (!node->parentOrHostNode()->hasTagName(HTMLNames::aTag) && + !node->parentOrHostNode()->hasTagName(HTMLNames::liTag)))) + hasTextToWrap = true; + } + } +#endif + } + o = bidiNext(this, o, 0, false, &endOfInline); + } + +#ifdef ANDROID_LAYOUT + // try to make sure that inline text will not span wider than the + // screen size unless the container has a fixed height, + if (doTextWrap && hasTextToWrap) { + // check all the nested containing blocks, unless it is table or + // table-cell, to make sure there is no fixed height as it implies + // fixed layout. If we constrain the text to fit screen, we may + // cause text overlap with the block after. + bool isConstrained = false; + RenderObject* obj = this; + while (obj) { + if (obj->style()->height().isFixed() && (!obj->isTable() && !obj->isTableCell())) { + isConstrained = true; + break; + } + if (obj->isFloating() || obj->isPositioned()) { + // floating and absolute or fixed positioning are done out + // of normal flow. Don't need to worry about height any more. + break; + } + obj = obj->container(); + } + if (!isConstrained) { + int textWrapWidth = view()->frameView()->textWrapWidth(); + int padding = paddingLeft() + paddingRight(); + if (textWrapWidth > 0 && width() > (textWrapWidth + padding)) { + // limit the content width (width excluding padding) to be + // (textWrapWidth - 2 * ANDROID_FCTS_MARGIN_PADDING) + int maxWidth = textWrapWidth - 2 * ANDROID_FCTS_MARGIN_PADDING + padding; + setWidth(min(width(), maxWidth)); + m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, maxWidth); + m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, maxWidth); + + IntRect overflow = layoutOverflowRect(); + if (overflow.width() > maxWidth) { + overflow.setWidth(maxWidth); + clearLayoutOverflow(); + addLayoutOverflow(overflow); + } + } + } + } +#endif + // We want to skip ahead to the first dirty line + InlineBidiResolver resolver; + unsigned floatIndex; + bool firstLine = true; + bool previousLineBrokeCleanly = true; + RootInlineBox* startLine = determineStartPosition(firstLine, fullLayout, previousLineBrokeCleanly, resolver, floats, floatIndex, + useRepaintBounds, repaintLogicalTop, repaintLogicalBottom); + + if (fullLayout && hasInlineChild && !selfNeedsLayout()) { + setNeedsLayout(true, false); // Mark ourselves as needing a full layout. This way we'll repaint like + // we're supposed to. + RenderView* v = view(); + if (v && !v->doingFullRepaint() && hasLayer()) { + // Because we waited until we were already inside layout to discover + // that the block really needed a full layout, we missed our chance to repaint the layer + // before layout started. Luckily the layer has cached the repaint rect for its original + // position and size, and so we can use that to make a repaint happen now. + repaintUsingContainer(containerForRepaint(), layer()->repaintRect()); + } + } + + FloatingObject* lastFloat = m_floatingObjects ? m_floatingObjects->last() : 0; + + LineMidpointState& lineMidpointState = resolver.midpointState(); + + // We also find the first clean line and extract these lines. We will add them back + // if we determine that we're able to synchronize after handling all our dirty lines. + InlineIterator cleanLineStart; + BidiStatus cleanLineBidiStatus; + int endLineLogicalTop = 0; + RootInlineBox* endLine = (fullLayout || !startLine) ? + 0 : determineEndPosition(startLine, cleanLineStart, cleanLineBidiStatus, endLineLogicalTop); + + if (startLine) { + if (!useRepaintBounds) { + useRepaintBounds = true; + repaintLogicalTop = logicalHeight(); + repaintLogicalBottom = logicalHeight(); + } + RenderArena* arena = renderArena(); + RootInlineBox* box = startLine; + while (box) { + repaintLogicalTop = min(repaintLogicalTop, beforeSideVisualOverflowForLine(box)); + repaintLogicalBottom = max(repaintLogicalBottom, afterSideVisualOverflowForLine(box)); + RootInlineBox* next = box->nextRootBox(); + box->deleteLine(arena); + box = next; + } + } + + InlineIterator end = resolver.position(); + + if (!fullLayout && lastRootBox() && lastRootBox()->endsWithBreak()) { + // If the last line before the start line ends with a line break that clear floats, + // adjust the height accordingly. + // A line break can be either the first or the last object on a line, depending on its direction. + if (InlineBox* lastLeafChild = lastRootBox()->lastLeafChild()) { + RenderObject* lastObject = lastLeafChild->renderer(); + if (!lastObject->isBR()) + lastObject = lastRootBox()->firstLeafChild()->renderer(); + if (lastObject->isBR()) { + EClear clear = lastObject->style()->clear(); + if (clear != CNONE) + newLine(clear); + } + } + } + + bool endLineMatched = false; + bool checkForEndLineMatch = endLine; + bool checkForFloatsFromLastLine = false; + + bool isLineEmpty = true; + bool paginated = view()->layoutState() && view()->layoutState()->isPaginated(); + + VerticalPositionCache verticalPositionCache; + + while (!end.atEnd()) { + // FIXME: Is this check necessary before the first iteration or can it be moved to the end? + if (checkForEndLineMatch && (endLineMatched = matchedEndLine(resolver, cleanLineStart, cleanLineBidiStatus, endLine, endLineLogicalTop, repaintLogicalBottom, repaintLogicalTop))) + break; + + lineMidpointState.reset(); + + isLineEmpty = true; + + EClear clear = CNONE; + bool hyphenated; + + InlineIterator oldEnd = end; + FloatingObject* lastFloatFromPreviousLine = m_floatingObjects ? m_floatingObjects->last() : 0; + end = findNextLineBreak(resolver, firstLine, isLineEmpty, previousLineBrokeCleanly, hyphenated, &clear, lastFloatFromPreviousLine); + if (resolver.position().atEnd()) { + resolver.deleteRuns(); + checkForFloatsFromLastLine = true; + break; + } + ASSERT(end != resolver.position()); + + if (!isLineEmpty) { + resolver.createBidiRunsForLine(end, style()->visuallyOrdered(), previousLineBrokeCleanly); + ASSERT(resolver.position() == end); + + BidiRun* trailingSpaceRun = 0; + if (!previousLineBrokeCleanly && resolver.runCount() && resolver.logicallyLastRun()->m_object->style()->breakOnlyAfterWhiteSpace() + && resolver.logicallyLastRun()->m_object->style()->autoWrap()) { + trailingSpaceRun = resolver.logicallyLastRun(); + RenderObject* lastObject = trailingSpaceRun->m_object; + if (lastObject->isText()) { + RenderText* lastText = toRenderText(lastObject); + const UChar* characters = lastText->characters(); + int firstSpace = trailingSpaceRun->stop(); + while (firstSpace > trailingSpaceRun->start()) { + UChar current = characters[firstSpace - 1]; + if (!isCollapsibleSpace(current, lastText)) + break; + firstSpace--; + } + if (firstSpace == trailingSpaceRun->stop()) + trailingSpaceRun = 0; + else { + TextDirection direction = style()->direction(); + bool shouldReorder = trailingSpaceRun != (direction == LTR ? resolver.lastRun() : resolver.firstRun()); + if (firstSpace != trailingSpaceRun->start()) { + BidiContext* baseContext = resolver.context(); + while (BidiContext* parent = baseContext->parent()) + baseContext = parent; + + BidiRun* newTrailingRun = new (renderArena()) BidiRun(firstSpace, trailingSpaceRun->m_stop, trailingSpaceRun->m_object, baseContext, OtherNeutral); + trailingSpaceRun->m_stop = firstSpace; + if (direction == LTR) + resolver.addRun(newTrailingRun); + else + resolver.prependRun(newTrailingRun); + trailingSpaceRun = newTrailingRun; + shouldReorder = false; + } + if (shouldReorder) { + if (direction == LTR) { + resolver.moveRunToEnd(trailingSpaceRun); + trailingSpaceRun->m_level = 0; + } else { + resolver.moveRunToBeginning(trailingSpaceRun); + trailingSpaceRun->m_level = 1; + } + } + } + } else + trailingSpaceRun = 0; + } + + // Now that the runs have been ordered, we create the line boxes. + // At the same time we figure out where border/padding/margin should be applied for + // inline flow boxes. + + RootInlineBox* lineBox = 0; + int oldLogicalHeight = logicalHeight(); + if (resolver.runCount()) { + if (hyphenated) + resolver.logicallyLastRun()->m_hasHyphen = true; + lineBox = constructLine(resolver.runCount(), resolver.firstRun(), resolver.lastRun(), firstLine, !end.obj, end.obj && !end.pos ? end.obj : 0); + if (lineBox) { + lineBox->setEndsWithBreak(previousLineBrokeCleanly); + +#if ENABLE(SVG) + bool isSVGRootInlineBox = lineBox->isSVGRootInlineBox(); +#else + bool isSVGRootInlineBox = false; +#endif + + GlyphOverflowAndFallbackFontsMap textBoxDataMap; + + // Now we position all of our text runs horizontally. + if (!isSVGRootInlineBox) + computeInlineDirectionPositionsForLine(lineBox, firstLine, resolver.firstRun(), trailingSpaceRun, end.atEnd(), textBoxDataMap); + + // Now position our text runs vertically. + computeBlockDirectionPositionsForLine(lineBox, resolver.firstRun(), textBoxDataMap, verticalPositionCache); + +#if ENABLE(SVG) + // SVG text layout code computes vertical & horizontal positions on its own. + // Note that we still need to execute computeVerticalPositionsForLine() as + // it calls InlineTextBox::positionLineBox(), which tracks whether the box + // contains reversed text or not. If we wouldn't do that editing and thus + // text selection in RTL boxes would not work as expected. + if (isSVGRootInlineBox) { + ASSERT(isSVGText()); + static_cast<SVGRootInlineBox*>(lineBox)->computePerCharacterLayoutInformation(); + } +#endif + + // Compute our overflow now. + lineBox->computeOverflow(lineBox->lineTop(), lineBox->lineBottom(), document()->inNoQuirksMode(), textBoxDataMap); + +#if PLATFORM(MAC) + // Highlight acts as an overflow inflation. + if (style()->highlight() != nullAtom) + lineBox->addHighlightOverflow(); +#endif + } + } + + resolver.deleteRuns(); + + if (lineBox) { + lineBox->setLineBreakInfo(end.obj, end.pos, resolver.status()); + if (useRepaintBounds) { + repaintLogicalTop = min(repaintLogicalTop, beforeSideVisualOverflowForLine(lineBox)); + repaintLogicalBottom = max(repaintLogicalBottom, afterSideVisualOverflowForLine(lineBox)); + } + + if (paginated) { + int adjustment = 0; + adjustLinePositionForPagination(lineBox, adjustment); + if (adjustment) { + int oldLineWidth = availableLogicalWidthForLine(oldLogicalHeight, firstLine); + lineBox->adjustPosition(0, adjustment); + if (useRepaintBounds) // This can only be a positive adjustment, so no need to update repaintTop. + repaintLogicalBottom = max(repaintLogicalBottom, afterSideVisualOverflowForLine(lineBox)); + + if (availableLogicalWidthForLine(oldLogicalHeight + adjustment, firstLine) != oldLineWidth) { + // We have to delete this line, remove all floats that got added, and let line layout re-run. + lineBox->deleteLine(renderArena()); + removeFloatingObjectsBelow(lastFloatFromPreviousLine, oldLogicalHeight); + setLogicalHeight(oldLogicalHeight + adjustment); + resolver.setPosition(oldEnd); + end = oldEnd; + continue; + } + + setLogicalHeight(lineBox->blockLogicalHeight()); + } + } + } + + firstLine = false; + newLine(clear); + } + + if (m_floatingObjects && lastRootBox()) { + if (lastFloat) { + for (FloatingObject* f = m_floatingObjects->last(); f != lastFloat; f = m_floatingObjects->prev()) { + } + m_floatingObjects->next(); + } else + m_floatingObjects->first(); + for (FloatingObject* f = m_floatingObjects->current(); f; f = m_floatingObjects->next()) { + lastRootBox()->floats().append(f->m_renderer); + ASSERT(f->m_renderer == floats[floatIndex].object); + // If a float's geometry has changed, give up on syncing with clean lines. + if (floats[floatIndex].rect != f->frameRect()) + checkForEndLineMatch = false; + floatIndex++; + } + lastFloat = m_floatingObjects->last(); + } + + lineMidpointState.reset(); + resolver.setPosition(end); + } + + if (endLine) { + if (endLineMatched) { + // Attach all the remaining lines, and then adjust their y-positions as needed. + int delta = logicalHeight() - endLineLogicalTop; + for (RootInlineBox* line = endLine; line; line = line->nextRootBox()) { + line->attachLine(); + if (paginated) { + delta -= line->paginationStrut(); + adjustLinePositionForPagination(line, delta); + } + if (delta) { + repaintLogicalTop = min(repaintLogicalTop, beforeSideVisualOverflowForLine(line) + min(delta, 0)); + repaintLogicalBottom = max(repaintLogicalBottom, afterSideVisualOverflowForLine(line) + max(delta, 0)); + line->adjustPosition(0, delta); + } + if (Vector<RenderBox*>* cleanLineFloats = line->floatsPtr()) { + Vector<RenderBox*>::iterator end = cleanLineFloats->end(); + for (Vector<RenderBox*>::iterator f = cleanLineFloats->begin(); f != end; ++f) { + insertFloatingObject(*f); + setLogicalHeight(logicalTopForChild(*f) - marginBeforeForChild(*f) + delta); + positionNewFloats(); + } + } + } + setLogicalHeight(lastRootBox()->blockLogicalHeight()); + } else { + // Delete all the remaining lines. + RootInlineBox* line = endLine; + RenderArena* arena = renderArena(); + while (line) { + repaintLogicalTop = min(repaintLogicalTop, beforeSideVisualOverflowForLine(line)); + repaintLogicalBottom = max(repaintLogicalBottom, afterSideVisualOverflowForLine(line)); + RootInlineBox* next = line->nextRootBox(); + line->deleteLine(arena); + line = next; + } + } + } + if (m_floatingObjects && (checkForFloatsFromLastLine || positionNewFloats()) && lastRootBox()) { + // In case we have a float on the last line, it might not be positioned up to now. + // This has to be done before adding in the bottom border/padding, or the float will + // include the padding incorrectly. -dwh + if (checkForFloatsFromLastLine) { + int bottomVisualOverflow = afterSideVisualOverflowForLine(lastRootBox()); + int bottomLayoutOverflow = afterSideLayoutOverflowForLine(lastRootBox()); + TrailingFloatsRootInlineBox* trailingFloatsLineBox = new (renderArena()) TrailingFloatsRootInlineBox(this); + m_lineBoxes.appendLineBox(trailingFloatsLineBox); + trailingFloatsLineBox->setConstructed(); + GlyphOverflowAndFallbackFontsMap textBoxDataMap; + VerticalPositionCache verticalPositionCache; + trailingFloatsLineBox->alignBoxesInBlockDirection(logicalHeight(), textBoxDataMap, verticalPositionCache); + IntRect logicalLayoutOverflow(0, logicalHeight(), 1, bottomLayoutOverflow); + IntRect logicalVisualOverflow(0, logicalHeight(), 1, bottomVisualOverflow); + trailingFloatsLineBox->setOverflowFromLogicalRects(logicalLayoutOverflow, logicalVisualOverflow); + trailingFloatsLineBox->setBlockLogicalHeight(logicalHeight()); + } + if (lastFloat) { + for (FloatingObject* f = m_floatingObjects->last(); f != lastFloat; f = m_floatingObjects->prev()) { + } + m_floatingObjects->next(); + } else + m_floatingObjects->first(); + for (FloatingObject* f = m_floatingObjects->current(); f; f = m_floatingObjects->next()) + lastRootBox()->floats().append(f->m_renderer); + lastFloat = m_floatingObjects->last(); + } + size_t floatCount = floats.size(); + // Floats that did not have layout did not repaint when we laid them out. They would have + // painted by now if they had moved, but if they stayed at (0, 0), they still need to be + // painted. + for (size_t i = 0; i < floatCount; ++i) { + if (!floats[i].everHadLayout) { + RenderBox* f = floats[i].object; + if (!f->x() && !f->y() && f->checkForRepaintDuringLayout()) + f->repaint(); + } + } + } + + // Expand the last line to accommodate Ruby and emphasis marks. + int lastLineAnnotationsAdjustment = 0; + if (lastRootBox()) { + int lowestAllowedPosition = max(lastRootBox()->lineBottom(), logicalHeight() + paddingAfter()); + if (!style()->isFlippedLinesWritingMode()) + lastLineAnnotationsAdjustment = lastRootBox()->computeUnderAnnotationAdjustment(lowestAllowedPosition); + else + lastLineAnnotationsAdjustment = lastRootBox()->computeOverAnnotationAdjustment(lowestAllowedPosition); + } + + // Now add in the bottom border/padding. + setLogicalHeight(logicalHeight() + lastLineAnnotationsAdjustment + borderAfter() + paddingAfter() + scrollbarLogicalHeight()); + + if (!firstLineBox() && hasLineIfEmpty()) + setLogicalHeight(logicalHeight() + lineHeight(true, style()->isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes)); + + // See if we have any lines that spill out of our block. If we do, then we will possibly need to + // truncate text. + if (hasTextOverflow) + checkLinesForTextOverflow(); +} + +RootInlineBox* RenderBlock::determineStartPosition(bool& firstLine, bool& fullLayout, bool& previousLineBrokeCleanly, + InlineBidiResolver& resolver, Vector<FloatWithRect>& floats, unsigned& numCleanFloats, + bool& useRepaintBounds, int& repaintLogicalTop, int& repaintLogicalBottom) +{ + RootInlineBox* curr = 0; + RootInlineBox* last = 0; + + bool dirtiedByFloat = false; + if (!fullLayout) { + // Paginate all of the clean lines. + bool paginated = view()->layoutState() && view()->layoutState()->isPaginated(); + int paginationDelta = 0; + size_t floatIndex = 0; + for (curr = firstRootBox(); curr && !curr->isDirty(); curr = curr->nextRootBox()) { + if (paginated) { + paginationDelta -= curr->paginationStrut(); + adjustLinePositionForPagination(curr, paginationDelta); + if (paginationDelta) { + if (containsFloats() || !floats.isEmpty()) { + // FIXME: Do better eventually. For now if we ever shift because of pagination and floats are present just go to a full layout. + fullLayout = true; + break; + } + + if (!useRepaintBounds) + useRepaintBounds = true; + + repaintLogicalTop = min(repaintLogicalTop, beforeSideVisualOverflowForLine(curr) + min(paginationDelta, 0)); + repaintLogicalBottom = max(repaintLogicalBottom, afterSideVisualOverflowForLine(curr) + max(paginationDelta, 0)); + curr->adjustPosition(0, paginationDelta); + } + } + + if (Vector<RenderBox*>* cleanLineFloats = curr->floatsPtr()) { + Vector<RenderBox*>::iterator end = cleanLineFloats->end(); + for (Vector<RenderBox*>::iterator o = cleanLineFloats->begin(); o != end; ++o) { + RenderBox* f = *o; + f->layoutIfNeeded(); + IntSize newSize(f->width() + f->marginLeft() + f->marginRight(), f->height() + f->marginTop() + f->marginBottom()); + ASSERT(floatIndex < floats.size()); + if (floats[floatIndex].object != f) { + // A new float has been inserted before this line or before its last known float. + // Just do a full layout. + fullLayout = true; + break; + } + if (floats[floatIndex].rect.size() != newSize) { + int floatTop = style()->isHorizontalWritingMode() ? floats[floatIndex].rect.y() : floats[floatIndex].rect.x(); + int floatHeight = style()->isHorizontalWritingMode() ? max(floats[floatIndex].rect.height(), newSize.height()) + : max(floats[floatIndex].rect.width(), newSize.width()); + curr->markDirty(); + markLinesDirtyInBlockRange(curr->blockLogicalHeight(), floatTop + floatHeight, curr); + floats[floatIndex].rect.setSize(newSize); + dirtiedByFloat = true; + } + floatIndex++; + } + } + if (dirtiedByFloat || fullLayout) + break; + } + // Check if a new float has been inserted after the last known float. + if (!curr && floatIndex < floats.size()) + fullLayout = true; + } + + if (fullLayout) { + // Nuke all our lines. + if (firstRootBox()) { + RenderArena* arena = renderArena(); + curr = firstRootBox(); + while (curr) { + RootInlineBox* next = curr->nextRootBox(); + curr->deleteLine(arena); + curr = next; + } + ASSERT(!firstLineBox() && !lastLineBox()); + } + } else { + if (curr) { + // We have a dirty line. + if (RootInlineBox* prevRootBox = curr->prevRootBox()) { + // We have a previous line. + if (!dirtiedByFloat && (!prevRootBox->endsWithBreak() || (prevRootBox->lineBreakObj()->isText() && prevRootBox->lineBreakPos() >= toRenderText(prevRootBox->lineBreakObj())->textLength()))) + // The previous line didn't break cleanly or broke at a newline + // that has been deleted, so treat it as dirty too. + curr = prevRootBox; + } + } else { + // No dirty lines were found. + // If the last line didn't break cleanly, treat it as dirty. + if (lastRootBox() && !lastRootBox()->endsWithBreak()) + curr = lastRootBox(); + } + + // If we have no dirty lines, then last is just the last root box. + last = curr ? curr->prevRootBox() : lastRootBox(); + } + + numCleanFloats = 0; + if (!floats.isEmpty()) { + int savedLogicalHeight = logicalHeight(); + // Restore floats from clean lines. + RootInlineBox* line = firstRootBox(); + while (line != curr) { + if (Vector<RenderBox*>* cleanLineFloats = line->floatsPtr()) { + Vector<RenderBox*>::iterator end = cleanLineFloats->end(); + for (Vector<RenderBox*>::iterator f = cleanLineFloats->begin(); f != end; ++f) { + insertFloatingObject(*f); + setLogicalHeight(logicalTopForChild(*f) - marginBeforeForChild(*f)); + positionNewFloats(); + ASSERT(floats[numCleanFloats].object == *f); + numCleanFloats++; + } + } + line = line->nextRootBox(); + } + setLogicalHeight(savedLogicalHeight); + } + + firstLine = !last; + previousLineBrokeCleanly = !last || last->endsWithBreak(); + + RenderObject* startObj; + int pos = 0; + if (last) { + setLogicalHeight(last->blockLogicalHeight()); + startObj = last->lineBreakObj(); + pos = last->lineBreakPos(); + resolver.setStatus(last->lineBreakBidiStatus()); + } else { + bool ltr = style()->isLeftToRightDirection() + #if ENABLE(SVG) + || (style()->unicodeBidi() == UBNormal && isSVGText()) + #endif + ; + + Direction direction = ltr ? LeftToRight : RightToLeft; + resolver.setLastStrongDir(direction); + resolver.setLastDir(direction); + resolver.setEorDir(direction); + resolver.setContext(BidiContext::create(ltr ? 0 : 1, direction, style()->unicodeBidi() == Override)); + + startObj = bidiFirst(this, &resolver); + } + + resolver.setPosition(InlineIterator(this, startObj, pos)); + + return curr; +} + +RootInlineBox* RenderBlock::determineEndPosition(RootInlineBox* startLine, InlineIterator& cleanLineStart, BidiStatus& cleanLineBidiStatus, int& logicalTop) +{ + RootInlineBox* last = 0; + if (!startLine) + last = 0; + else { + for (RootInlineBox* curr = startLine->nextRootBox(); curr; curr = curr->nextRootBox()) { + if (curr->isDirty()) + last = 0; + else if (!last) + last = curr; + } + } + + if (!last) + return 0; + + RootInlineBox* prev = last->prevRootBox(); + cleanLineStart = InlineIterator(this, prev->lineBreakObj(), prev->lineBreakPos()); + cleanLineBidiStatus = prev->lineBreakBidiStatus(); + logicalTop = prev->blockLogicalHeight(); + + for (RootInlineBox* line = last; line; line = line->nextRootBox()) + line->extractLine(); // Disconnect all line boxes from their render objects while preserving + // their connections to one another. + + return last; +} + +bool RenderBlock::matchedEndLine(const InlineBidiResolver& resolver, const InlineIterator& endLineStart, const BidiStatus& endLineStatus, RootInlineBox*& endLine, + int& endLogicalTop, int& repaintLogicalBottom, int& repaintLogicalTop) +{ + if (resolver.position() == endLineStart) { + if (resolver.status() != endLineStatus) + return false; + + int delta = logicalHeight() - endLogicalTop; + if (!delta || !m_floatingObjects) + return true; + + // See if any floats end in the range along which we want to shift the lines vertically. + int logicalTop = min(logicalHeight(), endLogicalTop); + + RootInlineBox* lastLine = endLine; + while (RootInlineBox* nextLine = lastLine->nextRootBox()) + lastLine = nextLine; + + int logicalBottom = lastLine->blockLogicalHeight() + abs(delta); + + for (FloatingObject* f = m_floatingObjects->first(); f; f = m_floatingObjects->next()) { + if (logicalBottomForFloat(f) >= logicalTop && logicalBottomForFloat(f) < logicalBottom) + return false; + } + + return true; + } + + // The first clean line doesn't match, but we can check a handful of following lines to try + // to match back up. + static int numLines = 8; // The # of lines we're willing to match against. + RootInlineBox* line = endLine; + for (int i = 0; i < numLines && line; i++, line = line->nextRootBox()) { + if (line->lineBreakObj() == resolver.position().obj && line->lineBreakPos() == resolver.position().pos) { + // We have a match. + if (line->lineBreakBidiStatus() != resolver.status()) + return false; // ...but the bidi state doesn't match. + RootInlineBox* result = line->nextRootBox(); + + // Set our logical top to be the block height of endLine. + if (result) + endLogicalTop = line->blockLogicalHeight(); + + int delta = logicalHeight() - endLogicalTop; + if (delta && m_floatingObjects) { + // See if any floats end in the range along which we want to shift the lines vertically. + int logicalTop = min(logicalHeight(), endLogicalTop); + + RootInlineBox* lastLine = endLine; + while (RootInlineBox* nextLine = lastLine->nextRootBox()) + lastLine = nextLine; + + int logicalBottom = lastLine->blockLogicalHeight() + abs(delta); + + for (FloatingObject* f = m_floatingObjects->first(); f; f = m_floatingObjects->next()) { + if (logicalBottomForFloat(f) >= logicalTop && logicalBottomForFloat(f) < logicalBottom) + return false; + } + } + + // Now delete the lines that we failed to sync. + RootInlineBox* boxToDelete = endLine; + RenderArena* arena = renderArena(); + while (boxToDelete && boxToDelete != result) { + repaintLogicalTop = min(repaintLogicalTop, beforeSideVisualOverflowForLine(boxToDelete)); + repaintLogicalBottom = max(repaintLogicalBottom, afterSideVisualOverflowForLine(boxToDelete)); + RootInlineBox* next = boxToDelete->nextRootBox(); + boxToDelete->deleteLine(arena); + boxToDelete = next; + } + + endLine = result; + return result; + } + } + + return false; +} + +static inline bool skipNonBreakingSpace(const InlineIterator& it, bool isLineEmpty, bool previousLineBrokeCleanly) +{ + if (it.obj->style()->nbspMode() != SPACE || it.current() != noBreakSpace) + return false; + + // FIXME: This is bad. It makes nbsp inconsistent with space and won't work correctly + // with m_minWidth/m_maxWidth. + // Do not skip a non-breaking space if it is the first character + // on a line after a clean line break (or on the first line, since previousLineBrokeCleanly starts off + // |true|). + if (isLineEmpty && previousLineBrokeCleanly) + return false; + + return true; +} + +static inline bool shouldCollapseWhiteSpace(const RenderStyle* style, bool isLineEmpty, bool previousLineBrokeCleanly) +{ + return style->collapseWhiteSpace() || (style->whiteSpace() == PRE_WRAP && (!isLineEmpty || !previousLineBrokeCleanly)); +} + +static inline bool shouldPreserveNewline(RenderObject* object) +{ +#if ENABLE(SVG) + if (object->isSVGInlineText()) + return false; +#endif + + return object->style()->preserveNewline(); +} + +static bool inlineFlowRequiresLineBox(RenderInline* flow) +{ + // FIXME: Right now, we only allow line boxes for inlines that are truly empty. + // We need to fix this, though, because at the very least, inlines containing only + // ignorable whitespace should should also have line boxes. + return !flow->firstChild() && flow->hasInlineDirectionBordersPaddingOrMargin(); +} + +bool RenderBlock::requiresLineBox(const InlineIterator& it, bool isLineEmpty, bool previousLineBrokeCleanly) +{ + if (it.obj->isFloatingOrPositioned()) + return false; + + if (it.obj->isRenderInline() && !inlineFlowRequiresLineBox(toRenderInline(it.obj))) + return false; + + if (!shouldCollapseWhiteSpace(it.obj->style(), isLineEmpty, previousLineBrokeCleanly) || it.obj->isBR()) + return true; + + UChar current = it.current(); + return current != ' ' && current != '\t' && current != softHyphen && (current != '\n' || shouldPreserveNewline(it.obj)) + && !skipNonBreakingSpace(it, isLineEmpty, previousLineBrokeCleanly); +} + +bool RenderBlock::generatesLineBoxesForInlineChild(RenderObject* inlineObj, bool isLineEmpty, bool previousLineBrokeCleanly) +{ + ASSERT(inlineObj->parent() == this); + + InlineIterator it(this, inlineObj, 0); + while (!it.atEnd() && !requiresLineBox(it, isLineEmpty, previousLineBrokeCleanly)) + it.increment(); + + return !it.atEnd(); +} + +// FIXME: The entire concept of the skipTrailingWhitespace function is flawed, since we really need to be building +// line boxes even for containers that may ultimately collapse away. Otherwise we'll never get positioned +// elements quite right. In other words, we need to build this function's work into the normal line +// object iteration process. +// NB. this function will insert any floating elements that would otherwise +// be skipped but it will not position them. +void RenderBlock::skipTrailingWhitespace(InlineIterator& iterator, bool isLineEmpty, bool previousLineBrokeCleanly) +{ + while (!iterator.atEnd() && !requiresLineBox(iterator, isLineEmpty, previousLineBrokeCleanly)) { + RenderObject* object = iterator.obj; + if (object->isFloating()) { + insertFloatingObject(toRenderBox(object)); + } else if (object->isPositioned()) { + // FIXME: The math here is actually not really right. It's a best-guess approximation that + // will work for the common cases + RenderObject* c = object->container(); + if (c->isRenderInline()) { + // A relative positioned inline encloses us. In this case, we also have to determine our + // position as though we were an inline. Set |staticX| and |staticY| on the relative positioned + // inline so that we can obtain the value later. + toRenderInline(c)->layer()->setStaticX(style()->isLeftToRightDirection() ? logicalLeftOffsetForLine(height(), false) : logicalRightOffsetForLine(height(), false)); + toRenderInline(c)->layer()->setStaticY(height()); + } + + RenderBox* box = toRenderBox(object); + if (box->style()->hasStaticX()) { + if (box->style()->isOriginalDisplayInlineType()) + box->layer()->setStaticX(style()->isLeftToRightDirection() ? logicalLeftOffsetForLine(height(), false) : width() - logicalRightOffsetForLine(height(), false)); + else + box->layer()->setStaticX(style()->isLeftToRightDirection() ? borderLeft() + paddingLeft() : borderRight() + paddingRight()); + } + + if (box->style()->hasStaticY()) + box->layer()->setStaticY(height()); + } + iterator.increment(); + } +} + +int RenderBlock::skipLeadingWhitespace(InlineBidiResolver& resolver, bool firstLine, bool isLineEmpty, bool previousLineBrokeCleanly, + FloatingObject* lastFloatFromPreviousLine) +{ + int availableWidth = availableLogicalWidthForLine(logicalHeight(), firstLine); + while (!resolver.position().atEnd() && !requiresLineBox(resolver.position(), isLineEmpty, previousLineBrokeCleanly)) { + RenderObject* object = resolver.position().obj; + if (object->isFloating()) { + positionNewFloatOnLine(insertFloatingObject(toRenderBox(object)), lastFloatFromPreviousLine); + availableWidth = availableLogicalWidthForLine(logicalHeight(), firstLine); + } else if (object->isPositioned()) { + // FIXME: The math here is actually not really right. It's a best-guess approximation that + // will work for the common cases + RenderObject* c = object->container(); + if (c->isRenderInline()) { + // A relative positioned inline encloses us. In this case, we also have to determine our + // position as though we were an inline. Set |staticX| and |staticY| on the relative positioned + // inline so that we can obtain the value later. + toRenderInline(c)->layer()->setStaticX(style()->isLeftToRightDirection() ? logicalLeftOffsetForLine(height(), firstLine) : logicalRightOffsetForLine(height(), firstLine)); + toRenderInline(c)->layer()->setStaticY(height()); + } + + RenderBox* box = toRenderBox(object); + if (box->style()->hasStaticX()) { + if (box->style()->isOriginalDisplayInlineType()) + box->layer()->setStaticX(style()->isLeftToRightDirection() ? logicalLeftOffsetForLine(height(), firstLine) : width() - logicalRightOffsetForLine(height(), firstLine)); + else + box->layer()->setStaticX(style()->isLeftToRightDirection() ? borderLeft() + paddingLeft() : borderRight() + paddingRight()); + } + + if (box->style()->hasStaticY()) + box->layer()->setStaticY(height()); + } + resolver.increment(); + } + resolver.commitExplicitEmbedding(); + return availableWidth; +} + +// This is currently just used for list markers and inline flows that have line boxes. Neither should +// have an effect on whitespace at the start of the line. +static bool shouldSkipWhitespaceAfterStartObject(RenderBlock* block, RenderObject* o, LineMidpointState& lineMidpointState) +{ + RenderObject* next = bidiNext(block, o); + if (next && !next->isBR() && next->isText() && toRenderText(next)->textLength() > 0) { + RenderText* nextText = toRenderText(next); + UChar nextChar = nextText->characters()[0]; + if (nextText->style()->isCollapsibleWhiteSpace(nextChar)) { + addMidpoint(lineMidpointState, InlineIterator(0, o, 0)); + return true; + } + } + + return false; +} + +void RenderBlock::fitBelowFloats(int widthToFit, bool firstLine, int& availableWidth) +{ + ASSERT(widthToFit > availableWidth); + + int floatLogicalBottom; + int lastFloatLogicalBottom = logicalHeight(); + int newLineWidth = availableWidth; + while (true) { + floatLogicalBottom = nextFloatLogicalBottomBelow(lastFloatLogicalBottom); + if (!floatLogicalBottom) + break; + + newLineWidth = availableLogicalWidthForLine(floatLogicalBottom, firstLine); + lastFloatLogicalBottom = floatLogicalBottom; + if (newLineWidth >= widthToFit) + break; + } + + if (newLineWidth > availableWidth) { + setLogicalHeight(lastFloatLogicalBottom); + availableWidth = newLineWidth; + } +} + +static inline unsigned textWidth(RenderText* text, unsigned from, unsigned len, const Font& font, int xPos, bool isFixedPitch, bool collapseWhiteSpace) +{ + if (isFixedPitch || (!from && len == text->textLength())) + return text->width(from, len, font, xPos); + return font.width(TextRun(text->characters() + from, len, !collapseWhiteSpace, xPos)); +} + +static void tryHyphenating(RenderText* text, const Font& font, const AtomicString& localeIdentifier, int lastSpace, int pos, int xPos, int availableWidth, bool isFixedPitch, bool collapseWhiteSpace, int lastSpaceWordSpacing, InlineIterator& lineBreak, int nextBreakable, bool& hyphenated) +{ + const AtomicString& hyphenString = text->style()->hyphenString(); + int hyphenWidth = font.width(TextRun(hyphenString.characters(), hyphenString.length())); + + int maxPrefixWidth = availableWidth - xPos - hyphenWidth - lastSpaceWordSpacing; + // If the maximum width available for the prefix before the hyphen is small, then it is very unlikely + // that an hyphenation opportunity exists, so do not bother to look for it. + if (maxPrefixWidth <= font.pixelSize() * 5 / 4) + return; + + unsigned prefixLength = font.offsetForPosition(TextRun(text->characters() + lastSpace, pos - lastSpace, !collapseWhiteSpace, xPos + lastSpaceWordSpacing), maxPrefixWidth, false); + if (!prefixLength) + return; + + prefixLength = lastHyphenLocation(text->characters() + lastSpace, pos - lastSpace, prefixLength + 1, localeIdentifier); + if (!prefixLength) + return; + +#if !ASSERT_DISABLED + int prefixWidth = hyphenWidth + textWidth(text, lastSpace, prefixLength, font, xPos, isFixedPitch, collapseWhiteSpace) + lastSpaceWordSpacing; + ASSERT(xPos + prefixWidth <= availableWidth); +#else + UNUSED_PARAM(isFixedPitch); +#endif + + lineBreak.obj = text; + lineBreak.pos = lastSpace + prefixLength; + lineBreak.nextBreakablePosition = nextBreakable; + hyphenated = true; +} + +InlineIterator RenderBlock::findNextLineBreak(InlineBidiResolver& resolver, bool firstLine, bool& isLineEmpty, bool& previousLineBrokeCleanly, + bool& hyphenated, EClear* clear, FloatingObject* lastFloatFromPreviousLine) +{ + ASSERT(resolver.position().block == this); + + bool appliedStartWidth = resolver.position().pos > 0; + LineMidpointState& lineMidpointState = resolver.midpointState(); + + int width = skipLeadingWhitespace(resolver, firstLine, isLineEmpty, previousLineBrokeCleanly, lastFloatFromPreviousLine); + + int w = 0; + int tmpW = 0; + + if (resolver.position().atEnd()) + return resolver.position(); + + // This variable is used only if whitespace isn't set to PRE, and it tells us whether + // or not we are currently ignoring whitespace. + bool ignoringSpaces = false; + InlineIterator ignoreStart; + + // This variable tracks whether the very last character we saw was a space. We use + // this to detect when we encounter a second space so we know we have to terminate + // a run. + bool currentCharacterIsSpace = false; + bool currentCharacterIsWS = false; + RenderObject* trailingSpaceObject = 0; + + InlineIterator lBreak = resolver.position(); + + RenderObject *o = resolver.position().obj; + RenderObject *last = o; + unsigned pos = resolver.position().pos; + int nextBreakable = resolver.position().nextBreakablePosition; + bool atStart = true; + + bool prevLineBrokeCleanly = previousLineBrokeCleanly; + previousLineBrokeCleanly = false; + + hyphenated = false; + + bool autoWrapWasEverTrueOnLine = false; + bool floatsFitOnLine = true; + + // Firefox and Opera will allow a table cell to grow to fit an image inside it under + // very specific circumstances (in order to match common WinIE renderings). + // Not supporting the quirk has caused us to mis-render some real sites. (See Bugzilla 10517.) + bool allowImagesToBreak = !document()->inQuirksMode() || !isTableCell() || !style()->logicalWidth().isIntrinsicOrAuto(); + + EWhiteSpace currWS = style()->whiteSpace(); + EWhiteSpace lastWS = currWS; + while (o) { + currWS = o->isReplaced() ? o->parent()->style()->whiteSpace() : o->style()->whiteSpace(); + lastWS = last->isReplaced() ? last->parent()->style()->whiteSpace() : last->style()->whiteSpace(); + + bool autoWrap = RenderStyle::autoWrap(currWS); + autoWrapWasEverTrueOnLine = autoWrapWasEverTrueOnLine || autoWrap; + +#if ENABLE(SVG) + bool preserveNewline = o->isSVGInlineText() ? false : RenderStyle::preserveNewline(currWS); +#else + bool preserveNewline = RenderStyle::preserveNewline(currWS); +#endif + + bool collapseWhiteSpace = RenderStyle::collapseWhiteSpace(currWS); + + if (o->isBR()) { + if (w + tmpW <= width) { + lBreak.obj = o; + lBreak.pos = 0; + lBreak.nextBreakablePosition = -1; + lBreak.increment(); + + // A <br> always breaks a line, so don't let the line be collapsed + // away. Also, the space at the end of a line with a <br> does not + // get collapsed away. It only does this if the previous line broke + // cleanly. Otherwise the <br> has no effect on whether the line is + // empty or not. + if (prevLineBrokeCleanly) + isLineEmpty = false; + trailingSpaceObject = 0; + previousLineBrokeCleanly = true; + + if (!isLineEmpty && clear) + *clear = o->style()->clear(); + } + goto end; + } + + if (o->isFloatingOrPositioned()) { + // add to special objects... + if (o->isFloating()) { + RenderBox* floatBox = toRenderBox(o); + FloatingObject* f = insertFloatingObject(floatBox); + // check if it fits in the current line. + // If it does, position it now, otherwise, position + // it after moving to next line (in newLine() func) + if (floatsFitOnLine && logicalWidthForFloat(f) + w + tmpW <= width) { + positionNewFloatOnLine(f, lastFloatFromPreviousLine); + width = availableLogicalWidthForLine(logicalHeight(), firstLine); + } else + floatsFitOnLine = false; + } else if (o->isPositioned()) { + // If our original display wasn't an inline type, then we can + // go ahead and determine our static x position now. + RenderBox* box = toRenderBox(o); + bool isInlineType = box->style()->isOriginalDisplayInlineType(); + bool needToSetStaticX = box->style()->hasStaticX(); + if (box->style()->hasStaticX() && !isInlineType) { + box->layer()->setStaticX(o->parent()->style()->isLeftToRightDirection() ? + borderLeft() + paddingLeft() : + borderRight() + paddingRight()); + needToSetStaticX = false; + } + + // If our original display was an INLINE type, then we can go ahead + // and determine our static y position now. + bool needToSetStaticY = box->style()->hasStaticY(); + if (box->style()->hasStaticY() && isInlineType) { + box->layer()->setStaticY(height()); + needToSetStaticY = false; + } + + bool needToCreateLineBox = needToSetStaticX || needToSetStaticY; + RenderObject* c = o->container(); + if (c->isRenderInline() && (!needToSetStaticX || !needToSetStaticY)) + needToCreateLineBox = true; + + // If we're ignoring spaces, we have to stop and include this object and + // then start ignoring spaces again. + if (needToCreateLineBox) { + trailingSpaceObject = 0; + ignoreStart.obj = o; + ignoreStart.pos = 0; + if (ignoringSpaces) { + addMidpoint(lineMidpointState, ignoreStart); // Stop ignoring spaces. + addMidpoint(lineMidpointState, ignoreStart); // Start ignoring again. + } + + } + } + } else if (o->isRenderInline()) { + // Right now, we should only encounter empty inlines here. + ASSERT(!o->firstChild()); + + RenderInline* flowBox = toRenderInline(o); + + // Now that some inline flows have line boxes, if we are already ignoring spaces, we need + // to make sure that we stop to include this object and then start ignoring spaces again. + // If this object is at the start of the line, we need to behave like list markers and + // start ignoring spaces. + if (inlineFlowRequiresLineBox(flowBox)) { + isLineEmpty = false; + if (ignoringSpaces) { + trailingSpaceObject = 0; + addMidpoint(lineMidpointState, InlineIterator(0, o, 0)); // Stop ignoring spaces. + addMidpoint(lineMidpointState, InlineIterator(0, o, 0)); // Start ignoring again. + } else if (style()->collapseWhiteSpace() && resolver.position().obj == o + && shouldSkipWhitespaceAfterStartObject(this, o, lineMidpointState)) { + // Like with list markers, we start ignoring spaces to make sure that any + // additional spaces we see will be discarded. + currentCharacterIsSpace = true; + currentCharacterIsWS = true; + ignoringSpaces = true; + } + } + + tmpW += flowBox->marginStart() + flowBox->borderStart() + flowBox->paddingStart() + + flowBox->marginEnd() + flowBox->borderEnd() + flowBox->paddingEnd(); + } else if (o->isReplaced()) { + RenderBox* replacedBox = toRenderBox(o); + + // Break on replaced elements if either has normal white-space. + if ((autoWrap || RenderStyle::autoWrap(lastWS)) && (!o->isImage() || allowImagesToBreak)) { + w += tmpW; + tmpW = 0; + lBreak.obj = o; + lBreak.pos = 0; + lBreak.nextBreakablePosition = -1; + } + + if (ignoringSpaces) + addMidpoint(lineMidpointState, InlineIterator(0, o, 0)); + + isLineEmpty = false; + ignoringSpaces = false; + currentCharacterIsSpace = false; + currentCharacterIsWS = false; + trailingSpaceObject = 0; + + // Optimize for a common case. If we can't find whitespace after the list + // item, then this is all moot. + int replacedLogicalWidth = logicalWidthForChild(replacedBox) + marginStartForChild(replacedBox) + marginEndForChild(replacedBox) + inlineLogicalWidth(o); + if (o->isListMarker()) { + if (style()->collapseWhiteSpace() && shouldSkipWhitespaceAfterStartObject(this, o, lineMidpointState)) { + // Like with inline flows, we start ignoring spaces to make sure that any + // additional spaces we see will be discarded. + currentCharacterIsSpace = true; + currentCharacterIsWS = true; + ignoringSpaces = true; + } + if (toRenderListMarker(o)->isInside()) + tmpW += replacedLogicalWidth; + } else + tmpW += replacedLogicalWidth; + } else if (o->isText()) { + if (!pos) + appliedStartWidth = false; + + RenderText* t = toRenderText(o); + +#if ENABLE(SVG) + bool isSVGText = t->isSVGInlineText(); +#endif + + int strlen = t->textLength(); + int len = strlen - pos; + const UChar* str = t->characters(); + + RenderStyle* style = t->style(firstLine); + const Font& f = style->font(); + bool isFixedPitch = f.isFixedPitch(); + bool canHyphenate = style->hyphens() == HyphensAuto && WebCore::canHyphenate(style->hyphenationLocale()); + + int lastSpace = pos; + int wordSpacing = o->style()->wordSpacing(); + int lastSpaceWordSpacing = 0; + + // Non-zero only when kerning is enabled, in which case we measure words with their trailing + // space, then subtract its width. + int wordTrailingSpaceWidth = f.typesettingFeatures() & Kerning ? f.width(TextRun(&space, 1)) + wordSpacing : 0; + + int wrapW = tmpW + inlineLogicalWidth(o, !appliedStartWidth, true); + int charWidth = 0; + bool breakNBSP = autoWrap && o->style()->nbspMode() == SPACE; + // Auto-wrapping text should wrap in the middle of a word only if it could not wrap before the word, + // which is only possible if the word is the first thing on the line, that is, if |w| is zero. + bool breakWords = o->style()->breakWords() && ((autoWrap && !w) || currWS == PRE); + bool midWordBreak = false; + bool breakAll = o->style()->wordBreak() == BreakAllWordBreak && autoWrap; + int hyphenWidth = 0; + + if (t->isWordBreak()) { + w += tmpW; + tmpW = 0; + lBreak.obj = o; + lBreak.pos = 0; + lBreak.nextBreakablePosition = -1; + ASSERT(!len); + } + + while (len) { + bool previousCharacterIsSpace = currentCharacterIsSpace; + bool previousCharacterIsWS = currentCharacterIsWS; + UChar c = str[pos]; + currentCharacterIsSpace = c == ' ' || c == '\t' || (!preserveNewline && (c == '\n')); + + if (!collapseWhiteSpace || !currentCharacterIsSpace) + isLineEmpty = false; + + if (c == softHyphen && autoWrap && !hyphenWidth && style->hyphens() != HyphensNone) { + const AtomicString& hyphenString = style->hyphenString(); + hyphenWidth = f.width(TextRun(hyphenString.characters(), hyphenString.length())); + tmpW += hyphenWidth; + } + +#if ENABLE(SVG) + if (isSVGText) { + RenderSVGInlineText* svgInlineText = static_cast<RenderSVGInlineText*>(t); + if (pos > 0) { + if (svgInlineText->characterStartsNewTextChunk(pos)) { + addMidpoint(lineMidpointState, InlineIterator(0, o, pos - 1)); + addMidpoint(lineMidpointState, InlineIterator(0, o, pos)); + } + } + } +#endif + + bool applyWordSpacing = false; + + currentCharacterIsWS = currentCharacterIsSpace || (breakNBSP && c == noBreakSpace); + + if ((breakAll || breakWords) && !midWordBreak) { + wrapW += charWidth; + charWidth = textWidth(t, pos, 1, f, w + wrapW, isFixedPitch, collapseWhiteSpace); + midWordBreak = w + wrapW + charWidth > width; + } + + bool betweenWords = c == '\n' || (currWS != PRE && !atStart && isBreakable(str, pos, strlen, nextBreakable, breakNBSP) && (style->hyphens() != HyphensNone || (pos && str[pos - 1] != softHyphen))); + + if (betweenWords || midWordBreak) { + bool stoppedIgnoringSpaces = false; + if (ignoringSpaces) { + if (!currentCharacterIsSpace) { + // Stop ignoring spaces and begin at this + // new point. + ignoringSpaces = false; + lastSpaceWordSpacing = 0; + lastSpace = pos; // e.g., "Foo goo", don't add in any of the ignored spaces. + addMidpoint(lineMidpointState, InlineIterator(0, o, pos)); + stoppedIgnoringSpaces = true; + } else { + // Just keep ignoring these spaces. + pos++; + len--; + continue; + } + } + + int additionalTmpW; + if (wordTrailingSpaceWidth && currentCharacterIsSpace) + additionalTmpW = textWidth(t, lastSpace, pos + 1 - lastSpace, f, w + tmpW, isFixedPitch, collapseWhiteSpace) - wordTrailingSpaceWidth + lastSpaceWordSpacing; + else + additionalTmpW = textWidth(t, lastSpace, pos - lastSpace, f, w + tmpW, isFixedPitch, collapseWhiteSpace) + lastSpaceWordSpacing; + tmpW += additionalTmpW; + if (!appliedStartWidth) { + tmpW += inlineLogicalWidth(o, true, false); + appliedStartWidth = true; + } + + applyWordSpacing = wordSpacing && currentCharacterIsSpace && !previousCharacterIsSpace; + + if (!w && autoWrap && tmpW > width) + fitBelowFloats(tmpW, firstLine, width); + + if (autoWrap || breakWords) { + // If we break only after white-space, consider the current character + // as candidate width for this line. + bool lineWasTooWide = false; + if (w + tmpW <= width && currentCharacterIsWS && o->style()->breakOnlyAfterWhiteSpace() && !midWordBreak) { + int charWidth = textWidth(t, pos, 1, f, w + tmpW, isFixedPitch, collapseWhiteSpace) + (applyWordSpacing ? wordSpacing : 0); + // Check if line is too big even without the extra space + // at the end of the line. If it is not, do nothing. + // If the line needs the extra whitespace to be too long, + // then move the line break to the space and skip all + // additional whitespace. + if (w + tmpW + charWidth > width) { + lineWasTooWide = true; + lBreak.obj = o; + lBreak.pos = pos; + lBreak.nextBreakablePosition = nextBreakable; + skipTrailingWhitespace(lBreak, isLineEmpty, previousLineBrokeCleanly); + } + } + if (lineWasTooWide || w + tmpW > width) { + if (canHyphenate && w + tmpW > width) { + tryHyphenating(t, f, style->hyphenationLocale(), lastSpace, pos, w + tmpW - additionalTmpW, width, isFixedPitch, collapseWhiteSpace, lastSpaceWordSpacing, lBreak, nextBreakable, hyphenated); + if (hyphenated) + goto end; + } + if (lBreak.obj && shouldPreserveNewline(lBreak.obj) && lBreak.obj->isText() && toRenderText(lBreak.obj)->textLength() && !toRenderText(lBreak.obj)->isWordBreak() && toRenderText(lBreak.obj)->characters()[lBreak.pos] == '\n') { + if (!stoppedIgnoringSpaces && pos > 0) { + // We need to stop right before the newline and then start up again. + addMidpoint(lineMidpointState, InlineIterator(0, o, pos - 1)); // Stop + addMidpoint(lineMidpointState, InlineIterator(0, o, pos)); // Start + } + lBreak.increment(); + previousLineBrokeCleanly = true; + } + if (lBreak.obj && lBreak.pos && lBreak.obj->isText() && toRenderText(lBreak.obj)->textLength() && toRenderText(lBreak.obj)->characters()[lBreak.pos - 1] == softHyphen && style->hyphens() != HyphensNone) + hyphenated = true; + goto end; // Didn't fit. Jump to the end. + } else { + if (!betweenWords || (midWordBreak && !autoWrap)) + tmpW -= additionalTmpW; + if (hyphenWidth) { + // Subtract the width of the soft hyphen out since we fit on a line. + tmpW -= hyphenWidth; + hyphenWidth = 0; + } + } + } + + if (c == '\n' && preserveNewline) { + if (!stoppedIgnoringSpaces && pos > 0) { + // We need to stop right before the newline and then start up again. + addMidpoint(lineMidpointState, InlineIterator(0, o, pos - 1)); // Stop + addMidpoint(lineMidpointState, InlineIterator(0, o, pos)); // Start + } + lBreak.obj = o; + lBreak.pos = pos; + lBreak.nextBreakablePosition = nextBreakable; + lBreak.increment(); + previousLineBrokeCleanly = true; + return lBreak; + } + + if (autoWrap && betweenWords) { + w += tmpW; + wrapW = 0; + tmpW = 0; + lBreak.obj = o; + lBreak.pos = pos; + lBreak.nextBreakablePosition = nextBreakable; + // Auto-wrapping text should not wrap in the middle of a word once it has had an + // opportunity to break after a word. + breakWords = false; + } + + if (midWordBreak) { + // Remember this as a breakable position in case + // adding the end width forces a break. + lBreak.obj = o; + lBreak.pos = pos; + lBreak.nextBreakablePosition = nextBreakable; + midWordBreak &= (breakWords || breakAll); + } + + if (betweenWords) { + lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0; + lastSpace = pos; + } + + if (!ignoringSpaces && o->style()->collapseWhiteSpace()) { + // If we encounter a newline, or if we encounter a + // second space, we need to go ahead and break up this + // run and enter a mode where we start collapsing spaces. + if (currentCharacterIsSpace && previousCharacterIsSpace) { + ignoringSpaces = true; + + // We just entered a mode where we are ignoring + // spaces. Create a midpoint to terminate the run + // before the second space. + addMidpoint(lineMidpointState, ignoreStart); + } + } + } else if (ignoringSpaces) { + // Stop ignoring spaces and begin at this + // new point. + ignoringSpaces = false; + lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0; + lastSpace = pos; // e.g., "Foo goo", don't add in any of the ignored spaces. + addMidpoint(lineMidpointState, InlineIterator(0, o, pos)); + } + + if (currentCharacterIsSpace && !previousCharacterIsSpace) { + ignoreStart.obj = o; + ignoreStart.pos = pos; + } + + if (!currentCharacterIsWS && previousCharacterIsWS) { + if (autoWrap && o->style()->breakOnlyAfterWhiteSpace()) { + lBreak.obj = o; + lBreak.pos = pos; + lBreak.nextBreakablePosition = nextBreakable; + } + } + + if (collapseWhiteSpace && currentCharacterIsSpace && !ignoringSpaces) + trailingSpaceObject = o; + else if (!o->style()->collapseWhiteSpace() || !currentCharacterIsSpace) + trailingSpaceObject = 0; + + pos++; + len--; + atStart = false; + } + + // IMPORTANT: pos is > length here! + int additionalTmpW = ignoringSpaces ? 0 : textWidth(t, lastSpace, pos - lastSpace, f, w + tmpW, isFixedPitch, collapseWhiteSpace) + lastSpaceWordSpacing; + tmpW += additionalTmpW; + tmpW += inlineLogicalWidth(o, !appliedStartWidth, true); + + if (canHyphenate && w + tmpW > width) { + tryHyphenating(t, f, style->hyphenationLocale(), lastSpace, pos, w + tmpW - additionalTmpW, width, isFixedPitch, collapseWhiteSpace, lastSpaceWordSpacing, lBreak, nextBreakable, hyphenated); + if (hyphenated) + goto end; + } + } else + ASSERT_NOT_REACHED(); + + RenderObject* next = bidiNext(this, o); + bool checkForBreak = autoWrap; + if (w && w + tmpW > width && lBreak.obj && currWS == NOWRAP) + checkForBreak = true; + else if (next && o->isText() && next->isText() && !next->isBR()) { + if (autoWrap || (next->style()->autoWrap())) { + if (currentCharacterIsSpace) + checkForBreak = true; + else { + checkForBreak = false; + RenderText* nextText = toRenderText(next); + if (nextText->textLength()) { + UChar c = nextText->characters()[0]; + if (c == ' ' || c == '\t' || (c == '\n' && !shouldPreserveNewline(next))) + // If the next item on the line is text, and if we did not end with + // a space, then the next text run continues our word (and so it needs to + // keep adding to |tmpW|. Just update and continue. + checkForBreak = true; + } else if (nextText->isWordBreak()) + checkForBreak = true; + bool willFitOnLine = w + tmpW <= width; + if (!willFitOnLine && !w) { + fitBelowFloats(tmpW, firstLine, width); + willFitOnLine = tmpW <= width; + } + bool canPlaceOnLine = willFitOnLine || !autoWrapWasEverTrueOnLine; + if (canPlaceOnLine && checkForBreak) { + w += tmpW; + tmpW = 0; + lBreak.obj = next; + lBreak.pos = 0; + lBreak.nextBreakablePosition = -1; + } + } + } + } + + if (checkForBreak && (w + tmpW > width)) { + // if we have floats, try to get below them. + if (currentCharacterIsSpace && !ignoringSpaces && o->style()->collapseWhiteSpace()) + trailingSpaceObject = 0; + + if (w) + goto end; + + fitBelowFloats(tmpW, firstLine, width); + + // |width| may have been adjusted because we got shoved down past a float (thus + // giving us more room), so we need to retest, and only jump to + // the end label if we still don't fit on the line. -dwh + if (w + tmpW > width) + goto end; + } + + if (!o->isFloatingOrPositioned()) { + last = o; + if (last->isReplaced() && autoWrap && (!last->isImage() || allowImagesToBreak) && (!last->isListMarker() || toRenderListMarker(last)->isInside())) { + w += tmpW; + tmpW = 0; + lBreak.obj = next; + lBreak.pos = 0; + lBreak.nextBreakablePosition = -1; + } + } + + o = next; + nextBreakable = -1; + + // Clear out our character space bool, since inline <pre>s don't collapse whitespace + // with adjacent inline normal/nowrap spans. + if (!collapseWhiteSpace) + currentCharacterIsSpace = false; + + pos = 0; + atStart = false; + } + + + if (w + tmpW <= width || lastWS == NOWRAP) { + lBreak.obj = 0; + lBreak.pos = 0; + lBreak.nextBreakablePosition = -1; + } + + end: + if (lBreak == resolver.position() && (!lBreak.obj || !lBreak.obj->isBR())) { + // we just add as much as possible + if (style()->whiteSpace() == PRE) { + // FIXME: Don't really understand this case. + if (pos != 0) { + lBreak.obj = o; + lBreak.pos = pos - 1; + } else { + lBreak.obj = last; + lBreak.pos = last->isText() ? last->length() : 0; + lBreak.nextBreakablePosition = -1; + } + } else if (lBreak.obj) { + // Don't ever break in the middle of a word if we can help it. + // There's no room at all. We just have to be on this line, + // even though we'll spill out. + lBreak.obj = o; + lBreak.pos = pos; + lBreak.nextBreakablePosition = -1; + } + } + + // make sure we consume at least one char/object. + if (lBreak == resolver.position()) + lBreak.increment(); + + // Sanity check our midpoints. + checkMidpoints(lineMidpointState, lBreak); + + if (trailingSpaceObject) { + // This object is either going to be part of the last midpoint, or it is going + // to be the actual endpoint. In both cases we just decrease our pos by 1 level to + // exclude the space, allowing it to - in effect - collapse into the newline. + if (lineMidpointState.numMidpoints % 2) { + InlineIterator* midpoints = lineMidpointState.midpoints.data(); + midpoints[lineMidpointState.numMidpoints - 1].pos--; + } + //else if (lBreak.pos > 0) + // lBreak.pos--; + else if (lBreak.obj == 0 && trailingSpaceObject->isText()) { + // Add a new end midpoint that stops right at the very end. + RenderText* text = toRenderText(trailingSpaceObject); + unsigned length = text->textLength(); + unsigned pos = length >= 2 ? length - 2 : UINT_MAX; + InlineIterator endMid(0, trailingSpaceObject, pos); + addMidpoint(lineMidpointState, endMid); + } + } + + // We might have made lBreak an iterator that points past the end + // of the object. Do this adjustment to make it point to the start + // of the next object instead to avoid confusing the rest of the + // code. + if (lBreak.pos > 0) { + lBreak.pos--; + lBreak.increment(); + } + + return lBreak; +} + +void RenderBlock::addOverflowFromInlineChildren() +{ + int endPadding = hasOverflowClip() ? paddingEnd() : 0; + // FIXME: Need to find another way to do this, since scrollbars could show when we don't want them to. + if (hasOverflowClip() && !endPadding && node() && node()->isContentEditable() && node() == node()->rootEditableElement() && style()->isLeftToRightDirection()) + endPadding = 1; + for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) { + addLayoutOverflow(curr->paddedLayoutOverflowRect(endPadding)); + if (!hasOverflowClip()) + addVisualOverflow(curr->visualOverflowRect()); + } +} + +int RenderBlock::beforeSideVisualOverflowForLine(RootInlineBox* line) const +{ + // Overflow is in the block's coordinate space, which means it isn't purely physical. For flipped blocks (rl and bt), + // we continue to use top and left overflow even though physically it's bottom and right. + if (style()->isHorizontalWritingMode()) + return line->topVisualOverflow(); + return line->leftVisualOverflow(); +} + +int RenderBlock::afterSideVisualOverflowForLine(RootInlineBox* line) const +{ + // Overflow is in the block's coordinate space, which means it isn't purely physical. For flipped blocks (rl and bt), + // we continue to use bottom and right overflow even though physically it's top and left. + if (style()->isHorizontalWritingMode()) + return line->bottomVisualOverflow(); + return line->rightVisualOverflow(); +} + +int RenderBlock::beforeSideLayoutOverflowForLine(RootInlineBox* line) const +{ + // Overflow is in the block's coordinate space, which means it isn't purely physical. For flipped blocks (rl and bt), + // we continue to use top and left overflow even though physically it's bottom and right. + if (style()->isHorizontalWritingMode()) + return line->topLayoutOverflow(); + return line->leftLayoutOverflow(); +} + +int RenderBlock::afterSideLayoutOverflowForLine(RootInlineBox* line) const +{ + // Overflow is in the block's coordinate space, which means it isn't purely physical. For flipped blocks (rl and bt), + // we continue to use bottom and right overflow even though physically it's top and left. + if (style()->isHorizontalWritingMode()) + return line->bottomLayoutOverflow(); + return line->rightLayoutOverflow(); +} + +void RenderBlock::deleteEllipsisLineBoxes() +{ + for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) + curr->clearTruncation(); +} + +void RenderBlock::checkLinesForTextOverflow() +{ + // Determine the width of the ellipsis using the current font. + // FIXME: CSS3 says this is configurable, also need to use 0x002E (FULL STOP) if horizontal ellipsis is "not renderable" + TextRun ellipsisRun(&horizontalEllipsis, 1); + DEFINE_STATIC_LOCAL(AtomicString, ellipsisStr, (&horizontalEllipsis, 1)); + const Font& firstLineFont = firstLineStyle()->font(); + const Font& font = style()->font(); + int firstLineEllipsisWidth = firstLineFont.width(ellipsisRun); + int ellipsisWidth = (font == firstLineFont) ? firstLineEllipsisWidth : font.width(ellipsisRun); + + // For LTR text truncation, we want to get the right edge of our padding box, and then we want to see + // if the right edge of a line box exceeds that. For RTL, we use the left edge of the padding box and + // check the left edge of the line box to see if it is less + // Include the scrollbar for overflow blocks, which means we want to use "contentWidth()" + bool ltr = style()->isLeftToRightDirection(); + for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) { + int blockRightEdge = logicalRightOffsetForLine(curr->y(), curr == firstRootBox()); + int blockLeftEdge = logicalLeftOffsetForLine(curr->y(), curr == firstRootBox()); + int lineBoxEdge = ltr ? curr->x() + curr->logicalWidth() : curr->x(); + if ((ltr && lineBoxEdge > blockRightEdge) || (!ltr && lineBoxEdge < blockLeftEdge)) { + // This line spills out of our box in the appropriate direction. Now we need to see if the line + // can be truncated. In order for truncation to be possible, the line must have sufficient space to + // accommodate our truncation string, and no replaced elements (images, tables) can overlap the ellipsis + // space. + int width = curr == firstRootBox() ? firstLineEllipsisWidth : ellipsisWidth; + int blockEdge = ltr ? blockRightEdge : blockLeftEdge; + if (curr->canAccommodateEllipsis(ltr, blockEdge, lineBoxEdge, width)) + curr->placeEllipsis(ellipsisStr, ltr, blockLeftEdge, blockRightEdge, width); + } + } +} + +} diff --git a/Source/WebCore/rendering/RenderBox.cpp b/Source/WebCore/rendering/RenderBox.cpp new file mode 100644 index 0000000..355d385 --- /dev/null +++ b/Source/WebCore/rendering/RenderBox.cpp @@ -0,0 +1,3428 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) + * (C) 2005, 2006 Samuel Weinig (sam.weinig@gmail.com) + * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * 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 "RenderBox.h" + +#include "CachedImage.h" +#include "Chrome.h" +#include "ChromeClient.h" +#include "Document.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "HitTestResult.h" +#include "htmlediting.h" +#include "HTMLElement.h" +#include "HTMLNames.h" +#include "ImageBuffer.h" +#include "FloatQuad.h" +#include "Frame.h" +#include "Page.h" +#if PLATFORM(ANDROID) +#include "PlatformBridge.h" +#endif +#include "RenderArena.h" +#include "RenderFlexibleBox.h" +#include "RenderInline.h" +#include "RenderLayer.h" +#include "RenderTableCell.h" +#include "RenderTheme.h" +#ifdef ANDROID_LAYOUT +#include "Settings.h" +#endif +#include "RenderView.h" +#include "TransformState.h" +#include <algorithm> +#include <math.h> + +#if ENABLE(WML) +#include "WMLNames.h" +#endif + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +// Used by flexible boxes when flexing this element. +typedef WTF::HashMap<const RenderBox*, int> OverrideSizeMap; +static OverrideSizeMap* gOverrideSizeMap = 0; + +bool RenderBox::s_hadOverflowClip = false; + +RenderBox::RenderBox(Node* node) + : RenderBoxModelObject(node) + , m_marginLeft(0) + , m_marginRight(0) + , m_marginTop(0) + , m_marginBottom(0) + , m_minPreferredLogicalWidth(-1) + , m_maxPreferredLogicalWidth(-1) + , m_inlineBoxWrapper(0) +#ifdef ANDROID_LAYOUT + , m_visibleWidth(0) + , m_isVisibleWidthChangedBeforeLayout(false) +#endif +{ + setIsBox(); +} + +RenderBox::~RenderBox() +{ +} + +int RenderBox::marginBefore() const +{ + switch (style()->writingMode()) { + case TopToBottomWritingMode: + return m_marginTop; + case BottomToTopWritingMode: + return m_marginBottom; + case LeftToRightWritingMode: + return m_marginLeft; + case RightToLeftWritingMode: + return m_marginRight; + } + ASSERT_NOT_REACHED(); + return m_marginTop; +} + +int RenderBox::marginAfter() const +{ + switch (style()->writingMode()) { + case TopToBottomWritingMode: + return m_marginBottom; + case BottomToTopWritingMode: + return m_marginTop; + case LeftToRightWritingMode: + return m_marginRight; + case RightToLeftWritingMode: + return m_marginLeft; + } + ASSERT_NOT_REACHED(); + return m_marginBottom; +} + +int RenderBox::marginStart() const +{ + if (style()->isHorizontalWritingMode()) + return style()->isLeftToRightDirection() ? m_marginLeft : m_marginRight; + return style()->isLeftToRightDirection() ? m_marginTop : m_marginBottom; +} + +int RenderBox::marginEnd() const +{ + if (style()->isHorizontalWritingMode()) + return style()->isLeftToRightDirection() ? m_marginRight : m_marginLeft; + return style()->isLeftToRightDirection() ? m_marginBottom : m_marginTop; +} + +void RenderBox::setMarginStart(int margin) +{ + if (style()->isHorizontalWritingMode()) { + if (style()->isLeftToRightDirection()) + m_marginLeft = margin; + else + m_marginRight = margin; + } else { + if (style()->isLeftToRightDirection()) + m_marginTop = margin; + else + m_marginBottom = margin; + } +} + +void RenderBox::setMarginEnd(int margin) +{ + if (style()->isHorizontalWritingMode()) { + if (style()->isLeftToRightDirection()) + m_marginRight = margin; + else + m_marginLeft = margin; + } else { + if (style()->isLeftToRightDirection()) + m_marginBottom = margin; + else + m_marginTop = margin; + } +} + +void RenderBox::setMarginBefore(int margin) +{ + switch (style()->writingMode()) { + case TopToBottomWritingMode: + m_marginTop = margin; + break; + case BottomToTopWritingMode: + m_marginBottom = margin; + break; + case LeftToRightWritingMode: + m_marginLeft = margin; + break; + case RightToLeftWritingMode: + m_marginRight = margin; + break; + } +} + +void RenderBox::setMarginAfter(int margin) +{ + switch (style()->writingMode()) { + case TopToBottomWritingMode: + m_marginBottom = margin; + break; + case BottomToTopWritingMode: + m_marginTop = margin; + break; + case LeftToRightWritingMode: + m_marginRight = margin; + break; + case RightToLeftWritingMode: + m_marginLeft = margin; + break; + } +} + +void RenderBox::destroy() +{ + // A lot of the code in this function is just pasted into + // RenderWidget::destroy. If anything in this function changes, + // be sure to fix RenderWidget::destroy() as well. + if (hasOverrideSize()) + gOverrideSizeMap->remove(this); + + if (style() && (style()->height().isPercent() || style()->minHeight().isPercent() || style()->maxHeight().isPercent())) + RenderBlock::removePercentHeightDescendant(this); + + RenderBoxModelObject::destroy(); +} + +void RenderBox::removeFloatingOrPositionedChildFromBlockLists() +{ + ASSERT(isFloatingOrPositioned()); + + if (documentBeingDestroyed()) + return; + + if (isFloating()) { + RenderBlock* outermostBlock = containingBlock(); + for (RenderBlock* p = outermostBlock; p && !p->isRenderView(); p = p->containingBlock()) { + if (p->containsFloat(this)) + outermostBlock = p; + } + + if (outermostBlock) { + RenderObject* parent = outermostBlock->parent(); + if (parent && parent->isFlexibleBox()) + outermostBlock = toRenderBlock(parent); + + outermostBlock->markAllDescendantsWithFloatsForLayout(this, false); + } + } + + if (isPositioned()) { + RenderObject* p; + for (p = parent(); p; p = p->parent()) { + if (p->isRenderBlock()) + toRenderBlock(p)->removePositionedObject(this); + } + } +} + +void RenderBox::styleWillChange(StyleDifference diff, const RenderStyle* newStyle) +{ + s_hadOverflowClip = hasOverflowClip(); + + if (style()) { + // The background of the root element or the body element could propagate up to + // the canvas. Just dirty the entire canvas when our style changes substantially. + if (diff >= StyleDifferenceRepaint && node() && + (node()->hasTagName(htmlTag) || node()->hasTagName(bodyTag))) + view()->repaint(); + + // When a layout hint happens and an object's position style changes, we have to do a layout + // to dirty the render tree using the old position value now. + if (diff == StyleDifferenceLayout && parent() && style()->position() != newStyle->position()) { + markContainingBlocksForLayout(); + if (style()->position() == StaticPosition) + repaint(); + else if (newStyle->position() == AbsolutePosition || newStyle->position() == FixedPosition) + parent()->setChildNeedsLayout(true); + if (isFloating() && !isPositioned() && (newStyle->position() == AbsolutePosition || newStyle->position() == FixedPosition)) + removeFloatingOrPositionedChildFromBlockLists(); + } + } else if (newStyle && isBody()) + view()->repaint(); + + if (FrameView *frameView = view()->frameView()) { + bool newStyleIsFixed = newStyle && newStyle->position() == FixedPosition; + bool oldStyleIsFixed = style() && style()->position() == FixedPosition; + if (newStyleIsFixed != oldStyleIsFixed) { + if (newStyleIsFixed) + frameView->addFixedObject(); + else + frameView->removeFixedObject(); + } + } + + RenderBoxModelObject::styleWillChange(diff, newStyle); +} + +void RenderBox::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBoxModelObject::styleDidChange(diff, oldStyle); + + if (needsLayout() && oldStyle && (oldStyle->height().isPercent() || oldStyle->minHeight().isPercent() || oldStyle->maxHeight().isPercent())) + RenderBlock::removePercentHeightDescendant(this); + + // If our zoom factor changes and we have a defined scrollLeft/Top, we need to adjust that value into the + // new zoomed coordinate space. + if (hasOverflowClip() && oldStyle && style() && oldStyle->effectiveZoom() != style()->effectiveZoom()) { + if (int left = layer()->scrollXOffset()) { + left = (left / oldStyle->effectiveZoom()) * style()->effectiveZoom(); + layer()->scrollToXOffset(left); + } + if (int top = layer()->scrollYOffset()) { + top = (top / oldStyle->effectiveZoom()) * style()->effectiveZoom(); + layer()->scrollToYOffset(top); + } + } + + bool isBodyRenderer = isBody(); + bool isRootRenderer = isRoot(); + + // Set the text color if we're the body. + if (isBodyRenderer) + document()->setTextColor(style()->visitedDependentColor(CSSPropertyColor)); + + if (isRootRenderer || isBodyRenderer) { + // Propagate the new writing mode and direction up to the RenderView. + RenderView* viewRenderer = view(); + RenderStyle* viewStyle = viewRenderer->style(); + if (viewStyle->direction() != style()->direction() && (isRootRenderer || !document()->directionSetOnDocumentElement())) { + viewStyle->setDirection(style()->direction()); + if (isBodyRenderer) + document()->documentElement()->renderer()->style()->setDirection(style()->direction()); + setNeedsLayoutAndPrefWidthsRecalc(); + } + + if (viewStyle->writingMode() != style()->writingMode() && (isRootRenderer || !document()->writingModeSetOnDocumentElement())) { + viewStyle->setWritingMode(style()->writingMode()); + if (isBodyRenderer) + document()->documentElement()->renderer()->style()->setWritingMode(style()->writingMode()); + setNeedsLayoutAndPrefWidthsRecalc(); + } + } +} + +void RenderBox::updateBoxModelInfoFromStyle() +{ + RenderBoxModelObject::updateBoxModelInfoFromStyle(); + + bool isRootObject = isRoot(); + bool isViewObject = isRenderView(); + + // The root and the RenderView always paint their backgrounds/borders. + if (isRootObject || isViewObject) + setHasBoxDecorations(true); + + setPositioned(style()->position() == AbsolutePosition || style()->position() == FixedPosition); + setFloating(!isPositioned() && style()->isFloating()); + + // We also handle <body> and <html>, whose overflow applies to the viewport. + if (style()->overflowX() != OVISIBLE && !isRootObject && (isRenderBlock() || isTableRow() || isTableSection())) { + bool boxHasOverflowClip = true; + if (isBody()) { + // Overflow on the body can propagate to the viewport under the following conditions. + // (1) The root element is <html>. + // (2) We are the primary <body> (can be checked by looking at document.body). + // (3) The root element has visible overflow. + if (document()->documentElement()->hasTagName(htmlTag) && + document()->body() == node() && + document()->documentElement()->renderer()->style()->overflowX() == OVISIBLE) + boxHasOverflowClip = false; + } + + // Check for overflow clip. + // It's sufficient to just check one direction, since it's illegal to have visible on only one overflow value. + if (boxHasOverflowClip) { + if (!s_hadOverflowClip) + // Erase the overflow + repaint(); + setHasOverflowClip(); + } + } + + setHasTransform(style()->hasTransformRelatedProperty()); + setHasReflection(style()->boxReflect()); +} + +void RenderBox::layout() +{ + ASSERT(needsLayout()); + + RenderObject* child = firstChild(); + if (!child) { + setNeedsLayout(false); + return; + } + + LayoutStateMaintainer statePusher(view(), this, IntSize(x(), y()), style()->isFlippedBlocksWritingMode()); + while (child) { + child->layoutIfNeeded(); + ASSERT(!child->needsLayout()); + child = child->nextSibling(); + } + statePusher.pop(); + setNeedsLayout(false); +} + +// More IE extensions. clientWidth and clientHeight represent the interior of an object +// excluding border and scrollbar. +int RenderBox::clientWidth() const +{ + return width() - borderLeft() - borderRight() - verticalScrollbarWidth(); +} + +int RenderBox::clientHeight() const +{ + return height() - borderTop() - borderBottom() - horizontalScrollbarHeight(); +} + +int RenderBox::scrollWidth() const +{ + if (hasOverflowClip()) + return layer()->scrollWidth(); + // For objects with visible overflow, this matches IE. + // FIXME: Need to work right with writing modes. + if (style()->isLeftToRightDirection()) + return max(clientWidth(), rightLayoutOverflow() - borderLeft()); + return clientWidth() - min(0, leftLayoutOverflow() - borderLeft()); +} + +int RenderBox::scrollHeight() const +{ + if (hasOverflowClip()) + return layer()->scrollHeight(); + // For objects with visible overflow, this matches IE. + // FIXME: Need to work right with writing modes. + return max(clientHeight(), bottomLayoutOverflow() - borderTop()); +} + +int RenderBox::scrollLeft() const +{ + return hasOverflowClip() ? layer()->scrollXOffset() : 0; +} + +int RenderBox::scrollTop() const +{ + return hasOverflowClip() ? layer()->scrollYOffset() : 0; +} + +void RenderBox::setScrollLeft(int newLeft) +{ + if (hasOverflowClip()) + layer()->scrollToXOffset(newLeft); +} + +void RenderBox::setScrollTop(int newTop) +{ + if (hasOverflowClip()) + layer()->scrollToYOffset(newTop); +} + +void RenderBox::absoluteRects(Vector<IntRect>& rects, int tx, int ty) +{ + rects.append(IntRect(tx, ty, width(), height())); +} + +void RenderBox::absoluteQuads(Vector<FloatQuad>& quads) +{ + quads.append(localToAbsoluteQuad(FloatRect(0, 0, width(), height()))); +} + +void RenderBox::updateLayerTransform() +{ + // Transform-origin depends on box size, so we need to update the layer transform after layout. + if (hasLayer()) + layer()->updateTransform(); +} + +IntRect RenderBox::absoluteContentBox() const +{ + IntRect rect = contentBoxRect(); + FloatPoint absPos = localToAbsolute(FloatPoint()); + rect.move(absPos.x(), absPos.y()); + return rect; +} + +FloatQuad RenderBox::absoluteContentQuad() const +{ + IntRect rect = contentBoxRect(); + return localToAbsoluteQuad(FloatRect(rect)); +} + +IntRect RenderBox::outlineBoundsForRepaint(RenderBoxModelObject* repaintContainer, IntPoint* cachedOffsetToRepaintContainer) const +{ + IntRect box = borderBoundingBox(); + adjustRectForOutlineAndShadow(box); + + FloatQuad containerRelativeQuad = FloatRect(box); + if (cachedOffsetToRepaintContainer) + containerRelativeQuad.move(cachedOffsetToRepaintContainer->x(), cachedOffsetToRepaintContainer->y()); + else + containerRelativeQuad = localToContainerQuad(containerRelativeQuad, repaintContainer); + + box = containerRelativeQuad.enclosingBoundingBox(); + + // FIXME: layoutDelta needs to be applied in parts before/after transforms and + // repaint containers. https://bugs.webkit.org/show_bug.cgi?id=23308 + box.move(view()->layoutDelta()); + + return box; +} + +void RenderBox::addFocusRingRects(Vector<IntRect>& rects, int tx, int ty) +{ + if (width() && height()) + rects.append(IntRect(tx, ty, width(), height())); +} + +IntRect RenderBox::reflectionBox() const +{ + IntRect result; + if (!style()->boxReflect()) + return result; + IntRect box = borderBoxRect(); + result = box; + switch (style()->boxReflect()->direction()) { + case ReflectionBelow: + result.move(0, box.height() + reflectionOffset()); + break; + case ReflectionAbove: + result.move(0, -box.height() - reflectionOffset()); + break; + case ReflectionLeft: + result.move(-box.width() - reflectionOffset(), 0); + break; + case ReflectionRight: + result.move(box.width() + reflectionOffset(), 0); + break; + } + return result; +} + +int RenderBox::reflectionOffset() const +{ + if (!style()->boxReflect()) + return 0; + if (style()->boxReflect()->direction() == ReflectionLeft || style()->boxReflect()->direction() == ReflectionRight) + return style()->boxReflect()->offset().calcValue(borderBoxRect().width()); + return style()->boxReflect()->offset().calcValue(borderBoxRect().height()); +} + +IntRect RenderBox::reflectedRect(const IntRect& r) const +{ + if (!style()->boxReflect()) + return IntRect(); + + IntRect box = borderBoxRect(); + IntRect result = r; + switch (style()->boxReflect()->direction()) { + case ReflectionBelow: + result.setY(box.bottom() + reflectionOffset() + (box.bottom() - r.bottom())); + break; + case ReflectionAbove: + result.setY(box.y() - reflectionOffset() - box.height() + (box.bottom() - r.bottom())); + break; + case ReflectionLeft: + result.setX(box.x() - reflectionOffset() - box.width() + (box.right() - r.right())); + break; + case ReflectionRight: + result.setX(box.right() + reflectionOffset() + (box.right() - r.right())); + break; + } + return result; +} + +int RenderBox::verticalScrollbarWidth() const +{ + return includeVerticalScrollbarSize() ? layer()->verticalScrollbarWidth() : 0; +} + +int RenderBox::horizontalScrollbarHeight() const +{ + return includeHorizontalScrollbarSize() ? layer()->horizontalScrollbarHeight() : 0; +} + +bool RenderBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier, Node** stopNode) +{ + RenderLayer* l = layer(); + if (l && l->scroll(direction, granularity, multiplier)) { + if (stopNode) + *stopNode = node(); + return true; + } + + if (stopNode && *stopNode && *stopNode == node()) + return true; + + RenderBlock* b = containingBlock(); + if (b && !b->isRenderView()) + return b->scroll(direction, granularity, multiplier, stopNode); + return false; +} + +bool RenderBox::logicalScroll(ScrollLogicalDirection direction, ScrollGranularity granularity, float multiplier, Node** stopNode) +{ + bool scrolled = false; + + RenderLayer* l = layer(); + if (l) { +#if PLATFORM(MAC) + // On Mac only we reset the inline direction position when doing a document scroll (e.g., hitting Home/End). + if (granularity == ScrollByDocument) + scrolled = l->scroll(logicalToPhysical(ScrollInlineDirectionBackward, style()->isHorizontalWritingMode(), style()->isFlippedBlocksWritingMode()), ScrollByDocument, multiplier); +#endif + if (l->scroll(logicalToPhysical(direction, style()->isHorizontalWritingMode(), style()->isFlippedBlocksWritingMode()), granularity, multiplier)) + scrolled = true; + + if (scrolled) { + if (stopNode) + *stopNode = node(); + return true; + } + } + + if (stopNode && *stopNode && *stopNode == node()) + return true; + + RenderBlock* b = containingBlock(); + if (b && !b->isRenderView()) + return b->logicalScroll(direction, granularity, multiplier, stopNode); + return false; +} + +bool RenderBox::canBeScrolledAndHasScrollableArea() const +{ + return canBeProgramaticallyScrolled(false) && (scrollHeight() != clientHeight() || scrollWidth() != clientWidth()); +} + +bool RenderBox::canBeProgramaticallyScrolled(bool) const +{ + return (hasOverflowClip() && (scrollsOverflow() || (node() && node()->isContentEditable()))) || (node() && node()->isDocumentNode()); +} + +void RenderBox::autoscroll() +{ + if (layer()) + layer()->autoscroll(); +} + +void RenderBox::panScroll(const IntPoint& source) +{ + if (layer()) + layer()->panScrollFromPoint(source); +} + +int RenderBox::minPreferredLogicalWidth() const +{ + if (preferredLogicalWidthsDirty()) + const_cast<RenderBox*>(this)->computePreferredLogicalWidths(); + + return m_minPreferredLogicalWidth; +} + +int RenderBox::maxPreferredLogicalWidth() const +{ + if (preferredLogicalWidthsDirty()) + const_cast<RenderBox*>(this)->computePreferredLogicalWidths(); + + return m_maxPreferredLogicalWidth; +} + +int RenderBox::overrideSize() const +{ + if (!hasOverrideSize()) + return -1; + return gOverrideSizeMap->get(this); +} + +void RenderBox::setOverrideSize(int s) +{ + if (s == -1) { + if (hasOverrideSize()) { + setHasOverrideSize(false); + gOverrideSizeMap->remove(this); + } + } else { + if (!gOverrideSizeMap) + gOverrideSizeMap = new OverrideSizeMap(); + setHasOverrideSize(true); + gOverrideSizeMap->set(this, s); + } +} + +int RenderBox::overrideWidth() const +{ + return hasOverrideSize() ? overrideSize() : width(); +} + +int RenderBox::overrideHeight() const +{ + return hasOverrideSize() ? overrideSize() : height(); +} + +int RenderBox::computeBorderBoxLogicalWidth(int width) const +{ + int bordersPlusPadding = borderAndPaddingLogicalWidth(); + if (style()->boxSizing() == CONTENT_BOX) + return width + bordersPlusPadding; + return max(width, bordersPlusPadding); +} + +int RenderBox::computeBorderBoxLogicalHeight(int height) const +{ + int bordersPlusPadding = borderAndPaddingLogicalHeight(); + if (style()->boxSizing() == CONTENT_BOX) + return height + bordersPlusPadding; + return max(height, bordersPlusPadding); +} + +int RenderBox::computeContentBoxLogicalWidth(int width) const +{ + if (style()->boxSizing() == BORDER_BOX) + width -= borderAndPaddingLogicalWidth(); + return max(0, width); +} + +int RenderBox::computeContentBoxLogicalHeight(int height) const +{ + if (style()->boxSizing() == BORDER_BOX) + height -= borderAndPaddingLogicalHeight(); + return max(0, height); +} + +// Hit Testing +bool RenderBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int xPos, int yPos, int tx, int ty, HitTestAction action) +{ + tx += x(); + ty += y(); + + // Check kids first. + for (RenderObject* child = lastChild(); child; child = child->previousSibling()) { + if (!child->hasLayer() && child->nodeAtPoint(request, result, xPos, yPos, tx, ty, action)) { + updateHitTestResult(result, IntPoint(xPos - tx, yPos - ty)); + return true; + } + } + + // Check our bounds next. For this purpose always assume that we can only be hit in the + // foreground phase (which is true for replaced elements like images). + IntRect boundsRect = IntRect(tx, ty, width(), height()); + if (visibleToHitTesting() && action == HitTestForeground && boundsRect.intersects(result.rectForPoint(xPos, yPos))) { + updateHitTestResult(result, IntPoint(xPos - tx, yPos - ty)); + if (!result.addNodeToRectBasedTestResult(node(), xPos, yPos, boundsRect)) + return true; + } + + return false; +} + +// --------------------- painting stuff ------------------------------- + +void RenderBox::paint(PaintInfo& paintInfo, int tx, int ty) +{ + tx += x(); + ty += y(); + + // default implementation. Just pass paint through to the children + PaintInfo childInfo(paintInfo); + childInfo.updatePaintingRootForChildren(this); + for (RenderObject* child = firstChild(); child; child = child->nextSibling()) + child->paint(childInfo, tx, ty); +} + +void RenderBox::paintRootBoxDecorations(PaintInfo& paintInfo, int tx, int ty) +{ + const FillLayer* bgLayer = style()->backgroundLayers(); + Color bgColor = style()->visitedDependentColor(CSSPropertyBackgroundColor); + RenderObject* bodyObject = 0; + if (!hasBackground() && node() && node()->hasTagName(HTMLNames::htmlTag)) { + // Locate the <body> element using the DOM. This is easier than trying + // to crawl around a render tree with potential :before/:after content and + // anonymous blocks created by inline <body> tags etc. We can locate the <body> + // render object very easily via the DOM. + HTMLElement* body = document()->body(); + bodyObject = (body && body->hasLocalName(bodyTag)) ? body->renderer() : 0; + if (bodyObject) { + bgLayer = bodyObject->style()->backgroundLayers(); + bgColor = bodyObject->style()->visitedDependentColor(CSSPropertyBackgroundColor); + } + } + + // The background of the box generated by the root element covers the entire canvas, so just use + // the RenderView's docTop/Left/Width/Height accessors. + paintFillLayers(paintInfo, bgColor, bgLayer, view()->docLeft(), view()->docTop(), view()->docWidth(), view()->docHeight(), CompositeSourceOver, bodyObject); + + if (style()->hasBorder() && style()->display() != INLINE) + paintBorder(paintInfo.context, tx, ty, width(), height(), style()); +} + +void RenderBox::paintBoxDecorations(PaintInfo& paintInfo, int tx, int ty) +{ + if (!paintInfo.shouldPaintWithinRoot(this)) + return; + + if (isRoot()) { + paintRootBoxDecorations(paintInfo, tx, ty); + return; + } + + return paintBoxDecorationsWithSize(paintInfo, tx, ty, width(), height()); +} + +void RenderBox::paintBoxDecorationsWithSize(PaintInfo& paintInfo, int tx, int ty, int width, int height) +{ + // border-fit can adjust where we paint our border and background. If set, we snugly fit our line box descendants. (The iChat + // balloon layout is an example of this). + borderFitAdjust(tx, width); + + // FIXME: Should eventually give the theme control over whether the box shadow should paint, since controls could have + // custom shadows of their own. + paintBoxShadow(paintInfo.context, tx, ty, width, height, style(), Normal); + + // If we have a native theme appearance, paint that before painting our background. + // The theme will tell us whether or not we should also paint the CSS background. + bool themePainted = style()->hasAppearance() && !theme()->paint(this, paintInfo, IntRect(tx, ty, width, height)); + if (!themePainted) { + // The <body> only paints its background if the root element has defined a background + // independent of the body. Go through the DOM to get to the root element's render object, + // since the root could be inline and wrapped in an anonymous block. + if (!isBody() || document()->documentElement()->renderer()->hasBackground()) + paintFillLayers(paintInfo, style()->visitedDependentColor(CSSPropertyBackgroundColor), style()->backgroundLayers(), tx, ty, width, height); + if (style()->hasAppearance()) + theme()->paintDecorations(this, paintInfo, IntRect(tx, ty, width, height)); + } + paintBoxShadow(paintInfo.context, tx, ty, width, height, style(), Inset); + + // The theme will tell us whether or not we should also paint the CSS border. + if ((!style()->hasAppearance() || (!themePainted && theme()->paintBorderOnly(this, paintInfo, IntRect(tx, ty, width, height)))) && style()->hasBorder()) + paintBorder(paintInfo.context, tx, ty, width, height, style()); +} + +void RenderBox::paintMask(PaintInfo& paintInfo, int tx, int ty) +{ + if (!paintInfo.shouldPaintWithinRoot(this) || style()->visibility() != VISIBLE || paintInfo.phase != PaintPhaseMask || paintInfo.context->paintingDisabled()) + return; + + int w = width(); + int h = height(); + + // border-fit can adjust where we paint our border and background. If set, we snugly fit our line box descendants. (The iChat + // balloon layout is an example of this). + borderFitAdjust(tx, w); + + paintMaskImages(paintInfo, tx, ty, w, h); +} + +void RenderBox::paintMaskImages(const PaintInfo& paintInfo, int tx, int ty, int w, int h) +{ + // Figure out if we need to push a transparency layer to render our mask. + bool pushTransparencyLayer = false; + bool compositedMask = hasLayer() && layer()->hasCompositedMask(); + CompositeOperator compositeOp = CompositeSourceOver; + + bool allMaskImagesLoaded = true; + + if (!compositedMask) { + // If the context has a rotation, scale or skew, then use a transparency layer to avoid + // pixel cruft around the edge of the mask. + const AffineTransform& currentCTM = paintInfo.context->getCTM(); + pushTransparencyLayer = !currentCTM.isIdentityOrTranslationOrFlipped(); + + StyleImage* maskBoxImage = style()->maskBoxImage().image(); + const FillLayer* maskLayers = style()->maskLayers(); + + // Don't render a masked element until all the mask images have loaded, to prevent a flash of unmasked content. + if (maskBoxImage) + allMaskImagesLoaded &= maskBoxImage->isLoaded(); + + if (maskLayers) + allMaskImagesLoaded &= maskLayers->imagesAreLoaded(); + + // Before all images have loaded, just use an empty transparency layer as the mask. + if (!allMaskImagesLoaded) + pushTransparencyLayer = true; + + if (maskBoxImage && maskLayers->hasImage()) { + // We have a mask-box-image and mask-image, so need to composite them together before using the result as a mask. + pushTransparencyLayer = true; + } else { + // We have to use an extra image buffer to hold the mask. Multiple mask images need + // to composite together using source-over so that they can then combine into a single unified mask that + // can be composited with the content using destination-in. SVG images need to be able to set compositing modes + // as they draw images contained inside their sub-document, so we paint all our images into a separate buffer + // and composite that buffer as the mask. + // We have to check that the mask images to be rendered contain at least one image that can be actually used in rendering + // before pushing the transparency layer. + for (const FillLayer* fillLayer = maskLayers->next(); fillLayer; fillLayer = fillLayer->next()) { + if (fillLayer->hasImage() && fillLayer->image()->canRender(style()->effectiveZoom())) { + pushTransparencyLayer = true; + // We found one image that can be used in rendering, exit the loop + break; + } + } + } + + compositeOp = CompositeDestinationIn; + if (pushTransparencyLayer) { + paintInfo.context->setCompositeOperation(CompositeDestinationIn); + paintInfo.context->beginTransparencyLayer(1.0f); + compositeOp = CompositeSourceOver; + } + } + + if (allMaskImagesLoaded) { + paintFillLayers(paintInfo, Color(), style()->maskLayers(), tx, ty, w, h, compositeOp); + paintNinePieceImage(paintInfo.context, tx, ty, w, h, style(), style()->maskBoxImage(), compositeOp); + } + + if (pushTransparencyLayer) + paintInfo.context->endTransparencyLayer(); +} + +IntRect RenderBox::maskClipRect() +{ + IntRect bbox = borderBoxRect(); + if (style()->maskBoxImage().image()) + return bbox; + + IntRect result; + for (const FillLayer* maskLayer = style()->maskLayers(); maskLayer; maskLayer = maskLayer->next()) { + if (maskLayer->image()) { + IntRect maskRect; + IntPoint phase; + IntSize tileSize; + calculateBackgroundImageGeometry(maskLayer, bbox.x(), bbox.y(), bbox.width(), bbox.height(), maskRect, phase, tileSize); + result.unite(maskRect); + } + } + return result; +} + +void RenderBox::paintFillLayers(const PaintInfo& paintInfo, const Color& c, const FillLayer* fillLayer, int tx, int ty, int width, int height, CompositeOperator op, RenderObject* backgroundObject) +{ + if (!fillLayer) + return; + + paintFillLayers(paintInfo, c, fillLayer->next(), tx, ty, width, height, op, backgroundObject); + paintFillLayer(paintInfo, c, fillLayer, tx, ty, width, height, op, backgroundObject); +} + +void RenderBox::paintFillLayer(const PaintInfo& paintInfo, const Color& c, const FillLayer* fillLayer, int tx, int ty, int width, int height, CompositeOperator op, RenderObject* backgroundObject) +{ + paintFillLayerExtended(paintInfo, c, fillLayer, tx, ty, width, height, 0, op, backgroundObject); +} + +#if USE(ACCELERATED_COMPOSITING) +static bool layersUseImage(WrappedImagePtr image, const FillLayer* layers) +{ + for (const FillLayer* curLayer = layers; curLayer; curLayer = curLayer->next()) { + if (curLayer->image() && image == curLayer->image()->data()) + return true; + } + + return false; +} +#endif + +void RenderBox::imageChanged(WrappedImagePtr image, const IntRect*) +{ + if (!parent()) + return; + + if ((style()->borderImage().image() && style()->borderImage().image()->data() == image) || + (style()->maskBoxImage().image() && style()->maskBoxImage().image()->data() == image)) { + repaint(); + return; + } + + bool didFullRepaint = repaintLayerRectsForImage(image, style()->backgroundLayers(), true); + if (!didFullRepaint) + repaintLayerRectsForImage(image, style()->maskLayers(), false); + + +#if USE(ACCELERATED_COMPOSITING) + if (hasLayer() && layer()->hasCompositedMask() && layersUseImage(image, style()->maskLayers())) + layer()->contentChanged(RenderLayer::MaskImageChanged); +#endif +} + +bool RenderBox::repaintLayerRectsForImage(WrappedImagePtr image, const FillLayer* layers, bool drawingBackground) +{ + IntRect rendererRect; + RenderBox* layerRenderer = 0; + + for (const FillLayer* curLayer = layers; curLayer; curLayer = curLayer->next()) { + if (curLayer->image() && image == curLayer->image()->data() && curLayer->image()->canRender(style()->effectiveZoom())) { + // Now that we know this image is being used, compute the renderer and the rect + // if we haven't already + if (!layerRenderer) { + bool drawingRootBackground = drawingBackground && (isRoot() || (isBody() && !document()->documentElement()->renderer()->hasBackground())); + if (drawingRootBackground) { + layerRenderer = view(); + + int rw; + int rh; + + if (FrameView* frameView = toRenderView(layerRenderer)->frameView()) { + rw = frameView->contentsWidth(); + rh = frameView->contentsHeight(); + } else { + rw = layerRenderer->width(); + rh = layerRenderer->height(); + } + rendererRect = IntRect(-layerRenderer->marginLeft(), + -layerRenderer->marginTop(), + max(layerRenderer->width() + layerRenderer->marginLeft() + layerRenderer->marginRight() + layerRenderer->borderLeft() + layerRenderer->borderRight(), rw), + max(layerRenderer->height() + layerRenderer->marginTop() + layerRenderer->marginBottom() + layerRenderer->borderTop() + layerRenderer->borderBottom(), rh)); + } else { + layerRenderer = this; + rendererRect = borderBoxRect(); + } + } + + IntRect repaintRect; + IntPoint phase; + IntSize tileSize; + layerRenderer->calculateBackgroundImageGeometry(curLayer, rendererRect.x(), rendererRect.y(), rendererRect.width(), rendererRect.height(), repaintRect, phase, tileSize); + layerRenderer->repaintRectangle(repaintRect); + if (repaintRect == rendererRect) + return true; + } + } + return false; +} + +#if PLATFORM(MAC) + +void RenderBox::paintCustomHighlight(int tx, int ty, const AtomicString& type, bool behindText) +{ + Frame* frame = this->frame(); + if (!frame) + return; + Page* page = frame->page(); + if (!page) + return; + + InlineBox* boxWrap = inlineBoxWrapper(); + RootInlineBox* r = boxWrap ? boxWrap->root() : 0; + if (r) { + FloatRect rootRect(tx + r->x(), ty + r->selectionTop(), r->logicalWidth(), r->selectionHeight()); + FloatRect imageRect(tx + x(), rootRect.y(), width(), rootRect.height()); + page->chrome()->client()->paintCustomHighlight(node(), type, imageRect, rootRect, behindText, false); + } else { + FloatRect imageRect(tx + x(), ty + y(), width(), height()); + page->chrome()->client()->paintCustomHighlight(node(), type, imageRect, imageRect, behindText, false); + } +} + +#endif + +bool RenderBox::pushContentsClip(PaintInfo& paintInfo, int tx, int ty) +{ + if (paintInfo.phase == PaintPhaseBlockBackground || paintInfo.phase == PaintPhaseSelfOutline || paintInfo.phase == PaintPhaseMask) + return false; + + bool isControlClip = hasControlClip(); + bool isOverflowClip = hasOverflowClip() && !layer()->isSelfPaintingLayer(); + + if (!isControlClip && !isOverflowClip) + return false; + + if (paintInfo.phase == PaintPhaseOutline) + paintInfo.phase = PaintPhaseChildOutlines; + else if (paintInfo.phase == PaintPhaseChildBlockBackground) { + paintInfo.phase = PaintPhaseBlockBackground; + paintObject(paintInfo, tx, ty); + paintInfo.phase = PaintPhaseChildBlockBackgrounds; + } + IntRect clipRect(isControlClip ? controlClipRect(tx, ty) : overflowClipRect(tx, ty)); + paintInfo.context->save(); + if (style()->hasBorderRadius()) { + IntSize topLeft, topRight, bottomLeft, bottomRight; + IntRect borderRect = IntRect(tx, ty, width(), height()); + style()->getBorderRadiiForRect(borderRect, topLeft, topRight, bottomLeft, bottomRight); + + paintInfo.context->addRoundedRectClip(borderRect, topLeft, topRight, bottomLeft, bottomRight); + } + + paintInfo.context->clip(clipRect); + return true; +} + +void RenderBox::popContentsClip(PaintInfo& paintInfo, PaintPhase originalPhase, int tx, int ty) +{ + ASSERT(hasControlClip() || (hasOverflowClip() && !layer()->isSelfPaintingLayer())); + + paintInfo.context->restore(); + if (originalPhase == PaintPhaseOutline) { + paintInfo.phase = PaintPhaseSelfOutline; + paintObject(paintInfo, tx, ty); + paintInfo.phase = originalPhase; + } else if (originalPhase == PaintPhaseChildBlockBackground) + paintInfo.phase = originalPhase; +} + +IntRect RenderBox::overflowClipRect(int tx, int ty) +{ + // FIXME: When overflow-clip (CSS3) is implemented, we'll obtain the property + // here. + + int bLeft = borderLeft(); + int bTop = borderTop(); + + int clipX = tx + bLeft; + int clipY = ty + bTop; + int clipWidth = width() - bLeft - borderRight(); + int clipHeight = height() - bTop - borderBottom(); + + // Subtract out scrollbars if we have them. + if (layer()) { + clipWidth -= layer()->verticalScrollbarWidth(); + clipHeight -= layer()->horizontalScrollbarHeight(); + } + + return IntRect(clipX, clipY, clipWidth, clipHeight); +} + +IntRect RenderBox::clipRect(int tx, int ty) +{ + int clipX = tx; + int clipY = ty; + int clipWidth = width(); + int clipHeight = height(); + + if (!style()->clipLeft().isAuto()) { + int c = style()->clipLeft().calcValue(width()); + clipX += c; + clipWidth -= c; + } + + if (!style()->clipRight().isAuto()) + clipWidth -= width() - style()->clipRight().calcValue(width()); + + if (!style()->clipTop().isAuto()) { + int c = style()->clipTop().calcValue(height()); + clipY += c; + clipHeight -= c; + } + + if (!style()->clipBottom().isAuto()) + clipHeight -= height() - style()->clipBottom().calcValue(height()); + + return IntRect(clipX, clipY, clipWidth, clipHeight); +} + +int RenderBox::containingBlockLogicalWidthForContent() const +{ + RenderBlock* cb = containingBlock(); + if (shrinkToAvoidFloats()) + return cb->availableLogicalWidthForLine(y(), false); + return cb->availableLogicalWidth(); +} + +int RenderBox::perpendicularContainingBlockLogicalHeight() const +{ + RenderBlock* cb = containingBlock(); + RenderStyle* containingBlockStyle = cb->style(); + Length logicalHeightLength = containingBlockStyle->logicalHeight(); + + // FIXME: For now just support fixed heights. Eventually should support percentage heights as well. + if (!logicalHeightLength.isFixed()) { + // Rather than making the child be completely unconstrained, WinIE uses the viewport width and height + // as a constraint. We do that for now as well even though it's likely being unconstrained is what the spec + // will decide. + return containingBlockStyle->isHorizontalWritingMode() ? view()->frameView()->visibleHeight() : view()->frameView()->visibleWidth(); + } + + // Use the content box logical height as specified by the style. + return cb->computeContentBoxLogicalHeight(logicalHeightLength.value()); +} + +void RenderBox::mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool fixed, bool useTransforms, TransformState& transformState) const +{ + if (repaintContainer == this) + return; + + if (RenderView* v = view()) { + if (v->layoutStateEnabled() && !repaintContainer) { + LayoutState* layoutState = v->layoutState(); + IntSize offset = layoutState->m_paintOffset; + offset.expand(x(), y()); + if (style()->position() == RelativePosition && layer()) + offset += layer()->relativePositionOffset(); + transformState.move(offset); + return; + } + } + + bool containerSkipped; + RenderObject* o = container(repaintContainer, &containerSkipped); + if (!o) + return; + + bool isFixedPos = style()->position() == FixedPosition; + bool hasTransform = hasLayer() && layer()->transform(); + if (hasTransform) { + // If this box has a transform, it acts as a fixed position container for fixed descendants, + // and may itself also be fixed position. So propagate 'fixed' up only if this box is fixed position. + fixed &= isFixedPos; + } else + fixed |= isFixedPos; + + IntSize containerOffset = offsetFromContainer(o, roundedIntPoint(transformState.mappedPoint())); + + bool preserve3D = useTransforms && (o->style()->preserves3D() || style()->preserves3D()); + if (useTransforms && shouldUseTransformFromContainer(o)) { + TransformationMatrix t; + getTransformFromContainer(o, containerOffset, t); + transformState.applyTransform(t, preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); + } else + transformState.move(containerOffset.width(), containerOffset.height(), preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); + + if (containerSkipped) { + // There can't be a transform between repaintContainer and o, because transforms create containers, so it should be safe + // to just subtract the delta between the repaintContainer and o. + IntSize containerOffset = repaintContainer->offsetFromAncestorContainer(o); + transformState.move(-containerOffset.width(), -containerOffset.height(), preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); + return; + } + + o->mapLocalToContainer(repaintContainer, fixed, useTransforms, transformState); +} + +void RenderBox::mapAbsoluteToLocalPoint(bool fixed, bool useTransforms, TransformState& transformState) const +{ + // We don't expect absoluteToLocal() to be called during layout (yet) + ASSERT(!view() || !view()->layoutStateEnabled()); + + bool isFixedPos = style()->position() == FixedPosition; + bool hasTransform = hasLayer() && layer()->transform(); + if (hasTransform) { + // If this box has a transform, it acts as a fixed position container for fixed descendants, + // and may itself also be fixed position. So propagate 'fixed' up only if this box is fixed position. + fixed &= isFixedPos; + } else + fixed |= isFixedPos; + + RenderObject* o = container(); + if (!o) + return; + + o->mapAbsoluteToLocalPoint(fixed, useTransforms, transformState); + + IntSize containerOffset = offsetFromContainer(o, IntPoint()); + + bool preserve3D = useTransforms && (o->style()->preserves3D() || style()->preserves3D()); + if (useTransforms && shouldUseTransformFromContainer(o)) { + TransformationMatrix t; + getTransformFromContainer(o, containerOffset, t); + transformState.applyTransform(t, preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); + } else + transformState.move(-containerOffset.width(), -containerOffset.height(), preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); +} + +IntSize RenderBox::offsetFromContainer(RenderObject* o, const IntPoint& point) const +{ + ASSERT(o == container()); + + IntSize offset; + if (isRelPositioned()) + offset += relativePositionOffset(); + + if (!isInline() || isReplaced()) { + if (style()->position() != AbsolutePosition && style()->position() != FixedPosition) { + o->adjustForColumns(offset, IntPoint(point.x() + x(), point.y() + y())); + offset += locationOffsetIncludingFlipping(); + } else + offset += locationOffset(); + } + + if (o->hasOverflowClip()) + offset -= toRenderBox(o)->layer()->scrolledContentOffset(); + + if (style()->position() == AbsolutePosition && o->isRelPositioned() && o->isRenderInline()) + offset += toRenderInline(o)->relativePositionedInlineOffset(this); + + return offset; +} + +InlineBox* RenderBox::createInlineBox() +{ + return new (renderArena()) InlineBox(this); +} + +void RenderBox::dirtyLineBoxes(bool fullLayout) +{ + if (m_inlineBoxWrapper) { + if (fullLayout) { + m_inlineBoxWrapper->destroy(renderArena()); + m_inlineBoxWrapper = 0; + } else + m_inlineBoxWrapper->dirtyLineBoxes(); + } +} + +void RenderBox::positionLineBox(InlineBox* box) +{ + if (isPositioned()) { + // Cache the x position only if we were an INLINE type originally. + bool wasInline = style()->isOriginalDisplayInlineType(); + if (wasInline && style()->hasStaticX()) { + // The value is cached in the xPos of the box. We only need this value if + // our object was inline originally, since otherwise it would have ended up underneath + // the inlines. + layer()->setStaticX(box->x()); + setChildNeedsLayout(true, false); // Just go ahead and mark the positioned object as needing layout, so it will update its position properly. + } else if (!wasInline && style()->hasStaticY()) { + // Our object was a block originally, so we make our normal flow position be + // just below the line box (as though all the inlines that came before us got + // wrapped in an anonymous block, which is what would have happened had we been + // in flow). This value was cached in the y() of the box. + layer()->setStaticY(box->y()); + setChildNeedsLayout(true, false); // Just go ahead and mark the positioned object as needing layout, so it will update its position properly. + } + + // Nuke the box. + box->remove(); + box->destroy(renderArena()); + } else if (isReplaced()) { + setLocation(box->x(), box->y()); + m_inlineBoxWrapper = box; + } +} + +void RenderBox::deleteLineBoxWrapper() +{ + if (m_inlineBoxWrapper) { + if (!documentBeingDestroyed()) + m_inlineBoxWrapper->remove(); + m_inlineBoxWrapper->destroy(renderArena()); + m_inlineBoxWrapper = 0; + } +} + +IntRect RenderBox::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) +{ + if (style()->visibility() != VISIBLE && !enclosingLayer()->hasVisibleContent()) + return IntRect(); + + IntRect r = visualOverflowRect(); + + RenderView* v = view(); + if (v) { + // FIXME: layoutDelta needs to be applied in parts before/after transforms and + // repaint containers. https://bugs.webkit.org/show_bug.cgi?id=23308 + r.move(v->layoutDelta()); + } + + if (style()) { + if (style()->hasAppearance()) + // The theme may wish to inflate the rect used when repainting. + theme()->adjustRepaintRect(this, r); + + // We have to use maximalOutlineSize() because a child might have an outline + // that projects outside of our overflowRect. + if (v) { + ASSERT(style()->outlineSize() <= v->maximalOutlineSize()); + r.inflate(v->maximalOutlineSize()); + } + } + + computeRectForRepaint(repaintContainer, r); + return r; +} + +void RenderBox::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& rect, bool fixed) +{ + // The rect we compute at each step is shifted by our x/y offset in the parent container's coordinate space. + // Only when we cross a writing mode boundary will we have to possibly flipForWritingMode (to convert into a more appropriate + // offset corner for the enclosing container). This allows for a fully RL or BT document to repaint + // properly even during layout, since the rect remains flipped all the way until the end. + // + // RenderView::computeRectForRepaint then converts the rect to physical coordinates. We also convert to + // physical when we hit a repaintContainer boundary. Therefore the final rect returned is always in the + // physical coordinate space of the repaintContainer. + if (RenderView* v = view()) { + // LayoutState is only valid for root-relative repainting + if (v->layoutStateEnabled() && !repaintContainer) { + LayoutState* layoutState = v->layoutState(); + + if (layer() && layer()->transform()) + rect = layer()->transform()->mapRect(rect); + + if (style()->position() == RelativePosition && layer()) + rect.move(layer()->relativePositionOffset()); + + rect.move(x(), y()); + rect.move(layoutState->m_paintOffset); + if (layoutState->m_clipped) + rect.intersect(layoutState->m_clipRect); + return; + } + } + + if (hasReflection()) + rect.unite(reflectedRect(rect)); + + if (repaintContainer == this) { + if (repaintContainer->style()->isFlippedBlocksWritingMode()) + flipForWritingMode(rect); + return; + } + + bool containerSkipped; + RenderObject* o = container(repaintContainer, &containerSkipped); + if (!o) + return; + + if (isWritingModeRoot() && !isPositioned()) + flipForWritingMode(rect); + IntPoint topLeft = rect.location(); + topLeft.move(x(), y()); + + EPosition position = style()->position(); + + // We are now in our parent container's coordinate space. Apply our transform to obtain a bounding box + // in the parent's coordinate space that encloses us. + if (layer() && layer()->transform()) { + fixed = position == FixedPosition; + rect = layer()->transform()->mapRect(rect); + topLeft = rect.location(); + topLeft.move(x(), y()); + } else if (position == FixedPosition) + fixed = true; + + if (position == AbsolutePosition && o->isRelPositioned() && o->isRenderInline()) + topLeft += toRenderInline(o)->relativePositionedInlineOffset(this); + else if (position == RelativePosition && layer()) { + // Apply the relative position offset when invalidating a rectangle. The layer + // is translated, but the render box isn't, so we need to do this to get the + // right dirty rect. Since this is called from RenderObject::setStyle, the relative position + // flag on the RenderObject has been cleared, so use the one on the style(). + topLeft += layer()->relativePositionOffset(); + } + + if (o->isBlockFlow() && position != AbsolutePosition && position != FixedPosition) { + RenderBlock* cb = toRenderBlock(o); + if (cb->hasColumns()) { + IntRect repaintRect(topLeft, rect.size()); + cb->adjustRectForColumns(repaintRect); + topLeft = repaintRect.location(); + rect = repaintRect; + } + } + + // FIXME: We ignore the lightweight clipping rect that controls use, since if |o| is in mid-layout, + // its controlClipRect will be wrong. For overflow clip we use the values cached by the layer. + if (o->hasOverflowClip()) { + RenderBox* containerBox = toRenderBox(o); + + // o->height() is inaccurate if we're in the middle of a layout of |o|, so use the + // layer's size instead. Even if the layer's size is wrong, the layer itself will repaint + // anyway if its size does change. + topLeft -= containerBox->layer()->scrolledContentOffset(); // For overflow:auto/scroll/hidden. + + IntRect repaintRect(topLeft, rect.size()); + IntRect boxRect(0, 0, containerBox->layer()->width(), containerBox->layer()->height()); + rect = intersection(repaintRect, boxRect); + if (rect.isEmpty()) + return; + } else + rect.setLocation(topLeft); + + if (containerSkipped) { + // If the repaintContainer is below o, then we need to map the rect into repaintContainer's coordinates. + IntSize containerOffset = repaintContainer->offsetFromAncestorContainer(o); + rect.move(-containerOffset); + return; + } + + o->computeRectForRepaint(repaintContainer, rect, fixed); +} + +void RenderBox::repaintDuringLayoutIfMoved(const IntRect& rect) +{ + int newX = x(); + int newY = y(); + int newWidth = width(); + int newHeight = height(); + if (rect.x() != newX || rect.y() != newY) { + // The child moved. Invalidate the object's old and new positions. We have to do this + // since the object may not have gotten a layout. + m_frameRect = rect; + repaint(); + repaintOverhangingFloats(true); + m_frameRect = IntRect(newX, newY, newWidth, newHeight); + repaint(); + repaintOverhangingFloats(true); + } +} + +#ifdef ANDROID_LAYOUT +void RenderBox::setVisibleWidth(int newWidth) { + const Settings* settings = document()->settings(); + ASSERT(settings); + if (settings->layoutAlgorithm() != Settings::kLayoutFitColumnToScreen + || m_visibleWidth == newWidth) + return; + m_isVisibleWidthChangedBeforeLayout = true; + m_visibleWidth = newWidth; +} + +bool RenderBox::checkAndSetRelayoutChildren(bool* relayoutChildren) { + if (m_isVisibleWidthChangedBeforeLayout) { + m_isVisibleWidthChangedBeforeLayout = false; + *relayoutChildren = true; + return true; + } + return false; +} +#endif + +void RenderBox::computeLogicalWidth() +{ +#ifdef ANDROID_LAYOUT + if (view()->frameView()) + setVisibleWidth(view()->frameView()->textWrapWidth()); +#endif + + if (isPositioned()) { + // FIXME: This calculation is not patched for block-flow yet. + // https://bugs.webkit.org/show_bug.cgi?id=46500 + computePositionedLogicalWidth(); + return; + } + + // If layout is limited to a subtree, the subtree root's logical width does not change. + if (node() && view()->frameView() && view()->frameView()->layoutRoot(true) == this) + return; + + // The parent box is flexing us, so it has increased or decreased our + // width. Use the width from the style context. + // FIXME: Account for block-flow in flexible boxes. + // https://bugs.webkit.org/show_bug.cgi?id=46418 + if (hasOverrideSize() && parent()->style()->boxOrient() == HORIZONTAL + && parent()->isFlexibleBox() && parent()->isFlexingChildren()) { + setLogicalWidth(overrideSize()); + return; + } + + // FIXME: Account for block-flow in flexible boxes. + // https://bugs.webkit.org/show_bug.cgi?id=46418 + bool inVerticalBox = parent()->isFlexibleBox() && (parent()->style()->boxOrient() == VERTICAL); + bool stretching = (parent()->style()->boxAlign() == BSTRETCH); + bool treatAsReplaced = shouldComputeSizeAsReplaced() && (!inVerticalBox || !stretching); + + Length logicalWidthLength = (treatAsReplaced) ? Length(computeReplacedLogicalWidth(), Fixed) : style()->logicalWidth(); + + RenderBlock* cb = containingBlock(); + int containerLogicalWidth = max(0, containingBlockLogicalWidthForContent()); + bool hasPerpendicularContainingBlock = cb->style()->isHorizontalWritingMode() != style()->isHorizontalWritingMode(); + int containerWidthInInlineDirection = containerLogicalWidth; + if (hasPerpendicularContainingBlock) + containerWidthInInlineDirection = perpendicularContainingBlockLogicalHeight(); + + if (isInline() && !isInlineBlockOrInlineTable()) { + // just calculate margins + setMarginStart(style()->marginStart().calcMinValue(containerLogicalWidth)); + setMarginEnd(style()->marginEnd().calcMinValue(containerLogicalWidth)); +#ifdef ANDROID_LAYOUT + if (treatAsReplaced) { +#else + if (treatAsReplaced) +#endif + setLogicalWidth(max(logicalWidthLength.value() + borderAndPaddingLogicalWidth(), minPreferredLogicalWidth())); + +#ifdef ANDROID_LAYOUT + // in SSR mode with replaced box, if the box width is wider than the container width, + // it will be shrinked to fit to the container. + if (containerLogicalWidth && (width() + m_marginLeft + m_marginRight) > containerLogicalWidth && + document()->frame()->settings()->layoutAlgorithm() == Settings::kLayoutSSR) { + m_marginLeft = m_marginRight = 0; + setWidth(containerLogicalWidth); + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = containerLogicalWidth; + } + } +#endif + return; + } + + // Width calculations + if (treatAsReplaced) + setLogicalWidth(logicalWidthLength.value() + borderAndPaddingLogicalWidth()); + else { + // Calculate LogicalWidth + setLogicalWidth(computeLogicalWidthUsing(LogicalWidth, containerWidthInInlineDirection)); + + // Calculate MaxLogicalWidth + if (!style()->logicalMaxWidth().isUndefined()) { + int maxLogicalWidth = computeLogicalWidthUsing(MaxLogicalWidth, containerWidthInInlineDirection); + if (logicalWidth() > maxLogicalWidth) { + setLogicalWidth(maxLogicalWidth); + logicalWidthLength = style()->logicalMaxWidth(); + } + } + + // Calculate MinLogicalWidth + int minLogicalWidth = computeLogicalWidthUsing(MinLogicalWidth, containerWidthInInlineDirection); + if (logicalWidth() < minLogicalWidth) { + setLogicalWidth(minLogicalWidth); + logicalWidthLength = style()->logicalMinWidth(); + } + } + + // Fieldsets are currently the only objects that stretch to their minimum width. + if (stretchesToMinIntrinsicLogicalWidth()) { + setLogicalWidth(max(logicalWidth(), minPreferredLogicalWidth())); + logicalWidthLength = Length(logicalWidth(), Fixed); + } + + // Margin calculations. + if (logicalWidthLength.isAuto() || hasPerpendicularContainingBlock || isFloating() || isInline()) { + setMarginStart(style()->marginStart().calcMinValue(containerLogicalWidth)); + setMarginEnd(style()->marginEnd().calcMinValue(containerLogicalWidth)); + } else + computeInlineDirectionMargins(cb, containerLogicalWidth, logicalWidth()); + +#ifdef ANDROID_LAYOUT + // in SSR mode with non-replaced box, we use ANDROID_SSR_MARGIN_PADDING for left/right margin. + // If the box width is wider than the container width, it will be shrinked to fit to the container. + if (containerLogicalWidth && !treatAsReplaced && + document()->settings()->layoutAlgorithm() == Settings::kLayoutSSR) { + setWidth(width() + m_marginLeft + m_marginRight); + m_marginLeft = m_marginLeft > ANDROID_SSR_MARGIN_PADDING ? ANDROID_SSR_MARGIN_PADDING : m_marginLeft; + m_marginRight = m_marginRight > ANDROID_SSR_MARGIN_PADDING ? ANDROID_SSR_MARGIN_PADDING : m_marginRight; + if (width() > containerLogicalWidth) { + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = containerLogicalWidth-(m_marginLeft + m_marginRight); + setWidth(m_minPreferredLogicalWidth); + } else + setWidth(width() -(m_marginLeft + m_marginRight)); + } +#endif + + if (!hasPerpendicularContainingBlock && containerLogicalWidth && containerLogicalWidth != (logicalWidth() + marginStart() + marginEnd()) + && !isFloating() && !isInline() && !cb->isFlexibleBox()) + cb->setMarginEndForChild(this, containerLogicalWidth - logicalWidth() - cb->marginStartForChild(this)); +} + +int RenderBox::computeLogicalWidthUsing(LogicalWidthType widthType, int availableLogicalWidth) +{ + int logicalWidthResult = logicalWidth(); + Length logicalWidth; + if (widthType == LogicalWidth) + logicalWidth = style()->logicalWidth(); + else if (widthType == MinLogicalWidth) + logicalWidth = style()->logicalMinWidth(); + else + logicalWidth = style()->logicalMaxWidth(); + + if (logicalWidth.isIntrinsicOrAuto()) { + int marginStart = style()->marginStart().calcMinValue(availableLogicalWidth); + int marginEnd = style()->marginEnd().calcMinValue(availableLogicalWidth); + if (availableLogicalWidth) + logicalWidthResult = availableLogicalWidth - marginStart - marginEnd; + + if (sizesToIntrinsicLogicalWidth(widthType)) { + logicalWidthResult = max(logicalWidthResult, minPreferredLogicalWidth()); + logicalWidthResult = min(logicalWidthResult, maxPreferredLogicalWidth()); + } + } else // FIXME: If the containing block flow is perpendicular to our direction we need to use the available logical height instead. + logicalWidthResult = computeBorderBoxLogicalWidth(logicalWidth.calcValue(availableLogicalWidth)); + + return logicalWidthResult; +} + +bool RenderBox::sizesToIntrinsicLogicalWidth(LogicalWidthType widthType) const +{ + // Marquees in WinIE are like a mixture of blocks and inline-blocks. They size as though they're blocks, + // but they allow text to sit on the same line as the marquee. + if (isFloating() || (isInlineBlockOrInlineTable() && !isHTMLMarquee())) + return true; + + // This code may look a bit strange. Basically width:intrinsic should clamp the size when testing both + // min-width and width. max-width is only clamped if it is also intrinsic. + Length logicalWidth = (widthType == MaxLogicalWidth) ? style()->logicalMaxWidth() : style()->logicalWidth(); + if (logicalWidth.type() == Intrinsic) + return true; + + // Children of a horizontal marquee do not fill the container by default. + // FIXME: Need to deal with MAUTO value properly. It could be vertical. + // FIXME: Think about block-flow here. Need to find out how marquee direction relates to + // block-flow (as well as how marquee overflow should relate to block flow). + // https://bugs.webkit.org/show_bug.cgi?id=46472 + if (parent()->style()->overflowX() == OMARQUEE) { + EMarqueeDirection dir = parent()->style()->marqueeDirection(); + if (dir == MAUTO || dir == MFORWARD || dir == MBACKWARD || dir == MLEFT || dir == MRIGHT) + return true; + } + + // Flexible horizontal boxes lay out children at their intrinsic widths. Also vertical boxes + // that don't stretch their kids lay out their children at their intrinsic widths. + // FIXME: Think about block-flow here. + // https://bugs.webkit.org/show_bug.cgi?id=46473 + if (parent()->isFlexibleBox() + && (parent()->style()->boxOrient() == HORIZONTAL || parent()->style()->boxAlign() != BSTRETCH)) + return true; + + // Button, input, select, textarea, legend and datagrid treat + // width value of 'auto' as 'intrinsic' unless it's in a + // stretching vertical flexbox. + // FIXME: Think about block-flow here. + // https://bugs.webkit.org/show_bug.cgi?id=46473 + if (logicalWidth.type() == Auto && !(parent()->isFlexibleBox() && parent()->style()->boxOrient() == VERTICAL && parent()->style()->boxAlign() == BSTRETCH) && node() && (node()->hasTagName(inputTag) || node()->hasTagName(selectTag) || node()->hasTagName(buttonTag) || node()->hasTagName(textareaTag) || node()->hasTagName(legendTag) || node()->hasTagName(datagridTag))) + return true; + + return false; +} + +void RenderBox::computeInlineDirectionMargins(RenderBlock* containingBlock, int containerWidth, int childWidth) +{ + const RenderStyle* containingBlockStyle = containingBlock->style(); + Length marginStartLength = style()->marginStartUsing(containingBlockStyle); + Length marginEndLength = style()->marginEndUsing(containingBlockStyle); + + // Case One: The object is being centered in the containing block's available logical width. + if ((marginStartLength.isAuto() && marginEndLength.isAuto() && childWidth < containerWidth) + || (!marginStartLength.isAuto() && !marginEndLength.isAuto() && containingBlock->style()->textAlign() == WEBKIT_CENTER)) { + containingBlock->setMarginStartForChild(this, max(0, (containerWidth - childWidth) / 2)); + containingBlock->setMarginEndForChild(this, containerWidth - childWidth - containingBlock->marginStartForChild(this)); + return; + } + + // Case Two: The object is being pushed to the start of the containing block's available logical width. + if (marginEndLength.isAuto() && childWidth < containerWidth) { + containingBlock->setMarginStartForChild(this, marginStartLength.calcValue(containerWidth)); + containingBlock->setMarginEndForChild(this, containerWidth - childWidth - containingBlock->marginStartForChild(this)); + return; + } + + // Case Three: The object is being pushed to the end of the containing block's available logical width. + bool pushToEndFromTextAlign = !marginEndLength.isAuto() && ((!containingBlockStyle->isLeftToRightDirection() && containingBlockStyle->textAlign() == WEBKIT_LEFT) + || (containingBlockStyle->isLeftToRightDirection() && containingBlockStyle->textAlign() == WEBKIT_RIGHT)); + if ((marginStartLength.isAuto() && childWidth < containerWidth) || pushToEndFromTextAlign) { + containingBlock->setMarginEndForChild(this, marginEndLength.calcValue(containerWidth)); + containingBlock->setMarginStartForChild(this, containerWidth - childWidth - containingBlock->marginEndForChild(this)); + return; + } + + // Case Four: Either no auto margins, or our width is >= the container width (css2.1, 10.3.3). In that case + // auto margins will just turn into 0. + containingBlock->setMarginStartForChild(this, marginStartLength.calcMinValue(containerWidth)); + containingBlock->setMarginEndForChild(this, marginEndLength.calcMinValue(containerWidth)); +} + +void RenderBox::computeLogicalHeight() +{ + // Cell height is managed by the table and inline non-replaced elements do not support a height property. + if (isTableCell() || (isInline() && !isReplaced())) + return; + + Length h; + if (isPositioned()) { + // FIXME: This calculation is not patched for block-flow yet. + // https://bugs.webkit.org/show_bug.cgi?id=46500 + computePositionedLogicalHeight(); + } else { + RenderBlock* cb = containingBlock(); + bool hasPerpendicularContainingBlock = cb->style()->isHorizontalWritingMode() != style()->isHorizontalWritingMode(); + + if (!hasPerpendicularContainingBlock) + computeBlockDirectionMargins(cb); + + // For tables, calculate margins only. + if (isTable()) { + if (hasPerpendicularContainingBlock) + computeInlineDirectionMargins(cb, containingBlockLogicalWidthForContent(), logicalHeight()); + return; + } + + // FIXME: Account for block-flow in flexible boxes. + // https://bugs.webkit.org/show_bug.cgi?id=46418 + bool inHorizontalBox = parent()->isFlexibleBox() && parent()->style()->boxOrient() == HORIZONTAL; + bool stretching = parent()->style()->boxAlign() == BSTRETCH; + bool treatAsReplaced = shouldComputeSizeAsReplaced() && (!inHorizontalBox || !stretching); + bool checkMinMaxHeight = false; + + // The parent box is flexing us, so it has increased or decreased our height. We have to + // grab our cached flexible height. + // FIXME: Account for block-flow in flexible boxes. + // https://bugs.webkit.org/show_bug.cgi?id=46418 + if (hasOverrideSize() && parent()->isFlexibleBox() && parent()->style()->boxOrient() == VERTICAL + && parent()->isFlexingChildren()) + h = Length(overrideSize() - borderAndPaddingLogicalHeight(), Fixed); + else if (treatAsReplaced) + h = Length(computeReplacedLogicalHeight(), Fixed); + else { + h = style()->logicalHeight(); + checkMinMaxHeight = true; + } + + // Block children of horizontal flexible boxes fill the height of the box. + // FIXME: Account for block-flow in flexible boxes. + // https://bugs.webkit.org/show_bug.cgi?id=46418 + if (h.isAuto() && parent()->isFlexibleBox() && parent()->style()->boxOrient() == HORIZONTAL + && parent()->isStretchingChildren()) { + h = Length(parentBox()->contentLogicalHeight() - marginBefore() - marginAfter() - borderAndPaddingLogicalHeight(), Fixed); + checkMinMaxHeight = false; + } + + int heightResult; + if (checkMinMaxHeight) { +#ifdef ANDROID_LAYOUT + // in SSR mode, ignore CSS height as layout is so different + if (document()->settings()->layoutAlgorithm() == Settings::kLayoutSSR) + heightResult = -1; + else +#endif + heightResult = computeLogicalHeightUsing(style()->logicalHeight()); + if (heightResult == -1) + heightResult = logicalHeight(); + int minH = computeLogicalHeightUsing(style()->logicalMinHeight()); // Leave as -1 if unset. + int maxH = style()->logicalMaxHeight().isUndefined() ? heightResult : computeLogicalHeightUsing(style()->logicalMaxHeight()); + if (maxH == -1) + maxH = heightResult; + heightResult = min(maxH, heightResult); + heightResult = max(minH, heightResult); + } else { + // The only times we don't check min/max height are when a fixed length has + // been given as an override. Just use that. The value has already been adjusted + // for box-sizing. + heightResult = h.value() + borderAndPaddingLogicalHeight(); + } + + setLogicalHeight(heightResult); + + if (hasPerpendicularContainingBlock) + computeInlineDirectionMargins(cb, containingBlockLogicalWidthForContent(), heightResult); + } + + // WinIE quirk: The <html> block always fills the entire canvas in quirks mode. The <body> always fills the + // <html> block in quirks mode. Only apply this quirk if the block is normal flow and no height + // is specified. When we're printing, we also need this quirk if the body or root has a percentage + // height since we don't set a height in RenderView when we're printing. So without this quirk, the + // height has nothing to be a percentage of, and it ends up being 0. That is bad. + bool paginatedContentNeedsBaseHeight = document()->printing() && h.isPercent() + && (isRoot() || (isBody() && document()->documentElement()->renderer()->style()->logicalHeight().isPercent())); + if (stretchesToViewport() || paginatedContentNeedsBaseHeight) { + // FIXME: Finish accounting for block flow here. + // https://bugs.webkit.org/show_bug.cgi?id=46603 + int margins = collapsedMarginBefore() + collapsedMarginAfter(); + int visHeight; + if (document()->printing()) + visHeight = static_cast<int>(view()->pageLogicalHeight()); + else { + if (style()->isHorizontalWritingMode()) + visHeight = view()->viewHeight(); + else + visHeight = view()->viewWidth(); + } + if (isRoot()) + setLogicalHeight(max(logicalHeight(), visHeight - margins)); + else { + int marginsBordersPadding = margins + parentBox()->marginBefore() + parentBox()->marginAfter() + parentBox()->borderAndPaddingLogicalHeight(); + setLogicalHeight(max(logicalHeight(), visHeight - marginsBordersPadding)); + } + } +} + +int RenderBox::computeLogicalHeightUsing(const Length& h) +{ + int logicalHeight = -1; + if (!h.isAuto()) { + if (h.isFixed()) + logicalHeight = h.value(); + else if (h.isPercent()) + logicalHeight = computePercentageLogicalHeight(h); + if (logicalHeight != -1) { + logicalHeight = computeBorderBoxLogicalHeight(logicalHeight); + return logicalHeight; + } + } + return logicalHeight; +} + +int RenderBox::computePercentageLogicalHeight(const Length& height) +{ + int result = -1; + bool skippedAutoHeightContainingBlock = false; + RenderBlock* cb = containingBlock(); + if (document()->inQuirksMode()) { + // In quirks mode, blocks with auto height are skipped, and we keep looking for an enclosing + // block that may have a specified height and then use it. In strict mode, this violates the + // specification, which states that percentage heights just revert to auto if the containing + // block has an auto height. + while (!cb->isRenderView() && !cb->isBody() && !cb->isTableCell() && !cb->isPositioned() && cb->style()->logicalHeight().isAuto()) { + skippedAutoHeightContainingBlock = true; + cb = cb->containingBlock(); + cb->addPercentHeightDescendant(this); + } + } + + // A positioned element that specified both top/bottom or that specifies height should be treated as though it has a height + // explicitly specified that can be used for any percentage computations. + // FIXME: We can't just check top/bottom here. + // https://bugs.webkit.org/show_bug.cgi?id=46500 + bool isPositionedWithSpecifiedHeight = cb->isPositioned() && (!cb->style()->logicalHeight().isAuto() || (!cb->style()->top().isAuto() && !cb->style()->bottom().isAuto())); + + bool includeBorderPadding = isTable(); + + // Table cells violate what the CSS spec says to do with heights. Basically we + // don't care if the cell specified a height or not. We just always make ourselves + // be a percentage of the cell's current content height. + if (cb->isTableCell()) { + if (!skippedAutoHeightContainingBlock) { + result = cb->overrideSize(); + if (result == -1) { + // Normally we would let the cell size intrinsically, but scrolling overflow has to be + // treated differently, since WinIE lets scrolled overflow regions shrink as needed. + // While we can't get all cases right, we can at least detect when the cell has a specified + // height or when the table has a specified height. In these cases we want to initially have + // no size and allow the flexing of the table or the cell to its specified height to cause us + // to grow to fill the space. This could end up being wrong in some cases, but it is + // preferable to the alternative (sizing intrinsically and making the row end up too big). + RenderTableCell* cell = toRenderTableCell(cb); + if (scrollsOverflowY() && (!cell->style()->logicalHeight().isAuto() || !cell->table()->style()->logicalHeight().isAuto())) + return 0; + return -1; + } + includeBorderPadding = true; + } + } + // Otherwise we only use our percentage height if our containing block had a specified + // height. + else if (cb->style()->logicalHeight().isFixed()) + result = cb->computeContentBoxLogicalHeight(cb->style()->logicalHeight().value()); + else if (cb->style()->logicalHeight().isPercent() && !isPositionedWithSpecifiedHeight) { + // We need to recur and compute the percentage height for our containing block. + result = cb->computePercentageLogicalHeight(cb->style()->logicalHeight()); + if (result != -1) + result = cb->computeContentBoxLogicalHeight(result); + } else if (cb->isRenderView() || (cb->isBody() && document()->inQuirksMode()) || isPositionedWithSpecifiedHeight) { + // Don't allow this to affect the block' height() member variable, since this + // can get called while the block is still laying out its kids. + int oldHeight = cb->logicalHeight(); + cb->computeLogicalHeight(); + result = cb->contentLogicalHeight(); + cb->setLogicalHeight(oldHeight); + } else if (cb->isRoot() && isPositioned()) + // Match the positioned objects behavior, which is that positioned objects will fill their viewport + // always. Note we could only hit this case by recurring into computePercentageLogicalHeight on a positioned containing block. + result = cb->computeContentBoxLogicalHeight(cb->availableLogicalHeight()); + + if (result != -1) { + result = height.calcValue(result); + if (includeBorderPadding) { + // It is necessary to use the border-box to match WinIE's broken + // box model. This is essential for sizing inside + // table cells using percentage heights. + result -= borderAndPaddingLogicalHeight(); + result = max(0, result); + } + } + return result; +} + +int RenderBox::computeReplacedLogicalWidth(bool includeMaxWidth) const +{ + int logicalWidth = computeReplacedLogicalWidthUsing(style()->logicalWidth()); + int minLogicalWidth = computeReplacedLogicalWidthUsing(style()->logicalMinWidth()); + int maxLogicalWidth = !includeMaxWidth || style()->logicalMaxWidth().isUndefined() ? logicalWidth : computeReplacedLogicalWidthUsing(style()->logicalMaxWidth()); + + return max(minLogicalWidth, min(logicalWidth, maxLogicalWidth)); +} + +int RenderBox::computeReplacedLogicalWidthUsing(Length logicalWidth) const +{ + switch (logicalWidth.type()) { + case Fixed: + return computeContentBoxLogicalWidth(logicalWidth.value()); + case Percent: { + // FIXME: containingBlockLogicalWidthForContent() is wrong if the replaced element's block-flow is perpendicular to the + // containing block's block-flow. + // https://bugs.webkit.org/show_bug.cgi?id=46496 + const int cw = isPositioned() ? containingBlockWidthForPositioned(toRenderBoxModelObject(container())) : containingBlockLogicalWidthForContent(); + if (cw > 0) + return computeContentBoxLogicalWidth(logicalWidth.calcMinValue(cw)); + } + // fall through + default: + return intrinsicLogicalWidth(); + } +} + +int RenderBox::computeReplacedLogicalHeight() const +{ + int logicalHeight = computeReplacedLogicalHeightUsing(style()->logicalHeight()); + int minLogicalHeight = computeReplacedLogicalHeightUsing(style()->logicalMinHeight()); + int maxLogicalHeight = style()->logicalMaxHeight().isUndefined() ? logicalHeight : computeReplacedLogicalHeightUsing(style()->logicalMaxHeight()); + + return max(minLogicalHeight, min(logicalHeight, maxLogicalHeight)); +} + +int RenderBox::computeReplacedLogicalHeightUsing(Length logicalHeight) const +{ + switch (logicalHeight.type()) { + case Fixed: + return computeContentBoxLogicalHeight(logicalHeight.value()); + case Percent: + { + RenderObject* cb = isPositioned() ? container() : containingBlock(); + while (cb->isAnonymous()) { + cb = cb->containingBlock(); + toRenderBlock(cb)->addPercentHeightDescendant(const_cast<RenderBox*>(this)); + } + + // FIXME: This calculation is not patched for block-flow yet. + // https://bugs.webkit.org/show_bug.cgi?id=46500 + if (cb->isPositioned() && cb->style()->height().isAuto() && !(cb->style()->top().isAuto() || cb->style()->bottom().isAuto())) { + ASSERT(cb->isRenderBlock()); + RenderBlock* block = toRenderBlock(cb); + int oldHeight = block->height(); + block->computeLogicalHeight(); + int newHeight = block->computeContentBoxLogicalHeight(block->contentHeight()); + block->setHeight(oldHeight); + return computeContentBoxLogicalHeight(logicalHeight.calcValue(newHeight)); + } + + // FIXME: availableLogicalHeight() is wrong if the replaced element's block-flow is perpendicular to the + // containing block's block-flow. + // https://bugs.webkit.org/show_bug.cgi?id=46496 + int availableHeight = isPositioned() ? containingBlockHeightForPositioned(toRenderBoxModelObject(cb)) : toRenderBox(cb)->availableLogicalHeight(); + + // It is necessary to use the border-box to match WinIE's broken + // box model. This is essential for sizing inside + // table cells using percentage heights. + // FIXME: This needs to be made block-flow-aware. If the cell and image are perpendicular block-flows, this isn't right. + // https://bugs.webkit.org/show_bug.cgi?id=46997 + if (cb->isTableCell() && (cb->style()->logicalHeight().isAuto() || cb->style()->logicalHeight().isPercent())) { + // Don't let table cells squeeze percent-height replaced elements + // <http://bugs.webkit.org/show_bug.cgi?id=15359> + availableHeight = max(availableHeight, intrinsicLogicalHeight()); + return logicalHeight.calcValue(availableHeight - borderAndPaddingLogicalHeight()); + } + + return computeContentBoxLogicalHeight(logicalHeight.calcValue(availableHeight)); + } + default: + return intrinsicLogicalHeight(); + } +} + +int RenderBox::availableLogicalHeight() const +{ + return availableLogicalHeightUsing(style()->logicalHeight()); +} + +int RenderBox::availableLogicalHeightUsing(const Length& h) const +{ + if (h.isFixed()) + return computeContentBoxLogicalHeight(h.value()); + + if (isRenderView()) + return style()->isHorizontalWritingMode() ? toRenderView(this)->frameView()->visibleHeight() : toRenderView(this)->frameView()->visibleWidth(); + + // We need to stop here, since we don't want to increase the height of the table + // artificially. We're going to rely on this cell getting expanded to some new + // height, and then when we lay out again we'll use the calculation below. + if (isTableCell() && (h.isAuto() || h.isPercent())) + return overrideSize() - borderAndPaddingLogicalWidth(); + + if (h.isPercent()) + return computeContentBoxLogicalHeight(h.calcValue(containingBlock()->availableLogicalHeight())); + + // FIXME: We can't just check top/bottom here. + // https://bugs.webkit.org/show_bug.cgi?id=46500 + if (isRenderBlock() && isPositioned() && style()->height().isAuto() && !(style()->top().isAuto() || style()->bottom().isAuto())) { + RenderBlock* block = const_cast<RenderBlock*>(toRenderBlock(this)); + int oldHeight = block->logicalHeight(); + block->computeLogicalHeight(); + int newHeight = block->computeContentBoxLogicalHeight(block->contentLogicalHeight()); + block->setLogicalHeight(oldHeight); + return computeContentBoxLogicalHeight(newHeight); + } + + return containingBlock()->availableLogicalHeight(); +} + +void RenderBox::computeBlockDirectionMargins(RenderBlock* containingBlock) +{ + if (isTableCell()) { + // FIXME: Not right if we allow cells to have different directionality than the table. If we do allow this, though, + // we may just do it with an extra anonymous block inside the cell. + setMarginBefore(0); + setMarginAfter(0); + return; + } + + // Margins are calculated with respect to the logical width of + // the containing block (8.3) + int cw = containingBlockLogicalWidthForContent(); + + RenderStyle* containingBlockStyle = containingBlock->style(); + containingBlock->setMarginBeforeForChild(this, style()->marginBeforeUsing(containingBlockStyle).calcMinValue(cw)); + containingBlock->setMarginAfterForChild(this, style()->marginAfterUsing(containingBlockStyle).calcMinValue(cw)); +} + +int RenderBox::containingBlockWidthForPositioned(const RenderBoxModelObject* containingBlock) const +{ +#if PLATFORM(ANDROID) + // Fixed element's position should be decided by the visible screen size. + // That is in the doc coordindate. + if (style()->position() == FixedPosition && containingBlock->isRenderView()) { + const RenderView* view = toRenderView(containingBlock); + return PlatformBridge::screenWidthInDocCoord(view->frameView()); + } +#endif + if (containingBlock->isBox()) { + const RenderBox* containingBlockBox = toRenderBox(containingBlock); + return containingBlockBox->width() - containingBlockBox->borderLeft() - containingBlockBox->borderRight() - containingBlockBox->verticalScrollbarWidth(); + } + + ASSERT(containingBlock->isRenderInline() && containingBlock->isRelPositioned()); + + const RenderInline* flow = toRenderInline(containingBlock); + InlineFlowBox* first = flow->firstLineBox(); + InlineFlowBox* last = flow->lastLineBox(); + + // If the containing block is empty, return a width of 0. + if (!first || !last) + return 0; + + int fromLeft; + int fromRight; + if (containingBlock->style()->isLeftToRightDirection()) { + fromLeft = first->logicalLeft() + first->borderLogicalLeft(); + fromRight = last->logicalLeft() + last->logicalWidth() - last->borderLogicalRight(); + } else { + fromRight = first->logicalLeft() + first->logicalWidth() - first->borderLogicalRight(); + fromLeft = last->logicalLeft() + last->borderLogicalLeft(); + } + + return max(0, (fromRight - fromLeft)); +} + +int RenderBox::containingBlockHeightForPositioned(const RenderBoxModelObject* containingBlock) const +{ +#if PLATFORM(ANDROID) + // Fixed element's position should be decided by the visible screen size. + // That is in the doc coordindate. + if (style()->position() == FixedPosition && containingBlock->isRenderView()) { + const RenderView* view = toRenderView(containingBlock); + return PlatformBridge::screenHeightInDocCoord(view->frameView()); + } +#endif + int heightResult = 0; + if (containingBlock->isBox()) + heightResult = toRenderBox(containingBlock)->height(); + else if (containingBlock->isRenderInline()) { + ASSERT(containingBlock->isRelPositioned()); + heightResult = toRenderInline(containingBlock)->linesBoundingBox().height(); + } + return heightResult - containingBlock->borderTop() - containingBlock->borderBottom(); +} + +void RenderBox::computePositionedLogicalWidth() +{ + if (isReplaced()) { + computePositionedLogicalWidthReplaced(); + return; + } + + // QUESTIONS + // FIXME 1: Which RenderObject's 'direction' property should used: the + // containing block (cb) as the spec seems to imply, the parent (parent()) as + // was previously done in calculating the static distances, or ourself, which + // was also previously done for deciding what to override when you had + // over-constrained margins? Also note that the container block is used + // in similar situations in other parts of the RenderBox class (see computeLogicalWidth() + // and computeMarginsInContainingBlockInlineDirection()). For now we are using the parent for quirks + // mode and the containing block for strict mode. + + // FIXME 2: Should we still deal with these the cases of 'left' or 'right' having + // the type 'static' in determining whether to calculate the static distance? + // NOTE: 'static' is not a legal value for 'left' or 'right' as of CSS 2.1. + + // FIXME 3: Can perhaps optimize out cases when max-width/min-width are greater + // than or less than the computed width(). Be careful of box-sizing and + // percentage issues. + + // The following is based off of the W3C Working Draft from April 11, 2006 of + // CSS 2.1: Section 10.3.7 "Absolutely positioned, non-replaced elements" + // <http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width> + // (block-style-comments in this function and in computePositionedLogicalWidthUsing() + // correspond to text from the spec) + + + // We don't use containingBlock(), since we may be positioned by an enclosing + // relative positioned inline. + const RenderBoxModelObject* containerBlock = toRenderBoxModelObject(container()); + + const int containerWidth = containingBlockWidthForPositioned(containerBlock); + + // To match WinIE, in quirks mode use the parent's 'direction' property + // instead of the the container block's. + TextDirection containerDirection = (document()->inQuirksMode()) ? parent()->style()->direction() : containerBlock->style()->direction(); + + const int bordersPlusPadding = borderAndPaddingWidth(); + const Length marginLeft = style()->marginLeft(); + const Length marginRight = style()->marginRight(); + Length left = style()->left(); + Length right = style()->right(); + + /*---------------------------------------------------------------------------*\ + * For the purposes of this section and the next, the term "static position" + * (of an element) refers, roughly, to the position an element would have had + * in the normal flow. More precisely: + * + * * The static position for 'left' is the distance from the left edge of the + * containing block to the left margin edge of a hypothetical box that would + * have been the first box of the element if its 'position' property had + * been 'static' and 'float' had been 'none'. The value is negative if the + * hypothetical box is to the left of the containing block. + * * The static position for 'right' is the distance from the right edge of the + * containing block to the right margin edge of the same hypothetical box as + * above. The value is positive if the hypothetical box is to the left of the + * containing block's edge. + * + * But rather than actually calculating the dimensions of that hypothetical box, + * user agents are free to make a guess at its probable position. + * + * For the purposes of calculating the static position, the containing block of + * fixed positioned elements is the initial containing block instead of the + * viewport, and all scrollable boxes should be assumed to be scrolled to their + * origin. + \*---------------------------------------------------------------------------*/ + + // see FIXME 2 + // Calculate the static distance if needed. + if (left.isAuto() && right.isAuto()) { + if (containerDirection == LTR) { + // 'staticX' should already have been set through layout of the parent. + int staticPosition = layer()->staticX() - containerBlock->borderLeft(); + for (RenderObject* curr = parent(); curr && curr != containerBlock; curr = curr->parent()) { + if (curr->isBox()) + staticPosition += toRenderBox(curr)->x(); + } + left.setValue(Fixed, staticPosition); + } else { + RenderBox* enclosingBox = parent()->enclosingBox(); + // 'staticX' should already have been set through layout of the parent. + int staticPosition = layer()->staticX() + containerWidth + containerBlock->borderRight(); + staticPosition -= enclosingBox->width(); + for (RenderObject* curr = enclosingBox; curr && curr != containerBlock; curr = curr->parent()) { + if (curr->isBox()) + staticPosition -= toRenderBox(curr)->x(); + } + right.setValue(Fixed, staticPosition); + } + } + + // Calculate constraint equation values for 'width' case. + int widthResult; + int xResult; + computePositionedLogicalWidthUsing(style()->width(), containerBlock, containerDirection, + containerWidth, bordersPlusPadding, + left, right, marginLeft, marginRight, + widthResult, m_marginLeft, m_marginRight, xResult); + setWidth(widthResult); + setX(xResult); + + // Calculate constraint equation values for 'max-width' case. + if (!style()->maxWidth().isUndefined()) { + int maxWidth; + int maxMarginLeft; + int maxMarginRight; + int maxXPos; + + computePositionedLogicalWidthUsing(style()->maxWidth(), containerBlock, containerDirection, + containerWidth, bordersPlusPadding, + left, right, marginLeft, marginRight, + maxWidth, maxMarginLeft, maxMarginRight, maxXPos); + + if (width() > maxWidth) { + setWidth(maxWidth); + m_marginLeft = maxMarginLeft; + m_marginRight = maxMarginRight; + m_frameRect.setX(maxXPos); + } + } + + // Calculate constraint equation values for 'min-width' case. + if (!style()->minWidth().isZero()) { + int minWidth; + int minMarginLeft; + int minMarginRight; + int minXPos; + + computePositionedLogicalWidthUsing(style()->minWidth(), containerBlock, containerDirection, + containerWidth, bordersPlusPadding, + left, right, marginLeft, marginRight, + minWidth, minMarginLeft, minMarginRight, minXPos); + + if (width() < minWidth) { + setWidth(minWidth); + m_marginLeft = minMarginLeft; + m_marginRight = minMarginRight; + m_frameRect.setX(minXPos); + } + } + + if (stretchesToMinIntrinsicLogicalWidth() && width() < minPreferredLogicalWidth() - bordersPlusPadding) { + computePositionedLogicalWidthUsing(Length(minPreferredLogicalWidth() - bordersPlusPadding, Fixed), containerBlock, containerDirection, + containerWidth, bordersPlusPadding, + left, right, marginLeft, marginRight, + widthResult, m_marginLeft, m_marginRight, xResult); + setWidth(widthResult); + setX(xResult); + } + + // Put width() into correct form. + setWidth(width() + bordersPlusPadding); +} + +void RenderBox::computePositionedLogicalWidthUsing(Length width, const RenderBoxModelObject* containerBlock, TextDirection containerDirection, + const int containerWidth, const int bordersPlusPadding, + const Length left, const Length right, const Length marginLeft, const Length marginRight, + int& widthValue, int& marginLeftValue, int& marginRightValue, int& xPos) +{ + // 'left' and 'right' cannot both be 'auto' because one would of been + // converted to the static position already + ASSERT(!(left.isAuto() && right.isAuto())); + + int leftValue = 0; + + bool widthIsAuto = width.isIntrinsicOrAuto(); + bool leftIsAuto = left.isAuto(); + bool rightIsAuto = right.isAuto(); + + if (!leftIsAuto && !widthIsAuto && !rightIsAuto) { + /*-----------------------------------------------------------------------*\ + * If none of the three is 'auto': If both 'margin-left' and 'margin- + * right' are 'auto', solve the equation under the extra constraint that + * the two margins get equal values, unless this would make them negative, + * in which case when direction of the containing block is 'ltr' ('rtl'), + * set 'margin-left' ('margin-right') to zero and solve for 'margin-right' + * ('margin-left'). If one of 'margin-left' or 'margin-right' is 'auto', + * solve the equation for that value. If the values are over-constrained, + * ignore the value for 'left' (in case the 'direction' property of the + * containing block is 'rtl') or 'right' (in case 'direction' is 'ltr') + * and solve for that value. + \*-----------------------------------------------------------------------*/ + // NOTE: It is not necessary to solve for 'right' in the over constrained + // case because the value is not used for any further calculations. + + leftValue = left.calcValue(containerWidth); + widthValue = computeContentBoxLogicalWidth(width.calcValue(containerWidth)); + + const int availableSpace = containerWidth - (leftValue + widthValue + right.calcValue(containerWidth) + bordersPlusPadding); + + // Margins are now the only unknown + if (marginLeft.isAuto() && marginRight.isAuto()) { + // Both margins auto, solve for equality + if (availableSpace >= 0) { + marginLeftValue = availableSpace / 2; // split the difference + marginRightValue = availableSpace - marginLeftValue; // account for odd valued differences + } else { + // see FIXME 1 + if (containerDirection == LTR) { + marginLeftValue = 0; + marginRightValue = availableSpace; // will be negative + } else { + marginLeftValue = availableSpace; // will be negative + marginRightValue = 0; + } + } + } else if (marginLeft.isAuto()) { + // Solve for left margin + marginRightValue = marginRight.calcValue(containerWidth); + marginLeftValue = availableSpace - marginRightValue; + } else if (marginRight.isAuto()) { + // Solve for right margin + marginLeftValue = marginLeft.calcValue(containerWidth); + marginRightValue = availableSpace - marginLeftValue; + } else { + // Over-constrained, solve for left if direction is RTL + marginLeftValue = marginLeft.calcValue(containerWidth); + marginRightValue = marginRight.calcValue(containerWidth); + + // see FIXME 1 -- used to be "this->style()->direction()" + if (containerDirection == RTL) + leftValue = (availableSpace + leftValue) - marginLeftValue - marginRightValue; + } + } else { + /*--------------------------------------------------------------------*\ + * Otherwise, set 'auto' values for 'margin-left' and 'margin-right' + * to 0, and pick the one of the following six rules that applies. + * + * 1. 'left' and 'width' are 'auto' and 'right' is not 'auto', then the + * width is shrink-to-fit. Then solve for 'left' + * + * OMIT RULE 2 AS IT SHOULD NEVER BE HIT + * ------------------------------------------------------------------ + * 2. 'left' and 'right' are 'auto' and 'width' is not 'auto', then if + * the 'direction' property of the containing block is 'ltr' set + * 'left' to the static position, otherwise set 'right' to the + * static position. Then solve for 'left' (if 'direction is 'rtl') + * or 'right' (if 'direction' is 'ltr'). + * ------------------------------------------------------------------ + * + * 3. 'width' and 'right' are 'auto' and 'left' is not 'auto', then the + * width is shrink-to-fit . Then solve for 'right' + * 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve + * for 'left' + * 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve + * for 'width' + * 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve + * for 'right' + * + * Calculation of the shrink-to-fit width is similar to calculating the + * width of a table cell using the automatic table layout algorithm. + * Roughly: calculate the preferred width by formatting the content + * without breaking lines other than where explicit line breaks occur, + * and also calculate the preferred minimum width, e.g., by trying all + * possible line breaks. CSS 2.1 does not define the exact algorithm. + * Thirdly, calculate the available width: this is found by solving + * for 'width' after setting 'left' (in case 1) or 'right' (in case 3) + * to 0. + * + * Then the shrink-to-fit width is: + * min(max(preferred minimum width, available width), preferred width). + \*--------------------------------------------------------------------*/ + // NOTE: For rules 3 and 6 it is not necessary to solve for 'right' + // because the value is not used for any further calculations. + + // Calculate margins, 'auto' margins are ignored. + marginLeftValue = marginLeft.calcMinValue(containerWidth); + marginRightValue = marginRight.calcMinValue(containerWidth); + + const int availableSpace = containerWidth - (marginLeftValue + marginRightValue + bordersPlusPadding); + + // FIXME: Is there a faster way to find the correct case? + // Use rule/case that applies. + if (leftIsAuto && widthIsAuto && !rightIsAuto) { + // RULE 1: (use shrink-to-fit for width, and solve of left) + int rightValue = right.calcValue(containerWidth); + + // FIXME: would it be better to have shrink-to-fit in one step? + int preferredWidth = maxPreferredLogicalWidth() - bordersPlusPadding; + int preferredMinWidth = minPreferredLogicalWidth() - bordersPlusPadding; + int availableWidth = availableSpace - rightValue; + widthValue = min(max(preferredMinWidth, availableWidth), preferredWidth); + leftValue = availableSpace - (widthValue + rightValue); + } else if (!leftIsAuto && widthIsAuto && rightIsAuto) { + // RULE 3: (use shrink-to-fit for width, and no need solve of right) + leftValue = left.calcValue(containerWidth); + + // FIXME: would it be better to have shrink-to-fit in one step? + int preferredWidth = maxPreferredLogicalWidth() - bordersPlusPadding; + int preferredMinWidth = minPreferredLogicalWidth() - bordersPlusPadding; + int availableWidth = availableSpace - leftValue; + widthValue = min(max(preferredMinWidth, availableWidth), preferredWidth); + } else if (leftIsAuto && !width.isAuto() && !rightIsAuto) { + // RULE 4: (solve for left) + widthValue = computeContentBoxLogicalWidth(width.calcValue(containerWidth)); + leftValue = availableSpace - (widthValue + right.calcValue(containerWidth)); + } else if (!leftIsAuto && widthIsAuto && !rightIsAuto) { + // RULE 5: (solve for width) + leftValue = left.calcValue(containerWidth); + widthValue = availableSpace - (leftValue + right.calcValue(containerWidth)); + } else if (!leftIsAuto&& !widthIsAuto && rightIsAuto) { + // RULE 6: (no need solve for right) + leftValue = left.calcValue(containerWidth); + widthValue = computeContentBoxLogicalWidth(width.calcValue(containerWidth)); + } + } + + // Use computed values to calculate the horizontal position. + + // FIXME: This hack is needed to calculate the xPos for a 'rtl' relatively + // positioned, inline because right now, it is using the xPos + // of the first line box when really it should use the last line box. When + // this is fixed elsewhere, this block should be removed. + if (containerBlock->isRenderInline() && !containerBlock->style()->isLeftToRightDirection()) { + const RenderInline* flow = toRenderInline(containerBlock); + InlineFlowBox* firstLine = flow->firstLineBox(); + InlineFlowBox* lastLine = flow->lastLineBox(); + if (firstLine && lastLine && firstLine != lastLine) { + xPos = leftValue + marginLeftValue + lastLine->borderLogicalLeft() + (lastLine->x() - firstLine->x()); + return; + } + } + + xPos = leftValue + marginLeftValue + containerBlock->borderLeft(); +} + +void RenderBox::computePositionedLogicalHeight() +{ + if (isReplaced()) { + computePositionedLogicalHeightReplaced(); + return; + } + + // The following is based off of the W3C Working Draft from April 11, 2006 of + // CSS 2.1: Section 10.6.4 "Absolutely positioned, non-replaced elements" + // <http://www.w3.org/TR/2005/WD-CSS21-20050613/visudet.html#abs-non-replaced-height> + // (block-style-comments in this function and in computePositionedLogicalHeightUsing() + // correspond to text from the spec) + + + // We don't use containingBlock(), since we may be positioned by an enclosing relpositioned inline. + const RenderBoxModelObject* containerBlock = toRenderBoxModelObject(container()); + + const int containerHeight = containingBlockHeightForPositioned(containerBlock); + + const int bordersPlusPadding = borderAndPaddingHeight(); + const Length marginTop = style()->marginTop(); + const Length marginBottom = style()->marginBottom(); + Length top = style()->top(); + Length bottom = style()->bottom(); + + /*---------------------------------------------------------------------------*\ + * For the purposes of this section and the next, the term "static position" + * (of an element) refers, roughly, to the position an element would have had + * in the normal flow. More precisely, the static position for 'top' is the + * distance from the top edge of the containing block to the top margin edge + * of a hypothetical box that would have been the first box of the element if + * its 'position' property had been 'static' and 'float' had been 'none'. The + * value is negative if the hypothetical box is above the containing block. + * + * But rather than actually calculating the dimensions of that hypothetical + * box, user agents are free to make a guess at its probable position. + * + * For the purposes of calculating the static position, the containing block + * of fixed positioned elements is the initial containing block instead of + * the viewport. + \*---------------------------------------------------------------------------*/ + + // see FIXME 2 + // Calculate the static distance if needed. + if (top.isAuto() && bottom.isAuto()) { + // staticY should already have been set through layout of the parent() + int staticTop = layer()->staticY() - containerBlock->borderTop(); + for (RenderObject* po = parent(); po && po != containerBlock; po = po->parent()) { + if (po->isBox() && !po->isTableRow()) + staticTop += toRenderBox(po)->y(); + } + top.setValue(Fixed, staticTop); + } + + + int h; // Needed to compute overflow. + int y; + + // Calculate constraint equation values for 'height' case. + computePositionedLogicalHeightUsing(style()->height(), containerBlock, containerHeight, bordersPlusPadding, + top, bottom, marginTop, marginBottom, + h, m_marginTop, m_marginBottom, y); + setY(y); + + // Avoid doing any work in the common case (where the values of min-height and max-height are their defaults). + // see FIXME 3 + + // Calculate constraint equation values for 'max-height' case. + if (!style()->maxHeight().isUndefined()) { + int maxHeight; + int maxMarginTop; + int maxMarginBottom; + int maxYPos; + + computePositionedLogicalHeightUsing(style()->maxHeight(), containerBlock, containerHeight, bordersPlusPadding, + top, bottom, marginTop, marginBottom, + maxHeight, maxMarginTop, maxMarginBottom, maxYPos); + + if (h > maxHeight) { + h = maxHeight; + m_marginTop = maxMarginTop; + m_marginBottom = maxMarginBottom; + m_frameRect.setY(maxYPos); + } + } + + // Calculate constraint equation values for 'min-height' case. + if (!style()->minHeight().isZero()) { + int minHeight; + int minMarginTop; + int minMarginBottom; + int minYPos; + + computePositionedLogicalHeightUsing(style()->minHeight(), containerBlock, containerHeight, bordersPlusPadding, + top, bottom, marginTop, marginBottom, + minHeight, minMarginTop, minMarginBottom, minYPos); + + if (h < minHeight) { + h = minHeight; + m_marginTop = minMarginTop; + m_marginBottom = minMarginBottom; + m_frameRect.setY(minYPos); + } + } + + // Set final height value. + setHeight(h + bordersPlusPadding); +} + +void RenderBox::computePositionedLogicalHeightUsing(Length h, const RenderBoxModelObject* containerBlock, + const int containerHeight, const int bordersPlusPadding, + const Length top, const Length bottom, const Length marginTop, const Length marginBottom, + int& heightValue, int& marginTopValue, int& marginBottomValue, int& yPos) +{ + // 'top' and 'bottom' cannot both be 'auto' because 'top would of been + // converted to the static position in computePositionedLogicalHeight() + ASSERT(!(top.isAuto() && bottom.isAuto())); + + int contentHeight = height() - bordersPlusPadding; + + int topValue = 0; + + bool heightIsAuto = h.isAuto(); + bool topIsAuto = top.isAuto(); + bool bottomIsAuto = bottom.isAuto(); + + // Height is never unsolved for tables. + if (isTable()) { + h.setValue(Fixed, contentHeight); + heightIsAuto = false; + } + + if (!topIsAuto && !heightIsAuto && !bottomIsAuto) { + /*-----------------------------------------------------------------------*\ + * If none of the three are 'auto': If both 'margin-top' and 'margin- + * bottom' are 'auto', solve the equation under the extra constraint that + * the two margins get equal values. If one of 'margin-top' or 'margin- + * bottom' is 'auto', solve the equation for that value. If the values + * are over-constrained, ignore the value for 'bottom' and solve for that + * value. + \*-----------------------------------------------------------------------*/ + // NOTE: It is not necessary to solve for 'bottom' in the over constrained + // case because the value is not used for any further calculations. + + heightValue = computeContentBoxLogicalHeight(h.calcValue(containerHeight)); + topValue = top.calcValue(containerHeight); + + const int availableSpace = containerHeight - (topValue + heightValue + bottom.calcValue(containerHeight) + bordersPlusPadding); + + // Margins are now the only unknown + if (marginTop.isAuto() && marginBottom.isAuto()) { + // Both margins auto, solve for equality + // NOTE: This may result in negative values. + marginTopValue = availableSpace / 2; // split the difference + marginBottomValue = availableSpace - marginTopValue; // account for odd valued differences + } else if (marginTop.isAuto()) { + // Solve for top margin + marginBottomValue = marginBottom.calcValue(containerHeight); + marginTopValue = availableSpace - marginBottomValue; + } else if (marginBottom.isAuto()) { + // Solve for bottom margin + marginTopValue = marginTop.calcValue(containerHeight); + marginBottomValue = availableSpace - marginTopValue; + } else { + // Over-constrained, (no need solve for bottom) + marginTopValue = marginTop.calcValue(containerHeight); + marginBottomValue = marginBottom.calcValue(containerHeight); + } + } else { + /*--------------------------------------------------------------------*\ + * Otherwise, set 'auto' values for 'margin-top' and 'margin-bottom' + * to 0, and pick the one of the following six rules that applies. + * + * 1. 'top' and 'height' are 'auto' and 'bottom' is not 'auto', then + * the height is based on the content, and solve for 'top'. + * + * OMIT RULE 2 AS IT SHOULD NEVER BE HIT + * ------------------------------------------------------------------ + * 2. 'top' and 'bottom' are 'auto' and 'height' is not 'auto', then + * set 'top' to the static position, and solve for 'bottom'. + * ------------------------------------------------------------------ + * + * 3. 'height' and 'bottom' are 'auto' and 'top' is not 'auto', then + * the height is based on the content, and solve for 'bottom'. + * 4. 'top' is 'auto', 'height' and 'bottom' are not 'auto', and + * solve for 'top'. + * 5. 'height' is 'auto', 'top' and 'bottom' are not 'auto', and + * solve for 'height'. + * 6. 'bottom' is 'auto', 'top' and 'height' are not 'auto', and + * solve for 'bottom'. + \*--------------------------------------------------------------------*/ + // NOTE: For rules 3 and 6 it is not necessary to solve for 'bottom' + // because the value is not used for any further calculations. + + // Calculate margins, 'auto' margins are ignored. + marginTopValue = marginTop.calcMinValue(containerHeight); + marginBottomValue = marginBottom.calcMinValue(containerHeight); + + const int availableSpace = containerHeight - (marginTopValue + marginBottomValue + bordersPlusPadding); + + // Use rule/case that applies. + if (topIsAuto && heightIsAuto && !bottomIsAuto) { + // RULE 1: (height is content based, solve of top) + heightValue = contentHeight; + topValue = availableSpace - (heightValue + bottom.calcValue(containerHeight)); + } else if (!topIsAuto && heightIsAuto && bottomIsAuto) { + // RULE 3: (height is content based, no need solve of bottom) + topValue = top.calcValue(containerHeight); + heightValue = contentHeight; + } else if (topIsAuto && !heightIsAuto && !bottomIsAuto) { + // RULE 4: (solve of top) + heightValue = computeContentBoxLogicalHeight(h.calcValue(containerHeight)); + topValue = availableSpace - (heightValue + bottom.calcValue(containerHeight)); + } else if (!topIsAuto && heightIsAuto && !bottomIsAuto) { + // RULE 5: (solve of height) + topValue = top.calcValue(containerHeight); + heightValue = max(0, availableSpace - (topValue + bottom.calcValue(containerHeight))); + } else if (!topIsAuto && !heightIsAuto && bottomIsAuto) { + // RULE 6: (no need solve of bottom) + heightValue = computeContentBoxLogicalHeight(h.calcValue(containerHeight)); + topValue = top.calcValue(containerHeight); + } + } + + // Use computed values to calculate the vertical position. + yPos = topValue + marginTopValue + containerBlock->borderTop(); +} + +void RenderBox::computePositionedLogicalWidthReplaced() +{ + // The following is based off of the W3C Working Draft from April 11, 2006 of + // CSS 2.1: Section 10.3.8 "Absolutely positioned, replaced elements" + // <http://www.w3.org/TR/2005/WD-CSS21-20050613/visudet.html#abs-replaced-width> + // (block-style-comments in this function correspond to text from the spec and + // the numbers correspond to numbers in spec) + + // We don't use containingBlock(), since we may be positioned by an enclosing + // relative positioned inline. + const RenderBoxModelObject* containerBlock = toRenderBoxModelObject(container()); + + const int containerWidth = containingBlockWidthForPositioned(containerBlock); + + // To match WinIE, in quirks mode use the parent's 'direction' property + // instead of the the container block's. + TextDirection containerDirection = (document()->inQuirksMode()) ? parent()->style()->direction() : containerBlock->style()->direction(); + + // Variables to solve. + Length left = style()->left(); + Length right = style()->right(); + Length marginLeft = style()->marginLeft(); + Length marginRight = style()->marginRight(); + + + /*-----------------------------------------------------------------------*\ + * 1. The used value of 'width' is determined as for inline replaced + * elements. + \*-----------------------------------------------------------------------*/ + // NOTE: This value of width is FINAL in that the min/max width calculations + // are dealt with in computeReplacedWidth(). This means that the steps to produce + // correct max/min in the non-replaced version, are not necessary. + setWidth(computeReplacedLogicalWidth() + borderAndPaddingWidth()); + const int availableSpace = containerWidth - width(); + + /*-----------------------------------------------------------------------*\ + * 2. If both 'left' and 'right' have the value 'auto', then if 'direction' + * of the containing block is 'ltr', set 'left' to the static position; + * else if 'direction' is 'rtl', set 'right' to the static position. + \*-----------------------------------------------------------------------*/ + // see FIXME 2 + if (left.isAuto() && right.isAuto()) { + // see FIXME 1 + if (containerDirection == LTR) { + // 'staticX' should already have been set through layout of the parent. + int staticPosition = layer()->staticX() - containerBlock->borderLeft(); + for (RenderObject* po = parent(); po && po != containerBlock; po = po->parent()) { + if (po->isBox()) + staticPosition += toRenderBox(po)->x(); + } + left.setValue(Fixed, staticPosition); + } else { + RenderObject* po = parent(); + // 'staticX' should already have been set through layout of the parent. + int staticPosition = layer()->staticX() + containerWidth + containerBlock->borderRight(); + for ( ; po && po != containerBlock; po = po->parent()) { + if (po->isBox()) + staticPosition += toRenderBox(po)->x(); + } + right.setValue(Fixed, staticPosition); + } + } + + /*-----------------------------------------------------------------------*\ + * 3. If 'left' or 'right' are 'auto', replace any 'auto' on 'margin-left' + * or 'margin-right' with '0'. + \*-----------------------------------------------------------------------*/ + if (left.isAuto() || right.isAuto()) { + if (marginLeft.isAuto()) + marginLeft.setValue(Fixed, 0); + if (marginRight.isAuto()) + marginRight.setValue(Fixed, 0); + } + + /*-----------------------------------------------------------------------*\ + * 4. If at this point both 'margin-left' and 'margin-right' are still + * 'auto', solve the equation under the extra constraint that the two + * margins must get equal values, unless this would make them negative, + * in which case when the direction of the containing block is 'ltr' + * ('rtl'), set 'margin-left' ('margin-right') to zero and solve for + * 'margin-right' ('margin-left'). + \*-----------------------------------------------------------------------*/ + int leftValue = 0; + int rightValue = 0; + + if (marginLeft.isAuto() && marginRight.isAuto()) { + // 'left' and 'right' cannot be 'auto' due to step 3 + ASSERT(!(left.isAuto() && right.isAuto())); + + leftValue = left.calcValue(containerWidth); + rightValue = right.calcValue(containerWidth); + + int difference = availableSpace - (leftValue + rightValue); + if (difference > 0) { + m_marginLeft = difference / 2; // split the difference + m_marginRight = difference - m_marginLeft; // account for odd valued differences + } else { + // see FIXME 1 + if (containerDirection == LTR) { + m_marginLeft = 0; + m_marginRight = difference; // will be negative + } else { + m_marginLeft = difference; // will be negative + m_marginRight = 0; + } + } + + /*-----------------------------------------------------------------------*\ + * 5. If at this point there is an 'auto' left, solve the equation for + * that value. + \*-----------------------------------------------------------------------*/ + } else if (left.isAuto()) { + m_marginLeft = marginLeft.calcValue(containerWidth); + m_marginRight = marginRight.calcValue(containerWidth); + rightValue = right.calcValue(containerWidth); + + // Solve for 'left' + leftValue = availableSpace - (rightValue + m_marginLeft + m_marginRight); + } else if (right.isAuto()) { + m_marginLeft = marginLeft.calcValue(containerWidth); + m_marginRight = marginRight.calcValue(containerWidth); + leftValue = left.calcValue(containerWidth); + + // Solve for 'right' + rightValue = availableSpace - (leftValue + m_marginLeft + m_marginRight); + } else if (marginLeft.isAuto()) { + m_marginRight = marginRight.calcValue(containerWidth); + leftValue = left.calcValue(containerWidth); + rightValue = right.calcValue(containerWidth); + + // Solve for 'margin-left' + m_marginLeft = availableSpace - (leftValue + rightValue + m_marginRight); + } else if (marginRight.isAuto()) { + m_marginLeft = marginLeft.calcValue(containerWidth); + leftValue = left.calcValue(containerWidth); + rightValue = right.calcValue(containerWidth); + + // Solve for 'margin-right' + m_marginRight = availableSpace - (leftValue + rightValue + m_marginLeft); + } else { + // Nothing is 'auto', just calculate the values. + m_marginLeft = marginLeft.calcValue(containerWidth); + m_marginRight = marginRight.calcValue(containerWidth); + rightValue = right.calcValue(containerWidth); + leftValue = left.calcValue(containerWidth); + } + + /*-----------------------------------------------------------------------*\ + * 6. If at this point the values are over-constrained, ignore the value + * for either 'left' (in case the 'direction' property of the + * containing block is 'rtl') or 'right' (in case 'direction' is + * 'ltr') and solve for that value. + \*-----------------------------------------------------------------------*/ + // NOTE: It is not necessary to solve for 'right' when the direction is + // LTR because the value is not used. + int totalWidth = width() + leftValue + rightValue + m_marginLeft + m_marginRight; + if (totalWidth > containerWidth && (containerDirection == RTL)) + leftValue = containerWidth - (totalWidth - leftValue); + + // Use computed values to calculate the horizontal position. + + // FIXME: This hack is needed to calculate the xPos for a 'rtl' relatively + // positioned, inline containing block because right now, it is using the xPos + // of the first line box when really it should use the last line box. When + // this is fixed elsewhere, this block should be removed. + if (containerBlock->isRenderInline() && !containerBlock->style()->isLeftToRightDirection()) { + const RenderInline* flow = toRenderInline(containerBlock); + InlineFlowBox* firstLine = flow->firstLineBox(); + InlineFlowBox* lastLine = flow->lastLineBox(); + if (firstLine && lastLine && firstLine != lastLine) { + m_frameRect.setX(leftValue + m_marginLeft + lastLine->borderLogicalLeft() + (lastLine->x() - firstLine->x())); + return; + } + } + + m_frameRect.setX(leftValue + m_marginLeft + containerBlock->borderLeft()); +} + +void RenderBox::computePositionedLogicalHeightReplaced() +{ + // The following is based off of the W3C Working Draft from April 11, 2006 of + // CSS 2.1: Section 10.6.5 "Absolutely positioned, replaced elements" + // <http://www.w3.org/TR/2005/WD-CSS21-20050613/visudet.html#abs-replaced-height> + // (block-style-comments in this function correspond to text from the spec and + // the numbers correspond to numbers in spec) + + // We don't use containingBlock(), since we may be positioned by an enclosing relpositioned inline. + const RenderBoxModelObject* containerBlock = toRenderBoxModelObject(container()); + + const int containerHeight = containingBlockHeightForPositioned(containerBlock); + + // Variables to solve. + Length top = style()->top(); + Length bottom = style()->bottom(); + Length marginTop = style()->marginTop(); + Length marginBottom = style()->marginBottom(); + + + /*-----------------------------------------------------------------------*\ + * 1. The used value of 'height' is determined as for inline replaced + * elements. + \*-----------------------------------------------------------------------*/ + // NOTE: This value of height is FINAL in that the min/max height calculations + // are dealt with in computeReplacedHeight(). This means that the steps to produce + // correct max/min in the non-replaced version, are not necessary. + setHeight(computeReplacedLogicalHeight() + borderAndPaddingHeight()); + const int availableSpace = containerHeight - height(); + + /*-----------------------------------------------------------------------*\ + * 2. If both 'top' and 'bottom' have the value 'auto', replace 'top' + * with the element's static position. + \*-----------------------------------------------------------------------*/ + // see FIXME 2 + if (top.isAuto() && bottom.isAuto()) { + // staticY should already have been set through layout of the parent(). + int staticTop = layer()->staticY() - containerBlock->borderTop(); + for (RenderObject* po = parent(); po && po != containerBlock; po = po->parent()) { + if (po->isBox() && !po->isTableRow()) + staticTop += toRenderBox(po)->y(); + } + top.setValue(Fixed, staticTop); + } + + /*-----------------------------------------------------------------------*\ + * 3. If 'bottom' is 'auto', replace any 'auto' on 'margin-top' or + * 'margin-bottom' with '0'. + \*-----------------------------------------------------------------------*/ + // FIXME: The spec. says that this step should only be taken when bottom is + // auto, but if only top is auto, this makes step 4 impossible. + if (top.isAuto() || bottom.isAuto()) { + if (marginTop.isAuto()) + marginTop.setValue(Fixed, 0); + if (marginBottom.isAuto()) + marginBottom.setValue(Fixed, 0); + } + + /*-----------------------------------------------------------------------*\ + * 4. If at this point both 'margin-top' and 'margin-bottom' are still + * 'auto', solve the equation under the extra constraint that the two + * margins must get equal values. + \*-----------------------------------------------------------------------*/ + int topValue = 0; + int bottomValue = 0; + + if (marginTop.isAuto() && marginBottom.isAuto()) { + // 'top' and 'bottom' cannot be 'auto' due to step 2 and 3 combined. + ASSERT(!(top.isAuto() || bottom.isAuto())); + + topValue = top.calcValue(containerHeight); + bottomValue = bottom.calcValue(containerHeight); + + int difference = availableSpace - (topValue + bottomValue); + // NOTE: This may result in negative values. + m_marginTop = difference / 2; // split the difference + m_marginBottom = difference - m_marginTop; // account for odd valued differences + + /*-----------------------------------------------------------------------*\ + * 5. If at this point there is only one 'auto' left, solve the equation + * for that value. + \*-----------------------------------------------------------------------*/ + } else if (top.isAuto()) { + m_marginTop = marginTop.calcValue(containerHeight); + m_marginBottom = marginBottom.calcValue(containerHeight); + bottomValue = bottom.calcValue(containerHeight); + + // Solve for 'top' + topValue = availableSpace - (bottomValue + m_marginTop + m_marginBottom); + } else if (bottom.isAuto()) { + m_marginTop = marginTop.calcValue(containerHeight); + m_marginBottom = marginBottom.calcValue(containerHeight); + topValue = top.calcValue(containerHeight); + + // Solve for 'bottom' + // NOTE: It is not necessary to solve for 'bottom' because we don't ever + // use the value. + } else if (marginTop.isAuto()) { + m_marginBottom = marginBottom.calcValue(containerHeight); + topValue = top.calcValue(containerHeight); + bottomValue = bottom.calcValue(containerHeight); + + // Solve for 'margin-top' + m_marginTop = availableSpace - (topValue + bottomValue + m_marginBottom); + } else if (marginBottom.isAuto()) { + m_marginTop = marginTop.calcValue(containerHeight); + topValue = top.calcValue(containerHeight); + bottomValue = bottom.calcValue(containerHeight); + + // Solve for 'margin-bottom' + m_marginBottom = availableSpace - (topValue + bottomValue + m_marginTop); + } else { + // Nothing is 'auto', just calculate the values. + m_marginTop = marginTop.calcValue(containerHeight); + m_marginBottom = marginBottom.calcValue(containerHeight); + topValue = top.calcValue(containerHeight); + // NOTE: It is not necessary to solve for 'bottom' because we don't ever + // use the value. + } + + /*-----------------------------------------------------------------------*\ + * 6. If at this point the values are over-constrained, ignore the value + * for 'bottom' and solve for that value. + \*-----------------------------------------------------------------------*/ + // NOTE: It is not necessary to do this step because we don't end up using + // the value of 'bottom' regardless of whether the values are over-constrained + // or not. + + // Use computed values to calculate the vertical position. + m_frameRect.setY(topValue + m_marginTop + containerBlock->borderTop()); +} + +IntRect RenderBox::localCaretRect(InlineBox* box, int caretOffset, int* extraWidthToEndOfLine) +{ + // VisiblePositions at offsets inside containers either a) refer to the positions before/after + // those containers (tables and select elements) or b) refer to the position inside an empty block. + // They never refer to children. + // FIXME: Paint the carets inside empty blocks differently than the carets before/after elements. + + // FIXME: What about border and padding? + IntRect rect(x(), y(), caretWidth, height()); + bool ltr = box ? box->isLeftToRightDirection() : style()->isLeftToRightDirection(); + + if ((!caretOffset) ^ ltr) + rect.move(IntSize(width() - caretWidth, 0)); + + if (box) { + RootInlineBox* rootBox = box->root(); + int top = rootBox->lineTop(); + rect.setY(top); + rect.setHeight(rootBox->lineBottom() - top); + } + + // If height of box is smaller than font height, use the latter one, + // otherwise the caret might become invisible. + // + // Also, if the box is not a replaced element, always use the font height. + // This prevents the "big caret" bug described in: + // <rdar://problem/3777804> Deleting all content in a document can result in giant tall-as-window insertion point + // + // FIXME: ignoring :first-line, missing good reason to take care of + int fontHeight = style()->font().height(); + if (fontHeight > rect.height() || (!isReplaced() && !isTable())) + rect.setHeight(fontHeight); + + if (extraWidthToEndOfLine) + *extraWidthToEndOfLine = x() + width() - rect.right(); + + // Move to local coords + rect.move(-x(), -y()); + return rect; +} + +VisiblePosition RenderBox::positionForPoint(const IntPoint& point) +{ + // no children...return this render object's element, if there is one, and offset 0 + if (!firstChild()) + return createVisiblePosition(node() ? firstDeepEditingPositionForNode(node()) : Position(0, 0)); + + int xPos = point.x(); + int yPos = point.y(); + + if (isTable() && node()) { + int right = contentWidth() + borderAndPaddingWidth(); + int bottom = contentHeight() + borderAndPaddingHeight(); + + if (xPos < 0 || xPos > right || yPos < 0 || yPos > bottom) { + if (xPos <= right / 2) + return createVisiblePosition(firstDeepEditingPositionForNode(node())); + return createVisiblePosition(lastDeepEditingPositionForNode(node())); + } + } + + // Pass off to the closest child. + int minDist = INT_MAX; + RenderBox* closestRenderer = 0; + int newX = xPos; + int newY = yPos; + if (isTableRow()) { + newX += x(); + newY += y(); + } + for (RenderObject* renderObject = firstChild(); renderObject; renderObject = renderObject->nextSibling()) { + if ((!renderObject->firstChild() && !renderObject->isInline() && !renderObject->isBlockFlow() ) + || renderObject->style()->visibility() != VISIBLE) + continue; + + if (!renderObject->isBox()) + continue; + + RenderBox* renderer = toRenderBox(renderObject); + + int top = renderer->borderTop() + renderer->paddingTop() + (isTableRow() ? 0 : renderer->y()); + int bottom = top + renderer->contentHeight(); + int left = renderer->borderLeft() + renderer->paddingLeft() + (isTableRow() ? 0 : renderer->x()); + int right = left + renderer->contentWidth(); + + if (xPos <= right && xPos >= left && yPos <= top && yPos >= bottom) { + if (renderer->isTableRow()) + return renderer->positionForCoordinates(xPos + newX - renderer->x(), yPos + newY - renderer->y()); + return renderer->positionForCoordinates(xPos - renderer->x(), yPos - renderer->y()); + } + + // Find the distance from (x, y) to the box. Split the space around the box into 8 pieces + // and use a different compare depending on which piece (x, y) is in. + IntPoint cmp; + if (xPos > right) { + if (yPos < top) + cmp = IntPoint(right, top); + else if (yPos > bottom) + cmp = IntPoint(right, bottom); + else + cmp = IntPoint(right, yPos); + } else if (xPos < left) { + if (yPos < top) + cmp = IntPoint(left, top); + else if (yPos > bottom) + cmp = IntPoint(left, bottom); + else + cmp = IntPoint(left, yPos); + } else { + if (yPos < top) + cmp = IntPoint(xPos, top); + else + cmp = IntPoint(xPos, bottom); + } + + int x1minusx2 = cmp.x() - xPos; + int y1minusy2 = cmp.y() - yPos; + + int dist = x1minusx2 * x1minusx2 + y1minusy2 * y1minusy2; + if (dist < minDist) { + closestRenderer = renderer; + minDist = dist; + } + } + + if (closestRenderer) + return closestRenderer->positionForCoordinates(newX - closestRenderer->x(), newY - closestRenderer->y()); + + return createVisiblePosition(firstDeepEditingPositionForNode(node())); +} + +bool RenderBox::shrinkToAvoidFloats() const +{ + // FIXME: Technically we should be able to shrink replaced elements on a line, but this is difficult to accomplish, since this + // involves doing a relayout during findNextLineBreak and somehow overriding the containingBlockWidth method to return the + // current remaining width on a line. + if ((isInline() && !isHTMLMarquee()) || !avoidsFloats()) + return false; + + // All auto-width objects that avoid floats should always use lineWidth. + return style()->width().isAuto(); +} + +bool RenderBox::avoidsFloats() const +{ + return isReplaced() || hasOverflowClip() || isHR() || isLegend() || isWritingModeRoot(); +} + +void RenderBox::addShadowOverflow() +{ + int shadowLeft; + int shadowRight; + int shadowTop; + int shadowBottom; + style()->getBoxShadowExtent(shadowTop, shadowRight, shadowBottom, shadowLeft); + IntRect borderBox = borderBoxRect(); + int overflowLeft = borderBox.x() + shadowLeft; + int overflowRight = borderBox.right() + shadowRight; + int overflowTop = borderBox.y() + shadowTop; + int overflowBottom = borderBox.bottom() + shadowBottom; + addVisualOverflow(IntRect(overflowLeft, overflowTop, overflowRight - overflowLeft, overflowBottom - overflowTop)); +} + +void RenderBox::addOverflowFromChild(RenderBox* child, const IntSize& delta) +{ + // Only propagate layout overflow from the child if the child isn't clipping its overflow. If it is, then + // its overflow is internal to it, and we don't care about it. layoutOverflowRectForPropagation takes care of this + // and just propagates the border box rect instead. + IntRect childLayoutOverflowRect = child->layoutOverflowRectForPropagation(style()); + childLayoutOverflowRect.move(delta); + addLayoutOverflow(childLayoutOverflowRect); + + // Add in visual overflow from the child. Even if the child clips its overflow, it may still + // have visual overflow of its own set from box shadows or reflections. It is unnecessary to propagate this + // overflow if we are clipping our own overflow. + if (child->hasSelfPaintingLayer() || hasOverflowClip()) + return; + IntRect childVisualOverflowRect = child->visualOverflowRectForPropagation(style()); + childVisualOverflowRect.move(delta); + addVisualOverflow(childVisualOverflowRect); +} + +void RenderBox::addLayoutOverflow(const IntRect& rect) +{ + IntRect clientBox = clientBoxRect(); + if (clientBox.contains(rect) || rect.isEmpty()) + return; + + // For overflow clip objects, we don't want to propagate overflow into unreachable areas. + IntRect overflowRect(rect); + if (hasOverflowClip() || isRenderView()) { + // Overflow is in the block's coordinate space and thus is flipped for horizontal-bt and vertical-rl + // writing modes. At this stage that is actually a simplification, since we can treat horizontal-tb/bt as the same + // and vertical-lr/rl as the same. + bool hasTopOverflow = !style()->isLeftToRightDirection() && !style()->isHorizontalWritingMode(); + bool hasLeftOverflow = !style()->isLeftToRightDirection() && style()->isHorizontalWritingMode(); + + if (!hasTopOverflow) + overflowRect.shiftTopEdgeTo(max(overflowRect.y(), clientBox.y())); + else + overflowRect.shiftBottomEdgeTo(min(overflowRect.bottom(), clientBox.bottom())); + if (!hasLeftOverflow) + overflowRect.shiftLeftEdgeTo(max(overflowRect.x(), clientBox.x())); + else + overflowRect.shiftRightEdgeTo(min(overflowRect.right(), clientBox.right())); + + // Now re-test with the adjusted rectangle and see if it has become unreachable or fully + // contained. + if (clientBox.contains(overflowRect) || overflowRect.isEmpty()) + return; + } + + if (!m_overflow) + m_overflow.set(new RenderOverflow(clientBox, borderBoxRect())); + + m_overflow->addLayoutOverflow(overflowRect); +} + +void RenderBox::addVisualOverflow(const IntRect& rect) +{ + IntRect borderBox = borderBoxRect(); + if (borderBox.contains(rect) || rect.isEmpty()) + return; + + if (!m_overflow) + m_overflow.set(new RenderOverflow(clientBoxRect(), borderBox)); + + m_overflow->addVisualOverflow(rect); +} + +void RenderBox::clearLayoutOverflow() +{ + if (!m_overflow) + return; + + if (visualOverflowRect() == borderBoxRect()) { + m_overflow.clear(); + return; + } + + m_overflow->resetLayoutOverflow(borderBoxRect()); +} + +int RenderBox::lineHeight(bool /*firstLine*/, LineDirectionMode direction, LinePositionMode /*linePositionMode*/) const +{ + if (isReplaced()) + return direction == HorizontalLine ? m_marginTop + height() + m_marginBottom : m_marginRight + width() + m_marginLeft; + return 0; +} + +int RenderBox::baselinePosition(FontBaseline baselineType, bool /*firstLine*/, LineDirectionMode direction, LinePositionMode /*linePositionMode*/) const +{ + if (isReplaced()) { + int result = direction == HorizontalLine ? m_marginTop + height() + m_marginBottom : m_marginRight + width() + m_marginLeft; + if (baselineType == AlphabeticBaseline) + return result; + return result - result / 2; + } + return 0; +} + + +RenderLayer* RenderBox::enclosingFloatPaintingLayer() const +{ + const RenderObject* curr = this; + while (curr) { + RenderLayer* layer = curr->hasLayer() && curr->isBox() ? toRenderBoxModelObject(curr)->layer() : 0; + if (layer && layer->isSelfPaintingLayer()) + return layer; + curr = curr->parent(); + } + return 0; +} + +IntRect RenderBox::logicalVisualOverflowRectForPropagation(RenderStyle* parentStyle) const +{ + IntRect rect = visualOverflowRectForPropagation(parentStyle); + if (!parentStyle->isHorizontalWritingMode()) + return rect.transposedRect(); + return rect; +} + +IntRect RenderBox::visualOverflowRectForPropagation(RenderStyle* parentStyle) const +{ + // If the writing modes of the child and parent match, then we don't have to + // do anything fancy. Just return the result. + IntRect rect = visualOverflowRect(); + if (parentStyle->writingMode() == style()->writingMode()) + return rect; + + // We are putting ourselves into our parent's coordinate space. If there is a flipped block mismatch + // in a particular axis, then we have to flip the rect along that axis. + if (style()->writingMode() == RightToLeftWritingMode || parentStyle->writingMode() == RightToLeftWritingMode) + rect.setX(width() - rect.right()); + else if (style()->writingMode() == BottomToTopWritingMode || parentStyle->writingMode() == BottomToTopWritingMode) + rect.setY(height() - rect.bottom()); + + return rect; +} + +IntRect RenderBox::logicalLayoutOverflowRectForPropagation(RenderStyle* parentStyle) const +{ + IntRect rect = layoutOverflowRectForPropagation(parentStyle); + if (!parentStyle->isHorizontalWritingMode()) + return rect.transposedRect(); + return rect; +} + +IntRect RenderBox::layoutOverflowRectForPropagation(RenderStyle* parentStyle) const +{ + // Only propagate interior layout overflow if we don't clip it. + IntRect rect = borderBoxRect(); + if (!hasOverflowClip()) + rect.unite(layoutOverflowRect()); + + if (isRelPositioned() || hasTransform()) { + // If we are relatively positioned or if we have a transform, then we have to convert + // this rectangle into physical coordinates, apply relative positioning and transforms + // to it, and then convert it back. + flipForWritingMode(rect); + + if (hasTransform()) + rect = layer()->currentTransform().mapRect(rect); + + if (isRelPositioned()) + rect.move(relativePositionOffsetX(), relativePositionOffsetY()); + + // Now we need to flip back. + flipForWritingMode(rect); + } + + // If the writing modes of the child and parent match, then we don't have to + // do anything fancy. Just return the result. + if (parentStyle->writingMode() == style()->writingMode()) + return rect; + + // We are putting ourselves into our parent's coordinate space. If there is a flipped block mismatch + // in a particular axis, then we have to flip the rect along that axis. + if (style()->writingMode() == RightToLeftWritingMode || parentStyle->writingMode() == RightToLeftWritingMode) + rect.setX(width() - rect.right()); + else if (style()->writingMode() == BottomToTopWritingMode || parentStyle->writingMode() == BottomToTopWritingMode) + rect.setY(height() - rect.bottom()); + + return rect; +} + +IntPoint RenderBox::flipForWritingMode(const RenderBox* child, const IntPoint& point, FlippingAdjustment adjustment) const +{ + if (!style()->isFlippedBlocksWritingMode()) + return point; + + // The child is going to add in its x() and y(), so we have to make sure it ends up in + // the right place. + if (style()->isHorizontalWritingMode()) + return IntPoint(point.x(), point.y() + height() - child->height() - child->y() - (adjustment == ParentToChildFlippingAdjustment ? child->y() : 0)); + return IntPoint(point.x() + width() - child->width() - child->x() - (adjustment == ParentToChildFlippingAdjustment ? child->x() : 0), point.y()); +} + +void RenderBox::flipForWritingMode(IntRect& rect) const +{ + if (!style()->isFlippedBlocksWritingMode()) + return; + + if (style()->isHorizontalWritingMode()) + rect.setY(height() - rect.bottom()); + else + rect.setX(width() - rect.right()); +} + +int RenderBox::flipForWritingMode(int position) const +{ + if (!style()->isFlippedBlocksWritingMode()) + return position; + return logicalHeight() - position; +} + +IntPoint RenderBox::flipForWritingMode(const IntPoint& position) const +{ + if (!style()->isFlippedBlocksWritingMode()) + return position; + return style()->isHorizontalWritingMode() ? IntPoint(position.x(), height() - position.y()) : IntPoint(width() - position.x(), position.y()); +} + +IntSize RenderBox::flipForWritingMode(const IntSize& offset) const +{ + if (!style()->isFlippedBlocksWritingMode()) + return offset; + return style()->isHorizontalWritingMode() ? IntSize(offset.width(), height() - offset.height()) : IntSize(width() - offset.width(), offset.height()); +} + +IntSize RenderBox::locationOffsetIncludingFlipping() const +{ + if (!parent() || !parent()->isBox()) + return locationOffset(); + + IntRect rect(frameRect()); + parentBox()->flipForWritingMode(rect); + return IntSize(rect.x(), rect.y()); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderBox.h b/Source/WebCore/rendering/RenderBox.h new file mode 100644 index 0000000..53a4cfe --- /dev/null +++ b/Source/WebCore/rendering/RenderBox.h @@ -0,0 +1,523 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2003, 2006, 2007 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderBox_h +#define RenderBox_h + +#include "RenderBoxModelObject.h" +#include "RenderOverflow.h" +#include "ScrollTypes.h" + +namespace WebCore { + +enum LogicalWidthType { LogicalWidth, MinLogicalWidth, MaxLogicalWidth }; + +class RenderBox : public RenderBoxModelObject { +public: + RenderBox(Node*); + virtual ~RenderBox(); + + // Use this with caution! No type checking is done! + RenderBox* firstChildBox() const; + RenderBox* lastChildBox() const; + + int x() const { return m_frameRect.x(); } + int y() const { return m_frameRect.y(); } + int width() const { return m_frameRect.width(); } + int height() const { return m_frameRect.height(); } + + void setX(int x) { m_frameRect.setX(x); } + void setY(int y) { m_frameRect.setY(y); } + void setWidth(int width) { m_frameRect.setWidth(width); } + void setHeight(int height) { m_frameRect.setHeight(height); } + + int logicalLeft() const { return style()->isHorizontalWritingMode() ? x() : y(); } + int logicalRight() const { return logicalLeft() + logicalWidth(); } + int logicalTop() const { return style()->isHorizontalWritingMode() ? y() : x(); } + int logicalBottom() const { return logicalTop() + logicalHeight(); } + int logicalWidth() const { return style()->isHorizontalWritingMode() ? width() : height(); } + int logicalHeight() const { return style()->isHorizontalWritingMode() ? height() : width(); } + + void setLogicalLeft(int left) + { + if (style()->isHorizontalWritingMode()) + setX(left); + else + setY(left); + } + void setLogicalTop(int top) + { + if (style()->isHorizontalWritingMode()) + setY(top); + else + setX(top); + } + void setLogicalWidth(int size) + { + if (style()->isHorizontalWritingMode()) + setWidth(size); + else + setHeight(size); + } + void setLogicalHeight(int size) + { + if (style()->isHorizontalWritingMode()) + setHeight(size); + else + setWidth(size); + } + void setLogicalLocation(int left, int top) + { + if (style()->isHorizontalWritingMode()) + setLocation(left, top); + else + setLocation(top, left); + } + + IntPoint location() const { return m_frameRect.location(); } + IntSize locationOffset() const { return IntSize(x(), y()); } + IntSize size() const { return m_frameRect.size(); } + + void setLocation(const IntPoint& location) { m_frameRect.setLocation(location); } + void setLocation(int x, int y) { setLocation(IntPoint(x, y)); } + + void setSize(const IntSize& size) { m_frameRect.setSize(size); } + void move(int dx, int dy) { m_frameRect.move(dx, dy); } + + IntRect frameRect() const { return m_frameRect; } + void setFrameRect(const IntRect& rect) { m_frameRect = rect; } + + IntRect borderBoxRect() const { return IntRect(0, 0, width(), height()); } + virtual IntRect borderBoundingBox() const { return borderBoxRect(); } + + // The content area of the box (excludes padding and border). + IntRect contentBoxRect() const { return IntRect(borderLeft() + paddingLeft(), borderTop() + paddingTop(), contentWidth(), contentHeight()); } + // The content box in absolute coords. Ignores transforms. + IntRect absoluteContentBox() const; + // The content box converted to absolute coords (taking transforms into account). + FloatQuad absoluteContentQuad() const; + + // Bounds of the outline box in absolute coords. Respects transforms + virtual IntRect outlineBoundsForRepaint(RenderBoxModelObject* /*repaintContainer*/, IntPoint* cachedOffsetToRepaintContainer) const; + virtual void addFocusRingRects(Vector<IntRect>&, int tx, int ty); + + // Use this with caution! No type checking is done! + RenderBox* previousSiblingBox() const; + RenderBox* nextSiblingBox() const; + RenderBox* parentBox() const; + + IntRect layoutOverflowRect() const { return m_overflow ? m_overflow->layoutOverflowRect() : clientBoxRect(); } + int topLayoutOverflow() const { return m_overflow? m_overflow->topLayoutOverflow() : borderTop(); } + int bottomLayoutOverflow() const { return m_overflow ? m_overflow->bottomLayoutOverflow() : borderTop() + clientHeight(); } + int leftLayoutOverflow() const { return m_overflow ? m_overflow->leftLayoutOverflow() : borderLeft(); } + int rightLayoutOverflow() const { return m_overflow ? m_overflow->rightLayoutOverflow() : borderLeft() + clientWidth(); } + int logicalLeftLayoutOverflow() const { return style()->isHorizontalWritingMode() ? leftLayoutOverflow() : topLayoutOverflow(); } + int logicalRightLayoutOverflow() const { return style()->isHorizontalWritingMode() ? rightLayoutOverflow() : bottomLayoutOverflow(); } + + IntRect visualOverflowRect() const { return m_overflow ? m_overflow->visualOverflowRect() : borderBoxRect(); } + int topVisualOverflow() const { return m_overflow? m_overflow->topVisualOverflow() : 0; } + int bottomVisualOverflow() const { return m_overflow ? m_overflow->bottomVisualOverflow() : height(); } + int leftVisualOverflow() const { return m_overflow ? m_overflow->leftVisualOverflow() : 0; } + int rightVisualOverflow() const { return m_overflow ? m_overflow->rightVisualOverflow() : width(); } + int logicalLeftVisualOverflow() const { return style()->isHorizontalWritingMode() ? leftVisualOverflow() : topVisualOverflow(); } + int logicalRightVisualOverflow() const { return style()->isHorizontalWritingMode() ? rightVisualOverflow() : bottomVisualOverflow(); } + + void addLayoutOverflow(const IntRect&); + void addVisualOverflow(const IntRect&); + + void addShadowOverflow(); + void addOverflowFromChild(RenderBox* child) { addOverflowFromChild(child, IntSize(child->x(), child->y())); } + void addOverflowFromChild(RenderBox* child, const IntSize& delta); + void clearLayoutOverflow(); + + void updateLayerTransform(); + + void blockDirectionOverflow(bool isLineHorizontal, int& logicalTopLayoutOverflow, int& logicalBottomLayoutOverflow, + int& logicalTopVisualOverflow, int& logicalBottomVisualOverflow); + + int contentWidth() const { return clientWidth() - paddingLeft() - paddingRight(); } + int contentHeight() const { return clientHeight() - paddingTop() - paddingBottom(); } + int contentLogicalWidth() const { return style()->isHorizontalWritingMode() ? contentWidth() : contentHeight(); } + int contentLogicalHeight() const { return style()->isHorizontalWritingMode() ? contentHeight() : contentWidth(); } + + // IE extensions. Used to calculate offsetWidth/Height. Overridden by inlines (RenderFlow) + // to return the remaining width on a given line (and the height of a single line). + virtual int offsetWidth() const { return width(); } + virtual int offsetHeight() const { return height(); } + + // More IE extensions. clientWidth and clientHeight represent the interior of an object + // excluding border and scrollbar. clientLeft/Top are just the borderLeftWidth and borderTopWidth. + int clientLeft() const { return borderLeft(); } + int clientTop() const { return borderTop(); } + int clientWidth() const; + int clientHeight() const; + int clientLogicalBottom() const { return style()->isHorizontalWritingMode() ? clientTop() + clientHeight() : clientLeft() + clientWidth(); } + IntRect clientBoxRect() const { return IntRect(clientLeft(), clientTop(), clientWidth(), clientHeight()); } + + // scrollWidth/scrollHeight will be the same as clientWidth/clientHeight unless the + // object has overflow:hidden/scroll/auto specified and also has overflow. + // scrollLeft/Top return the current scroll position. These methods are virtual so that objects like + // textareas can scroll shadow content (but pretend that they are the objects that are + // scrolling). + virtual int scrollLeft() const; + virtual int scrollTop() const; + virtual int scrollWidth() const; + virtual int scrollHeight() const; + virtual void setScrollLeft(int); + virtual void setScrollTop(int); + + virtual int marginTop() const { return m_marginTop; } + virtual int marginBottom() const { return m_marginBottom; } + virtual int marginLeft() const { return m_marginLeft; } + virtual int marginRight() const { return m_marginRight; } + void setMarginTop(int margin) { m_marginTop = margin; } + void setMarginBottom(int margin) { m_marginBottom = margin; } + void setMarginLeft(int margin) { m_marginLeft = margin; } + void setMarginRight(int margin) { m_marginRight = margin; } + virtual int marginBefore() const; + virtual int marginAfter() const; + virtual int marginStart() const; + virtual int marginEnd() const; + void setMarginStart(int); + void setMarginEnd(int); + void setMarginBefore(int); + void setMarginAfter(int); + + // The following five functions are used to implement collapsing margins. + // All objects know their maximal positive and negative margins. The + // formula for computing a collapsed margin is |maxPosMargin| - |maxNegmargin|. + // For a non-collapsing box, such as a leaf element, this formula will simply return + // the margin of the element. Blocks override the maxMarginBefore and maxMarginAfter + // methods. + enum MarginSign { PositiveMargin, NegativeMargin }; + virtual bool isSelfCollapsingBlock() const { return false; } + virtual int collapsedMarginBefore() const { return marginBefore(); } + virtual int collapsedMarginAfter() const { return marginAfter(); } + + virtual void absoluteRects(Vector<IntRect>&, int tx, int ty); + virtual void absoluteQuads(Vector<FloatQuad>&); + + IntRect reflectionBox() const; + int reflectionOffset() const; + // Given a rect in the object's coordinate space, returns the corresponding rect in the reflection. + IntRect reflectedRect(const IntRect&) const; + + virtual void layout(); + virtual void paint(PaintInfo&, int tx, int ty); + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + + virtual void destroy(); + + virtual int minPreferredLogicalWidth() const; + virtual int maxPreferredLogicalWidth() const; + + int overrideSize() const; + int overrideWidth() const; + int overrideHeight() const; + virtual void setOverrideSize(int); + + virtual IntSize offsetFromContainer(RenderObject*, const IntPoint&) const; + + int computeBorderBoxLogicalWidth(int width) const; + int computeBorderBoxLogicalHeight(int height) const; + int computeContentBoxLogicalWidth(int width) const; + int computeContentBoxLogicalHeight(int height) const; + + virtual void borderFitAdjust(int& /*x*/, int& /*w*/) const { } // Shrink the box in which the border paints if border-fit is set. + + // Resolve auto margins in the inline direction of the containing block so that objects can be pushed to the start, middle or end + // of the containing block. + void computeInlineDirectionMargins(RenderBlock* containingBlock, int containerWidth, int childWidth); + + // Used to resolve margins in the containing block's block-flow direction. + void computeBlockDirectionMargins(RenderBlock* containingBlock); + + void positionLineBox(InlineBox*); + + virtual InlineBox* createInlineBox(); + void dirtyLineBoxes(bool fullLayout); + + // For inline replaced elements, this function returns the inline box that owns us. Enables + // the replaced RenderObject to quickly determine what line it is contained on and to easily + // iterate over structures on the line. + InlineBox* inlineBoxWrapper() const { return m_inlineBoxWrapper; } + void setInlineBoxWrapper(InlineBox* boxWrapper) { m_inlineBoxWrapper = boxWrapper; } + void deleteLineBoxWrapper(); + + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer); + virtual void computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect&, bool fixed = false); + + virtual void repaintDuringLayoutIfMoved(const IntRect&); + + virtual int containingBlockLogicalWidthForContent() const; + int perpendicularContainingBlockLogicalHeight() const; + + virtual void computeLogicalWidth(); + virtual void computeLogicalHeight(); + + bool stretchesToViewport() const + { + return document()->inQuirksMode() && style()->logicalHeight().isAuto() && !isFloatingOrPositioned() && (isRoot() || isBody()); + } + + virtual IntSize intrinsicSize() const { return IntSize(); } + int intrinsicLogicalWidth() const { return style()->isHorizontalWritingMode() ? intrinsicSize().width() : intrinsicSize().height(); } + int intrinsicLogicalHeight() const { return style()->isHorizontalWritingMode() ? intrinsicSize().height() : intrinsicSize().width(); } + + // Whether or not the element shrinks to its intrinsic width (rather than filling the width + // of a containing block). HTML4 buttons, <select>s, <input>s, legends, and floating/compact elements do this. + bool sizesToIntrinsicLogicalWidth(LogicalWidthType) const; + virtual bool stretchesToMinIntrinsicLogicalWidth() const { return false; } + + int computeLogicalWidthUsing(LogicalWidthType, int availableLogicalWidth); + int computeLogicalHeightUsing(const Length& height); + int computeReplacedLogicalWidthUsing(Length width) const; + int computeReplacedLogicalHeightUsing(Length height) const; + + virtual int computeReplacedLogicalWidth(bool includeMaxWidth = true) const; + virtual int computeReplacedLogicalHeight() const; + + int computePercentageLogicalHeight(const Length& height); + + // Block flows subclass availableWidth to handle multi column layout (shrinking the width available to children when laying out.) + virtual int availableLogicalWidth() const { return contentLogicalWidth(); } + int availableLogicalHeight() const; + int availableLogicalHeightUsing(const Length&) const; + + // There are a few cases where we need to refer specifically to the available physical width and available physical height. + // Relative positioning is one of those cases, since left/top offsets are physical. + int availableWidth() const { return style()->isHorizontalWritingMode() ? availableLogicalWidth() : availableLogicalHeight(); } + int availableHeight() const { return style()->isHorizontalWritingMode() ? availableLogicalHeight() : availableLogicalWidth(); } + + virtual int verticalScrollbarWidth() const; + int horizontalScrollbarHeight() const; + int scrollbarLogicalHeight() const { return style()->isHorizontalWritingMode() ? horizontalScrollbarHeight() : verticalScrollbarWidth(); } + virtual bool scroll(ScrollDirection, ScrollGranularity, float multiplier = 1, Node** stopNode = 0); + virtual bool logicalScroll(ScrollLogicalDirection, ScrollGranularity, float multiplier = 1, Node** stopNode = 0); + bool canBeScrolledAndHasScrollableArea() const; + virtual bool canBeProgramaticallyScrolled(bool) const; + virtual void autoscroll(); + virtual void stopAutoscroll() { } + virtual void panScroll(const IntPoint&); + bool hasAutoVerticalScrollbar() const { return hasOverflowClip() && (style()->overflowY() == OAUTO || style()->overflowY() == OOVERLAY); } + bool hasAutoHorizontalScrollbar() const { return hasOverflowClip() && (style()->overflowX() == OAUTO || style()->overflowX() == OOVERLAY); } + bool scrollsOverflow() const { return scrollsOverflowX() || scrollsOverflowY(); } + bool scrollsOverflowX() const { return hasOverflowClip() && (style()->overflowX() == OSCROLL || hasAutoHorizontalScrollbar()); } + bool scrollsOverflowY() const { return hasOverflowClip() && (style()->overflowY() == OSCROLL || hasAutoVerticalScrollbar()); } + + virtual IntRect localCaretRect(InlineBox*, int caretOffset, int* extraWidthToEndOfLine = 0); + + virtual IntRect overflowClipRect(int tx, int ty); + IntRect clipRect(int tx, int ty); + virtual bool hasControlClip() const { return false; } + virtual IntRect controlClipRect(int /*tx*/, int /*ty*/) const { return IntRect(); } + bool pushContentsClip(PaintInfo&, int tx, int ty); + void popContentsClip(PaintInfo&, PaintPhase originalPhase, int tx, int ty); + + virtual void paintObject(PaintInfo&, int /*tx*/, int /*ty*/) { ASSERT_NOT_REACHED(); } + virtual void paintBoxDecorations(PaintInfo&, int tx, int ty); + virtual void paintMask(PaintInfo&, int tx, int ty); + virtual void imageChanged(WrappedImagePtr, const IntRect* = 0); + + // Called when a positioned object moves but doesn't necessarily change size. A simplified layout is attempted + // that just updates the object's position. If the size does change, the object remains dirty. + void tryLayoutDoingPositionedMovementOnly() + { + int oldWidth = width(); + computeLogicalWidth(); + // If we shrink to fit our width may have changed, so we still need full layout. + if (oldWidth != width()) + return; + computeLogicalHeight(); + setNeedsLayout(false); + } + + IntRect maskClipRect(); + + virtual VisiblePosition positionForPoint(const IntPoint&); + + void removeFloatingOrPositionedChildFromBlockLists(); + + RenderLayer* enclosingFloatPaintingLayer() const; + + virtual int firstLineBoxBaseline() const { return -1; } + virtual int lastLineBoxBaseline() const { return -1; } + + bool shrinkToAvoidFloats() const; + virtual bool avoidsFloats() const; + + virtual void markForPaginationRelayoutIfNeeded() { } + + bool isWritingModeRoot() const { return !parent() || parent()->style()->writingMode() != style()->writingMode(); } + + virtual int lineHeight(bool firstLine, LineDirectionMode, LinePositionMode = PositionOnContainingLine) const; + virtual int baselinePosition(FontBaseline, bool firstLine, LineDirectionMode, LinePositionMode = PositionOnContainingLine) const; + + enum FlippingAdjustment { ChildToParentFlippingAdjustment, ParentToChildFlippingAdjustment }; + IntPoint flipForWritingMode(const RenderBox* child, const IntPoint&, FlippingAdjustment) const; + int flipForWritingMode(int position) const; // The offset is in the block direction (y for horizontal writing modes, x for vertical writing modes). + IntPoint flipForWritingMode(const IntPoint&) const; + IntSize flipForWritingMode(const IntSize&) const; + void flipForWritingMode(IntRect&) const; + IntSize locationOffsetIncludingFlipping() const; + + IntRect logicalVisualOverflowRectForPropagation(RenderStyle*) const; + IntRect visualOverflowRectForPropagation(RenderStyle*) const; + IntRect logicalLayoutOverflowRectForPropagation(RenderStyle*) const; + IntRect layoutOverflowRectForPropagation(RenderStyle*) const; + +#ifdef ANDROID_LAYOUT + int getVisibleWidth() const { return m_visibleWidth; } +#endif + +protected: + virtual void styleWillChange(StyleDifference, const RenderStyle* newStyle); + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + virtual void updateBoxModelInfoFromStyle(); + + void paintFillLayer(const PaintInfo&, const Color&, const FillLayer*, int tx, int ty, int width, int height, CompositeOperator op, RenderObject* backgroundObject); + void paintFillLayers(const PaintInfo&, const Color&, const FillLayer*, int tx, int ty, int width, int height, CompositeOperator = CompositeSourceOver, RenderObject* backgroundObject = 0); + + void paintBoxDecorationsWithSize(PaintInfo&, int tx, int ty, int width, int height); + void paintMaskImages(const PaintInfo&, int tx, int ty, int width, int height); + +#if PLATFORM(MAC) + void paintCustomHighlight(int tx, int ty, const AtomicString& type, bool behindText); +#endif + + void computePositionedLogicalWidth(); + + virtual bool shouldComputeSizeAsReplaced() const { return isReplaced() && !isInlineBlockOrInlineTable(); } + + virtual void mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool fixed, bool useTransforms, TransformState&) const; + virtual void mapAbsoluteToLocalPoint(bool fixed, bool useTransforms, TransformState&) const; + +private: + bool includeVerticalScrollbarSize() const { return hasOverflowClip() && (style()->overflowY() == OSCROLL || style()->overflowY() == OAUTO); } + bool includeHorizontalScrollbarSize() const { return hasOverflowClip() && (style()->overflowX() == OSCROLL || style()->overflowX() == OAUTO); } + + void paintRootBoxDecorations(PaintInfo&, int tx, int ty); + // Returns true if we did a full repaint + bool repaintLayerRectsForImage(WrappedImagePtr image, const FillLayer* layers, bool drawingBackground); + + int containingBlockWidthForPositioned(const RenderBoxModelObject* containingBlock) const; + int containingBlockHeightForPositioned(const RenderBoxModelObject* containingBlock) const; + + void computePositionedLogicalHeight(); + void computePositionedLogicalWidthUsing(Length width, const RenderBoxModelObject* cb, TextDirection containerDirection, + int containerWidth, int bordersPlusPadding, + Length left, Length right, Length marginLeft, Length marginRight, + int& widthValue, int& marginLeftValue, int& marginRightValue, int& xPos); + void computePositionedLogicalHeightUsing(Length height, const RenderBoxModelObject* cb, + int containerHeight, int bordersPlusPadding, + Length top, Length bottom, Length marginTop, Length marginBottom, + int& heightValue, int& marginTopValue, int& marginBottomValue, int& yPos); + + void computePositionedLogicalHeightReplaced(); + void computePositionedLogicalWidthReplaced(); + + // This function calculates the minimum and maximum preferred widths for an object. + // These values are used in shrink-to-fit layout systems. + // These include tables, positioned objects, floats and flexible boxes. + virtual void computePreferredLogicalWidths() { setPreferredLogicalWidthsDirty(false); } + +private: + // The width/height of the contents + borders + padding. The x/y location is relative to our container (which is not always our parent). + IntRect m_frameRect; + +protected: + +#ifdef ANDROID_LAYOUT + void setVisibleWidth(int newWidth); + bool checkAndSetRelayoutChildren(bool* relayoutChildren); +#endif + + int m_marginLeft; + int m_marginRight; + int m_marginTop; + int m_marginBottom; + + // The preferred logical width of the element if it were to break its lines at every possible opportunity. + int m_minPreferredLogicalWidth; + + // The preferred logical width of the element if it never breaks any lines at all. + int m_maxPreferredLogicalWidth; + + // For inline replaced elements, the inline box that owns us. + InlineBox* m_inlineBoxWrapper; + + // Our overflow information. + OwnPtr<RenderOverflow> m_overflow; + +private: + // Used to store state between styleWillChange and styleDidChange + static bool s_hadOverflowClip; + +#ifdef ANDROID_LAYOUT + int m_visibleWidth; + bool m_isVisibleWidthChangedBeforeLayout; +#endif +}; + +inline RenderBox* toRenderBox(RenderObject* object) +{ + ASSERT(!object || object->isBox()); + return static_cast<RenderBox*>(object); +} + +inline const RenderBox* toRenderBox(const RenderObject* object) +{ + ASSERT(!object || object->isBox()); + return static_cast<const RenderBox*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderBox(const RenderBox*); + +inline RenderBox* RenderBox::previousSiblingBox() const +{ + return toRenderBox(previousSibling()); +} + +inline RenderBox* RenderBox::nextSiblingBox() const +{ + return toRenderBox(nextSibling()); +} + +inline RenderBox* RenderBox::parentBox() const +{ + return toRenderBox(parent()); +} + +inline RenderBox* RenderBox::firstChildBox() const +{ + return toRenderBox(firstChild()); +} + +inline RenderBox* RenderBox::lastChildBox() const +{ + return toRenderBox(lastChild()); +} + +} // namespace WebCore + +#endif // RenderBox_h diff --git a/Source/WebCore/rendering/RenderBoxModelObject.cpp b/Source/WebCore/rendering/RenderBoxModelObject.cpp new file mode 100644 index 0000000..5098306 --- /dev/null +++ b/Source/WebCore/rendering/RenderBoxModelObject.cpp @@ -0,0 +1,1858 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) + * (C) 2005, 2006 Samuel Weinig (sam.weinig@gmail.com) + * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * 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 "RenderBoxModelObject.h" + +#include "GraphicsContext.h" +#include "HTMLFrameOwnerElement.h" +#include "HTMLNames.h" +#include "ImageBuffer.h" +#include "Path.h" +#include "RenderBlock.h" +#include "RenderInline.h" +#include "RenderLayer.h" +#include "RenderView.h" +#include <wtf/CurrentTime.h> + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +bool RenderBoxModelObject::s_wasFloating = false; +bool RenderBoxModelObject::s_hadLayer = false; +bool RenderBoxModelObject::s_layerWasSelfPainting = false; + +static const double cInterpolationCutoff = 800. * 800.; +static const double cLowQualityTimeThreshold = 0.500; // 500 ms + +typedef pair<RenderBoxModelObject*, const void*> LastPaintSizeMapKey; +typedef HashMap<LastPaintSizeMapKey, IntSize> LastPaintSizeMap; + +// The HashMap for storing continuation pointers. +// An inline can be split with blocks occuring in between the inline content. +// When this occurs we need a pointer to the next object. We can basically be +// split into a sequence of inlines and blocks. The continuation will either be +// an anonymous block (that houses other blocks) or it will be an inline flow. +// <b><i><p>Hello</p></i></b>. In this example the <i> will have a block as +// its continuation but the <b> will just have an inline as its continuation. +typedef HashMap<const RenderBoxModelObject*, RenderBoxModelObject*> ContinuationMap; +static ContinuationMap* continuationMap = 0; + +class ImageQualityController : public Noncopyable { +public: + ImageQualityController(); + bool shouldPaintAtLowQuality(GraphicsContext*, RenderBoxModelObject*, Image*, const void* layer, const IntSize&); + void keyDestroyed(LastPaintSizeMapKey key); + void objectDestroyed(RenderBoxModelObject*); + +private: + void highQualityRepaintTimerFired(Timer<ImageQualityController>*); + void restartTimer(); + + LastPaintSizeMap m_lastPaintSizeMap; + Timer<ImageQualityController> m_timer; + bool m_animatedResizeIsActive; +}; + +ImageQualityController::ImageQualityController() + : m_timer(this, &ImageQualityController::highQualityRepaintTimerFired) + , m_animatedResizeIsActive(false) +{ +} + +void ImageQualityController::keyDestroyed(LastPaintSizeMapKey key) +{ + m_lastPaintSizeMap.remove(key); + if (m_lastPaintSizeMap.isEmpty()) { + m_animatedResizeIsActive = false; + m_timer.stop(); + } +} + +void ImageQualityController::objectDestroyed(RenderBoxModelObject* object) +{ + Vector<LastPaintSizeMapKey> keysToDie; + for (LastPaintSizeMap::iterator it = m_lastPaintSizeMap.begin(); it != m_lastPaintSizeMap.end(); ++it) + if (it->first.first == object) + keysToDie.append(it->first); + for (Vector<LastPaintSizeMapKey>::iterator it = keysToDie.begin(); it != keysToDie.end(); ++it) + keyDestroyed(*it); +} + +void ImageQualityController::highQualityRepaintTimerFired(Timer<ImageQualityController>*) +{ + if (m_animatedResizeIsActive) { + m_animatedResizeIsActive = false; + for (LastPaintSizeMap::iterator it = m_lastPaintSizeMap.begin(); it != m_lastPaintSizeMap.end(); ++it) + it->first.first->repaint(); + } +} + +void ImageQualityController::restartTimer() +{ + m_timer.startOneShot(cLowQualityTimeThreshold); +} + +bool ImageQualityController::shouldPaintAtLowQuality(GraphicsContext* context, RenderBoxModelObject* object, Image* image, const void *layer, const IntSize& size) +{ + // If the image is not a bitmap image, then none of this is relevant and we just paint at high + // quality. + if (!image || !image->isBitmapImage() || context->paintingDisabled()) + return false; + + // Make sure to use the unzoomed image size, since if a full page zoom is in effect, the image + // is actually being scaled. + IntSize imageSize(image->width(), image->height()); + + // Look ourselves up in the hashtable. + LastPaintSizeMapKey key(object, layer); + LastPaintSizeMap::iterator i = m_lastPaintSizeMap.find(key); + + const AffineTransform& currentTransform = context->getCTM(); + bool contextIsScaled = !currentTransform.isIdentityOrTranslationOrFlipped(); + if (!contextIsScaled && imageSize == size) { + // There is no scale in effect. If we had a scale in effect before, we can just remove this object from the list. + if (i != m_lastPaintSizeMap.end()) + m_lastPaintSizeMap.remove(key); + + return false; + } + + // There is no need to hash scaled images that always use low quality mode when the page demands it. This is the iChat case. + if (object->document()->page()->inLowQualityImageInterpolationMode()) { + double totalPixels = static_cast<double>(image->width()) * static_cast<double>(image->height()); + if (totalPixels > cInterpolationCutoff) + return true; + } + // If an animated resize is active, paint in low quality and kick the timer ahead. + if (m_animatedResizeIsActive) { + m_lastPaintSizeMap.set(key, size); + restartTimer(); + return true; + } + // If this is the first time resizing this image, or its size is the + // same as the last resize, draw at high res, but record the paint + // size and set the timer. + if (i == m_lastPaintSizeMap.end() || size == i->second) { + restartTimer(); + m_lastPaintSizeMap.set(key, size); + return false; + } + // If the timer is no longer active, draw at high quality and don't + // set the timer. + if (!m_timer.isActive()) { + keyDestroyed(key); + return false; + } + // This object has been resized to two different sizes while the timer + // is active, so draw at low quality, set the flag for animated resizes and + // the object to the list for high quality redraw. + m_lastPaintSizeMap.set(key, size); + m_animatedResizeIsActive = true; + restartTimer(); + return true; +} + +static ImageQualityController* imageQualityController() +{ + static ImageQualityController* controller = new ImageQualityController; + return controller; +} + +void RenderBoxModelObject::setSelectionState(SelectionState s) +{ + if (selectionState() == s) + return; + + if (s == SelectionInside && selectionState() != SelectionNone) + return; + + if ((s == SelectionStart && selectionState() == SelectionEnd) + || (s == SelectionEnd && selectionState() == SelectionStart)) + RenderObject::setSelectionState(SelectionBoth); + else + RenderObject::setSelectionState(s); + + // FIXME: + // We should consider whether it is OK propagating to ancestor RenderInlines. + // This is a workaround for http://webkit.org/b/32123 + RenderBlock* cb = containingBlock(); + if (cb && !cb->isRenderView()) + cb->setSelectionState(s); +} + +bool RenderBoxModelObject::shouldPaintAtLowQuality(GraphicsContext* context, Image* image, const void* layer, const IntSize& size) +{ + return imageQualityController()->shouldPaintAtLowQuality(context, this, image, layer, size); +} + +RenderBoxModelObject::RenderBoxModelObject(Node* node) + : RenderObject(node) + , m_layer(0) +{ +} + +RenderBoxModelObject::~RenderBoxModelObject() +{ + // Our layer should have been destroyed and cleared by now + ASSERT(!hasLayer()); + ASSERT(!m_layer); + imageQualityController()->objectDestroyed(this); +} + +void RenderBoxModelObject::destroyLayer() +{ + ASSERT(!hasLayer()); // Callers should have already called setHasLayer(false) + ASSERT(m_layer); + m_layer->destroy(renderArena()); + m_layer = 0; +} + +void RenderBoxModelObject::destroy() +{ + // This must be done before we destroy the RenderObject. + if (m_layer) + m_layer->clearClipRects(); + + // A continuation of this RenderObject should be destroyed at subclasses. + ASSERT(!continuation()); + + // RenderObject::destroy calls back to destroyLayer() for layer destruction + RenderObject::destroy(); +} + +bool RenderBoxModelObject::hasSelfPaintingLayer() const +{ + return m_layer && m_layer->isSelfPaintingLayer(); +} + +void RenderBoxModelObject::styleWillChange(StyleDifference diff, const RenderStyle* newStyle) +{ + s_wasFloating = isFloating(); + s_hadLayer = hasLayer(); + if (s_hadLayer) + s_layerWasSelfPainting = layer()->isSelfPaintingLayer(); + + // If our z-index changes value or our visibility changes, + // we need to dirty our stacking context's z-order list. + if (style() && newStyle) { + if (parent()) { + // Do a repaint with the old style first, e.g., for example if we go from + // having an outline to not having an outline. + if (diff == StyleDifferenceRepaintLayer) { + layer()->repaintIncludingDescendants(); + if (!(style()->clip() == newStyle->clip())) + layer()->clearClipRectsIncludingDescendants(); + } else if (diff == StyleDifferenceRepaint || newStyle->outlineSize() < style()->outlineSize()) + repaint(); + } + + if (diff == StyleDifferenceLayout) { + // When a layout hint happens, we go ahead and do a repaint of the layer, since the layer could + // end up being destroyed. + if (hasLayer()) { + if (style()->position() != newStyle->position() || + style()->zIndex() != newStyle->zIndex() || + style()->hasAutoZIndex() != newStyle->hasAutoZIndex() || + !(style()->clip() == newStyle->clip()) || + style()->hasClip() != newStyle->hasClip() || + style()->opacity() != newStyle->opacity() || + style()->transform() != newStyle->transform()) + layer()->repaintIncludingDescendants(); + } else if (newStyle->hasTransform() || newStyle->opacity() < 1) { + // If we don't have a layer yet, but we are going to get one because of transform or opacity, + // then we need to repaint the old position of the object. + repaint(); + } + } + + if (hasLayer() && (style()->hasAutoZIndex() != newStyle->hasAutoZIndex() || + style()->zIndex() != newStyle->zIndex() || + style()->visibility() != newStyle->visibility())) { + layer()->dirtyStackingContextZOrderLists(); + if (style()->hasAutoZIndex() != newStyle->hasAutoZIndex() || style()->visibility() != newStyle->visibility()) + layer()->dirtyZOrderLists(); + } + } + + RenderObject::styleWillChange(diff, newStyle); +} + +void RenderBoxModelObject::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderObject::styleDidChange(diff, oldStyle); + updateBoxModelInfoFromStyle(); + + if (requiresLayer()) { + if (!layer()) { + if (s_wasFloating && isFloating()) + setChildNeedsLayout(true); + m_layer = new (renderArena()) RenderLayer(this); + setHasLayer(true); + m_layer->insertOnlyThisLayer(); + if (parent() && !needsLayout() && containingBlock()) + m_layer->updateLayerPositions(); + } + } else if (layer() && layer()->parent()) { + setHasTransform(false); // Either a transform wasn't specified or the object doesn't support transforms, so just null out the bit. + setHasReflection(false); + m_layer->removeOnlyThisLayer(); // calls destroyLayer() which clears m_layer + if (s_wasFloating && isFloating()) + setChildNeedsLayout(true); + } + + if (layer()) { + layer()->styleChanged(diff, oldStyle); + if (s_hadLayer && layer()->isSelfPaintingLayer() != s_layerWasSelfPainting) + setChildNeedsLayout(true); + } +} + +void RenderBoxModelObject::updateBoxModelInfoFromStyle() +{ + // Set the appropriate bits for a box model object. Since all bits are cleared in styleWillChange, + // we only check for bits that could possibly be set to true. + setHasBoxDecorations(hasBackground() || style()->hasBorder() || style()->hasAppearance() || style()->boxShadow()); + setInline(style()->isDisplayInlineType()); + setRelPositioned(style()->position() == RelativePosition); +} + +int RenderBoxModelObject::relativePositionOffsetX() const +{ + // Objects that shrink to avoid floats normally use available line width when computing containing block width. However + // in the case of relative positioning using percentages, we can't do this. The offset should always be resolved using the + // available width of the containing block. Therefore we don't use containingBlockLogicalWidthForContent() here, but instead explicitly + // call availableWidth on our containing block. + if (!style()->left().isAuto()) { + RenderBlock* cb = containingBlock(); + if (!style()->right().isAuto() && !containingBlock()->style()->isLeftToRightDirection()) + return -style()->right().calcValue(cb->availableWidth()); + return style()->left().calcValue(cb->availableWidth()); + } + if (!style()->right().isAuto()) { + RenderBlock* cb = containingBlock(); + return -style()->right().calcValue(cb->availableWidth()); + } + return 0; +} + +int RenderBoxModelObject::relativePositionOffsetY() const +{ + RenderBlock* containingBlock = this->containingBlock(); + + // If the containing block of a relatively positioned element does not + // specify a height, a percentage top or bottom offset should be resolved as + // auto. An exception to this is if the containing block has the WinIE quirk + // where <html> and <body> assume the size of the viewport. In this case, + // calculate the percent offset based on this height. + // See <https://bugs.webkit.org/show_bug.cgi?id=26396>. + if (!style()->top().isAuto() + && (!containingBlock->style()->height().isAuto() + || !style()->top().isPercent() + || containingBlock->stretchesToViewport())) + return style()->top().calcValue(containingBlock->availableHeight()); + + if (!style()->bottom().isAuto() + && (!containingBlock->style()->height().isAuto() + || !style()->bottom().isPercent() + || containingBlock->stretchesToViewport())) + return -style()->bottom().calcValue(containingBlock->availableHeight()); + + return 0; +} + +int RenderBoxModelObject::offsetLeft() const +{ + // If the element is the HTML body element or does not have an associated box + // return 0 and stop this algorithm. + if (isBody()) + return 0; + + RenderBoxModelObject* offsetPar = offsetParent(); + int xPos = (isBox() ? toRenderBox(this)->x() : 0); + + // If the offsetParent of the element is null, or is the HTML body element, + // return the distance between the canvas origin and the left border edge + // of the element and stop this algorithm. + if (offsetPar) { + if (offsetPar->isBox() && !offsetPar->isBody()) + xPos -= toRenderBox(offsetPar)->borderLeft(); + if (!isPositioned()) { + if (isRelPositioned()) + xPos += relativePositionOffsetX(); + RenderObject* curr = parent(); + while (curr && curr != offsetPar) { + // FIXME: What are we supposed to do inside SVG content? + if (curr->isBox() && !curr->isTableRow()) + xPos += toRenderBox(curr)->x(); + curr = curr->parent(); + } + if (offsetPar->isBox() && offsetPar->isBody() && !offsetPar->isRelPositioned() && !offsetPar->isPositioned()) + xPos += toRenderBox(offsetPar)->x(); + } + } + + return xPos; +} + +int RenderBoxModelObject::offsetTop() const +{ + // If the element is the HTML body element or does not have an associated box + // return 0 and stop this algorithm. + if (isBody()) + return 0; + + RenderBoxModelObject* offsetPar = offsetParent(); + int yPos = (isBox() ? toRenderBox(this)->y() : 0); + + // If the offsetParent of the element is null, or is the HTML body element, + // return the distance between the canvas origin and the top border edge + // of the element and stop this algorithm. + if (offsetPar) { + if (offsetPar->isBox() && !offsetPar->isBody()) + yPos -= toRenderBox(offsetPar)->borderTop(); + if (!isPositioned()) { + if (isRelPositioned()) + yPos += relativePositionOffsetY(); + RenderObject* curr = parent(); + while (curr && curr != offsetPar) { + // FIXME: What are we supposed to do inside SVG content? + if (curr->isBox() && !curr->isTableRow()) + yPos += toRenderBox(curr)->y(); + curr = curr->parent(); + } + if (offsetPar->isBox() && offsetPar->isBody() && !offsetPar->isRelPositioned() && !offsetPar->isPositioned()) + yPos += toRenderBox(offsetPar)->y(); + } + } + return yPos; +} + +int RenderBoxModelObject::paddingTop(bool) const +{ + int w = 0; + Length padding = style()->paddingTop(); + if (padding.isPercent()) + w = containingBlock()->availableLogicalWidth(); + return padding.calcMinValue(w); +} + +int RenderBoxModelObject::paddingBottom(bool) const +{ + int w = 0; + Length padding = style()->paddingBottom(); + if (padding.isPercent()) + w = containingBlock()->availableLogicalWidth(); + return padding.calcMinValue(w); +} + +int RenderBoxModelObject::paddingLeft(bool) const +{ + int w = 0; + Length padding = style()->paddingLeft(); + if (padding.isPercent()) + w = containingBlock()->availableLogicalWidth(); + return padding.calcMinValue(w); +} + +int RenderBoxModelObject::paddingRight(bool) const +{ + int w = 0; + Length padding = style()->paddingRight(); + if (padding.isPercent()) + w = containingBlock()->availableLogicalWidth(); + return padding.calcMinValue(w); +} + +int RenderBoxModelObject::paddingBefore(bool) const +{ + int w = 0; + Length padding = style()->paddingBefore(); + if (padding.isPercent()) + w = containingBlock()->availableLogicalWidth(); + return padding.calcMinValue(w); +} + +int RenderBoxModelObject::paddingAfter(bool) const +{ + int w = 0; + Length padding = style()->paddingAfter(); + if (padding.isPercent()) + w = containingBlock()->availableLogicalWidth(); + return padding.calcMinValue(w); +} + +int RenderBoxModelObject::paddingStart(bool) const +{ + int w = 0; + Length padding = style()->paddingStart(); + if (padding.isPercent()) + w = containingBlock()->availableLogicalWidth(); + return padding.calcMinValue(w); +} + +int RenderBoxModelObject::paddingEnd(bool) const +{ + int w = 0; + Length padding = style()->paddingEnd(); + if (padding.isPercent()) + w = containingBlock()->availableLogicalWidth(); + return padding.calcMinValue(w); +} + +void RenderBoxModelObject::paintFillLayerExtended(const PaintInfo& paintInfo, const Color& c, const FillLayer* bgLayer, int tx, int ty, int w, int h, InlineFlowBox* box, CompositeOperator op, RenderObject* backgroundObject) +{ + GraphicsContext* context = paintInfo.context; + if (context->paintingDisabled()) + return; + + bool includeLeftEdge = box ? box->includeLogicalLeftEdge() : true; + bool includeRightEdge = box ? box->includeLogicalRightEdge() : true; + int bLeft = includeLeftEdge ? borderLeft() : 0; + int bRight = includeRightEdge ? borderRight() : 0; + int pLeft = includeLeftEdge ? paddingLeft() : 0; + int pRight = includeRightEdge ? paddingRight() : 0; + + bool clippedToBorderRadius = false; + if (style()->hasBorderRadius() && (includeLeftEdge || includeRightEdge)) { + IntRect borderRect(tx, ty, w, h); + + if (borderRect.isEmpty()) + return; + + context->save(); + + IntSize topLeft, topRight, bottomLeft, bottomRight; + style()->getBorderRadiiForRect(borderRect, topLeft, topRight, bottomLeft, bottomRight); + + if (!includeLeftEdge) { + topLeft = IntSize(); + if (box->isHorizontal()) + bottomLeft = IntSize(); + else + topRight = IntSize(); + } + + if (!includeRightEdge) { + if (box->isHorizontal()) + topRight = IntSize(); + else + bottomLeft = IntSize(); + bottomRight = IntSize(); + } + + context->addRoundedRectClip(borderRect, topLeft, topRight, bottomLeft, bottomRight); + clippedToBorderRadius = true; + } + + bool clippedWithLocalScrolling = hasOverflowClip() && bgLayer->attachment() == LocalBackgroundAttachment; + if (clippedWithLocalScrolling) { + // Clip to the overflow area. + context->save(); + context->clip(toRenderBox(this)->overflowClipRect(tx, ty)); + + // Now adjust our tx, ty, w, h to reflect a scrolled content box with borders at the ends. + IntSize offset = layer()->scrolledContentOffset(); + tx -= offset.width(); + ty -= offset.height(); + w = bLeft + layer()->scrollWidth() + bRight; + h = borderTop() + layer()->scrollHeight() + borderBottom(); + } + + if (bgLayer->clip() == PaddingFillBox || bgLayer->clip() == ContentFillBox) { + // Clip to the padding or content boxes as necessary. + bool includePadding = bgLayer->clip() == ContentFillBox; + int x = tx + bLeft + (includePadding ? pLeft : 0); + int y = ty + borderTop() + (includePadding ? paddingTop() : 0); + int width = w - bLeft - bRight - (includePadding ? pLeft + pRight : 0); + int height = h - borderTop() - borderBottom() - (includePadding ? paddingTop() + paddingBottom() : 0); + context->save(); + context->clip(IntRect(x, y, width, height)); + } else if (bgLayer->clip() == TextFillBox) { + // We have to draw our text into a mask that can then be used to clip background drawing. + // First figure out how big the mask has to be. It should be no bigger than what we need + // to actually render, so we should intersect the dirty rect with the border box of the background. + IntRect maskRect(tx, ty, w, h); + maskRect.intersect(paintInfo.rect); + + // Now create the mask. + OwnPtr<ImageBuffer> maskImage = ImageBuffer::create(maskRect.size()); + if (!maskImage) + return; + + GraphicsContext* maskImageContext = maskImage->context(); + maskImageContext->translate(-maskRect.x(), -maskRect.y()); + + // Now add the text to the clip. We do this by painting using a special paint phase that signals to + // InlineTextBoxes that they should just add their contents to the clip. + PaintInfo info(maskImageContext, maskRect, PaintPhaseTextClip, true, 0, 0); + if (box) + box->paint(info, tx - box->x(), ty - box->y()); + else { + int x = isBox() ? toRenderBox(this)->x() : 0; + int y = isBox() ? toRenderBox(this)->y() : 0; + paint(info, tx - x, ty - y); + } + + // The mask has been created. Now we just need to clip to it. + context->save(); + context->clipToImageBuffer(maskImage.get(), maskRect); + } + + StyleImage* bg = bgLayer->image(); + bool shouldPaintBackgroundImage = bg && bg->canRender(style()->effectiveZoom()); + Color bgColor = c; + + // When this style flag is set, change existing background colors and images to a solid white background. + // If there's no bg color or image, leave it untouched to avoid affecting transparency. + // We don't try to avoid loading the background images, because this style flag is only set + // when printing, and at that point we've already loaded the background images anyway. (To avoid + // loading the background images we'd have to do this check when applying styles rather than + // while rendering.) + if (style()->forceBackgroundsToWhite()) { + // Note that we can't reuse this variable below because the bgColor might be changed + bool shouldPaintBackgroundColor = !bgLayer->next() && bgColor.isValid() && bgColor.alpha() > 0; + if (shouldPaintBackgroundImage || shouldPaintBackgroundColor) { + bgColor = Color::white; + shouldPaintBackgroundImage = false; + } + } + + bool isRoot = this->isRoot(); + + // Only fill with a base color (e.g., white) if we're the root document, since iframes/frames with + // no background in the child document should show the parent's background. + bool isOpaqueRoot = false; + if (isRoot) { + isOpaqueRoot = true; + if (!bgLayer->next() && !(bgColor.isValid() && bgColor.alpha() == 255) && view()->frameView()) { + Element* ownerElement = document()->ownerElement(); + if (ownerElement) { + if (!ownerElement->hasTagName(frameTag)) { + // Locate the <body> element using the DOM. This is easier than trying + // to crawl around a render tree with potential :before/:after content and + // anonymous blocks created by inline <body> tags etc. We can locate the <body> + // render object very easily via the DOM. + HTMLElement* body = document()->body(); + if (body) { + // Can't scroll a frameset document anyway. + isOpaqueRoot = body->hasLocalName(framesetTag); + } +#if ENABLE(SVG) + else { + // SVG documents and XML documents with SVG root nodes are transparent. + isOpaqueRoot = !document()->hasSVGRootNode(); + } +#endif + } + } else + isOpaqueRoot = !view()->frameView()->isTransparent(); + } + view()->frameView()->setContentIsOpaque(isOpaqueRoot); + } + + // Paint the color first underneath all images. + if (!bgLayer->next()) { + IntRect rect(tx, ty, w, h); + rect.intersect(paintInfo.rect); + // If we have an alpha and we are painting the root element, go ahead and blend with the base background color. + if (isOpaqueRoot) { + Color baseColor = view()->frameView()->baseBackgroundColor(); + if (baseColor.alpha() > 0) { + CompositeOperator previousOperator = context->compositeOperation(); + context->setCompositeOperation(CompositeCopy); + context->fillRect(rect, baseColor, style()->colorSpace()); + context->setCompositeOperation(previousOperator); + } else + context->clearRect(rect); + } + + if (bgColor.isValid() && bgColor.alpha() > 0) + context->fillRect(rect, bgColor, style()->colorSpace()); + } + + // no progressive loading of the background image + if (shouldPaintBackgroundImage) { + IntRect destRect; + IntPoint phase; + IntSize tileSize; + + calculateBackgroundImageGeometry(bgLayer, tx, ty, w, h, destRect, phase, tileSize); + IntPoint destOrigin = destRect.location(); + destRect.intersect(paintInfo.rect); + if (!destRect.isEmpty()) { + phase += destRect.location() - destOrigin; + CompositeOperator compositeOp = op == CompositeSourceOver ? bgLayer->composite() : op; + RenderObject* clientForBackgroundImage = backgroundObject ? backgroundObject : this; + Image* image = bg->image(clientForBackgroundImage, tileSize); + bool useLowQualityScaling = shouldPaintAtLowQuality(context, image, bgLayer, tileSize); + context->drawTiledImage(image, style()->colorSpace(), destRect, phase, tileSize, compositeOp, useLowQualityScaling); + } + } + + if (bgLayer->clip() != BorderFillBox) + // Undo the background clip + context->restore(); + + if (clippedToBorderRadius) + // Undo the border radius clip + context->restore(); + + if (clippedWithLocalScrolling) // Undo the clip for local background attachments. + context->restore(); +} + +IntSize RenderBoxModelObject::calculateFillTileSize(const FillLayer* fillLayer, IntSize positioningAreaSize) const +{ + StyleImage* image = fillLayer->image(); + image->setImageContainerSize(positioningAreaSize); // Use the box established by background-origin. + + EFillSizeType type = fillLayer->size().type; + + switch (type) { + case SizeLength: { + int w = positioningAreaSize.width(); + int h = positioningAreaSize.height(); + + Length layerWidth = fillLayer->size().size.width(); + Length layerHeight = fillLayer->size().size.height(); + + if (layerWidth.isFixed()) + w = layerWidth.value(); + else if (layerWidth.isPercent()) + w = layerWidth.calcValue(positioningAreaSize.width()); + + if (layerHeight.isFixed()) + h = layerHeight.value(); + else if (layerHeight.isPercent()) + h = layerHeight.calcValue(positioningAreaSize.height()); + + // If one of the values is auto we have to use the appropriate + // scale to maintain our aspect ratio. + if (layerWidth.isAuto() && !layerHeight.isAuto()) { + IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom()); + if (imageIntrinsicSize.height()) + w = imageIntrinsicSize.width() * h / imageIntrinsicSize.height(); + } else if (!layerWidth.isAuto() && layerHeight.isAuto()) { + IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom()); + if (imageIntrinsicSize.width()) + h = imageIntrinsicSize.height() * w / imageIntrinsicSize.width(); + } else if (layerWidth.isAuto() && layerHeight.isAuto()) { + // If both width and height are auto, use the image's intrinsic size. + IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom()); + w = imageIntrinsicSize.width(); + h = imageIntrinsicSize.height(); + } + + return IntSize(max(1, w), max(1, h)); + } + case Contain: + case Cover: { + IntSize imageIntrinsicSize = image->imageSize(this, 1); + float horizontalScaleFactor = imageIntrinsicSize.width() + ? static_cast<float>(positioningAreaSize.width()) / imageIntrinsicSize.width() : 1; + float verticalScaleFactor = imageIntrinsicSize.height() + ? static_cast<float>(positioningAreaSize.height()) / imageIntrinsicSize.height() : 1; + float scaleFactor = type == Contain ? min(horizontalScaleFactor, verticalScaleFactor) : max(horizontalScaleFactor, verticalScaleFactor); + return IntSize(max<int>(1, imageIntrinsicSize.width() * scaleFactor), max<int>(1, imageIntrinsicSize.height() * scaleFactor)); + } + case SizeNone: + break; + } + + return image->imageSize(this, style()->effectiveZoom()); +} + +void RenderBoxModelObject::calculateBackgroundImageGeometry(const FillLayer* fillLayer, int tx, int ty, int w, int h, + IntRect& destRect, IntPoint& phase, IntSize& tileSize) +{ + int left = 0; + int top = 0; + IntSize positioningAreaSize; + + // Determine the background positioning area and set destRect to the background painting area. + // destRect will be adjusted later if the background is non-repeating. + bool fixedAttachment = fillLayer->attachment() == FixedBackgroundAttachment; + +#if ENABLE(FAST_MOBILE_SCROLLING) + if (view()->frameView() && view()->frameView()->canBlitOnScroll()) { + // As a side effect of an optimization to blit on scroll, we do not honor the CSS + // property "background-attachment: fixed" because it may result in rendering + // artifacts. Note, these artifacts only appear if we are blitting on scroll of + // a page that has fixed background images. + fixedAttachment = false; + } +#endif + + if (!fixedAttachment) { + destRect = IntRect(tx, ty, w, h); + + int right = 0; + int bottom = 0; + // Scroll and Local. + if (fillLayer->origin() != BorderFillBox) { + left = borderLeft(); + right = borderRight(); + top = borderTop(); + bottom = borderBottom(); + if (fillLayer->origin() == ContentFillBox) { + left += paddingLeft(); + right += paddingRight(); + top += paddingTop(); + bottom += paddingBottom(); + } + } + + // The background of the box generated by the root element covers the entire canvas including + // its margins. Since those were added in already, we have to factor them out when computing + // the background positioning area. + if (isRoot()) { + positioningAreaSize = IntSize(toRenderBox(this)->width() - left - right, toRenderBox(this)->height() - top - bottom); + left += marginLeft(); + top += marginTop(); + } else + positioningAreaSize = IntSize(w - left - right, h - top - bottom); + } else { + destRect = viewRect(); + positioningAreaSize = destRect.size(); + } + + tileSize = calculateFillTileSize(fillLayer, positioningAreaSize); + + EFillRepeat backgroundRepeatX = fillLayer->repeatX(); + EFillRepeat backgroundRepeatY = fillLayer->repeatY(); + + int xPosition = fillLayer->xPosition().calcMinValue(positioningAreaSize.width() - tileSize.width(), true); + if (backgroundRepeatX == RepeatFill) + phase.setX(tileSize.width() ? tileSize.width() - (xPosition + left) % tileSize.width() : 0); + else { + destRect.move(max(xPosition + left, 0), 0); + phase.setX(-min(xPosition + left, 0)); + destRect.setWidth(tileSize.width() + min(xPosition + left, 0)); + } + + int yPosition = fillLayer->yPosition().calcMinValue(positioningAreaSize.height() - tileSize.height(), true); + if (backgroundRepeatY == RepeatFill) + phase.setY(tileSize.height() ? tileSize.height() - (yPosition + top) % tileSize.height() : 0); + else { + destRect.move(0, max(yPosition + top, 0)); + phase.setY(-min(yPosition + top, 0)); + destRect.setHeight(tileSize.height() + min(yPosition + top, 0)); + } + + if (fixedAttachment) + phase.move(max(tx - destRect.x(), 0), max(ty - destRect.y(), 0)); + + destRect.intersect(IntRect(tx, ty, w, h)); +} + +bool RenderBoxModelObject::paintNinePieceImage(GraphicsContext* graphicsContext, int tx, int ty, int w, int h, const RenderStyle* style, + const NinePieceImage& ninePieceImage, CompositeOperator op) +{ + StyleImage* styleImage = ninePieceImage.image(); + if (!styleImage) + return false; + + if (!styleImage->isLoaded()) + return true; // Never paint a nine-piece image incrementally, but don't paint the fallback borders either. + + if (!styleImage->canRender(style->effectiveZoom())) + return false; + + // FIXME: border-image is broken with full page zooming when tiling has to happen, since the tiling function + // doesn't have any understanding of the zoom that is in effect on the tile. + styleImage->setImageContainerSize(IntSize(w, h)); + IntSize imageSize = styleImage->imageSize(this, 1.0f); + int imageWidth = imageSize.width(); + int imageHeight = imageSize.height(); + + int topSlice = min(imageHeight, ninePieceImage.slices().top().calcValue(imageHeight)); + int bottomSlice = min(imageHeight, ninePieceImage.slices().bottom().calcValue(imageHeight)); + int leftSlice = min(imageWidth, ninePieceImage.slices().left().calcValue(imageWidth)); + int rightSlice = min(imageWidth, ninePieceImage.slices().right().calcValue(imageWidth)); + + ENinePieceImageRule hRule = ninePieceImage.horizontalRule(); + ENinePieceImageRule vRule = ninePieceImage.verticalRule(); + + bool fitToBorder = style->borderImage() == ninePieceImage; + + int leftWidth = fitToBorder ? style->borderLeftWidth() : leftSlice; + int topWidth = fitToBorder ? style->borderTopWidth() : topSlice; + int rightWidth = fitToBorder ? style->borderRightWidth() : rightSlice; + int bottomWidth = fitToBorder ? style->borderBottomWidth() : bottomSlice; + + bool drawLeft = leftSlice > 0 && leftWidth > 0; + bool drawTop = topSlice > 0 && topWidth > 0; + bool drawRight = rightSlice > 0 && rightWidth > 0; + bool drawBottom = bottomSlice > 0 && bottomWidth > 0; + bool drawMiddle = (imageWidth - leftSlice - rightSlice) > 0 && (w - leftWidth - rightWidth) > 0 && + (imageHeight - topSlice - bottomSlice) > 0 && (h - topWidth - bottomWidth) > 0; + + Image* image = styleImage->image(this, imageSize); + ColorSpace colorSpace = style->colorSpace(); + + if (drawLeft) { + // Paint the top and bottom left corners. + + // The top left corner rect is (tx, ty, leftWidth, topWidth) + // The rect to use from within the image is obtained from our slice, and is (0, 0, leftSlice, topSlice) + if (drawTop) + graphicsContext->drawImage(image, colorSpace, IntRect(tx, ty, leftWidth, topWidth), + IntRect(0, 0, leftSlice, topSlice), op); + + // The bottom left corner rect is (tx, ty + h - bottomWidth, leftWidth, bottomWidth) + // The rect to use from within the image is (0, imageHeight - bottomSlice, leftSlice, botomSlice) + if (drawBottom) + graphicsContext->drawImage(image, colorSpace, IntRect(tx, ty + h - bottomWidth, leftWidth, bottomWidth), + IntRect(0, imageHeight - bottomSlice, leftSlice, bottomSlice), op); + + // Paint the left edge. + // Have to scale and tile into the border rect. + graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx, ty + topWidth, leftWidth, + h - topWidth - bottomWidth), + IntRect(0, topSlice, leftSlice, imageHeight - topSlice - bottomSlice), + Image::StretchTile, (Image::TileRule)vRule, op); + } + + if (drawRight) { + // Paint the top and bottom right corners + // The top right corner rect is (tx + w - rightWidth, ty, rightWidth, topWidth) + // The rect to use from within the image is obtained from our slice, and is (imageWidth - rightSlice, 0, rightSlice, topSlice) + if (drawTop) + graphicsContext->drawImage(image, colorSpace, IntRect(tx + w - rightWidth, ty, rightWidth, topWidth), + IntRect(imageWidth - rightSlice, 0, rightSlice, topSlice), op); + + // The bottom right corner rect is (tx + w - rightWidth, ty + h - bottomWidth, rightWidth, bottomWidth) + // The rect to use from within the image is (imageWidth - rightSlice, imageHeight - bottomSlice, rightSlice, bottomSlice) + if (drawBottom) + graphicsContext->drawImage(image, colorSpace, IntRect(tx + w - rightWidth, ty + h - bottomWidth, rightWidth, bottomWidth), + IntRect(imageWidth - rightSlice, imageHeight - bottomSlice, rightSlice, bottomSlice), op); + + // Paint the right edge. + graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx + w - rightWidth, ty + topWidth, rightWidth, + h - topWidth - bottomWidth), + IntRect(imageWidth - rightSlice, topSlice, rightSlice, imageHeight - topSlice - bottomSlice), + Image::StretchTile, (Image::TileRule)vRule, op); + } + + // Paint the top edge. + if (drawTop) + graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx + leftWidth, ty, w - leftWidth - rightWidth, topWidth), + IntRect(leftSlice, 0, imageWidth - rightSlice - leftSlice, topSlice), + (Image::TileRule)hRule, Image::StretchTile, op); + + // Paint the bottom edge. + if (drawBottom) + graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx + leftWidth, ty + h - bottomWidth, + w - leftWidth - rightWidth, bottomWidth), + IntRect(leftSlice, imageHeight - bottomSlice, imageWidth - rightSlice - leftSlice, bottomSlice), + (Image::TileRule)hRule, Image::StretchTile, op); + + // Paint the middle. + if (drawMiddle) + graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx + leftWidth, ty + topWidth, w - leftWidth - rightWidth, + h - topWidth - bottomWidth), + IntRect(leftSlice, topSlice, imageWidth - rightSlice - leftSlice, imageHeight - topSlice - bottomSlice), + (Image::TileRule)hRule, (Image::TileRule)vRule, op); + + return true; +} + +#if HAVE(PATH_BASED_BORDER_RADIUS_DRAWING) +static bool borderWillArcInnerEdge(const IntSize& firstRadius, const IntSize& secondRadius, int firstBorderWidth, int secondBorderWidth, int middleBorderWidth) +{ + // FIXME: This test is insufficient. We need to take border style into account. + return (!firstRadius.width() || firstRadius.width() >= firstBorderWidth) + && (!firstRadius.height() || firstRadius.height() >= middleBorderWidth) + && (!secondRadius.width() || secondRadius.width() >= secondBorderWidth) + && (!secondRadius.height() || secondRadius.height() >= middleBorderWidth); +} + +void RenderBoxModelObject::paintBorder(GraphicsContext* graphicsContext, int tx, int ty, int w, int h, + const RenderStyle* style, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) +{ + if (paintNinePieceImage(graphicsContext, tx, ty, w, h, style, style->borderImage())) + return; + + if (graphicsContext->paintingDisabled()) + return; + + const Color& topColor = style->visitedDependentColor(CSSPropertyBorderTopColor); + const Color& bottomColor = style->visitedDependentColor(CSSPropertyBorderBottomColor); + const Color& leftColor = style->visitedDependentColor(CSSPropertyBorderLeftColor); + const Color& rightColor = style->visitedDependentColor(CSSPropertyBorderRightColor); + + bool topTransparent = style->borderTopIsTransparent(); + bool bottomTransparent = style->borderBottomIsTransparent(); + bool rightTransparent = style->borderRightIsTransparent(); + bool leftTransparent = style->borderLeftIsTransparent(); + + EBorderStyle topStyle = style->borderTopStyle(); + EBorderStyle bottomStyle = style->borderBottomStyle(); + EBorderStyle leftStyle = style->borderLeftStyle(); + EBorderStyle rightStyle = style->borderRightStyle(); + + bool horizontal = style->isHorizontalWritingMode(); + + bool renderTop = topStyle > BHIDDEN && !topTransparent && (horizontal || includeLogicalLeftEdge); + bool renderLeft = leftStyle > BHIDDEN && !leftTransparent && (!horizontal || includeLogicalLeftEdge); + bool renderRight = rightStyle > BHIDDEN && !rightTransparent && (!horizontal || includeLogicalRightEdge); + bool renderBottom = bottomStyle > BHIDDEN && !bottomTransparent && (horizontal || includeLogicalRightEdge); + + bool renderRadii = false; + Path roundedPath; + IntSize topLeft, topRight, bottomLeft, bottomRight; + IntRect borderRect(tx, ty, w, h); + + if (style->hasBorderRadius()) { + IntSize topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius; + style->getBorderRadiiForRect(borderRect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); + + int leftWidth = (!horizontal || includeLogicalLeftEdge) ? style->borderLeftWidth() : 0; + int rightWidth = (!horizontal || includeLogicalRightEdge) ? style->borderRightWidth() : 0; + int topWidth = (horizontal || includeLogicalLeftEdge) ? style->borderTopWidth() : 0; + int bottomWidth = (horizontal || includeLogicalRightEdge) ? style->borderBottomWidth() : 0; + + IntRect innerBorderRect = borderInnerRect(borderRect, topWidth, bottomWidth, leftWidth, rightWidth); + IntSize innerTopLeftRadius, innerTopRightRadius, innerBottomLeftRadius, innerBottomRightRadius; + + style->getInnerBorderRadiiForRectWithBorderWidths(innerBorderRect, topWidth, bottomWidth, leftWidth, rightWidth, innerTopLeftRadius, innerTopRightRadius, innerBottomLeftRadius, innerBottomRightRadius); + + IntSize innerTopLeft, innerTopRight, innerBottomLeft, innerBottomRight; + + if (includeLogicalLeftEdge) { + topLeft = topLeftRadius; + innerTopLeft = innerTopLeftRadius; + if (horizontal) { + bottomLeft = bottomLeftRadius; + innerBottomLeft = innerBottomLeftRadius; + } else { + topRight = topRightRadius; + innerTopRight = innerTopRightRadius; + } + } + + if (includeLogicalRightEdge) { + if (horizontal) { + topRight = topRightRadius; + innerTopRight = innerTopRightRadius; + } else { + bottomLeft = bottomLeftRadius; + innerBottomLeft = innerBottomLeftRadius; + } + bottomRight = bottomRightRadius; + innerBottomRight = innerBottomRightRadius; + } + + renderRadii = !topLeft.isZero() || !topRight.isZero() || !bottomLeft.isZero() || !bottomRight.isZero(); + + if (renderRadii) { + // Clip to the inner and outer radii rects. + graphicsContext->save(); + graphicsContext->addRoundedRectClip(borderRect, topLeft, topRight, bottomLeft, bottomRight); + graphicsContext->clipOutRoundedRect(innerBorderRect, innerTopLeft, innerTopRight, innerBottomLeft, innerBottomRight); + roundedPath.addRoundedRect(borderRect, topLeft, topRight, bottomLeft, bottomRight); + } + } + + bool upperLeftBorderStylesMatch = renderLeft && (topStyle == leftStyle) && (topColor == leftColor); + bool upperRightBorderStylesMatch = renderRight && (topStyle == rightStyle) && (topColor == rightColor) && (topStyle != OUTSET) && (topStyle != RIDGE) && (topStyle != INSET) && (topStyle != GROOVE); + bool lowerLeftBorderStylesMatch = renderLeft && (bottomStyle == leftStyle) && (bottomColor == leftColor) && (bottomStyle != OUTSET) && (bottomStyle != RIDGE) && (bottomStyle != INSET) && (bottomStyle != GROOVE); + bool lowerRightBorderStylesMatch = renderRight && (bottomStyle == rightStyle) && (bottomColor == rightColor); + + if (renderTop) { + int x = tx; + int x2 = tx + w; + + if (renderRadii && borderWillArcInnerEdge(topLeft, topRight, style->borderLeftWidth(), style->borderRightWidth(), style->borderTopWidth())) { + graphicsContext->save(); + clipBorderSidePolygon(graphicsContext, borderRect, topLeft, topRight, bottomLeft, bottomRight, BSTop, upperLeftBorderStylesMatch, upperRightBorderStylesMatch, style, includeLogicalLeftEdge, includeLogicalRightEdge); + float thickness = max(max(style->borderTopWidth(), style->borderLeftWidth()), style->borderRightWidth()); + drawBoxSideFromPath(graphicsContext, borderRect, roundedPath, style->borderTopWidth(), thickness, BSTop, style, topColor, topStyle); + graphicsContext->restore(); + } else { + bool ignoreLeft = (topColor == leftColor && topTransparent == leftTransparent && topStyle >= OUTSET + && (leftStyle == DOTTED || leftStyle == DASHED || leftStyle == SOLID || leftStyle == OUTSET)); + bool ignoreRight = (topColor == rightColor && topTransparent == rightTransparent && topStyle >= OUTSET + && (rightStyle == DOTTED || rightStyle == DASHED || rightStyle == SOLID || rightStyle == INSET)); + + drawLineForBoxSide(graphicsContext, x, ty, x2, ty + style->borderTopWidth(), BSTop, topColor, topStyle, + ignoreLeft ? 0 : style->borderLeftWidth(), ignoreRight ? 0 : style->borderRightWidth()); + } + } + + if (renderBottom) { + int x = tx; + int x2 = tx + w; + + if (renderRadii && borderWillArcInnerEdge(bottomLeft, bottomRight, style->borderLeftWidth(), style->borderRightWidth(), style->borderBottomWidth())) { + graphicsContext->save(); + clipBorderSidePolygon(graphicsContext, borderRect, topLeft, topRight, bottomLeft, bottomRight, BSBottom, lowerLeftBorderStylesMatch, lowerRightBorderStylesMatch, style, includeLogicalLeftEdge, includeLogicalRightEdge); + float thickness = max(max(style->borderBottomWidth(), style->borderLeftWidth()), style->borderRightWidth()); + drawBoxSideFromPath(graphicsContext, borderRect, roundedPath, style->borderBottomWidth(), thickness, BSBottom, style, bottomColor, bottomStyle); + graphicsContext->restore(); + } else { + bool ignoreLeft = (bottomColor == leftColor && bottomTransparent == leftTransparent && bottomStyle >= OUTSET + && (leftStyle == DOTTED || leftStyle == DASHED || leftStyle == SOLID || leftStyle == OUTSET)); + + bool ignoreRight = (bottomColor == rightColor && bottomTransparent == rightTransparent && bottomStyle >= OUTSET + && (rightStyle == DOTTED || rightStyle == DASHED || rightStyle == SOLID || rightStyle == INSET)); + + drawLineForBoxSide(graphicsContext, x, ty + h - style->borderBottomWidth(), x2, ty + h, BSBottom, bottomColor, + bottomStyle, ignoreLeft ? 0 : style->borderLeftWidth(), + ignoreRight ? 0 : style->borderRightWidth()); + } + } + + if (renderLeft) { + int y = ty; + int y2 = ty + h; + + if (renderRadii && borderWillArcInnerEdge(bottomLeft, topLeft, style->borderBottomWidth(), style->borderTopWidth(), style->borderLeftWidth())) { + graphicsContext->save(); + clipBorderSidePolygon(graphicsContext, borderRect, topLeft, topRight, bottomLeft, bottomRight, BSLeft, upperLeftBorderStylesMatch, lowerLeftBorderStylesMatch, style, includeLogicalLeftEdge, includeLogicalRightEdge); + float thickness = max(max(style->borderLeftWidth(), style->borderTopWidth()), style->borderBottomWidth()); + drawBoxSideFromPath(graphicsContext, borderRect, roundedPath, style->borderLeftWidth(), thickness, BSLeft, style, leftColor, leftStyle); + graphicsContext->restore(); + } else { + bool ignoreTop = (topColor == leftColor && topTransparent == leftTransparent && leftStyle >= OUTSET + && (topStyle == DOTTED || topStyle == DASHED || topStyle == SOLID || topStyle == OUTSET)); + + bool ignoreBottom = (bottomColor == leftColor && bottomTransparent == leftTransparent && leftStyle >= OUTSET + && (bottomStyle == DOTTED || bottomStyle == DASHED || bottomStyle == SOLID || bottomStyle == INSET)); + + drawLineForBoxSide(graphicsContext, tx, y, tx + style->borderLeftWidth(), y2, BSLeft, leftColor, + leftStyle, ignoreTop ? 0 : style->borderTopWidth(), ignoreBottom ? 0 : style->borderBottomWidth()); + } + } + + if (renderRight) { + if (renderRadii && borderWillArcInnerEdge(bottomRight, topRight, style->borderBottomWidth(), style->borderTopWidth(), style->borderRightWidth())) { + graphicsContext->save(); + clipBorderSidePolygon(graphicsContext, borderRect, topLeft, topRight, bottomLeft, bottomRight, BSRight, upperRightBorderStylesMatch, lowerRightBorderStylesMatch, style, includeLogicalLeftEdge, includeLogicalRightEdge); + float thickness = max(max(style->borderRightWidth(), style->borderTopWidth()), style->borderBottomWidth()); + drawBoxSideFromPath(graphicsContext, borderRect, roundedPath, style->borderRightWidth(), thickness, BSRight, style, rightColor, rightStyle); + graphicsContext->restore(); + } else { + bool ignoreTop = ((topColor == rightColor) && (topTransparent == rightTransparent) + && (rightStyle >= DOTTED || rightStyle == INSET) + && (topStyle == DOTTED || topStyle == DASHED || topStyle == SOLID || topStyle == OUTSET)); + + bool ignoreBottom = ((bottomColor == rightColor) && (bottomTransparent == rightTransparent) + && (rightStyle >= DOTTED || rightStyle == INSET) + && (bottomStyle == DOTTED || bottomStyle == DASHED || bottomStyle == SOLID || bottomStyle == INSET)); + + int y = ty; + int y2 = ty + h; + + drawLineForBoxSide(graphicsContext, tx + w - style->borderRightWidth(), y, tx + w, y2, BSRight, rightColor, + rightStyle, ignoreTop ? 0 : style->borderTopWidth(), + ignoreBottom ? 0 : style->borderBottomWidth()); + } + } + + if (renderRadii) + graphicsContext->restore(); +} +#else +void RenderBoxModelObject::paintBorder(GraphicsContext* graphicsContext, int tx, int ty, int w, int h, + const RenderStyle* style, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) +{ + // FIXME: This old version of paintBorder should be removed when all ports implement + // GraphicsContext::clipConvexPolygon()!! This should happen soon. + if (paintNinePieceImage(graphicsContext, tx, ty, w, h, style, style->borderImage())) + return; + + const Color& topColor = style->visitedDependentColor(CSSPropertyBorderTopColor); + const Color& bottomColor = style->visitedDependentColor(CSSPropertyBorderBottomColor); + const Color& leftColor = style->visitedDependentColor(CSSPropertyBorderLeftColor); + const Color& rightColor = style->visitedDependentColor(CSSPropertyBorderRightColor); + + bool topTransparent = style->borderTopIsTransparent(); + bool bottomTransparent = style->borderBottomIsTransparent(); + bool rightTransparent = style->borderRightIsTransparent(); + bool leftTransparent = style->borderLeftIsTransparent(); + + EBorderStyle topStyle = style->borderTopStyle(); + EBorderStyle bottomStyle = style->borderBottomStyle(); + EBorderStyle leftStyle = style->borderLeftStyle(); + EBorderStyle rightStyle = style->borderRightStyle(); + + bool horizontal = style->isHorizontalWritingMode(); + bool renderTop = topStyle > BHIDDEN && !topTransparent && (horizontal || includeLogicalLeftEdge); + bool renderLeft = leftStyle > BHIDDEN && !leftTransparent && (!horizontal || includeLogicalLeftEdge); + bool renderRight = rightStyle > BHIDDEN && !rightTransparent && (!horizontal || includeLogicalRightEdge); + bool renderBottom = bottomStyle > BHIDDEN && !bottomTransparent && (horizontal || includeLogicalRightEdge); + + bool renderRadii = false; + IntSize topLeft, topRight, bottomLeft, bottomRight; + + if (style->hasBorderRadius()) { + IntRect borderRect = IntRect(tx, ty, w, h); + + IntSize topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius; + style->getBorderRadiiForRect(borderRect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); + + if (includeLogicalLeftEdge) { + topLeft = topLeftRadius; + if (horizontal) + bottomLeft = bottomLeftRadius; + else + topRight = topRightRadius; + } + + if (includeLogicalRightEdge) { + if (horizontal) + topRight = topRightRadius; + else + bottomLeft = bottomLeftRadius; + bottomRight = bottomRightRadius; + } + + renderRadii = !topLeft.isZero() || !topRight.isZero() || !bottomLeft.isZero() || !bottomRight.isZero(); + + if (renderRadii) { + // Clip to the rounded rectangle. + graphicsContext->save(); + graphicsContext->addRoundedRectClip(borderRect, topLeft, topRight, bottomLeft, bottomRight); + } + } + + int firstAngleStart, secondAngleStart, firstAngleSpan, secondAngleSpan; + float thickness; + bool upperLeftBorderStylesMatch = renderLeft && (topStyle == leftStyle) && (topColor == leftColor); + bool upperRightBorderStylesMatch = renderRight && (topStyle == rightStyle) && (topColor == rightColor) && (topStyle != OUTSET) && (topStyle != RIDGE) && (topStyle != INSET) && (topStyle != GROOVE); + bool lowerLeftBorderStylesMatch = renderLeft && (bottomStyle == leftStyle) && (bottomColor == leftColor) && (bottomStyle != OUTSET) && (bottomStyle != RIDGE) && (bottomStyle != INSET) && (bottomStyle != GROOVE); + bool lowerRightBorderStylesMatch = renderRight && (bottomStyle == rightStyle) && (bottomColor == rightColor); + + if (renderTop) { + bool ignore_left = (renderRadii && topLeft.width() > 0) || + (topColor == leftColor && topTransparent == leftTransparent && topStyle >= OUTSET && + (leftStyle == DOTTED || leftStyle == DASHED || leftStyle == SOLID || leftStyle == OUTSET)); + + bool ignore_right = (renderRadii && topRight.width() > 0) || + (topColor == rightColor && topTransparent == rightTransparent && topStyle >= OUTSET && + (rightStyle == DOTTED || rightStyle == DASHED || rightStyle == SOLID || rightStyle == INSET)); + + int x = tx; + int x2 = tx + w; + if (renderRadii) { + x += topLeft.width(); + x2 -= topRight.width(); + } + + drawLineForBoxSide(graphicsContext, x, ty, x2, ty + style->borderTopWidth(), BSTop, topColor, topStyle, + ignore_left ? 0 : style->borderLeftWidth(), ignore_right ? 0 : style->borderRightWidth()); + + if (renderRadii) { + int leftY = ty; + + // We make the arc double thick and let the clip rect take care of clipping the extra off. + // We're doing this because it doesn't seem possible to match the curve of the clip exactly + // with the arc-drawing function. + thickness = style->borderTopWidth() * 2; + + if (topLeft.width()) { + int leftX = tx; + // The inner clip clips inside the arc. This is especially important for 1px borders. + bool applyLeftInnerClip = (style->borderLeftWidth() < topLeft.width()) + && (style->borderTopWidth() < topLeft.height()) + && (topStyle != DOUBLE || style->borderTopWidth() > 6); + if (applyLeftInnerClip) { + graphicsContext->save(); + graphicsContext->addInnerRoundedRectClip(IntRect(leftX, leftY, topLeft.width() * 2, topLeft.height() * 2), + style->borderTopWidth()); + } + + firstAngleStart = 90; + firstAngleSpan = upperLeftBorderStylesMatch ? 90 : 45; + + // Draw upper left arc + drawArcForBoxSide(graphicsContext, leftX, leftY, thickness, topLeft, firstAngleStart, firstAngleSpan, + BSTop, topColor, topStyle, true); + if (applyLeftInnerClip) + graphicsContext->restore(); + } + + if (topRight.width()) { + int rightX = tx + w - topRight.width() * 2; + bool applyRightInnerClip = (style->borderRightWidth() < topRight.width()) + && (style->borderTopWidth() < topRight.height()) + && (topStyle != DOUBLE || style->borderTopWidth() > 6); + if (applyRightInnerClip) { + graphicsContext->save(); + graphicsContext->addInnerRoundedRectClip(IntRect(rightX, leftY, topRight.width() * 2, topRight.height() * 2), + style->borderTopWidth()); + } + + if (upperRightBorderStylesMatch) { + secondAngleStart = 0; + secondAngleSpan = 90; + } else { + secondAngleStart = 45; + secondAngleSpan = 45; + } + + // Draw upper right arc + drawArcForBoxSide(graphicsContext, rightX, leftY, thickness, topRight, secondAngleStart, secondAngleSpan, + BSTop, topColor, topStyle, false); + if (applyRightInnerClip) + graphicsContext->restore(); + } + } + } + + if (renderBottom) { + bool ignore_left = (renderRadii && bottomLeft.width() > 0) || + (bottomColor == leftColor && bottomTransparent == leftTransparent && bottomStyle >= OUTSET && + (leftStyle == DOTTED || leftStyle == DASHED || leftStyle == SOLID || leftStyle == OUTSET)); + + bool ignore_right = (renderRadii && bottomRight.width() > 0) || + (bottomColor == rightColor && bottomTransparent == rightTransparent && bottomStyle >= OUTSET && + (rightStyle == DOTTED || rightStyle == DASHED || rightStyle == SOLID || rightStyle == INSET)); + + int x = tx; + int x2 = tx + w; + if (renderRadii) { + x += bottomLeft.width(); + x2 -= bottomRight.width(); + } + + drawLineForBoxSide(graphicsContext, x, ty + h - style->borderBottomWidth(), x2, ty + h, BSBottom, bottomColor, bottomStyle, + ignore_left ? 0 : style->borderLeftWidth(), ignore_right ? 0 : style->borderRightWidth()); + + if (renderRadii) { + thickness = style->borderBottomWidth() * 2; + + if (bottomLeft.width()) { + int leftX = tx; + int leftY = ty + h - bottomLeft.height() * 2; + bool applyLeftInnerClip = (style->borderLeftWidth() < bottomLeft.width()) + && (style->borderBottomWidth() < bottomLeft.height()) + && (bottomStyle != DOUBLE || style->borderBottomWidth() > 6); + if (applyLeftInnerClip) { + graphicsContext->save(); + graphicsContext->addInnerRoundedRectClip(IntRect(leftX, leftY, bottomLeft.width() * 2, bottomLeft.height() * 2), + style->borderBottomWidth()); + } + + if (lowerLeftBorderStylesMatch) { + firstAngleStart = 180; + firstAngleSpan = 90; + } else { + firstAngleStart = 225; + firstAngleSpan = 45; + } + + // Draw lower left arc + drawArcForBoxSide(graphicsContext, leftX, leftY, thickness, bottomLeft, firstAngleStart, firstAngleSpan, + BSBottom, bottomColor, bottomStyle, true); + if (applyLeftInnerClip) + graphicsContext->restore(); + } + + if (bottomRight.width()) { + int rightY = ty + h - bottomRight.height() * 2; + int rightX = tx + w - bottomRight.width() * 2; + bool applyRightInnerClip = (style->borderRightWidth() < bottomRight.width()) + && (style->borderBottomWidth() < bottomRight.height()) + && (bottomStyle != DOUBLE || style->borderBottomWidth() > 6); + if (applyRightInnerClip) { + graphicsContext->save(); + graphicsContext->addInnerRoundedRectClip(IntRect(rightX, rightY, bottomRight.width() * 2, bottomRight.height() * 2), + style->borderBottomWidth()); + } + + secondAngleStart = 270; + secondAngleSpan = lowerRightBorderStylesMatch ? 90 : 45; + + // Draw lower right arc + drawArcForBoxSide(graphicsContext, rightX, rightY, thickness, bottomRight, secondAngleStart, secondAngleSpan, + BSBottom, bottomColor, bottomStyle, false); + if (applyRightInnerClip) + graphicsContext->restore(); + } + } + } + + if (renderLeft) { + bool ignore_top = (renderRadii && topLeft.height() > 0) || + (topColor == leftColor && topTransparent == leftTransparent && leftStyle >= OUTSET && + (topStyle == DOTTED || topStyle == DASHED || topStyle == SOLID || topStyle == OUTSET)); + + bool ignore_bottom = (renderRadii && bottomLeft.height() > 0) || + (bottomColor == leftColor && bottomTransparent == leftTransparent && leftStyle >= OUTSET && + (bottomStyle == DOTTED || bottomStyle == DASHED || bottomStyle == SOLID || bottomStyle == INSET)); + + int y = ty; + int y2 = ty + h; + if (renderRadii) { + y += topLeft.height(); + y2 -= bottomLeft.height(); + } + + drawLineForBoxSide(graphicsContext, tx, y, tx + style->borderLeftWidth(), y2, BSLeft, leftColor, leftStyle, + ignore_top ? 0 : style->borderTopWidth(), ignore_bottom ? 0 : style->borderBottomWidth()); + + if (renderRadii && (!upperLeftBorderStylesMatch || !lowerLeftBorderStylesMatch)) { + int topX = tx; + thickness = style->borderLeftWidth() * 2; + + if (!upperLeftBorderStylesMatch && topLeft.width()) { + int topY = ty; + bool applyTopInnerClip = (style->borderLeftWidth() < topLeft.width()) + && (style->borderTopWidth() < topLeft.height()) + && (leftStyle != DOUBLE || style->borderLeftWidth() > 6); + if (applyTopInnerClip) { + graphicsContext->save(); + graphicsContext->addInnerRoundedRectClip(IntRect(topX, topY, topLeft.width() * 2, topLeft.height() * 2), + style->borderLeftWidth()); + } + + firstAngleStart = 135; + firstAngleSpan = 45; + + // Draw top left arc + drawArcForBoxSide(graphicsContext, topX, topY, thickness, topLeft, firstAngleStart, firstAngleSpan, + BSLeft, leftColor, leftStyle, true); + if (applyTopInnerClip) + graphicsContext->restore(); + } + + if (!lowerLeftBorderStylesMatch && bottomLeft.width()) { + int bottomY = ty + h - bottomLeft.height() * 2; + bool applyBottomInnerClip = (style->borderLeftWidth() < bottomLeft.width()) + && (style->borderBottomWidth() < bottomLeft.height()) + && (leftStyle != DOUBLE || style->borderLeftWidth() > 6); + if (applyBottomInnerClip) { + graphicsContext->save(); + graphicsContext->addInnerRoundedRectClip(IntRect(topX, bottomY, bottomLeft.width() * 2, bottomLeft.height() * 2), + style->borderLeftWidth()); + } + + secondAngleStart = 180; + secondAngleSpan = 45; + + // Draw bottom left arc + drawArcForBoxSide(graphicsContext, topX, bottomY, thickness, bottomLeft, secondAngleStart, secondAngleSpan, + BSLeft, leftColor, leftStyle, false); + if (applyBottomInnerClip) + graphicsContext->restore(); + } + } + } + + if (renderRight) { + bool ignore_top = (renderRadii && topRight.height() > 0) || + ((topColor == rightColor) && (topTransparent == rightTransparent) && + (rightStyle >= DOTTED || rightStyle == INSET) && + (topStyle == DOTTED || topStyle == DASHED || topStyle == SOLID || topStyle == OUTSET)); + + bool ignore_bottom = (renderRadii && bottomRight.height() > 0) || + ((bottomColor == rightColor) && (bottomTransparent == rightTransparent) && + (rightStyle >= DOTTED || rightStyle == INSET) && + (bottomStyle == DOTTED || bottomStyle == DASHED || bottomStyle == SOLID || bottomStyle == INSET)); + + int y = ty; + int y2 = ty + h; + if (renderRadii) { + y += topRight.height(); + y2 -= bottomRight.height(); + } + + drawLineForBoxSide(graphicsContext, tx + w - style->borderRightWidth(), y, tx + w, y2, BSRight, rightColor, rightStyle, + ignore_top ? 0 : style->borderTopWidth(), ignore_bottom ? 0 : style->borderBottomWidth()); + + if (renderRadii && (!upperRightBorderStylesMatch || !lowerRightBorderStylesMatch)) { + thickness = style->borderRightWidth() * 2; + + if (!upperRightBorderStylesMatch && topRight.width()) { + int topX = tx + w - topRight.width() * 2; + int topY = ty; + bool applyTopInnerClip = (style->borderRightWidth() < topRight.width()) + && (style->borderTopWidth() < topRight.height()) + && (rightStyle != DOUBLE || style->borderRightWidth() > 6); + if (applyTopInnerClip) { + graphicsContext->save(); + graphicsContext->addInnerRoundedRectClip(IntRect(topX, topY, topRight.width() * 2, topRight.height() * 2), + style->borderRightWidth()); + } + + firstAngleStart = 0; + firstAngleSpan = 45; + + // Draw top right arc + drawArcForBoxSide(graphicsContext, topX, topY, thickness, topRight, firstAngleStart, firstAngleSpan, + BSRight, rightColor, rightStyle, true); + if (applyTopInnerClip) + graphicsContext->restore(); + } + + if (!lowerRightBorderStylesMatch && bottomRight.width()) { + int bottomX = tx + w - bottomRight.width() * 2; + int bottomY = ty + h - bottomRight.height() * 2; + bool applyBottomInnerClip = (style->borderRightWidth() < bottomRight.width()) + && (style->borderBottomWidth() < bottomRight.height()) + && (rightStyle != DOUBLE || style->borderRightWidth() > 6); + if (applyBottomInnerClip) { + graphicsContext->save(); + graphicsContext->addInnerRoundedRectClip(IntRect(bottomX, bottomY, bottomRight.width() * 2, bottomRight.height() * 2), + style->borderRightWidth()); + } + + secondAngleStart = 315; + secondAngleSpan = 45; + + // Draw bottom right arc + drawArcForBoxSide(graphicsContext, bottomX, bottomY, thickness, bottomRight, secondAngleStart, secondAngleSpan, + BSRight, rightColor, rightStyle, false); + if (applyBottomInnerClip) + graphicsContext->restore(); + } + } + } + + if (renderRadii) + graphicsContext->restore(); +} +#endif + +void RenderBoxModelObject::clipBorderSidePolygon(GraphicsContext* graphicsContext, const IntRect& box, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, + const BoxSide side, bool firstEdgeMatches, bool secondEdgeMatches, const RenderStyle* style, + bool includeLogicalLeftEdge, bool includeLogicalRightEdge) +{ + FloatPoint quad[4]; + int tx = box.x(); + int ty = box.y(); + int w = box.width(); + int h = box.height(); + + bool horizontal = style->isHorizontalWritingMode(); + int leftWidth = (!horizontal || includeLogicalLeftEdge) ? style->borderLeftWidth() : 0; + int rightWidth = (!horizontal || includeLogicalRightEdge) ? style->borderRightWidth() : 0; + int topWidth = (horizontal || includeLogicalLeftEdge) ? style->borderTopWidth() : 0; + int bottomWidth = (horizontal || includeLogicalRightEdge) ? style->borderBottomWidth() : 0; + + // For each side, create an array of FloatPoints where each point is based on whichever value in each corner + // is larger -- the radius width/height or the border width/height -- as appropriate. + switch (side) { + case BSTop: + quad[0] = FloatPoint(tx, ty); + quad[1] = FloatPoint(tx + max(topLeft.width(), leftWidth), ty + max(topLeft.height(), topWidth)); + quad[2] = FloatPoint(tx + w - max(topRight.width(), rightWidth), ty + max(topRight.height(), topWidth)); + quad[3] = FloatPoint(tx + w, ty); + break; + case BSLeft: + quad[0] = FloatPoint(tx, ty); + quad[1] = FloatPoint(tx + max(topLeft.width(), leftWidth), ty + max(topLeft.height(), topWidth)); + quad[2] = FloatPoint(tx + max(bottomLeft.width(), leftWidth), ty + h - max(bottomLeft.height(), bottomWidth)); + quad[3] = FloatPoint(tx, ty + h); + break; + case BSBottom: + quad[0] = FloatPoint(tx, ty + h); + quad[1] = FloatPoint(tx + max(bottomLeft.width(), leftWidth), ty + h - max(bottomLeft.height(), bottomWidth)); + quad[2] = FloatPoint(tx + w - max(bottomRight.width(), rightWidth), ty + h - max(bottomRight.height(), bottomWidth)); + quad[3] = FloatPoint(tx + w, ty + h); + break; + case BSRight: + quad[0] = FloatPoint(tx + w, ty); + quad[1] = FloatPoint(tx + w - max(topRight.width(), rightWidth), ty + max(topRight.height(), topWidth)); + quad[2] = FloatPoint(tx + w - max(bottomRight.width(), rightWidth), ty + h - max(bottomRight.height(), bottomWidth)); + quad[3] = FloatPoint(tx + w, ty + h); + break; + default: + break; + } + + // If the border matches both of its adjacent sides, don't anti-alias the clip, and + // if neither side matches, anti-alias the clip. + if (firstEdgeMatches == secondEdgeMatches) { + graphicsContext->clipConvexPolygon(4, quad, !firstEdgeMatches); + return; + } + + FloatPoint firstQuad[4]; + firstQuad[0] = quad[0]; + firstQuad[1] = quad[1]; + firstQuad[2] = side == BSTop || side == BSBottom ? FloatPoint(quad[3].x(), quad[2].y()) + : FloatPoint(quad[2].x(), quad[3].y()); + firstQuad[3] = quad[3]; + graphicsContext->clipConvexPolygon(4, firstQuad, !firstEdgeMatches); + + FloatPoint secondQuad[4]; + secondQuad[0] = quad[0]; + secondQuad[1] = side == BSTop || side == BSBottom ? FloatPoint(quad[0].x(), quad[1].y()) + : FloatPoint(quad[1].x(), quad[0].y()); + secondQuad[2] = quad[2]; + secondQuad[3] = quad[3]; + graphicsContext->clipConvexPolygon(4, secondQuad, !secondEdgeMatches); +} + +static inline void uniformlyExpandBorderRadii(int delta, IntSize& topLeft, IntSize& topRight, IntSize& bottomLeft, IntSize& bottomRight) +{ + topLeft.expand(delta, delta); + topLeft.clampNegativeToZero(); + topRight.expand(delta, delta); + topRight.clampNegativeToZero(); + bottomLeft.expand(delta, delta); + bottomLeft.clampNegativeToZero(); + bottomRight.expand(delta, delta); + bottomRight.clampNegativeToZero(); +} + +void RenderBoxModelObject::paintBoxShadow(GraphicsContext* context, int tx, int ty, int w, int h, const RenderStyle* s, ShadowStyle shadowStyle, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) +{ + // FIXME: Deal with border-image. Would be great to use border-image as a mask. + + if (context->paintingDisabled()) + return; + + IntRect rect(tx, ty, w, h); + IntSize topLeft; + IntSize topRight; + IntSize bottomLeft; + IntSize bottomRight; + + bool hasBorderRadius = s->hasBorderRadius(); + bool isHorizontal = s->isHorizontalWritingMode(); + if (hasBorderRadius && (includeLogicalLeftEdge || includeLogicalRightEdge)) { + IntSize topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius; + s->getBorderRadiiForRect(rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); + + if (includeLogicalLeftEdge) { + if (shadowStyle == Inset) { + topLeftRadius.expand(-borderLeft(), -borderTop()); + topLeftRadius.clampNegativeToZero(); + if (isHorizontal) { + bottomLeftRadius.expand(-borderLeft(), -borderBottom()); + bottomLeftRadius.clampNegativeToZero(); + } else { + topRightRadius.expand(-borderRight(), -borderTop()); + topRightRadius.clampNegativeToZero(); + } + } + topLeft = topLeftRadius; + if (isHorizontal) + bottomLeft = bottomLeftRadius; + else + topRight = topRightRadius; + } + if (includeLogicalRightEdge) { + if (shadowStyle == Inset) { + if (isHorizontal) { + topRightRadius.expand(-borderRight(), -borderTop()); + topRightRadius.clampNegativeToZero(); + } else { + bottomLeftRadius.expand(-borderLeft(), -borderBottom()); + bottomLeftRadius.clampNegativeToZero(); + } + bottomRightRadius.expand(-borderRight(), -borderBottom()); + bottomRightRadius.clampNegativeToZero(); + } + if (isHorizontal) + topRight = topRightRadius; + else + bottomLeft = bottomLeftRadius; + bottomRight = bottomRightRadius; + } + } + + if (shadowStyle == Inset) { + rect.move(includeLogicalLeftEdge || !isHorizontal ? borderLeft() : 0, includeLogicalLeftEdge || isHorizontal ? borderTop() : 0); + rect.setWidth(rect.width() - ((includeLogicalLeftEdge || !isHorizontal) ? borderLeft() : 0) - ((includeLogicalRightEdge || !isHorizontal) ? borderRight() : 0)); + rect.setHeight(rect.height() - ((includeLogicalLeftEdge || isHorizontal) ? borderTop() : 0) - ((includeLogicalRightEdge || isHorizontal) ? borderBottom() : 0)); + } + + bool hasOpaqueBackground = s->visitedDependentColor(CSSPropertyBackgroundColor).isValid() && s->visitedDependentColor(CSSPropertyBackgroundColor).alpha() == 255; + for (const ShadowData* shadow = s->boxShadow(); shadow; shadow = shadow->next()) { + if (shadow->style() != shadowStyle) + continue; + + IntSize shadowOffset(shadow->x(), shadow->y()); + int shadowBlur = shadow->blur(); + int shadowSpread = shadow->spread(); + const Color& shadowColor = shadow->color(); + + if (shadow->style() == Normal) { + IntRect fillRect(rect); + fillRect.inflate(shadowSpread); + if (fillRect.isEmpty()) + continue; + + IntRect shadowRect(rect); + shadowRect.inflate(shadowBlur + shadowSpread); + shadowRect.move(shadowOffset); + + context->save(); + context->clip(shadowRect); + + // Move the fill just outside the clip, adding 1 pixel separation so that the fill does not + // bleed in (due to antialiasing) if the context is transformed. + IntSize extraOffset(w + max(0, shadowOffset.width()) + shadowBlur + 2 * shadowSpread + 1, 0); + shadowOffset -= extraOffset; + fillRect.move(extraOffset); + + context->setShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace()); + if (hasBorderRadius) { + IntRect rectToClipOut = rect; + IntSize topLeftToClipOut = topLeft; + IntSize topRightToClipOut = topRight; + IntSize bottomLeftToClipOut = bottomLeft; + IntSize bottomRightToClipOut = bottomRight; + + IntSize topLeftToFill = topLeft; + IntSize topRightToFill = topRight; + IntSize bottomLeftToFill = bottomLeft; + IntSize bottomRightToFill = bottomRight; + if (shadowSpread < 0) + uniformlyExpandBorderRadii(shadowSpread, topLeftToFill, topRightToFill, bottomLeftToFill, bottomRightToFill); + + // If the box is opaque, it is unnecessary to clip it out. However, doing so saves time + // when painting the shadow. On the other hand, it introduces subpixel gaps along the + // corners. Those are avoided by insetting the clipping path by one pixel. + if (hasOpaqueBackground) { + rectToClipOut.inflate(-1); + uniformlyExpandBorderRadii(-1, topLeftToClipOut, topRightToClipOut, bottomLeftToClipOut, bottomRightToClipOut); + } + + if (!rectToClipOut.isEmpty()) + context->clipOutRoundedRect(rectToClipOut, topLeftToClipOut, topRightToClipOut, bottomLeftToClipOut, bottomRightToClipOut); + context->fillRoundedRect(fillRect, topLeftToFill, topRightToFill, bottomLeftToFill, bottomRightToFill, Color::black, s->colorSpace()); + } else { + IntRect rectToClipOut = rect; + + // If the box is opaque, it is unnecessary to clip it out. However, doing so saves time + // when painting the shadow. On the other hand, it introduces subpixel gaps along the + // edges if they are not pixel-aligned. Those are avoided by insetting the clipping path + // by one pixel. + if (hasOpaqueBackground) { + AffineTransform currentTransformation = context->getCTM(); + if (currentTransformation.a() != 1 || (currentTransformation.d() != 1 && currentTransformation.d() != -1) + || currentTransformation.b() || currentTransformation.c()) + rectToClipOut.inflate(-1); + } + + if (!rectToClipOut.isEmpty()) + context->clipOut(rectToClipOut); + context->fillRect(fillRect, Color::black, s->colorSpace()); + } + + context->restore(); + } else { + // Inset shadow. + IntRect holeRect(rect); + holeRect.inflate(-shadowSpread); + + if (holeRect.isEmpty()) { + if (hasBorderRadius) + context->fillRoundedRect(rect, topLeft, topRight, bottomLeft, bottomRight, shadowColor, s->colorSpace()); + else + context->fillRect(rect, shadowColor, s->colorSpace()); + continue; + } + + if (!includeLogicalLeftEdge) { + if (isHorizontal) { + holeRect.move(-max(shadowOffset.width(), 0) - shadowBlur, 0); + holeRect.setWidth(holeRect.width() + max(shadowOffset.width(), 0) + shadowBlur); + } else { + holeRect.move(0, -max(shadowOffset.height(), 0) - shadowBlur); + holeRect.setHeight(holeRect.height() + max(shadowOffset.height(), 0) + shadowBlur); + } + } + if (!includeLogicalRightEdge) { + if (isHorizontal) + holeRect.setWidth(holeRect.width() - min(shadowOffset.width(), 0) + shadowBlur); + else + holeRect.setHeight(holeRect.height() - min(shadowOffset.height(), 0) + shadowBlur); + } + + Color fillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), 255); + + IntRect outerRect(rect); + outerRect.inflateX(w - 2 * shadowSpread); + outerRect.inflateY(h - 2 * shadowSpread); + + context->save(); + + Path path; + if (hasBorderRadius) { + path.addRoundedRect(rect, topLeft, topRight, bottomLeft, bottomRight); + context->clip(path); + path.clear(); + } else + context->clip(rect); + + IntSize extraOffset(2 * w + max(0, shadowOffset.width()) + shadowBlur - 2 * shadowSpread + 1, 0); + context->translate(extraOffset.width(), extraOffset.height()); + shadowOffset -= extraOffset; + + path.addRect(outerRect); + + if (hasBorderRadius) { + if (shadowSpread > 0) + uniformlyExpandBorderRadii(-shadowSpread, topLeft, topRight, bottomLeft, bottomRight); + path.addRoundedRect(holeRect, topLeft, topRight, bottomLeft, bottomRight); + } else + path.addRect(holeRect); + + context->setFillRule(RULE_EVENODD); + context->setFillColor(fillColor, s->colorSpace()); + context->setShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace()); + context->fillPath(path); + + context->restore(); + } + } +} + +int RenderBoxModelObject::containingBlockLogicalWidthForContent() const +{ + return containingBlock()->availableLogicalWidth(); +} + +RenderBoxModelObject* RenderBoxModelObject::continuation() const +{ + if (!continuationMap) + return 0; + return continuationMap->get(this); +} + +void RenderBoxModelObject::setContinuation(RenderBoxModelObject* continuation) +{ + if (continuation) { + if (!continuationMap) + continuationMap = new ContinuationMap; + continuationMap->set(this, continuation); + } else { + if (continuationMap) + continuationMap->remove(this); + } +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderBoxModelObject.h b/Source/WebCore/rendering/RenderBoxModelObject.h new file mode 100644 index 0000000..98e386b --- /dev/null +++ b/Source/WebCore/rendering/RenderBoxModelObject.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2003, 2006, 2007, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderBoxModelObject_h +#define RenderBoxModelObject_h + +#include "RenderObject.h" + +namespace WebCore { + +// Modes for some of the line-related functions. +enum LinePositionMode { PositionOnContainingLine, PositionOfInteriorLineBoxes }; +enum LineDirectionMode { HorizontalLine, VerticalLine }; + +// This class is the base for all objects that adhere to the CSS box model as described +// at http://www.w3.org/TR/CSS21/box.html + +class RenderBoxModelObject : public RenderObject { +public: + RenderBoxModelObject(Node*); + virtual ~RenderBoxModelObject(); + + virtual void destroy(); + + int relativePositionOffsetX() const; + int relativePositionOffsetY() const; + IntSize relativePositionOffset() const { return IntSize(relativePositionOffsetX(), relativePositionOffsetY()); } + IntSize relativePositionLogicalOffset() const { return style()->isHorizontalWritingMode() ? relativePositionOffset() : relativePositionOffset().transposedSize(); } + + // IE extensions. Used to calculate offsetWidth/Height. Overridden by inlines (RenderFlow) + // to return the remaining width on a given line (and the height of a single line). + virtual int offsetLeft() const; + virtual int offsetTop() const; + virtual int offsetWidth() const = 0; + virtual int offsetHeight() const = 0; + + virtual void styleWillChange(StyleDifference, const RenderStyle* newStyle); + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + virtual void updateBoxModelInfoFromStyle(); + + bool hasSelfPaintingLayer() const; + RenderLayer* layer() const { return m_layer; } + virtual bool requiresLayer() const { return isRoot() || isPositioned() || isRelPositioned() || isTransparent() || hasOverflowClip() || hasTransform() || hasMask() || hasReflection() || style()->specifiesColumns(); } + + // This will work on inlines to return the bounding box of all of the lines' border boxes. + virtual IntRect borderBoundingBox() const = 0; + + // Virtual since table cells override + virtual int paddingTop(bool includeIntrinsicPadding = true) const; + virtual int paddingBottom(bool includeIntrinsicPadding = true) const; + virtual int paddingLeft(bool includeIntrinsicPadding = true) const; + virtual int paddingRight(bool includeIntrinsicPadding = true) const; + virtual int paddingBefore(bool includeIntrinsicPadding = true) const; + virtual int paddingAfter(bool includeIntrinsicPadding = true) const; + virtual int paddingStart(bool includeIntrinsicPadding = true) const; + virtual int paddingEnd(bool includeIntrinsicPadding = true) const; + + virtual int borderTop() const { return style()->borderTopWidth(); } + virtual int borderBottom() const { return style()->borderBottomWidth(); } + virtual int borderLeft() const { return style()->borderLeftWidth(); } + virtual int borderRight() const { return style()->borderRightWidth(); } + virtual int borderBefore() const { return style()->borderBeforeWidth(); } + virtual int borderAfter() const { return style()->borderAfterWidth(); } + virtual int borderStart() const { return style()->borderStartWidth(); } + virtual int borderEnd() const { return style()->borderEndWidth(); } + + int borderAndPaddingHeight() const { return borderTop() + borderBottom() + paddingTop() + paddingBottom(); } + int borderAndPaddingWidth() const { return borderLeft() + borderRight() + paddingLeft() + paddingRight(); } + int borderAndPaddingLogicalHeight() const { return borderBefore() + borderAfter() + paddingBefore() + paddingAfter(); } + int borderAndPaddingLogicalWidth() const { return borderStart() + borderEnd() + paddingStart() + paddingEnd(); } + + virtual int marginTop() const = 0; + virtual int marginBottom() const = 0; + virtual int marginLeft() const = 0; + virtual int marginRight() const = 0; + virtual int marginBefore() const = 0; + virtual int marginAfter() const = 0; + virtual int marginStart() const = 0; + virtual int marginEnd() const = 0; + + bool hasInlineDirectionBordersPaddingOrMargin() const { return hasInlineDirectionBordersOrPadding() || marginStart()|| marginEnd(); } + bool hasInlineDirectionBordersOrPadding() const { return borderStart() || borderEnd() || paddingStart()|| paddingEnd(); } + + virtual int containingBlockLogicalWidthForContent() const; + + virtual void childBecameNonInline(RenderObject* /*child*/) { } + + void paintBorder(GraphicsContext*, int tx, int ty, int w, int h, const RenderStyle*, bool includeLogicalLeftEdge = true, bool includeLogicalRightEdge = true); + bool paintNinePieceImage(GraphicsContext*, int tx, int ty, int w, int h, const RenderStyle*, const NinePieceImage&, CompositeOperator = CompositeSourceOver); + void paintBoxShadow(GraphicsContext*, int tx, int ty, int w, int h, const RenderStyle*, ShadowStyle, bool includeLogicalLeftEdge = true, bool includeLogicalRightEdge = true); + void paintFillLayerExtended(const PaintInfo&, const Color&, const FillLayer*, int tx, int ty, int width, int height, InlineFlowBox* = 0, CompositeOperator = CompositeSourceOver, RenderObject* backgroundObject = 0); + + // Overridden by subclasses to determine line height and baseline position. + virtual int lineHeight(bool firstLine, LineDirectionMode, LinePositionMode = PositionOnContainingLine) const = 0; + virtual int baselinePosition(FontBaseline, bool firstLine, LineDirectionMode, LinePositionMode = PositionOnContainingLine) const = 0; + + // Called by RenderObject::destroy() (and RenderWidget::destroy()) and is the only way layers should ever be destroyed + void destroyLayer(); + + void highQualityRepaintTimerFired(Timer<RenderBoxModelObject>*); + + virtual void setSelectionState(SelectionState s); +protected: + void calculateBackgroundImageGeometry(const FillLayer*, int tx, int ty, int w, int h, IntRect& destRect, IntPoint& phase, IntSize& tileSize); + + bool shouldPaintAtLowQuality(GraphicsContext*, Image*, const void*, const IntSize&); + + RenderBoxModelObject* continuation() const; + void setContinuation(RenderBoxModelObject*); + +private: + virtual bool isBoxModelObject() const { return true; } + + IntSize calculateFillTileSize(const FillLayer*, IntSize scaledSize) const; + + void clipBorderSidePolygon(GraphicsContext*, const IntRect&, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, + const IntSize& bottomRight, const BoxSide side, bool firstEdgeMatches, bool secondEdgeMatches, const RenderStyle* style, + bool includeLogicalLeftEdge, bool includeLogicalRightEdge); + + friend class RenderView; + + RenderLayer* m_layer; + + // Used to store state between styleWillChange and styleDidChange + static bool s_wasFloating; + static bool s_hadLayer; + static bool s_layerWasSelfPainting; +}; + +inline RenderBoxModelObject* toRenderBoxModelObject(RenderObject* object) +{ + ASSERT(!object || object->isBoxModelObject()); + return static_cast<RenderBoxModelObject*>(object); +} + +inline const RenderBoxModelObject* toRenderBoxModelObject(const RenderObject* object) +{ + ASSERT(!object || object->isBoxModelObject()); + return static_cast<const RenderBoxModelObject*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderBoxModelObject(const RenderBoxModelObject*); + +} // namespace WebCore + +#endif // RenderBoxModelObject_h diff --git a/Source/WebCore/rendering/RenderButton.cpp b/Source/WebCore/rendering/RenderButton.cpp new file mode 100644 index 0000000..2642f23 --- /dev/null +++ b/Source/WebCore/rendering/RenderButton.cpp @@ -0,0 +1,197 @@ +/** + * Copyright (C) 2005 Apple Computer, Inc. + * + * 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 "RenderButton.h" + +#include "Document.h" +#include "GraphicsContext.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "RenderTextFragment.h" +#include "RenderTheme.h" + +#if ENABLE(WML) +#include "WMLDoElement.h" +#include "WMLNames.h" +#endif + +namespace WebCore { + +using namespace HTMLNames; + +RenderButton::RenderButton(Node* node) + : RenderFlexibleBox(node) + , m_buttonText(0) + , m_inner(0) + , m_default(false) +{ +} + +RenderButton::~RenderButton() +{ +} + +void RenderButton::addChild(RenderObject* newChild, RenderObject* beforeChild) +{ + if (!m_inner) { + // Create an anonymous block. + ASSERT(!firstChild()); + bool isFlexibleBox = style()->display() == BOX || style()->display() == INLINE_BOX; + m_inner = createAnonymousBlock(isFlexibleBox); + setupInnerStyle(m_inner->style()); + RenderFlexibleBox::addChild(m_inner); + } + + m_inner->addChild(newChild, beforeChild); +} + +void RenderButton::removeChild(RenderObject* oldChild) +{ + if (oldChild == m_inner || !m_inner) { + RenderFlexibleBox::removeChild(oldChild); + m_inner = 0; + } else + m_inner->removeChild(oldChild); +} + +void RenderButton::styleWillChange(StyleDifference diff, const RenderStyle* newStyle) +{ + if (m_inner) { + // RenderBlock::setStyle is going to apply a new style to the inner block, which + // will have the initial box flex value, 0. The current value is 1, because we set + // it right below. Here we change it back to 0 to avoid getting a spurious layout hint + // because of the difference. + m_inner->style()->setBoxFlex(0); + } + RenderBlock::styleWillChange(diff, newStyle); +} + +void RenderButton::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBlock::styleDidChange(diff, oldStyle); + + if (m_buttonText) + m_buttonText->setStyle(style()); + if (m_inner) // RenderBlock handled updating the anonymous block's style. + setupInnerStyle(m_inner->style()); + setReplaced(isInline()); + + if (!m_default && theme()->isDefault(this)) { + if (!m_timer) + m_timer.set(new Timer<RenderButton>(this, &RenderButton::timerFired)); + m_timer->startRepeating(0.03); + m_default = true; + } else if (m_default && !theme()->isDefault(this)) { + m_default = false; + m_timer.clear(); + } +} + +void RenderButton::setupInnerStyle(RenderStyle* innerStyle) +{ + ASSERT(innerStyle->refCount() == 1); + // RenderBlock::createAnonymousBlock creates a new RenderStyle, so this is + // safe to modify. + innerStyle->setBoxFlex(1.0f); + innerStyle->setBoxOrient(style()->boxOrient()); +} + +void RenderButton::updateFromElement() +{ + // If we're an input element, we may need to change our button text. + if (node()->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(node()); + String value = input->valueWithDefault(); + setText(value); + } + + +#if ENABLE(WML) + else if (node()->hasTagName(WMLNames::doTag)) { + WMLDoElement* doElement = static_cast<WMLDoElement*>(node()); + + String value = doElement->label(); + if (value.isEmpty()) + value = doElement->name(); + + setText(value); + } +#endif +} + +bool RenderButton::canHaveChildren() const +{ + // Input elements can't have children, but button elements can. We'll + // write the code assuming any other button types that might emerge in the future + // can also have children. + return !node()->hasTagName(inputTag); +} + +void RenderButton::setText(const String& str) +{ + if (str.isEmpty()) { + if (m_buttonText) { + m_buttonText->destroy(); + m_buttonText = 0; + } + } else { + if (m_buttonText) + m_buttonText->setText(str.impl()); + else { + m_buttonText = new (renderArena()) RenderTextFragment(document(), str.impl()); + m_buttonText->setStyle(style()); + addChild(m_buttonText); + } + } +} + +String RenderButton::text() const +{ + return m_buttonText ? m_buttonText->text() : 0; +} + +void RenderButton::updateBeforeAfterContent(PseudoId type) +{ + if (m_inner) + m_inner->children()->updateBeforeAfterContent(m_inner, type, this); + else + children()->updateBeforeAfterContent(this, type); +} + +IntRect RenderButton::controlClipRect(int tx, int ty) const +{ + // Clip to the padding box to at least give content the extra padding space. + return IntRect(tx + borderLeft(), ty + borderTop(), width() - borderLeft() - borderRight(), height() - borderTop() - borderBottom()); +} + +void RenderButton::timerFired(Timer<RenderButton>*) +{ + // FIXME Bug 25110: Ideally we would stop our timer when our Document + // enters the page cache. But we currently have no way of being notified + // when that happens, so we'll just ignore the timer firing as long as + // we're in the cache. + if (document()->inPageCache()) + return; + + repaint(); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderButton.h b/Source/WebCore/rendering/RenderButton.h new file mode 100644 index 0000000..252edb4 --- /dev/null +++ b/Source/WebCore/rendering/RenderButton.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2005 Apple Computer + * + * 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. + * + */ + +#ifndef RenderButton_h +#define RenderButton_h + +#include "RenderFlexibleBox.h" +#include "Timer.h" +#include <wtf/OwnPtr.h> + +namespace WebCore { + +class RenderTextFragment; + +// RenderButtons are just like normal flexboxes except that they will generate an anonymous block child. +// For inputs, they will also generate an anonymous RenderText and keep its style and content up +// to date as the button changes. +class RenderButton : public RenderFlexibleBox { +public: + explicit RenderButton(Node*); + virtual ~RenderButton(); + + virtual const char* renderName() const { return "RenderButton"; } + virtual bool isRenderButton() const { return true; } + + virtual void addChild(RenderObject* newChild, RenderObject *beforeChild = 0); + virtual void removeChild(RenderObject*); + virtual void removeLeftoverAnonymousBlock(RenderBlock*) { } + virtual bool createsAnonymousWrapper() const { return true; } + + void setupInnerStyle(RenderStyle*); + virtual void updateFromElement(); + + virtual void updateBeforeAfterContent(PseudoId); + + virtual bool hasControlClip() const { return true; } + virtual IntRect controlClipRect(int /*tx*/, int /*ty*/) const; + + void setText(const String&); + String text() const; + + virtual bool canHaveChildren() const; + +private: + virtual void styleWillChange(StyleDifference, const RenderStyle* newStyle); + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + virtual bool hasLineIfEmpty() const { return true; } + + virtual bool requiresForcedStyleRecalcPropagation() const { return true; } + + void timerFired(Timer<RenderButton>*); + + RenderTextFragment* m_buttonText; + RenderBlock* m_inner; + + OwnPtr<Timer<RenderButton> > m_timer; + bool m_default; +}; + +inline RenderButton* toRenderButton(RenderObject* object) +{ + ASSERT(!object || object->isRenderButton()); + return static_cast<RenderButton*>(object); +} + +inline const RenderButton* toRenderButton(const RenderObject* object) +{ + ASSERT(!object || object->isRenderButton()); + return static_cast<const RenderButton*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderButton(const RenderButton*); + +} // namespace WebCore + +#endif // RenderButton_h diff --git a/Source/WebCore/rendering/RenderCounter.cpp b/Source/WebCore/rendering/RenderCounter.cpp new file mode 100644 index 0000000..57c54f8 --- /dev/null +++ b/Source/WebCore/rendering/RenderCounter.cpp @@ -0,0 +1,496 @@ +/** + * Copyright (C) 2004 Allan Sandfeld Jensen (kde@carewolf.com) + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * 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 "RenderCounter.h" + +#include "CounterNode.h" +#include "Document.h" +#include "HTMLNames.h" +#include "HTMLOListElement.h" +#include "RenderListItem.h" +#include "RenderListMarker.h" +#include "RenderStyle.h" +#include <wtf/StdLibExtras.h> + +namespace WebCore { + +using namespace HTMLNames; + +typedef HashMap<RefPtr<AtomicStringImpl>, RefPtr<CounterNode> > CounterMap; +typedef HashMap<const RenderObject*, CounterMap*> CounterMaps; + +static CounterNode* makeCounterNode(RenderObject*, const AtomicString& identifier, bool alwaysCreateCounter); + +static CounterMaps& counterMaps() +{ + DEFINE_STATIC_LOCAL(CounterMaps, staticCounterMaps, ()); + return staticCounterMaps; +} + +static inline RenderObject* previousSiblingOrParent(RenderObject* object) +{ + if (RenderObject* sibling = object->previousSibling()) + return sibling; + return object->parent(); +} + +static bool planCounter(RenderObject* object, const AtomicString& identifier, bool& isReset, int& value) +{ + ASSERT(object); + + // Real text nodes don't have their own style so they can't have counters. + // We can't even look at their styles or we'll see extra resets and increments! + if (object->isText() && !object->isBR()) + return false; + + RenderStyle* style = object->style(); + ASSERT(style); + + if (const CounterDirectiveMap* directivesMap = style->counterDirectives()) { + CounterDirectives directives = directivesMap->get(identifier.impl()); + if (directives.m_reset) { + value = directives.m_resetValue; + if (directives.m_increment) + value += directives.m_incrementValue; + isReset = true; + return true; + } + if (directives.m_increment) { + value = directives.m_incrementValue; + isReset = false; + return true; + } + } + + if (identifier == "list-item") { + if (object->isListItem()) { + if (toRenderListItem(object)->hasExplicitValue()) { + value = toRenderListItem(object)->explicitValue(); + isReset = true; + return true; + } + value = 1; + isReset = false; + return true; + } + if (Node* e = object->node()) { + if (e->hasTagName(olTag)) { + value = static_cast<HTMLOListElement*>(e)->start(); + isReset = true; + return true; + } + if (e->hasTagName(ulTag) || e->hasTagName(menuTag) || e->hasTagName(dirTag)) { + value = 0; + isReset = true; + return true; + } + } + } + + return false; +} + +// - Finds the insertion point for the counter described by counterOwner, isReset and +// identifier in the CounterNode tree for identifier and sets parent and +// previousSibling accordingly. +// - The function returns true if the counter whose insertion point is searched is NOT +// the root of the tree. +// - The root of the tree is a counter reference that is not in the scope of any other +// counter with the same identifier. +// - All the counter references with the same identifier as this one that are in +// children or subsequent siblings of the renderer that owns the root of the tree +// form the rest of of the nodes of the tree. +// - The root of the tree is always a reset type reference. +// - A subtree rooted at any reset node in the tree is equivalent to all counter +// references that are in the scope of the counter or nested counter defined by that +// reset node. +// - Non-reset CounterNodes cannot have descendants. + +static bool findPlaceForCounter(RenderObject* counterOwner, const AtomicString& identifier, bool isReset, CounterNode*& parent, CounterNode*& previousSibling) +{ + // We cannot stop searching for counters with the same identifier before we also + // check this renderer, because it may affect the positioning in the tree of our counter. + RenderObject* searchEndRenderer = previousSiblingOrParent(counterOwner); + // We check renderers in preOrder from the renderer that our counter is attached to + // towards the begining of the document for counters with the same identifier as the one + // we are trying to find a place for. This is the next renderer to be checked. + RenderObject* currentRenderer = counterOwner->previousInPreOrder(); + previousSibling = 0; + while (currentRenderer) { + // A sibling without a parent means that the counter node tree was not constructed correctly so we stop + // traversing. In the future RenderCounter should handle RenderObjects that are not connected to the + // render tree at counter node creation. See bug 43812. + if (previousSibling && !previousSibling->parent()) + return false; + CounterNode* currentCounter = makeCounterNode(currentRenderer, identifier, false); + if (searchEndRenderer == currentRenderer) { + // We may be at the end of our search. + if (currentCounter) { + // We have a suitable counter on the EndSearchRenderer. + if (previousSibling) { // But we already found another counter that we come after. + if (currentCounter->actsAsReset()) { + // We found a reset counter that is on a renderer that is a sibling of ours or a parent. + if (isReset && currentRenderer->parent() == counterOwner->parent()) { + // We are also a reset counter and the previous reset was on a sibling renderer + // hence we are the next sibling of that counter if that reset is not a root or + // we are a root node if that reset is a root. + parent = currentCounter->parent(); + previousSibling = parent ? currentCounter : 0; + return parent; + } + // We are not a reset node or the previous reset must be on an ancestor of our renderer + // hence we must be a child of that reset counter. + parent = currentCounter; + ASSERT(previousSibling->parent() == currentCounter); + return true; + } + // CurrentCounter, the counter at the EndSearchRenderer, is not reset. + if (!isReset || currentRenderer->parent() != counterOwner->parent()) { + // If the node we are placing is not reset or we have found a counter that is attached + // to an ancestor of the placed counter's renderer we know we are a sibling of that node. + ASSERT(currentCounter->parent() == previousSibling->parent()); + parent = currentCounter->parent(); + return true; + } + } else { + // We are at the potential end of the search, but we had no previous sibling candidate + // In this case we follow pretty much the same logic as above but no ASSERTs about + // previousSibling, and when we are a sibling of the end counter we must set previousSibling + // to currentCounter. + if (currentCounter->actsAsReset()) { + if (isReset && currentRenderer->parent() == counterOwner->parent()) { + parent = currentCounter->parent(); + previousSibling = currentCounter; + return parent; + } + parent = currentCounter; + return true; + } + if (!isReset || currentRenderer->parent() != counterOwner->parent()) { + parent = currentCounter->parent(); + previousSibling = currentCounter; + return true; + } + previousSibling = currentCounter; + } + } + // We come here if the previous sibling or parent of our renderer had no + // good counter, or we are a reset node and the counter on the previous sibling + // of our renderer was not a reset counter. + // Set a new goal for the end of the search. + searchEndRenderer = previousSiblingOrParent(currentRenderer); + } else { + // We are searching descendants of a previous sibling of the renderer that the + // counter being placed is attached to. + if (currentCounter) { + // We found a suitable counter. + if (previousSibling) { + // Since we had a suitable previous counter before, we should only consider this one as our + // previousSibling if it is a reset counter and hence the current previousSibling is its child. + if (currentCounter->actsAsReset()) { + previousSibling = currentCounter; + // We are no longer interested in previous siblings of the currentRenderer or their children + // as counters they may have attached cannot be the previous sibling of the counter we are placing. + currentRenderer = currentRenderer->parent(); + continue; + } + } else + previousSibling = currentCounter; + currentRenderer = previousSiblingOrParent(currentRenderer); + continue; + } + } + // This function is designed so that the same test is not done twice in an iteration, except for this one + // which may be done twice in some cases. Rearranging the decision points though, to accommodate this + // performance improvement would create more code duplication than is worthwhile in my oppinion and may further + // impede the readability of this already complex algorithm. + if (previousSibling) + currentRenderer = previousSiblingOrParent(currentRenderer); + else + currentRenderer = currentRenderer->previousInPreOrder(); + } + return false; +} + +static CounterNode* makeCounterNode(RenderObject* object, const AtomicString& identifier, bool alwaysCreateCounter) +{ + ASSERT(object); + + if (object->m_hasCounterNodeMap) { + if (CounterMap* nodeMap = counterMaps().get(object)) { + if (CounterNode* node = nodeMap->get(identifier.impl()).get()) + return node; + } + } + + bool isReset = false; + int value = 0; + if (!planCounter(object, identifier, isReset, value) && !alwaysCreateCounter) + return 0; + + CounterNode* newParent = 0; + CounterNode* newPreviousSibling = 0; + RefPtr<CounterNode> newNode = CounterNode::create(object, isReset, value); + if (findPlaceForCounter(object, identifier, isReset, newParent, newPreviousSibling)) + newParent->insertAfter(newNode.get(), newPreviousSibling, identifier); + CounterMap* nodeMap; + if (object->m_hasCounterNodeMap) + nodeMap = counterMaps().get(object); + else { + nodeMap = new CounterMap; + counterMaps().set(object, nodeMap); + object->m_hasCounterNodeMap = true; + } + nodeMap->set(identifier.impl(), newNode); + if (newNode->parent() || !object->nextInPreOrder(object->parent())) + return newNode.get(); + // Checking if some nodes that were previously counter tree root nodes + // should become children of this node now. + CounterMaps& maps = counterMaps(); + RenderObject* stayWithin = object->parent(); + for (RenderObject* currentRenderer = object->nextInPreOrder(stayWithin); currentRenderer; currentRenderer = currentRenderer->nextInPreOrder(stayWithin)) { + if (!currentRenderer->m_hasCounterNodeMap) + continue; + CounterNode* currentCounter = maps.get(currentRenderer)->get(identifier.impl()).get(); + if (!currentCounter) + continue; + if (currentCounter->parent()) { + ASSERT(newNode->firstChild()); + if (currentRenderer->lastChild()) + currentRenderer = currentRenderer->lastChild(); + continue; + } + if (stayWithin != currentRenderer->parent() || !currentCounter->hasResetType()) + newNode->insertAfter(currentCounter, newNode->lastChild(), identifier); + if (currentRenderer->lastChild()) + currentRenderer = currentRenderer->lastChild(); + } + return newNode.get(); +} + +RenderCounter::RenderCounter(Document* node, const CounterContent& counter) + : RenderText(node, StringImpl::empty()) + , m_counter(counter) + , m_counterNode(0) +{ +} + +RenderCounter::~RenderCounter() +{ +} + +const char* RenderCounter::renderName() const +{ + return "RenderCounter"; +} + +bool RenderCounter::isCounter() const +{ + return true; +} + +PassRefPtr<StringImpl> RenderCounter::originalText() const +{ + if (!parent()) + return 0; + + if (!m_counterNode) + m_counterNode = makeCounterNode(parent(), m_counter.identifier(), true); + + CounterNode* child = m_counterNode; + int value = child->actsAsReset() ? child->value() : child->countInParent(); + + String text = listMarkerText(m_counter.listStyle(), value); + + if (!m_counter.separator().isNull()) { + if (!child->actsAsReset()) + child = child->parent(); + while (CounterNode* parent = child->parent()) { + text = listMarkerText(m_counter.listStyle(), child->countInParent()) + + m_counter.separator() + text; + child = parent; + } + } + + return text.impl(); +} + +void RenderCounter::computePreferredLogicalWidths(int lead) +{ + setTextInternal(originalText()); + RenderText::computePreferredLogicalWidths(lead); +} + +void RenderCounter::invalidate(const AtomicString& identifier) +{ + if (m_counter.identifier() != identifier) + return; + m_counterNode = 0; + setNeedsLayoutAndPrefWidthsRecalc(); +} + +static void destroyCounterNodeWithoutMapRemoval(const AtomicString& identifier, CounterNode* node) +{ + CounterNode* previous; + for (RefPtr<CounterNode> child = node->lastDescendant(); child && child != node; child = previous) { + previous = child->previousInPreOrder(); + child->parent()->removeChild(child.get(), identifier); + ASSERT(counterMaps().get(child->renderer())->get(identifier.impl()) == child); + counterMaps().get(child->renderer())->remove(identifier.impl()); + if (!child->renderer()->documentBeingDestroyed()) { + RenderObjectChildList* children = child->renderer()->virtualChildren(); + if (children) + children->invalidateCounters(child->renderer(), identifier); + } + } + RenderObject* renderer = node->renderer(); + if (!renderer->documentBeingDestroyed()) { + if (RenderObjectChildList* children = renderer->virtualChildren()) + children->invalidateCounters(renderer, identifier); + } + if (CounterNode* parent = node->parent()) + parent->removeChild(node, identifier); +} + +void RenderCounter::destroyCounterNodes(RenderObject* renderer) +{ + CounterMaps& maps = counterMaps(); + CounterMaps::iterator mapsIterator = maps.find(renderer); + if (mapsIterator == maps.end()) + return; + CounterMap* map = mapsIterator->second; + CounterMap::const_iterator end = map->end(); + for (CounterMap::const_iterator it = map->begin(); it != end; ++it) { + AtomicString identifier(it->first.get()); + destroyCounterNodeWithoutMapRemoval(identifier, it->second.get()); + } + maps.remove(mapsIterator); + delete map; + renderer->m_hasCounterNodeMap = false; +} + +void RenderCounter::destroyCounterNode(RenderObject* renderer, const AtomicString& identifier) +{ + CounterMap* map = counterMaps().get(renderer); + if (!map) + return; + CounterMap::iterator mapIterator = map->find(identifier.impl()); + if (mapIterator == map->end()) + return; + destroyCounterNodeWithoutMapRemoval(identifier, mapIterator->second.get()); + map->remove(mapIterator); + // We do not delete "map" here even if empty because we expect to reuse + // it soon. In order for a renderer to lose all its counters permanently, + // a style change for the renderer involving removal of all counter + // directives must occur, in which case, RenderCounter::destroyCounterNodes() + // must be called. + // The destruction of the Renderer (possibly caused by the removal of its + // associated DOM node) is the other case that leads to the permanent + // destruction of all counters attached to a Renderer. In this case + // RenderCounter::destroyCounterNodes() must be and is now called, too. + // RenderCounter::destroyCounterNodes() handles destruction of the counter + // map associated with a renderer, so there is no risk in leaking the map. +} + +static void updateCounters(RenderObject* renderer) +{ + ASSERT(renderer->style()); + const CounterDirectiveMap* directiveMap = renderer->style()->counterDirectives(); + if (!directiveMap) + return; + CounterDirectiveMap::const_iterator end = directiveMap->end(); + if (!renderer->m_hasCounterNodeMap) { + for (CounterDirectiveMap::const_iterator it = directiveMap->begin(); it != end; ++it) + makeCounterNode(renderer, AtomicString(it->first.get()), false); + return; + } + CounterMap* counterMap = counterMaps().get(renderer); + ASSERT(counterMap); + for (CounterDirectiveMap::const_iterator it = directiveMap->begin(); it != end; ++it) { + RefPtr<CounterNode> node = counterMap->get(it->first.get()); + if (!node) { + makeCounterNode(renderer, AtomicString(it->first.get()), false); + continue; + } + CounterNode* newParent = 0; + CounterNode* newPreviousSibling; + + findPlaceForCounter(renderer, AtomicString(it->first.get()), node->hasResetType(), newParent, newPreviousSibling); + if (node != counterMap->get(it->first.get())) + continue; + CounterNode* parent = node->parent(); + if (newParent == parent && newPreviousSibling == node->previousSibling()) + continue; + if (parent) + parent->removeChild(node.get(), it->first.get()); + if (newParent) + newParent->insertAfter(node.get(), newPreviousSibling, it->first.get()); + } +} + +void RenderCounter::rendererSubtreeAttached(RenderObject* renderer) +{ + for (RenderObject* descendant = renderer; descendant; descendant = descendant->nextInPreOrder(renderer)) + updateCounters(descendant); +} + +void RenderCounter::rendererStyleChanged(RenderObject* renderer, const RenderStyle* oldStyle, const RenderStyle* newStyle) +{ + const CounterDirectiveMap* newCounterDirectives; + const CounterDirectiveMap* oldCounterDirectives; + if (oldStyle && (oldCounterDirectives = oldStyle->counterDirectives())) { + if (newStyle && (newCounterDirectives = newStyle->counterDirectives())) { + CounterDirectiveMap::const_iterator newMapEnd = newCounterDirectives->end(); + CounterDirectiveMap::const_iterator oldMapEnd = oldCounterDirectives->end(); + for (CounterDirectiveMap::const_iterator it = newCounterDirectives->begin(); it != newMapEnd; ++it) { + CounterDirectiveMap::const_iterator oldMapIt = oldCounterDirectives->find(it->first); + if (oldMapIt != oldMapEnd) { + if (oldMapIt->second == it->second) + continue; + RenderCounter::destroyCounterNode(renderer, it->first.get()); + } + // We must create this node here, because the changed node may be a node with no display such as + // as those created by the increment or reset directives and the re-layout that will happen will + // not catch the change if the node had no children. + makeCounterNode(renderer, it->first.get(), false); + } + // Destroying old counters that do not exist in the new counterDirective map. + for (CounterDirectiveMap::const_iterator it = oldCounterDirectives->begin(); it !=oldMapEnd; ++it) { + if (!newCounterDirectives->contains(it->first)) + RenderCounter::destroyCounterNode(renderer, it->first.get()); + } + } else { + if (renderer->m_hasCounterNodeMap) + RenderCounter::destroyCounterNodes(renderer); + } + } else if (newStyle && (newCounterDirectives = newStyle->counterDirectives())) { + CounterDirectiveMap::const_iterator newMapEnd = newCounterDirectives->end(); + for (CounterDirectiveMap::const_iterator it = newCounterDirectives->begin(); it != newMapEnd; ++it) { + // We must create this node here, because the added node may be a node with no display such as + // as those created by the increment or reset directives and the re-layout that will happen will + // not catch the change if the node had no children. + makeCounterNode(renderer, it->first.get(), false); + } + } +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderCounter.h b/Source/WebCore/rendering/RenderCounter.h new file mode 100644 index 0000000..9373193 --- /dev/null +++ b/Source/WebCore/rendering/RenderCounter.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2004 Allan Sandfeld Jensen (kde@carewolf.com) + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderCounter_h +#define RenderCounter_h + +#include "CounterContent.h" +#include "RenderText.h" + +namespace WebCore { + +class CounterNode; + +class RenderCounter : public RenderText { +public: + RenderCounter(Document*, const CounterContent&); + virtual ~RenderCounter(); + + // Removes the reference to the CounterNode associated with this renderer + // if its identifier matches the argument. + // This is used to cause a counter display update when the CounterNode + // tree for identifier changes. + void invalidate(const AtomicString& identifier); + + static void destroyCounterNodes(RenderObject*); + static void destroyCounterNode(RenderObject*, const AtomicString& identifier); + static void rendererSubtreeAttached(RenderObject*); + static void rendererStyleChanged(RenderObject*, const RenderStyle* oldStyle, const RenderStyle* newStyle); + +private: + virtual const char* renderName() const; + virtual bool isCounter() const; + virtual PassRefPtr<StringImpl> originalText() const; + + virtual void computePreferredLogicalWidths(int leadWidth); + + CounterContent m_counter; + mutable CounterNode* m_counterNode; +}; + +inline RenderCounter* toRenderCounter(RenderObject* object) +{ + ASSERT(!object || object->isCounter()); + return static_cast<RenderCounter*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderCounter(const RenderCounter*); + +} // namespace WebCore + +#endif // RenderCounter_h diff --git a/Source/WebCore/rendering/RenderDataGrid.cpp b/Source/WebCore/rendering/RenderDataGrid.cpp new file mode 100644 index 0000000..c322389 --- /dev/null +++ b/Source/WebCore/rendering/RenderDataGrid.cpp @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if ENABLE(DATAGRID) + +#include "RenderDataGrid.h" + +#include "CSSStyleSelector.h" +#include "FocusController.h" +#include "Frame.h" +#include "GraphicsContext.h" +#include "Page.h" +#include "RenderView.h" +#include "Scrollbar.h" + +using std::min; + +namespace WebCore { + +static const int cDefaultWidth = 300; + +RenderDataGrid::RenderDataGrid(Element* elt) + : RenderBlock(elt) +{ +} + +RenderDataGrid::~RenderDataGrid() +{ +} + +void RenderDataGrid::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBlock::styleDidChange(diff, oldStyle); + recalcStyleForColumns(); +} + +void RenderDataGrid::recalcStyleForColumns() +{ + DataGridColumnList* columns = gridElement()->columns(); + unsigned length = columns->length(); + for (unsigned i = 0; i < length; ++i) + recalcStyleForColumn(columns->item(i)); +} + +void RenderDataGrid::recalcStyleForColumn(DataGridColumn* column) +{ + if (!column->columnStyle()) + column->setColumnStyle(document()->styleSelector()->pseudoStyleForDataGridColumn(column, style())); + if (!column->headerStyle()) + column->setHeaderStyle(document()->styleSelector()->pseudoStyleForDataGridColumnHeader(column, style())); +} + +RenderStyle* RenderDataGrid::columnStyle(DataGridColumn* column) +{ + if (!column->columnStyle()) + recalcStyleForColumn(column); + return column->columnStyle(); +} + +RenderStyle* RenderDataGrid::headerStyle(DataGridColumn* column) +{ + if (!column->headerStyle()) + recalcStyleForColumn(column); + return column->headerStyle(); +} + +void RenderDataGrid::computePreferredLogicalWidths() +{ + m_minPreferredLogicalWidth = 0; + m_maxPreferredLogicalWidth = 0; + + if (style()->width().isFixed() && style()->width().value() > 0) + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value()); + else + m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(cDefaultWidth); + + if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) { + m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); + m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); + } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent())) + m_minPreferredLogicalWidth = 0; + else + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth; + + if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) { + m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); + m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); + } + + int toAdd = borderAndPaddingWidth(); + m_minPreferredLogicalWidth += toAdd; + m_maxPreferredLogicalWidth += toAdd; + + setPreferredLogicalWidthsDirty(false); +} + +void RenderDataGrid::layout() +{ + RenderBlock::layout(); + layoutColumns(); +} + +void RenderDataGrid::layoutColumns() +{ + // FIXME: Implement. +} + +void RenderDataGrid::paintObject(PaintInfo& paintInfo, int tx, int ty) +{ + if (style()->visibility() != VISIBLE) + return; + + // Paint our background and border. + RenderBlock::paintObject(paintInfo, tx, ty); + + if (paintInfo.phase != PaintPhaseForeground) + return; + + // Paint our column headers first. + paintColumnHeaders(paintInfo, tx, ty); +} + +void RenderDataGrid::paintColumnHeaders(PaintInfo& paintInfo, int tx, int ty) +{ + DataGridColumnList* columns = gridElement()->columns(); + unsigned length = columns->length(); + for (unsigned i = 0; i < length; ++i) { + DataGridColumn* column = columns->item(i); + RenderStyle* columnStyle = headerStyle(column); + + // Don't render invisible columns. + if (!columnStyle || columnStyle->display() == NONE || columnStyle->visibility() != VISIBLE) + continue; + + // Paint the column header if it intersects the dirty rect. + IntRect columnRect(column->rect()); + columnRect.move(tx, ty); + if (columnRect.intersects(paintInfo.rect)) + paintColumnHeader(column, paintInfo, tx, ty); + } +} + +void RenderDataGrid::paintColumnHeader(DataGridColumn*, PaintInfo&, int, int) +{ + // FIXME: Implement. +} + +// Scrolling implementation functions +int RenderDataGrid::scrollSize(ScrollbarOrientation orientation) const +{ + return ((orientation == VerticallScrollbar) && m_vBar) ? (m_vBar->totalSize() - m_vBar->visibleSize()) : 0; +} + +void RenderDataGrid::setScrollOffsetFromAnimation(const IntPoint& offset) +{ + if (m_vBar) + m_vBar->setValue(offset.y(), Scrollbar::FromScrollAnimator); +} + +void RenderDataGrid::valueChanged(Scrollbar*) +{ + // FIXME: Implement. +} + +void RenderDataGrid::invalidateScrollbarRect(Scrollbar*, const IntRect&) +{ + // FIXME: Implement. +} + +bool RenderDataGrid::isActive() const +{ + Page* page = frame()->page(); + return page && page->focusController()->isActive(); +} + + +IntRect RenderDataGrid::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& scrollbarRect) const +{ + RenderView* view = this->view(); + if (!view) + return scrollbarRect; + + IntRect rect = scrollbarRect; + + int scrollbarLeft = width() - borderRight() - scrollbar->width(); + int scrollbarTop = borderTop(); + rect.move(scrollbarLeft, scrollbarTop); + + return view->frameView()->convertFromRenderer(this, rect); +} + +IntRect RenderDataGrid::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const +{ + RenderView* view = this->view(); + if (!view) + return parentRect; + + IntRect rect = view->frameView()->convertToRenderer(this, parentRect); + + int scrollbarLeft = width() - borderRight() - scrollbar->width(); + int scrollbarTop = borderTop(); + rect.move(-scrollbarLeft, -scrollbarTop); + return rect; +} + +IntPoint RenderDataGrid::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& scrollbarPoint) const +{ + RenderView* view = this->view(); + if (!view) + return scrollbarPoint; + + IntPoint point = scrollbarPoint; + + int scrollbarLeft = width() - borderRight() - scrollbar->width(); + int scrollbarTop = borderTop(); + point.move(scrollbarLeft, scrollbarTop); + + return view->frameView()->convertFromRenderer(this, point); +} + +IntPoint RenderDataGrid::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const +{ + RenderView* view = this->view(); + if (!view) + return parentPoint; + + IntPoint point = view->frameView()->convertToRenderer(this, parentPoint); + + int scrollbarLeft = width() - borderRight() - scrollbar->width(); + int scrollbarTop = borderTop(); + point.move(-scrollbarLeft, -scrollbarTop); + return point; +} + +} + +#endif diff --git a/Source/WebCore/rendering/RenderDataGrid.h b/Source/WebCore/rendering/RenderDataGrid.h new file mode 100644 index 0000000..1492d26 --- /dev/null +++ b/Source/WebCore/rendering/RenderDataGrid.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderDataGrid_h +#define RenderDataGrid_h + +#if ENABLE(DATAGRID) + +#include "HTMLDataGridElement.h" +#include "RenderBlock.h" +#include "ScrollbarClient.h" +#include "StyleImage.h" +#include <wtf/RefPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class RenderDataGrid : public RenderBlock, private ScrollbarClient { +public: + RenderDataGrid(Element*); + ~RenderDataGrid(); + + virtual const char* renderName() const { return "RenderDataGrid"; } + virtual bool canHaveChildren() const { return false; } + virtual void computePreferredLogicalWidths(); + virtual void layout(); + virtual void paintObject(PaintInfo&, int tx, int ty); + + void columnsChanged(); + +private: + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + virtual bool requiresForcedStyleRecalcPropagation() const { return true; } + + RenderStyle* columnStyle(DataGridColumn*); + RenderStyle* headerStyle(DataGridColumn*); + void recalcStyleForColumns(); + void recalcStyleForColumn(DataGridColumn*); + + void layoutColumns(); + void paintColumnHeaders(PaintInfo&, int tx, int ty); + void paintColumnHeader(DataGridColumn*, PaintInfo&, int tx, int ty); + + HTMLDataGridElement* gridElement() const { return static_cast<HTMLDataGridElement*>(node()); } + + // ScrollbarClient interface. + virtual int scrollSize(ScrollbarOrientation orientation) const; + virtual void setScrollOffsetFromAnimation(const IntPoint&); + virtual void valueChanged(Scrollbar*); + virtual void invalidateScrollbarRect(Scrollbar*, const IntRect&); + virtual bool isActive() const; + virtual bool scrollbarCornerPresent() const { return false; } // We don't support resize on data grids yet. If we did this would have to change. + virtual IntRect convertFromScrollbarToContainingView(const Scrollbar*, const IntRect&) const; + virtual IntRect convertFromContainingViewToScrollbar(const Scrollbar*, const IntRect&) const; + virtual IntPoint convertFromScrollbarToContainingView(const Scrollbar*, const IntPoint&) const; + virtual IntPoint convertFromContainingViewToScrollbar(const Scrollbar*, const IntPoint&) const; + + RefPtr<Scrollbar> m_vBar; +}; + +} + +#endif + +#endif // RenderDataGrid_h diff --git a/Source/WebCore/rendering/RenderDetails.cpp b/Source/WebCore/rendering/RenderDetails.cpp new file mode 100644 index 0000000..a1039f9 --- /dev/null +++ b/Source/WebCore/rendering/RenderDetails.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + * + * 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 "RenderDetails.h" + +namespace WebCore { + +RenderDetails::RenderDetails(Node* element) + : RenderBlock(element) +{ +} + +void RenderDetails::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBlock::styleDidChange(diff, oldStyle); + + // Ensure that if we ended up being inline that we set our replaced flag + // so that we're treated like an inline-block. + setReplaced(isInline()); +} + +} diff --git a/Source/WebCore/rendering/RenderDetails.h b/Source/WebCore/rendering/RenderDetails.h new file mode 100644 index 0000000..b8aebab --- /dev/null +++ b/Source/WebCore/rendering/RenderDetails.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + * + * 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. + * + */ + +#ifndef RenderDetails_h +#define RenderDetails_h + +#include "RenderBlock.h" + +namespace WebCore { + +class RenderDetails : public RenderBlock { +public: + explicit RenderDetails(Node*); + +private: + virtual const char* renderName() const { return "RenderDetails"; } + virtual bool isDetails() const { return true; } + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); +}; + +inline RenderDetails* toRenderDetails(RenderObject* object) +{ + ASSERT(!object || object->isDetails()); + return static_cast<RenderDetails*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderDetails(const RenderDetails*); + +} + +#endif // RenderDetails_h diff --git a/Source/WebCore/rendering/RenderDetailsMarker.cpp b/Source/WebCore/rendering/RenderDetailsMarker.cpp new file mode 100644 index 0000000..26e49d9 --- /dev/null +++ b/Source/WebCore/rendering/RenderDetailsMarker.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + * + * 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 "RenderDetailsMarker.h" + +namespace WebCore { + +RenderDetailsMarker::RenderDetailsMarker(Node* element) + : RenderBlock(element) +{ +} + +} diff --git a/Source/WebCore/rendering/RenderDetailsMarker.h b/Source/WebCore/rendering/RenderDetailsMarker.h new file mode 100644 index 0000000..08bdbd8 --- /dev/null +++ b/Source/WebCore/rendering/RenderDetailsMarker.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + * + * 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. + * + */ + +#ifndef RenderDetailsMarker_h +#define RenderDetailsMarker_h + +#include "RenderBlock.h" + +namespace WebCore { + +class RenderDetailsMarker : public RenderBlock { +public: + explicit RenderDetailsMarker(Node*); + +private: + virtual const char* renderName() const { return "RenderDetailsMarker"; } + virtual bool isDetailsMarker() const { return true; } +}; + +inline RenderDetailsMarker* toRenderDetailsMarker(RenderObject* object) +{ + ASSERT(!object || object->isDetails()); + return static_cast<RenderDetailsMarker*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderDetailsMarker(const RenderDetailsMarker*); + +} + +#endif // RenderDetailsMarker_h + diff --git a/Source/WebCore/rendering/RenderEmbeddedObject.cpp b/Source/WebCore/rendering/RenderEmbeddedObject.cpp new file mode 100644 index 0000000..fa31ddf --- /dev/null +++ b/Source/WebCore/rendering/RenderEmbeddedObject.cpp @@ -0,0 +1,309 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Simon Hausmann <hausmann@kde.org> + * (C) 2000 Stefan Schimanski (1Stein@gmx.de) + * Copyright (C) 2004, 2005, 2006, 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * 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 "RenderEmbeddedObject.h" + +#include "Chrome.h" +#include "ChromeClient.h" +#include "CSSValueKeywords.h" +#include "Font.h" +#include "FontSelector.h" +#include "Frame.h" +#include "FrameLoaderClient.h" +#include "GraphicsContext.h" +#include "HTMLEmbedElement.h" +#include "HTMLIFrameElement.h" +#include "HTMLNames.h" +#include "HTMLObjectElement.h" +#include "HTMLParamElement.h" +#include "LocalizedStrings.h" +#include "MIMETypeRegistry.h" +#include "MouseEvent.h" +#include "Page.h" +#include "Path.h" +#include "PluginViewBase.h" +#include "RenderTheme.h" +#include "RenderView.h" +#include "RenderWidgetProtector.h" +#include "Settings.h" +#include "Text.h" + +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) +#include "HTMLVideoElement.h" +#endif + +namespace WebCore { + +using namespace HTMLNames; + +static const float replacementTextRoundedRectHeight = 18; +static const float replacementTextRoundedRectLeftRightTextMargin = 6; +static const float replacementTextRoundedRectOpacity = 0.20f; +static const float replacementTextPressedRoundedRectOpacity = 0.65f; +static const float replacementTextRoundedRectRadius = 5; +static const float replacementTextTextOpacity = 0.55f; +static const float replacementTextPressedTextOpacity = 0.65f; + +static const Color& replacementTextRoundedRectPressedColor() +{ + static const Color lightGray(205, 205, 205); + return lightGray; +} + +RenderEmbeddedObject::RenderEmbeddedObject(Element* element) + : RenderPart(element) + , m_hasFallbackContent(false) + , m_showsMissingPluginIndicator(false) + , m_missingPluginIndicatorIsPressed(false) + , m_mouseDownWasInMissingPluginIndicator(false) +{ + view()->frameView()->setIsVisuallyNonEmpty(); +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + if (element->hasTagName(videoTag) || element->hasTagName(audioTag)) + setHasIntrinsicSize(); +#endif +} + +RenderEmbeddedObject::~RenderEmbeddedObject() +{ + if (frameView()) + frameView()->removeWidgetToUpdate(this); +} + +#if USE(ACCELERATED_COMPOSITING) +bool RenderEmbeddedObject::requiresLayer() const +{ + if (RenderPart::requiresLayer()) + return true; + + return allowsAcceleratedCompositing(); +} + +bool RenderEmbeddedObject::allowsAcceleratedCompositing() const +{ + return widget() && widget()->isPluginViewBase() && static_cast<PluginViewBase*>(widget())->platformLayer(); +} +#endif + +void RenderEmbeddedObject::setShowsMissingPluginIndicator() +{ + ASSERT(m_replacementText.isEmpty()); + m_replacementText = missingPluginText(); + m_showsMissingPluginIndicator = true; +} + +void RenderEmbeddedObject::setShowsCrashedPluginIndicator() +{ + ASSERT(m_replacementText.isEmpty()); + m_replacementText = crashedPluginText(); +} + +bool RenderEmbeddedObject::pluginCrashedOrWasMissing() const +{ + return !m_replacementText.isNull(); +} + +void RenderEmbeddedObject::setMissingPluginIndicatorIsPressed(bool pressed) +{ + if (m_missingPluginIndicatorIsPressed == pressed) + return; + + m_missingPluginIndicatorIsPressed = pressed; + repaint(); +} + +void RenderEmbeddedObject::paint(PaintInfo& paintInfo, int tx, int ty) +{ + if (pluginCrashedOrWasMissing()) { + RenderReplaced::paint(paintInfo, tx, ty); + return; + } + + RenderPart::paint(paintInfo, tx, ty); +} + +void RenderEmbeddedObject::paintReplaced(PaintInfo& paintInfo, int tx, int ty) +{ + if (!pluginCrashedOrWasMissing()) + return; + + if (paintInfo.phase == PaintPhaseSelection) + return; + + GraphicsContext* context = paintInfo.context; + if (context->paintingDisabled()) + return; + + FloatRect contentRect; + Path path; + FloatRect replacementTextRect; + Font font; + TextRun run(""); + float textWidth; + if (!getReplacementTextGeometry(tx, ty, contentRect, path, replacementTextRect, font, run, textWidth)) + return; + + context->save(); + context->clip(contentRect); + context->setAlpha(m_missingPluginIndicatorIsPressed ? replacementTextPressedRoundedRectOpacity : replacementTextRoundedRectOpacity); + context->setFillColor(m_missingPluginIndicatorIsPressed ? replacementTextRoundedRectPressedColor() : Color::white, style()->colorSpace()); + context->fillPath(path); + + float labelX = roundf(replacementTextRect.location().x() + (replacementTextRect.size().width() - textWidth) / 2); + float labelY = roundf(replacementTextRect.location().y() + (replacementTextRect.size().height() - font.height()) / 2 + font.ascent()); + context->setAlpha(m_missingPluginIndicatorIsPressed ? replacementTextPressedTextOpacity : replacementTextTextOpacity); + context->setFillColor(Color::black, style()->colorSpace()); + context->drawBidiText(font, run, FloatPoint(labelX, labelY)); + context->restore(); +} + +bool RenderEmbeddedObject::getReplacementTextGeometry(int tx, int ty, FloatRect& contentRect, Path& path, FloatRect& replacementTextRect, Font& font, TextRun& run, float& textWidth) +{ + contentRect = contentBoxRect(); + contentRect.move(tx, ty); + + FontDescription fontDescription; + RenderTheme::defaultTheme()->systemFont(CSSValueWebkitSmallControl, fontDescription); + fontDescription.setWeight(FontWeightBold); + Settings* settings = document()->settings(); + ASSERT(settings); + if (!settings) + return false; + fontDescription.setRenderingMode(settings->fontRenderingMode()); + fontDescription.setComputedSize(fontDescription.specifiedSize()); + font = Font(fontDescription, 0, 0); + font.update(0); + + run = TextRun(m_replacementText.characters(), m_replacementText.length()); + run.disableRoundingHacks(); + textWidth = font.floatWidth(run); + + replacementTextRect.setSize(FloatSize(textWidth + replacementTextRoundedRectLeftRightTextMargin * 2, replacementTextRoundedRectHeight)); + float x = (contentRect.size().width() / 2 - replacementTextRect.size().width() / 2) + contentRect.location().x(); + float y = (contentRect.size().height() / 2 - replacementTextRect.size().height() / 2) + contentRect.location().y(); + replacementTextRect.setLocation(FloatPoint(x, y)); + + path.addRoundedRect(replacementTextRect, FloatSize(replacementTextRoundedRectRadius, replacementTextRoundedRectRadius)); + + return true; +} + +void RenderEmbeddedObject::layout() +{ + ASSERT(needsLayout()); + + computeLogicalWidth(); + computeLogicalHeight(); + + RenderPart::layout(); + + m_overflow.clear(); + addShadowOverflow(); + + updateLayerTransform(); + + if (!widget() && frameView()) + frameView()->addWidgetToUpdate(this); + + setNeedsLayout(false); +} + +void RenderEmbeddedObject::viewCleared() +{ + // This is required for <object> elements whose contents are rendered by WebCore (e.g. src="foo.html"). + if (node() && widget() && widget()->isFrameView()) { + FrameView* view = static_cast<FrameView*>(widget()); + int marginWidth = -1; + int marginHeight = -1; + if (node()->hasTagName(iframeTag)) { + HTMLIFrameElement* frame = static_cast<HTMLIFrameElement*>(node()); + marginWidth = frame->marginWidth(); + marginHeight = frame->marginHeight(); + } + if (marginWidth != -1) + view->setMarginWidth(marginWidth); + if (marginHeight != -1) + view->setMarginHeight(marginHeight); + } +} + +bool RenderEmbeddedObject::isInMissingPluginIndicator(MouseEvent* event) +{ + FloatRect contentRect; + Path path; + FloatRect replacementTextRect; + Font font; + TextRun run(""); + float textWidth; + if (!getReplacementTextGeometry(0, 0, contentRect, path, replacementTextRect, font, run, textWidth)) + return false; + + return path.contains(absoluteToLocal(event->absoluteLocation(), false, true)); +} + +void RenderEmbeddedObject::handleMissingPluginIndicatorEvent(Event* event) +{ + if (Page* page = document()->page()) { + if (!page->chrome()->client()->shouldMissingPluginMessageBeButton()) + return; + } + + if (!event->isMouseEvent()) + return; + + MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); + HTMLPlugInElement* element = static_cast<HTMLPlugInElement*>(node()); + if (event->type() == eventNames().mousedownEvent && static_cast<MouseEvent*>(event)->button() == LeftButton) { + m_mouseDownWasInMissingPluginIndicator = isInMissingPluginIndicator(mouseEvent); + if (m_mouseDownWasInMissingPluginIndicator) { + if (Frame* frame = document()->frame()) { + frame->eventHandler()->setCapturingMouseEventsNode(element); + element->setIsCapturingMouseEvents(true); + } + setMissingPluginIndicatorIsPressed(true); + } + event->setDefaultHandled(); + } + if (event->type() == eventNames().mouseupEvent && static_cast<MouseEvent*>(event)->button() == LeftButton) { + if (m_missingPluginIndicatorIsPressed) { + if (Frame* frame = document()->frame()) { + frame->eventHandler()->setCapturingMouseEventsNode(0); + element->setIsCapturingMouseEvents(false); + } + setMissingPluginIndicatorIsPressed(false); + } + if (m_mouseDownWasInMissingPluginIndicator && isInMissingPluginIndicator(mouseEvent)) { + if (Page* page = document()->page()) + page->chrome()->client()->missingPluginButtonClicked(element); + } + m_mouseDownWasInMissingPluginIndicator = false; + event->setDefaultHandled(); + } + if (event->type() == eventNames().mousemoveEvent) { + setMissingPluginIndicatorIsPressed(m_mouseDownWasInMissingPluginIndicator && isInMissingPluginIndicator(mouseEvent)); + event->setDefaultHandled(); + } +} + +} diff --git a/Source/WebCore/rendering/RenderEmbeddedObject.h b/Source/WebCore/rendering/RenderEmbeddedObject.h new file mode 100644 index 0000000..8d09ede --- /dev/null +++ b/Source/WebCore/rendering/RenderEmbeddedObject.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Simon Hausmann <hausmann@kde.org> + * Copyright (C) 2004, 2005, 2006, 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderEmbeddedObject_h +#define RenderEmbeddedObject_h + +#include "RenderPart.h" + +namespace WebCore { + +class MouseEvent; + +// Renderer for embeds and objects, often, but not always, rendered via plug-ins. +// For example, <embed src="foo.html"> does not invoke a plug-in. +class RenderEmbeddedObject : public RenderPart { +public: + RenderEmbeddedObject(Element*); + virtual ~RenderEmbeddedObject(); + + bool pluginCrashedOrWasMissing() const; + + void setShowsMissingPluginIndicator(); + void setShowsCrashedPluginIndicator(); + bool showsMissingPluginIndicator() const { return m_showsMissingPluginIndicator; } + + // FIXME: This belongs on HTMLObjectElement. + bool hasFallbackContent() const { return m_hasFallbackContent; } + void setHasFallbackContent(bool hasFallbackContent) { m_hasFallbackContent = hasFallbackContent; } + + void handleMissingPluginIndicatorEvent(Event*); + +#if USE(ACCELERATED_COMPOSITING) + virtual bool allowsAcceleratedCompositing() const; +#endif + +private: + virtual const char* renderName() const { return "RenderEmbeddedObject"; } + virtual bool isEmbeddedObject() const { return true; } + + virtual void paintReplaced(PaintInfo&, int, int); + virtual void paint(PaintInfo& paintInfo, int, int); + +#if USE(ACCELERATED_COMPOSITING) + virtual bool requiresLayer() const; +#endif + + virtual void layout(); + virtual void viewCleared(); + + void setMissingPluginIndicatorIsPressed(bool); + bool isInMissingPluginIndicator(MouseEvent*); + bool getReplacementTextGeometry(int tx, int ty, FloatRect& contentRect, Path&, FloatRect& replacementTextRect, Font&, TextRun&, float& textWidth); + + String m_replacementText; + bool m_hasFallbackContent; // FIXME: This belongs on HTMLObjectElement. + bool m_showsMissingPluginIndicator; + bool m_missingPluginIndicatorIsPressed; + bool m_mouseDownWasInMissingPluginIndicator; +}; + +inline RenderEmbeddedObject* toRenderEmbeddedObject(RenderObject* object) +{ + ASSERT(!object || !strcmp(object->renderName(), "RenderEmbeddedObject")); + return static_cast<RenderEmbeddedObject*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderEmbeddedObject(const RenderEmbeddedObject*); + +} // namespace WebCore + +#endif // RenderEmbeddedObject_h diff --git a/Source/WebCore/rendering/RenderFieldset.cpp b/Source/WebCore/rendering/RenderFieldset.cpp new file mode 100644 index 0000000..c83396c --- /dev/null +++ b/Source/WebCore/rendering/RenderFieldset.cpp @@ -0,0 +1,223 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + * + * 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 "RenderFieldset.h" + +#include "CSSPropertyNames.h" +#include "HTMLNames.h" +#include "GraphicsContext.h" + +#if ENABLE(WML) +#include "WMLNames.h" +#endif + +using std::min; +using std::max; + +namespace WebCore { + +using namespace HTMLNames; + +RenderFieldset::RenderFieldset(Node* element) + : RenderBlock(element) +{ +} + +void RenderFieldset::computePreferredLogicalWidths() +{ + RenderBlock::computePreferredLogicalWidths(); + if (RenderBox* legend = findLegend()) { + int legendMinWidth = legend->minPreferredLogicalWidth(); + + Length legendMarginLeft = legend->style()->marginLeft(); + Length legendMarginRight = legend->style()->marginLeft(); + + if (legendMarginLeft.isFixed()) + legendMinWidth += legendMarginLeft.value(); + + if (legendMarginRight.isFixed()) + legendMinWidth += legendMarginRight.value(); + + m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, legendMinWidth + borderAndPaddingWidth()); + } +} + +RenderObject* RenderFieldset::layoutSpecialExcludedChild(bool relayoutChildren) +{ + RenderBox* legend = findLegend(); + if (legend) { + if (relayoutChildren) + legend->setNeedsLayout(true); + legend->layoutIfNeeded(); + + int logicalLeft; + if (style()->isLeftToRightDirection()) { + switch (legend->style()->textAlign()) { + case CENTER: + logicalLeft = (logicalWidth() - logicalWidthForChild(legend)) / 2; + break; + case RIGHT: + logicalLeft = logicalWidth() - borderEnd() - paddingEnd() - logicalWidthForChild(legend); + break; + default: + logicalLeft = borderStart() + paddingStart() + marginStartForChild(legend); + break; + } + } else { + switch (legend->style()->textAlign()) { + case LEFT: + logicalLeft = borderStart() + paddingStart(); + break; + case CENTER: { + // Make sure that the extra pixel goes to the end side in RTL (since it went to the end side + // in LTR). + int centeredWidth = logicalWidth() - logicalWidthForChild(legend); + logicalLeft = centeredWidth - centeredWidth / 2; + break; + } + default: + logicalLeft = logicalWidth() - borderStart() - paddingStart() - marginStartForChild(legend) - logicalWidthForChild(legend); + break; + } + } + + setLogicalLeftForChild(legend, logicalLeft); + + int b = borderBefore(); + int h = logicalHeightForChild(legend); + setLogicalTopForChild(legend, max((b - h) / 2, 0)); + setLogicalHeight(max(b, h) + paddingBefore()); + } + return legend; +} + +RenderBox* RenderFieldset::findLegend() const +{ + for (RenderObject* legend = firstChild(); legend; legend = legend->nextSibling()) { + if (!legend->isFloatingOrPositioned() && legend->node() && + (legend->node()->hasTagName(legendTag) +#if ENABLE(WML) + || legend->node()->hasTagName(WMLNames::insertedLegendTag) +#endif + ) + ) + return toRenderBox(legend); + } + return 0; +} + +void RenderFieldset::paintBoxDecorations(PaintInfo& paintInfo, int tx, int ty) +{ + if (!paintInfo.shouldPaintWithinRoot(this)) + return; + + int w = width(); + int h = height(); + RenderBox* legend = findLegend(); + if (!legend) + return RenderBlock::paintBoxDecorations(paintInfo, tx, ty); + + // FIXME: We need to work with "rl" and "bt" block flow directions. In those + // cases the legend is embedded in the right and bottom borders respectively. + // https://bugs.webkit.org/show_bug.cgi?id=47236 + if (style()->isHorizontalWritingMode()) { + int yOff = (legend->y() > 0) ? 0 : (legend->height() - borderTop()) / 2; + h -= yOff; + ty += yOff; + } else { + int xOff = (legend->x() > 0) ? 0 : (legend->width() - borderLeft()) / 2; + w -= xOff; + tx += xOff; + } + + paintBoxShadow(paintInfo.context, tx, ty, w, h, style(), Normal); + + paintFillLayers(paintInfo, style()->visitedDependentColor(CSSPropertyBackgroundColor), style()->backgroundLayers(), tx, ty, w, h); + paintBoxShadow(paintInfo.context, tx, ty, w, h, style(), Inset); + + if (!style()->hasBorder()) + return; + + // Create a clipping region around the legend and paint the border as normal + GraphicsContext* graphicsContext = paintInfo.context; + graphicsContext->save(); + + // FIXME: We need to work with "rl" and "bt" block flow directions. In those + // cases the legend is embedded in the right and bottom borders respectively. + // https://bugs.webkit.org/show_bug.cgi?id=47236 + if (style()->isHorizontalWritingMode()) { + int clipTop = ty; + int clipHeight = max(static_cast<int>(style()->borderTopWidth()), legend->height()); + graphicsContext->clipOut(IntRect(tx + legend->x(), clipTop, legend->width(), clipHeight)); + } else { + int clipLeft = tx; + int clipWidth = max(static_cast<int>(style()->borderLeftWidth()), legend->width()); + graphicsContext->clipOut(IntRect(clipLeft, ty + legend->y(), clipWidth, legend->height())); + } + + paintBorder(paintInfo.context, tx, ty, w, h, style(), true, true); + + graphicsContext->restore(); +} + +void RenderFieldset::paintMask(PaintInfo& paintInfo, int tx, int ty) +{ + if (style()->visibility() != VISIBLE || paintInfo.phase != PaintPhaseMask) + return; + + int w = width(); + int h = height(); + RenderBox* legend = findLegend(); + if (!legend) + return RenderBlock::paintMask(paintInfo, tx, ty); + + // FIXME: We need to work with "rl" and "bt" block flow directions. In those + // cases the legend is embedded in the right and bottom borders respectively. + // https://bugs.webkit.org/show_bug.cgi?id=47236 + if (style()->isHorizontalWritingMode()) { + int yOff = (legend->y() > 0) ? 0 : (legend->height() - borderTop()) / 2; + h -= yOff; + ty += yOff; + } else { + int xOff = (legend->x() > 0) ? 0 : (legend->width() - borderLeft()) / 2; + w -= xOff; + tx += xOff; + } + + paintMaskImages(paintInfo, tx, ty, w, h); +} + +void RenderFieldset::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBlock::styleDidChange(diff, oldStyle); + + // WinIE renders fieldsets with display:inline like they're inline-blocks. For us, + // an inline-block is just a block element with replaced set to true and inline set + // to true. Ensure that if we ended up being inline that we set our replaced flag + // so that we're treated like an inline-block. + if (isInline()) + setReplaced(true); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderFieldset.h b/Source/WebCore/rendering/RenderFieldset.h new file mode 100644 index 0000000..b340794 --- /dev/null +++ b/Source/WebCore/rendering/RenderFieldset.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2006, 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderFieldset_h +#define RenderFieldset_h + +#include "RenderBlock.h" + +namespace WebCore { + +class RenderFieldset : public RenderBlock { +public: + explicit RenderFieldset(Node*); + + RenderBox* findLegend() const; + +private: + virtual const char* renderName() const { return "RenderFieldSet"; } + virtual bool isFieldset() const { return true; } + + virtual RenderObject* layoutSpecialExcludedChild(bool relayoutChildren); + + virtual void computePreferredLogicalWidths(); + virtual bool avoidsFloats() const { return true; } + virtual bool stretchesToMinIntrinsicLogicalWidth() const { return true; } + + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + virtual void paintBoxDecorations(PaintInfo&, int tx, int ty); + virtual void paintMask(PaintInfo&, int tx, int ty); +}; + +inline RenderFieldset* toRenderFieldset(RenderObject* object) +{ + ASSERT(!object || object->isFieldset()); + return static_cast<RenderFieldset*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderFieldset(const RenderFieldset*); + +} // namespace WebCore + +#endif // RenderFieldset_h diff --git a/Source/WebCore/rendering/RenderFileUploadControl.cpp b/Source/WebCore/rendering/RenderFileUploadControl.cpp new file mode 100644 index 0000000..3c10f43 --- /dev/null +++ b/Source/WebCore/rendering/RenderFileUploadControl.cpp @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * 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 "RenderFileUploadControl.h" + +#include "Chrome.h" +#include "FileList.h" +#include "Frame.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "ShadowElement.h" +#include "Icon.h" +#include "LocalizedStrings.h" +#include "Page.h" +#include "RenderButton.h" +#include "RenderText.h" +#include "RenderTheme.h" +#include "RenderView.h" +#include <math.h> + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +const int afterButtonSpacing = 4; +const int iconHeight = 16; +const int iconWidth = 16; +const int iconFilenameSpacing = 2; +const int defaultWidthNumChars = 34; +const int buttonShadowHeight = 2; + +RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement* input) + : RenderBlock(input) +{ + FileList* list = input->files(); + Vector<String> filenames; + unsigned length = list ? list->length() : 0; + for (unsigned i = 0; i < length; ++i) + filenames.append(list->item(i)->path()); + m_fileChooser = FileChooser::create(this, filenames); +} + +RenderFileUploadControl::~RenderFileUploadControl() +{ + if (m_button) + m_button->detach(); + m_fileChooser->disconnectClient(); +} + +void RenderFileUploadControl::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBlock::styleDidChange(diff, oldStyle); + if (m_button) + m_button->renderer()->setStyle(createButtonStyle(style())); + + setReplaced(isInline()); +} + +void RenderFileUploadControl::valueChanged() +{ + // dispatchFormControlChangeEvent may destroy this renderer + RefPtr<FileChooser> fileChooser = m_fileChooser; + + HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node()); + inputElement->setFileListFromRenderer(fileChooser->filenames()); + inputElement->dispatchFormControlChangeEvent(); + + // only repaint if it doesn't seem we have been destroyed + if (!fileChooser->disconnected()) + repaint(); +} + +bool RenderFileUploadControl::allowsMultipleFiles() +{ +#if ENABLE(DIRECTORY_UPLOAD) + if (allowsDirectoryUpload()) + return true; +#endif + + HTMLInputElement* input = static_cast<HTMLInputElement*>(node()); + return input->fastHasAttribute(multipleAttr); +} + +#if ENABLE(DIRECTORY_UPLOAD) +bool RenderFileUploadControl::allowsDirectoryUpload() +{ + HTMLInputElement* input = static_cast<HTMLInputElement*>(node()); + return input->fastHasAttribute(webkitdirectoryAttr); +} +#endif + +String RenderFileUploadControl::acceptTypes() +{ + return static_cast<HTMLInputElement*>(node())->accept(); +} + +void RenderFileUploadControl::chooseIconForFiles(FileChooser* chooser, const Vector<String>& filenames) +{ + if (Chrome* chromePointer = chrome()) + chromePointer->chooseIconForFiles(filenames, chooser); +} + +void RenderFileUploadControl::click() +{ + // Requires a user gesture to open the file dialog. + if (!frame() || !frame()->loader()->isProcessingUserGesture()) + return; + if (Chrome* chromePointer = chrome()) + chromePointer->runOpenPanel(frame(), m_fileChooser); +} + +Chrome* RenderFileUploadControl::chrome() const +{ + Frame* frame = node()->document()->frame(); + if (!frame) + return 0; + Page* page = frame->page(); + if (!page) + return 0; + return page->chrome(); +} + +void RenderFileUploadControl::updateFromElement() +{ + HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node()); + ASSERT(inputElement->isFileUpload()); + + if (!m_button) { + m_button = ShadowInputElement::create(inputElement); + m_button->setType("button"); + m_button->setValue(fileButtonChooseFileLabel()); + RefPtr<RenderStyle> buttonStyle = createButtonStyle(style()); + RenderObject* renderer = m_button->createRenderer(renderArena(), buttonStyle.get()); + m_button->setRenderer(renderer); + renderer->setStyle(buttonStyle.release()); + renderer->updateFromElement(); + m_button->setAttached(); + m_button->setInDocument(); + + addChild(renderer); + } + + m_button->setDisabled(!theme()->isEnabled(this)); + + // This only supports clearing out the files, but that's OK because for + // security reasons that's the only change the DOM is allowed to make. + FileList* files = inputElement->files(); + ASSERT(files); + if (files && files->isEmpty() && !m_fileChooser->filenames().isEmpty()) { + m_fileChooser->clear(); + repaint(); + } +} + +int RenderFileUploadControl::maxFilenameWidth() const +{ + return max(0, contentWidth() - m_button->renderBox()->width() - afterButtonSpacing + - (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0)); +} + +PassRefPtr<RenderStyle> RenderFileUploadControl::createButtonStyle(const RenderStyle* parentStyle) const +{ + RefPtr<RenderStyle> style = getCachedPseudoStyle(FILE_UPLOAD_BUTTON); + if (!style) { + style = RenderStyle::create(); + if (parentStyle) + style->inheritFrom(parentStyle); + } + + // Button text will wrap on file upload controls with widths smaller than the intrinsic button width + // without this setWhiteSpace. + style->setWhiteSpace(NOWRAP); + + return style.release(); +} + +void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, int tx, int ty) +{ + if (style()->visibility() != VISIBLE) + return; + ASSERT(m_fileChooser); + + // Push a clip. + if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) { + IntRect clipRect(tx + borderLeft(), ty + borderTop(), + width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight); + if (clipRect.isEmpty()) + return; + paintInfo.context->save(); + paintInfo.context->clip(clipRect); + } + + if (paintInfo.phase == PaintPhaseForeground) { + const String& displayedFilename = fileTextValue(); + unsigned length = displayedFilename.length(); + const UChar* string = displayedFilename.characters(); + TextRun textRun(string, length, false, 0, 0, !style()->isLeftToRightDirection(), style()->unicodeBidi() == Override); + + // Determine where the filename should be placed + int contentLeft = tx + borderLeft() + paddingLeft(); + int buttonAndIconWidth = m_button->renderBox()->width() + afterButtonSpacing + + (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0); + int textX; + if (style()->isLeftToRightDirection()) + textX = contentLeft + buttonAndIconWidth; + else + textX = contentLeft + contentWidth() - buttonAndIconWidth - style()->font().width(textRun); + // We want to match the button's baseline + RenderButton* buttonRenderer = toRenderButton(m_button->renderer()); + int textY = buttonRenderer->absoluteBoundingBoxRect().y() + + buttonRenderer->marginTop() + buttonRenderer->borderTop() + buttonRenderer->paddingTop() + + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine); + + paintInfo.context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace()); + + // Draw the filename + paintInfo.context->drawBidiText(style()->font(), textRun, IntPoint(textX, textY)); + + if (m_fileChooser->icon()) { + // Determine where the icon should be placed + int iconY = ty + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2; + int iconX; + if (style()->isLeftToRightDirection()) + iconX = contentLeft + m_button->renderBox()->width() + afterButtonSpacing; + else + iconX = contentLeft + contentWidth() - m_button->renderBox()->width() - afterButtonSpacing - iconWidth; + + // Draw the file icon + m_fileChooser->icon()->paint(paintInfo.context, IntRect(iconX, iconY, iconWidth, iconHeight)); + } + } + + // Paint the children. + RenderBlock::paintObject(paintInfo, tx, ty); + + // Pop the clip. + if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) + paintInfo.context->restore(); +} + +void RenderFileUploadControl::computePreferredLogicalWidths() +{ + ASSERT(preferredLogicalWidthsDirty()); + + m_minPreferredLogicalWidth = 0; + m_maxPreferredLogicalWidth = 0; + + if (style()->width().isFixed() && style()->width().value() > 0) + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value()); + else { + // Figure out how big the filename space needs to be for a given number of characters + // (using "0" as the nominal character). + const UChar ch = '0'; + float charWidth = style()->font().floatWidth(TextRun(&ch, 1, false, 0, 0, false, false, false)); + m_maxPreferredLogicalWidth = (int)ceilf(charWidth * defaultWidthNumChars); + } + + if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) { + m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); + m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); + } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent())) + m_minPreferredLogicalWidth = 0; + else + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth; + + if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) { + m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); + m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); + } + + int toAdd = borderAndPaddingWidth(); + m_minPreferredLogicalWidth += toAdd; + m_maxPreferredLogicalWidth += toAdd; + + setPreferredLogicalWidthsDirty(false); +} + +void RenderFileUploadControl::receiveDroppedFiles(const Vector<String>& paths) +{ + if (allowsMultipleFiles()) + m_fileChooser->chooseFiles(paths); + else + m_fileChooser->chooseFile(paths[0]); +} + +String RenderFileUploadControl::buttonValue() +{ + if (!m_button) + return String(); + + return m_button->value(); +} + +String RenderFileUploadControl::fileTextValue() const +{ + return m_fileChooser->basenameForWidth(style()->font(), maxFilenameWidth()); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderFileUploadControl.h b/Source/WebCore/rendering/RenderFileUploadControl.h new file mode 100644 index 0000000..c96800c --- /dev/null +++ b/Source/WebCore/rendering/RenderFileUploadControl.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2006, 2007, 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderFileUploadControl_h +#define RenderFileUploadControl_h + +#include "FileChooser.h" +#include "RenderBlock.h" + +namespace WebCore { + +class Chrome; +class HTMLInputElement; + +// Each RenderFileUploadControl contains a RenderButton (for opening the file chooser), and +// sufficient space to draw a file icon and filename. The RenderButton has a shadow node +// associated with it to receive click/hover events. + +class RenderFileUploadControl : public RenderBlock, private FileChooserClient { +public: + RenderFileUploadControl(HTMLInputElement*); + virtual ~RenderFileUploadControl(); + + virtual bool isFileUploadControl() const { return true; } + + void click(); + + void receiveDroppedFiles(const Vector<String>&); + + String buttonValue(); + String fileTextValue() const; + +private: + virtual const char* renderName() const { return "RenderFileUploadControl"; } + + virtual void updateFromElement(); + virtual void computePreferredLogicalWidths(); + virtual void paintObject(PaintInfo&, int tx, int ty); + + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + virtual bool requiresForcedStyleRecalcPropagation() const { return true; } + + // FileChooserClient methods. + void valueChanged(); + void repaint() { RenderBlock::repaint(); } + bool allowsMultipleFiles(); +#if ENABLE(DIRECTORY_UPLOAD) + bool allowsDirectoryUpload(); +#endif + String acceptTypes(); + void chooseIconForFiles(FileChooser*, const Vector<String>&); + + Chrome* chrome() const; + int maxFilenameWidth() const; + PassRefPtr<RenderStyle> createButtonStyle(const RenderStyle* parentStyle) const; + + RefPtr<HTMLInputElement> m_button; + RefPtr<FileChooser> m_fileChooser; +}; + +inline RenderFileUploadControl* toRenderFileUploadControl(RenderObject* object) +{ + ASSERT(!object || object->isFileUploadControl()); + return static_cast<RenderFileUploadControl*>(object); +} + +inline const RenderFileUploadControl* toRenderFileUploadControl(const RenderObject* object) +{ + ASSERT(!object || object->isFileUploadControl()); + return static_cast<const RenderFileUploadControl*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderFileUploadControl(const RenderFileUploadControl*); + +} // namespace WebCore + +#endif // RenderFileUploadControl_h diff --git a/Source/WebCore/rendering/RenderFlexibleBox.cpp b/Source/WebCore/rendering/RenderFlexibleBox.cpp new file mode 100644 index 0000000..5d96adb --- /dev/null +++ b/Source/WebCore/rendering/RenderFlexibleBox.cpp @@ -0,0 +1,1108 @@ +/* + * This file is part of the render object implementation for KHTML. + * + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2003 Apple Computer, Inc. + * + * 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 "RenderFlexibleBox.h" + +#include "CharacterNames.h" +#include "RenderLayer.h" +#include "RenderView.h" +#include <wtf/StdLibExtras.h> + +#ifdef ANDROID_LAYOUT +#include "Document.h" +#include "Settings.h" +#endif + +using namespace std; + +namespace WebCore { + +class FlexBoxIterator { +public: + FlexBoxIterator(RenderFlexibleBox* parent) + { + box = parent; + if (box->style()->boxOrient() == HORIZONTAL && !box->style()->isLeftToRightDirection()) + forward = box->style()->boxDirection() != BNORMAL; + else + forward = box->style()->boxDirection() == BNORMAL; + lastOrdinal = 1; + if (!forward) { + // No choice, since we're going backwards, we have to find out the highest ordinal up front. + RenderBox* child = box->firstChildBox(); + while (child) { + if (child->style()->boxOrdinalGroup() > lastOrdinal) + lastOrdinal = child->style()->boxOrdinalGroup(); + child = child->nextSiblingBox(); + } + } + + reset(); + } + + void reset() + { + current = 0; + currentOrdinal = forward ? 0 : lastOrdinal+1; + } + + RenderBox* first() + { + reset(); + return next(); + } + + RenderBox* next() + { + do { + if (!current) { + if (forward) { + currentOrdinal++; + if (currentOrdinal > lastOrdinal) + return 0; + current = box->firstChildBox(); + } else { + currentOrdinal--; + if (currentOrdinal == 0) + return 0; + current = box->lastChildBox(); + } + } + else + current = forward ? current->nextSiblingBox() : current->previousSiblingBox(); + if (current && current->style()->boxOrdinalGroup() > lastOrdinal) + lastOrdinal = current->style()->boxOrdinalGroup(); + } while (!current || current->style()->boxOrdinalGroup() != currentOrdinal || + current->style()->visibility() == COLLAPSE); + return current; + } + +private: + RenderFlexibleBox* box; + RenderBox* current; + bool forward; + unsigned int currentOrdinal; + unsigned int lastOrdinal; +}; + +RenderFlexibleBox::RenderFlexibleBox(Node* node) +:RenderBlock(node) +{ + setChildrenInline(false); // All of our children must be block-level + m_flexingChildren = m_stretchingChildren = false; +} + +RenderFlexibleBox::~RenderFlexibleBox() +{ +} + +void RenderFlexibleBox::calcHorizontalPrefWidths() +{ + for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { + // positioned children don't affect the minmaxwidth + if (child->isPositioned() || child->style()->visibility() == COLLAPSE) + continue; + + // A margin basically has three types: fixed, percentage, and auto (variable). + // Auto and percentage margins simply become 0 when computing min/max width. + // Fixed margins can be added in as is. + Length ml = child->style()->marginLeft(); + Length mr = child->style()->marginRight(); + int margin = 0, marginLeft = 0, marginRight = 0; + if (ml.isFixed()) + marginLeft += ml.value(); + if (mr.isFixed()) + marginRight += mr.value(); + margin = marginLeft + marginRight; + + m_minPreferredLogicalWidth += child->minPreferredLogicalWidth() + margin; + m_maxPreferredLogicalWidth += child->maxPreferredLogicalWidth() + margin; + } +} + +void RenderFlexibleBox::calcVerticalPrefWidths() +{ + for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { + // Positioned children and collapsed children don't affect the min/max width + if (child->isPositioned() || child->style()->visibility() == COLLAPSE) + continue; + + // A margin basically has three types: fixed, percentage, and auto (variable). + // Auto/percentage margins simply become 0 when computing min/max width. + // Fixed margins can be added in as is. + Length ml = child->style()->marginLeft(); + Length mr = child->style()->marginRight(); + int margin = 0; + if (ml.isFixed()) + margin += ml.value(); + if (mr.isFixed()) + margin += mr.value(); + + int w = child->minPreferredLogicalWidth() + margin; + m_minPreferredLogicalWidth = max(w, m_minPreferredLogicalWidth); + + w = child->maxPreferredLogicalWidth() + margin; + m_maxPreferredLogicalWidth = max(w, m_maxPreferredLogicalWidth); + } +} + +void RenderFlexibleBox::computePreferredLogicalWidths() +{ + ASSERT(preferredLogicalWidthsDirty()); + + if (style()->width().isFixed() && style()->width().value() > 0) + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value()); + else { + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = 0; + + if (hasMultipleLines() || isVertical()) + calcVerticalPrefWidths(); + else + calcHorizontalPrefWidths(); + + m_maxPreferredLogicalWidth = max(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth); + } + + if (hasOverflowClip() && style()->overflowY() == OSCROLL) { + layer()->setHasVerticalScrollbar(true); + int scrollbarWidth = verticalScrollbarWidth(); + m_maxPreferredLogicalWidth += scrollbarWidth; + m_minPreferredLogicalWidth += scrollbarWidth; + } + + if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) { + m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); + m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); + } + + if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) { + m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); + m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); + } + + int borderAndPadding = borderAndPaddingLogicalWidth(); + m_minPreferredLogicalWidth += borderAndPadding; + m_maxPreferredLogicalWidth += borderAndPadding; + + setPreferredLogicalWidthsDirty(false); +} + +void RenderFlexibleBox::layoutBlock(bool relayoutChildren, int /*pageHeight FIXME: Implement */) +{ + ASSERT(needsLayout()); + + if (!relayoutChildren && layoutOnlyPositionedObjects()) + return; + + LayoutRepainter repainter(*this, checkForRepaintDuringLayout()); + LayoutStateMaintainer statePusher(view(), this, IntSize(x(), y()), hasTransform() || hasReflection() || style()->isFlippedBlocksWritingMode()); + + int previousWidth = width(); + int previousHeight = height(); + + computeLogicalWidth(); + computeLogicalHeight(); + + m_overflow.clear(); + + if (previousWidth != width() || previousHeight != height() || + (parent()->isFlexibleBox() && parent()->style()->boxOrient() == HORIZONTAL && + parent()->style()->boxAlign() == BSTRETCH)) + relayoutChildren = true; + +#ifdef ANDROID_LAYOUT + checkAndSetRelayoutChildren(&relayoutChildren); +#endif + setHeight(0); + + m_flexingChildren = m_stretchingChildren = false; + + initMaxMarginValues(); + + // For overflow:scroll blocks, ensure we have both scrollbars in place always. + if (scrollsOverflow()) { + if (style()->overflowX() == OSCROLL) + layer()->setHasHorizontalScrollbar(true); + if (style()->overflowY() == OSCROLL) + layer()->setHasVerticalScrollbar(true); + } + + if (isHorizontal()) + layoutHorizontalBox(relayoutChildren); + else + layoutVerticalBox(relayoutChildren); + + int oldClientAfterEdge = clientLogicalBottom(); + computeLogicalHeight(); + + if (previousHeight != height()) + relayoutChildren = true; + + layoutPositionedObjects(relayoutChildren || isRoot()); + + if (!isFloatingOrPositioned() && height() == 0) { + // We are a block with no border and padding and a computed height + // of 0. The CSS spec states that zero-height blocks collapse their margins + // together. + // When blocks are self-collapsing, we just use the top margin values and set the + // bottom margin max values to 0. This way we don't factor in the values + // twice when we collapse with our previous vertically adjacent and + // following vertically adjacent blocks. + int pos = maxPositiveMarginBefore(); + int neg = maxNegativeMarginBefore(); + if (maxPositiveMarginAfter() > pos) + pos = maxPositiveMarginAfter(); + if (maxNegativeMarginAfter() > neg) + neg = maxNegativeMarginAfter(); + setMaxMarginBeforeValues(pos, neg); + setMaxMarginAfterValues(0, 0); + } + + computeOverflow(oldClientAfterEdge); + + statePusher.pop(); + + updateLayerTransform(); + + if (view()->layoutState()->pageLogicalHeight()) + setPageLogicalOffset(view()->layoutState()->pageLogicalOffset(y())); + + // Update our scrollbars if we're overflow:auto/scroll/hidden now that we know if + // we overflow or not. + if (hasOverflowClip()) + layer()->updateScrollInfoAfterLayout(); + + // Repaint with our new bounds if they are different from our old bounds. + repainter.repaintAfterLayout(); + + setNeedsLayout(false); +} + +// The first walk over our kids is to find out if we have any flexible children. +static void gatherFlexChildrenInfo(FlexBoxIterator& iterator, bool relayoutChildren, unsigned int& highestFlexGroup, unsigned int& lowestFlexGroup, bool& haveFlex) +{ + RenderBox* child = iterator.first(); + while (child) { + // Check to see if this child flexes. + if (!child->isPositioned() && child->style()->boxFlex() > 0.0f) { + // We always have to lay out flexible objects again, since the flex distribution + // may have changed, and we need to reallocate space. + child->setOverrideSize(-1); + if (!relayoutChildren) + child->setChildNeedsLayout(true, false); + haveFlex = true; + unsigned int flexGroup = child->style()->boxFlexGroup(); + if (lowestFlexGroup == 0) + lowestFlexGroup = flexGroup; + if (flexGroup < lowestFlexGroup) + lowestFlexGroup = flexGroup; + if (flexGroup > highestFlexGroup) + highestFlexGroup = flexGroup; + } + child = iterator.next(); + } +} + +void RenderFlexibleBox::layoutHorizontalBox(bool relayoutChildren) +{ + int toAdd = borderBottom() + paddingBottom() + horizontalScrollbarHeight(); + int yPos = borderTop() + paddingTop(); + int xPos = borderLeft() + paddingLeft(); + bool heightSpecified = false; + int oldHeight = 0; + + int remainingSpace = 0; + + + FlexBoxIterator iterator(this); + unsigned int highestFlexGroup = 0; + unsigned int lowestFlexGroup = 0; + bool haveFlex = false; + gatherFlexChildrenInfo(iterator, relayoutChildren, highestFlexGroup, lowestFlexGroup, haveFlex); + + RenderBox* child; + + RenderBlock::startDelayUpdateScrollInfo(); + + // We do 2 passes. The first pass is simply to lay everyone out at + // their preferred widths. The second pass handles flexing the children. + do { + // Reset our height. + setHeight(yPos); + + xPos = borderLeft() + paddingLeft(); + + // Our first pass is done without flexing. We simply lay the children + // out within the box. We have to do a layout first in order to determine + // our box's intrinsic height. + int maxAscent = 0, maxDescent = 0; + child = iterator.first(); + while (child) { + // make sure we relayout children if we need it. + if (relayoutChildren || (child->isReplaced() && (child->style()->width().isPercent() || child->style()->height().isPercent()))) + child->setChildNeedsLayout(true, false); + + if (child->isPositioned()) { + child = iterator.next(); + continue; + } + + // Compute the child's vertical margins. + child->computeBlockDirectionMargins(this); + + if (!child->needsLayout()) + child->markForPaginationRelayoutIfNeeded(); + + // Now do the layout. + child->layoutIfNeeded(); + + // Update our height and overflow height. + if (style()->boxAlign() == BBASELINE) { + int ascent = child->firstLineBoxBaseline(); + if (ascent == -1) + ascent = child->height() + child->marginBottom(); + ascent += child->marginTop(); + int descent = (child->marginTop() + child->height() + child->marginBottom()) - ascent; + + // Update our maximum ascent. + maxAscent = max(maxAscent, ascent); + + // Update our maximum descent. + maxDescent = max(maxDescent, descent); + + // Now update our height. + setHeight(max(yPos + maxAscent + maxDescent, height())); + } + else + setHeight(max(height(), yPos + child->marginTop() + child->height() + child->marginBottom())); + + child = iterator.next(); + } + + if (!iterator.first() && hasLineIfEmpty()) + setHeight(height() + lineHeight(true, style()->isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes)); + + setHeight(height() + toAdd); + + oldHeight = height(); + computeLogicalHeight(); + + relayoutChildren = false; + if (oldHeight != height()) + heightSpecified = true; + + // Now that our height is actually known, we can place our boxes. + m_stretchingChildren = (style()->boxAlign() == BSTRETCH); + child = iterator.first(); + while (child) { + if (child->isPositioned()) { + child->containingBlock()->insertPositionedObject(child); + if (child->style()->hasStaticX()) { + if (style()->isLeftToRightDirection()) + child->layer()->setStaticX(xPos); + else child->layer()->setStaticX(width() - xPos); + } + if (child->style()->hasStaticY()) { + RenderLayer* childLayer = child->layer(); + if (childLayer->staticY() != yPos) { + child->layer()->setStaticY(yPos); + child->setChildNeedsLayout(true, false); + } + } + child = iterator.next(); + continue; + } + + // We need to see if this child's height has changed, since we make block elements + // fill the height of a containing box by default. + // Now do a layout. + int oldChildHeight = child->height(); + child->computeLogicalHeight(); + if (oldChildHeight != child->height()) + child->setChildNeedsLayout(true, false); + + if (!child->needsLayout()) + child->markForPaginationRelayoutIfNeeded(); + + child->layoutIfNeeded(); + + // We can place the child now, using our value of box-align. + xPos += child->marginLeft(); + int childY = yPos; + switch (style()->boxAlign()) { + case BCENTER: + childY += child->marginTop() + max(0, (contentHeight() - (child->height() + child->marginTop() + child->marginBottom()))/2); + break; + case BBASELINE: { + int ascent = child->firstLineBoxBaseline(); + if (ascent == -1) + ascent = child->height() + child->marginBottom(); + ascent += child->marginTop(); + childY += child->marginTop() + (maxAscent - ascent); + break; + } + case BEND: + childY += contentHeight() - child->marginBottom() - child->height(); + break; + default: // BSTART + childY += child->marginTop(); + break; + } + + placeChild(child, xPos, childY); + + xPos += child->width() + child->marginRight(); + + child = iterator.next(); + } + + remainingSpace = borderLeft() + paddingLeft() + contentWidth() - xPos; + + m_stretchingChildren = false; + if (m_flexingChildren) + haveFlex = false; // We're done. + else if (haveFlex) { + // We have some flexible objects. See if we need to grow/shrink them at all. + if (!remainingSpace) + break; + + // Allocate the remaining space among the flexible objects. If we are trying to + // grow, then we go from the lowest flex group to the highest flex group. For shrinking, + // we go from the highest flex group to the lowest group. + bool expanding = remainingSpace > 0; + unsigned int start = expanding ? lowestFlexGroup : highestFlexGroup; + unsigned int end = expanding? highestFlexGroup : lowestFlexGroup; + for (unsigned int i = start; i <= end && remainingSpace; i++) { + // Always start off by assuming the group can get all the remaining space. + int groupRemainingSpace = remainingSpace; + do { + // Flexing consists of multiple passes, since we have to change ratios every time an object hits its max/min-width + // For a given pass, we always start off by computing the totalFlex of all objects that can grow/shrink at all, and + // computing the allowed growth before an object hits its min/max width (and thus + // forces a totalFlex recomputation). + int groupRemainingSpaceAtBeginning = groupRemainingSpace; + float totalFlex = 0.0f; + child = iterator.first(); + while (child) { + if (allowedChildFlex(child, expanding, i)) + totalFlex += child->style()->boxFlex(); + child = iterator.next(); + } + child = iterator.first(); + int spaceAvailableThisPass = groupRemainingSpace; + while (child) { + int allowedFlex = allowedChildFlex(child, expanding, i); + if (allowedFlex) { + int projectedFlex = (allowedFlex == INT_MAX) ? allowedFlex : (int)(allowedFlex * (totalFlex / child->style()->boxFlex())); + spaceAvailableThisPass = expanding ? min(spaceAvailableThisPass, projectedFlex) : max(spaceAvailableThisPass, projectedFlex); + } + child = iterator.next(); + } + + // The flex groups may not have any flexible objects this time around. + if (!spaceAvailableThisPass || totalFlex == 0.0f) { + // If we just couldn't grow/shrink any more, then it's time to transition to the next flex group. + groupRemainingSpace = 0; + continue; + } + + // Now distribute the space to objects. + child = iterator.first(); + while (child && spaceAvailableThisPass && totalFlex) { + if (allowedChildFlex(child, expanding, i)) { + int spaceAdd = (int)(spaceAvailableThisPass * (child->style()->boxFlex()/totalFlex)); + if (spaceAdd) { + child->setOverrideSize(child->overrideWidth() + spaceAdd); + m_flexingChildren = true; + relayoutChildren = true; + } + + spaceAvailableThisPass -= spaceAdd; + remainingSpace -= spaceAdd; + groupRemainingSpace -= spaceAdd; + + totalFlex -= child->style()->boxFlex(); + } + child = iterator.next(); + } + if (groupRemainingSpace == groupRemainingSpaceAtBeginning) { + // this is not advancing, avoid getting stuck by distributing the remaining pixels + child = iterator.first(); + int spaceAdd = groupRemainingSpace > 0 ? 1 : -1; + while (child && groupRemainingSpace) { + if (allowedChildFlex(child, expanding, i)) { + child->setOverrideSize(child->overrideWidth() + spaceAdd); + m_flexingChildren = true; + relayoutChildren = true; + remainingSpace -= spaceAdd; + groupRemainingSpace -= spaceAdd; + } + child = iterator.next(); + } + } + } while (groupRemainingSpace); + } + + // We didn't find any children that could grow. + if (haveFlex && !m_flexingChildren) + haveFlex = false; + } + } while (haveFlex); + + m_flexingChildren = false; + + RenderBlock::finishDelayUpdateScrollInfo(); + + if (remainingSpace > 0 && ((style()->isLeftToRightDirection() && style()->boxPack() != BSTART) + || (!style()->isLeftToRightDirection() && style()->boxPack() != BEND))) { + // Children must be repositioned. + int offset = 0; + if (style()->boxPack() == BJUSTIFY) { + // Determine the total number of children. + int totalChildren = 0; + child = iterator.first(); + while (child) { + if (child->isPositioned()) { + child = iterator.next(); + continue; + } + totalChildren++; + child = iterator.next(); + } + + // Iterate over the children and space them out according to the + // justification level. + if (totalChildren > 1) { + totalChildren--; + bool firstChild = true; + child = iterator.first(); + while (child) { + if (child->isPositioned()) { + child = iterator.next(); + continue; + } + + if (firstChild) { + firstChild = false; + child = iterator.next(); + continue; + } + + offset += remainingSpace/totalChildren; + remainingSpace -= (remainingSpace/totalChildren); + totalChildren--; + + placeChild(child, child->x()+offset, child->y()); + child = iterator.next(); + } + } + } else { + if (style()->boxPack() == BCENTER) + offset += remainingSpace/2; + else // END for LTR, START for RTL + offset += remainingSpace; + child = iterator.first(); + while (child) { + if (child->isPositioned()) { + child = iterator.next(); + continue; + } + placeChild(child, child->x()+offset, child->y()); + child = iterator.next(); + } + } + } + + // So that the computeLogicalHeight in layoutBlock() knows to relayout positioned objects because of + // a height change, we revert our height back to the intrinsic height before returning. + if (heightSpecified) + setHeight(oldHeight); +} + +void RenderFlexibleBox::layoutVerticalBox(bool relayoutChildren) +{ + int xPos = borderLeft() + paddingLeft(); + int yPos = borderTop() + paddingTop(); + if (!style()->isLeftToRightDirection()) + xPos = width() - paddingRight() - borderRight(); + int toAdd = borderBottom() + paddingBottom() + horizontalScrollbarHeight(); + bool heightSpecified = false; + int oldHeight = 0; + + int remainingSpace = 0; + + FlexBoxIterator iterator(this); + unsigned int highestFlexGroup = 0; + unsigned int lowestFlexGroup = 0; + bool haveFlex = false; + gatherFlexChildrenInfo(iterator, relayoutChildren, highestFlexGroup, lowestFlexGroup, haveFlex); + + RenderBox* child; + + // We confine the line clamp ugliness to vertical flexible boxes (thus keeping it out of + // mainstream block layout); this is not really part of the XUL box model. + bool haveLineClamp = !style()->lineClamp().isNone(); + if (haveLineClamp) + applyLineClamp(iterator, relayoutChildren); + + RenderBlock::startDelayUpdateScrollInfo(); + + // We do 2 passes. The first pass is simply to lay everyone out at + // their preferred widths. The second pass handles flexing the children. + // Our first pass is done without flexing. We simply lay the children + // out within the box. + do { + setHeight(borderTop() + paddingTop()); + int minHeight = height() + toAdd; + + child = iterator.first(); + while (child) { + // make sure we relayout children if we need it. + if (!haveLineClamp && (relayoutChildren || (child->isReplaced() && (child->style()->width().isPercent() || child->style()->height().isPercent())))) + child->setChildNeedsLayout(true, false); + + if (child->isPositioned()) { + child->containingBlock()->insertPositionedObject(child); + if (child->style()->hasStaticX()) { + if (style()->isLeftToRightDirection()) + child->layer()->setStaticX(borderLeft()+paddingLeft()); + else + child->layer()->setStaticX(borderRight()+paddingRight()); + } + if (child->style()->hasStaticY()) { + RenderLayer* childLayer = child->layer(); + if (childLayer->staticY() != height()) { + child->layer()->setStaticY(height()); + child->setChildNeedsLayout(true, false); + } + } + child = iterator.next(); + continue; + } + + // Compute the child's vertical margins. + child->computeBlockDirectionMargins(this); + + // Add in the child's marginTop to our height. + setHeight(height() + child->marginTop()); + + if (!child->needsLayout()) + child->markForPaginationRelayoutIfNeeded(); + + // Now do a layout. + child->layoutIfNeeded(); + + // We can place the child now, using our value of box-align. + int childX = borderLeft() + paddingLeft(); + switch (style()->boxAlign()) { + case BCENTER: + case BBASELINE: // Baseline just maps to center for vertical boxes + childX += child->marginLeft() + max(0, (contentWidth() - (child->width() + child->marginLeft() + child->marginRight()))/2); + break; + case BEND: + if (!style()->isLeftToRightDirection()) + childX += child->marginLeft(); + else + childX += contentWidth() - child->marginRight() - child->width(); + break; + default: // BSTART/BSTRETCH + if (style()->isLeftToRightDirection()) + childX += child->marginLeft(); + else + childX += contentWidth() - child->marginRight() - child->width(); + break; + } + + // Place the child. + placeChild(child, childX, height()); + setHeight(height() + child->height() + child->marginBottom()); + + child = iterator.next(); + } + + yPos = height(); + + if (!iterator.first() && hasLineIfEmpty()) + setHeight(height() + lineHeight(true, style()->isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes)); + + setHeight(height() + toAdd); + + // Negative margins can cause our height to shrink below our minimal height (border/padding). + // If this happens, ensure that the computed height is increased to the minimal height. + if (height() < minHeight) + setHeight(minHeight); + + // Now we have to calc our height, so we know how much space we have remaining. + oldHeight = height(); + computeLogicalHeight(); + if (oldHeight != height()) + heightSpecified = true; + + remainingSpace = borderTop() + paddingTop() + contentHeight() - yPos; + + if (m_flexingChildren) + haveFlex = false; // We're done. + else if (haveFlex) { + // We have some flexible objects. See if we need to grow/shrink them at all. + if (!remainingSpace) + break; + + // Allocate the remaining space among the flexible objects. If we are trying to + // grow, then we go from the lowest flex group to the highest flex group. For shrinking, + // we go from the highest flex group to the lowest group. + bool expanding = remainingSpace > 0; + unsigned int start = expanding ? lowestFlexGroup : highestFlexGroup; + unsigned int end = expanding? highestFlexGroup : lowestFlexGroup; + for (unsigned int i = start; i <= end && remainingSpace; i++) { + // Always start off by assuming the group can get all the remaining space. + int groupRemainingSpace = remainingSpace; + do { + // Flexing consists of multiple passes, since we have to change ratios every time an object hits its max/min-width + // For a given pass, we always start off by computing the totalFlex of all objects that can grow/shrink at all, and + // computing the allowed growth before an object hits its min/max width (and thus + // forces a totalFlex recomputation). + int groupRemainingSpaceAtBeginning = groupRemainingSpace; + float totalFlex = 0.0f; + child = iterator.first(); + while (child) { + if (allowedChildFlex(child, expanding, i)) + totalFlex += child->style()->boxFlex(); + child = iterator.next(); + } + child = iterator.first(); + int spaceAvailableThisPass = groupRemainingSpace; + while (child) { + int allowedFlex = allowedChildFlex(child, expanding, i); + if (allowedFlex) { + int projectedFlex = (allowedFlex == INT_MAX) ? allowedFlex : (int)(allowedFlex * (totalFlex / child->style()->boxFlex())); + spaceAvailableThisPass = expanding ? min(spaceAvailableThisPass, projectedFlex) : max(spaceAvailableThisPass, projectedFlex); + } + child = iterator.next(); + } + + // The flex groups may not have any flexible objects this time around. + if (!spaceAvailableThisPass || totalFlex == 0.0f) { + // If we just couldn't grow/shrink any more, then it's time to transition to the next flex group. + groupRemainingSpace = 0; + continue; + } + + // Now distribute the space to objects. + child = iterator.first(); + while (child && spaceAvailableThisPass && totalFlex) { + if (allowedChildFlex(child, expanding, i)) { + int spaceAdd = (int)(spaceAvailableThisPass * (child->style()->boxFlex()/totalFlex)); + if (spaceAdd) { + child->setOverrideSize(child->overrideHeight() + spaceAdd); + m_flexingChildren = true; + relayoutChildren = true; + } + + spaceAvailableThisPass -= spaceAdd; + remainingSpace -= spaceAdd; + groupRemainingSpace -= spaceAdd; + + totalFlex -= child->style()->boxFlex(); + } + child = iterator.next(); + } + if (groupRemainingSpace == groupRemainingSpaceAtBeginning) { + // this is not advancing, avoid getting stuck by distributing the remaining pixels + child = iterator.first(); + int spaceAdd = groupRemainingSpace > 0 ? 1 : -1; + while (child && groupRemainingSpace) { + if (allowedChildFlex(child, expanding, i)) { + child->setOverrideSize(child->overrideHeight() + spaceAdd); + m_flexingChildren = true; + relayoutChildren = true; + remainingSpace -= spaceAdd; + groupRemainingSpace -= spaceAdd; + } + child = iterator.next(); + } + } + } while (groupRemainingSpace); + } + + // We didn't find any children that could grow. + if (haveFlex && !m_flexingChildren) + haveFlex = false; + } + } while (haveFlex); + + RenderBlock::finishDelayUpdateScrollInfo(); + + if (style()->boxPack() != BSTART && remainingSpace > 0) { + // Children must be repositioned. + int offset = 0; + if (style()->boxPack() == BJUSTIFY) { + // Determine the total number of children. + int totalChildren = 0; + child = iterator.first(); + while (child) { + if (child->isPositioned()) { + child = iterator.next(); + continue; + } + totalChildren++; + child = iterator.next(); + } + + // Iterate over the children and space them out according to the + // justification level. + if (totalChildren > 1) { + totalChildren--; + bool firstChild = true; + child = iterator.first(); + while (child) { + if (child->isPositioned()) { + child = iterator.next(); + continue; + } + + if (firstChild) { + firstChild = false; + child = iterator.next(); + continue; + } + + offset += remainingSpace/totalChildren; + remainingSpace -= (remainingSpace/totalChildren); + totalChildren--; + placeChild(child, child->x(), child->y()+offset); + child = iterator.next(); + } + } + } else { + if (style()->boxPack() == BCENTER) + offset += remainingSpace/2; + else // END + offset += remainingSpace; + child = iterator.first(); + while (child) { + if (child->isPositioned()) { + child = iterator.next(); + continue; + } + placeChild(child, child->x(), child->y()+offset); + child = iterator.next(); + } + } + } + + // So that the computeLogicalHeight in layoutBlock() knows to relayout positioned objects because of + // a height change, we revert our height back to the intrinsic height before returning. + if (heightSpecified) + setHeight(oldHeight); +} + +void RenderFlexibleBox::applyLineClamp(FlexBoxIterator& iterator, bool relayoutChildren) +{ + int maxLineCount = 0; + for (RenderBox* child = iterator.first(); child; child = iterator.next()) { + if (child->isPositioned()) + continue; + + if (relayoutChildren || (child->isReplaced() && (child->style()->width().isPercent() || child->style()->height().isPercent())) + || (child->style()->height().isAuto() && child->isBlockFlow())) { + child->setChildNeedsLayout(true, false); + + // Dirty all the positioned objects. + if (child->isRenderBlock()) { + toRenderBlock(child)->markPositionedObjectsForLayout(); + toRenderBlock(child)->clearTruncation(); + } + } + child->layoutIfNeeded(); + if (child->style()->height().isAuto() && child->isBlockFlow()) + maxLineCount = max(maxLineCount, toRenderBlock(child)->lineCount()); + } + + // Get the number of lines and then alter all block flow children with auto height to use the + // specified height. We always try to leave room for at least one line. + LineClampValue lineClamp = style()->lineClamp(); + int numVisibleLines = lineClamp.isPercentage() ? max(1, (maxLineCount + 1) * lineClamp.value() / 100) : lineClamp.value(); + if (numVisibleLines >= maxLineCount) + return; + + for (RenderBox* child = iterator.first(); child; child = iterator.next()) { + if (child->isPositioned() || !child->style()->height().isAuto() || !child->isBlockFlow()) + continue; + + RenderBlock* blockChild = toRenderBlock(child); + int lineCount = blockChild->lineCount(); + if (lineCount <= numVisibleLines) + continue; + + int newHeight = blockChild->heightForLineCount(numVisibleLines); + if (newHeight == child->height()) + continue; + + child->setChildNeedsLayout(true, false); + child->setOverrideSize(newHeight); + m_flexingChildren = true; + child->layoutIfNeeded(); + m_flexingChildren = false; + child->setOverrideSize(-1); + + // FIXME: For now don't support RTL. + if (style()->direction() != LTR) + continue; + + // Get the last line + RootInlineBox* lastLine = blockChild->lineAtIndex(lineCount-1); + if (!lastLine) + continue; + + RootInlineBox* lastVisibleLine = blockChild->lineAtIndex(numVisibleLines-1); + if (!lastVisibleLine) + continue; + + const UChar ellipsisAndSpace[2] = { horizontalEllipsis, ' ' }; + DEFINE_STATIC_LOCAL(AtomicString, ellipsisAndSpaceStr, (ellipsisAndSpace, 2)); + DEFINE_STATIC_LOCAL(AtomicString, ellipsisStr, (&horizontalEllipsis, 1)); + const Font& font = style(numVisibleLines == 1)->font(); + + // Get ellipsis width, and if the last child is an anchor, it will go after the ellipsis, so add in a space and the anchor width too + int totalWidth; + InlineBox* anchorBox = lastLine->lastChild(); + if (anchorBox && anchorBox->renderer()->node() && anchorBox->renderer()->node()->isLink()) + totalWidth = anchorBox->logicalWidth() + font.width(TextRun(ellipsisAndSpace, 2)); + else { + anchorBox = 0; + totalWidth = font.width(TextRun(&horizontalEllipsis, 1)); + } + + // See if this width can be accommodated on the last visible line + RenderBlock* destBlock = toRenderBlock(lastVisibleLine->renderer()); + RenderBlock* srcBlock = toRenderBlock(lastLine->renderer()); + + // FIXME: Directions of src/destBlock could be different from our direction and from one another. + if (!srcBlock->style()->isLeftToRightDirection()) + continue; + + bool leftToRight = destBlock->style()->isLeftToRightDirection(); + if (!leftToRight) + continue; + + int blockRightEdge = destBlock->logicalRightOffsetForLine(lastVisibleLine->y(), false); + int blockLeftEdge = destBlock->logicalLeftOffsetForLine(lastVisibleLine->y(), false); + + int blockEdge = leftToRight ? blockRightEdge : blockLeftEdge; + if (!lastVisibleLine->canAccommodateEllipsis(leftToRight, blockEdge, lastVisibleLine->x() + lastVisibleLine->logicalWidth(), totalWidth)) + continue; + + // Let the truncation code kick in. + lastVisibleLine->placeEllipsis(anchorBox ? ellipsisAndSpaceStr : ellipsisStr, leftToRight, blockLeftEdge, blockRightEdge, totalWidth, anchorBox); + destBlock->setHasMarkupTruncation(true); + } +} + +void RenderFlexibleBox::placeChild(RenderBox* child, int x, int y) +{ + IntRect oldRect(child->x(), child->y() , child->width(), child->height()); + + // Place the child. + child->setLocation(x, y); + + // If the child moved, we have to repaint it as well as any floating/positioned + // descendants. An exception is if we need a layout. In this case, we know we're going to + // repaint ourselves (and the child) anyway. + if (!selfNeedsLayout() && child->checkForRepaintDuringLayout()) + child->repaintDuringLayoutIfMoved(oldRect); +} + +int RenderFlexibleBox::allowedChildFlex(RenderBox* child, bool expanding, unsigned int group) +{ + if (child->isPositioned() || child->style()->boxFlex() == 0.0f || child->style()->boxFlexGroup() != group) + return 0; + + if (expanding) { + if (isHorizontal()) { + // FIXME: For now just handle fixed values. + int maxW = INT_MAX; + int w = child->overrideWidth() - child->borderAndPaddingWidth(); + if (!child->style()->maxWidth().isUndefined() && + child->style()->maxWidth().isFixed()) + maxW = child->style()->maxWidth().value(); + else if (child->style()->maxWidth().type() == Intrinsic) + maxW = child->maxPreferredLogicalWidth(); + else if (child->style()->maxWidth().type() == MinIntrinsic) + maxW = child->minPreferredLogicalWidth(); + if (maxW == INT_MAX) + return maxW; + return max(0, maxW - w); + } else { + // FIXME: For now just handle fixed values. + int maxH = INT_MAX; + int h = child->overrideHeight() - child->borderAndPaddingHeight(); + if (!child->style()->maxHeight().isUndefined() && + child->style()->maxHeight().isFixed()) + maxH = child->style()->maxHeight().value(); + if (maxH == INT_MAX) + return maxH; + return max(0, maxH - h); + } + } + + // FIXME: For now just handle fixed values. + if (isHorizontal()) { + int minW = child->minPreferredLogicalWidth(); + int w = child->overrideWidth() - child->borderAndPaddingWidth(); + if (child->style()->minWidth().isFixed()) + minW = child->style()->minWidth().value(); + else if (child->style()->minWidth().type() == Intrinsic) + minW = child->maxPreferredLogicalWidth(); + else if (child->style()->minWidth().type() == MinIntrinsic) + minW = child->minPreferredLogicalWidth(); + + int allowedShrinkage = min(0, minW - w); + return allowedShrinkage; + } else { + if (child->style()->minHeight().isFixed()) { + int minH = child->style()->minHeight().value(); + int h = child->overrideHeight() - child->borderAndPaddingHeight(); + int allowedShrinkage = min(0, minH - h); + return allowedShrinkage; + } + } + + return 0; +} + +const char *RenderFlexibleBox::renderName() const +{ + if (isFloating()) + return "RenderFlexibleBox (floating)"; + if (isPositioned()) + return "RenderFlexibleBox (positioned)"; + if (isAnonymous()) + return "RenderFlexibleBox (generated)"; + if (isRelPositioned()) + return "RenderFlexibleBox (relative positioned)"; + return "RenderFlexibleBox"; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderFlexibleBox.h b/Source/WebCore/rendering/RenderFlexibleBox.h new file mode 100644 index 0000000..8525c29 --- /dev/null +++ b/Source/WebCore/rendering/RenderFlexibleBox.h @@ -0,0 +1,71 @@ +/* + * This file is part of the render object implementation for KHTML. + * + * Copyright (C) 2003 Apple Computer, Inc. + * + * 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. + * + */ + +#ifndef RenderFlexibleBox_h +#define RenderFlexibleBox_h + +#include "RenderBlock.h" + +namespace WebCore { + +class FlexBoxIterator; + +class RenderFlexibleBox : public RenderBlock { +public: + RenderFlexibleBox(Node*); + virtual ~RenderFlexibleBox(); + + virtual const char* renderName() const; + + virtual void computePreferredLogicalWidths(); + void calcHorizontalPrefWidths(); + void calcVerticalPrefWidths(); + + virtual void layoutBlock(bool relayoutChildren, int pageHeight); + void layoutHorizontalBox(bool relayoutChildren); + void layoutVerticalBox(bool relayoutChildren); + + virtual bool avoidsFloats() const { return true; } + + virtual bool isFlexibleBox() const { return true; } + virtual bool isFlexingChildren() const { return m_flexingChildren; } + virtual bool isStretchingChildren() const { return m_stretchingChildren; } + + void placeChild(RenderBox* child, int x, int y); + +protected: + int allowedChildFlex(RenderBox* child, bool expanding, unsigned group); + + bool hasMultipleLines() const { return style()->boxLines() == MULTIPLE; } + bool isVertical() const { return style()->boxOrient() == VERTICAL; } + bool isHorizontal() const { return style()->boxOrient() == HORIZONTAL; } + + bool m_flexingChildren : 1; + bool m_stretchingChildren : 1; + +private: + void applyLineClamp(FlexBoxIterator&, bool relayoutChildren); +}; + +} // namespace WebCore + +#endif // RenderFlexibleBox_h diff --git a/Source/WebCore/rendering/RenderForeignObject.cpp b/Source/WebCore/rendering/RenderForeignObject.cpp new file mode 100644 index 0000000..f9f6988 --- /dev/null +++ b/Source/WebCore/rendering/RenderForeignObject.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. + * Copyright (C) 2009 Google, Inc. + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) && ENABLE(SVG_FOREIGN_OBJECT) +#include "RenderForeignObject.h" + +#include "GraphicsContext.h" +#include "RenderSVGResource.h" +#include "RenderView.h" +#include "SVGForeignObjectElement.h" +#include "SVGRenderSupport.h" +#include "SVGSVGElement.h" +#include "TransformState.h" + +namespace WebCore { + +RenderForeignObject::RenderForeignObject(SVGForeignObjectElement* node) + : RenderSVGBlock(node) + , m_needsTransformUpdate(true) +{ +} + +RenderForeignObject::~RenderForeignObject() +{ +} + +void RenderForeignObject::paint(PaintInfo& paintInfo, int, int) +{ + if (paintInfo.context->paintingDisabled()) + return; + + PaintInfo childPaintInfo(paintInfo); + childPaintInfo.context->save(); + childPaintInfo.applyTransform(localTransform()); + + if (SVGRenderSupport::isOverflowHidden(this)) + childPaintInfo.context->clip(m_viewport); + + float opacity = style()->opacity(); + if (opacity < 1.0f) + childPaintInfo.context->beginTransparencyLayer(opacity); + + RenderBlock::paint(childPaintInfo, 0, 0); + + if (opacity < 1.0f) + childPaintInfo.context->endTransparencyLayer(); + + childPaintInfo.context->restore(); +} + +IntRect RenderForeignObject::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) +{ + return SVGRenderSupport::clippedOverflowRectForRepaint(this, repaintContainer); +} + +void RenderForeignObject::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& repaintRect, bool fixed) +{ + SVGRenderSupport::computeRectForRepaint(this, repaintContainer, repaintRect, fixed); +} + +const AffineTransform& RenderForeignObject::localToParentTransform() const +{ + m_localToParentTransform = localTransform(); + m_localToParentTransform.translate(m_viewport.x(), m_viewport.y()); + return m_localToParentTransform; +} + +void RenderForeignObject::computeLogicalWidth() +{ + // FIXME: Investigate in size rounding issues + setWidth(static_cast<int>(roundf(m_viewport.width()))); +} + +void RenderForeignObject::computeLogicalHeight() +{ + // FIXME: Investigate in size rounding issues + setHeight(static_cast<int>(roundf(m_viewport.height()))); +} + +void RenderForeignObject::layout() +{ + ASSERT(needsLayout()); + ASSERT(!view()->layoutStateEnabled()); // RenderSVGRoot disables layoutState for the SVG rendering tree. + + LayoutRepainter repainter(*this, checkForRepaintDuringLayout()); + SVGForeignObjectElement* foreign = static_cast<SVGForeignObjectElement*>(node()); + + bool updateCachedBoundariesInParents = false; + if (m_needsTransformUpdate) { + m_localTransform = foreign->animatedLocalTransform(); + m_needsTransformUpdate = false; + updateCachedBoundariesInParents = true; + } + + FloatRect oldViewport = m_viewport; + + // Cache viewport boundaries + FloatPoint viewportLocation(foreign->x().value(foreign), foreign->y().value(foreign)); + m_viewport = FloatRect(viewportLocation, FloatSize(foreign->width().value(foreign), foreign->height().value(foreign))); + if (!updateCachedBoundariesInParents) + updateCachedBoundariesInParents = oldViewport != m_viewport; + + // Set box origin to the foreignObject x/y translation, so positioned objects in XHTML content get correct + // positions. A regular RenderBoxModelObject would pull this information from RenderStyle - in SVG those + // properties are ignored for non <svg> elements, so we mimic what happens when specifying them through CSS. + + // FIXME: Investigate in location rounding issues - only affects RenderForeignObject & RenderSVGText + setLocation(roundedIntPoint(viewportLocation)); + + bool layoutChanged = m_everHadLayout && selfNeedsLayout(); + RenderBlock::layout(); + ASSERT(!needsLayout()); + + // If our bounds changed, notify the parents. + if (updateCachedBoundariesInParents) + RenderSVGBlock::setNeedsBoundariesUpdate(); + + // Invalidate all resources of this client if our layout changed. + if (layoutChanged) + SVGResourcesCache::clientLayoutChanged(this); + + repainter.repaintAfterLayout(); +} + +bool RenderForeignObject::nodeAtFloatPoint(const HitTestRequest& request, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction) +{ + FloatPoint localPoint = localTransform().inverse().mapPoint(pointInParent); + + // Early exit if local point is not contained in clipped viewport area + if (SVGRenderSupport::isOverflowHidden(this) && !m_viewport.contains(localPoint)) + return false; + + IntPoint roundedLocalPoint = roundedIntPoint(localPoint); + return RenderBlock::nodeAtPoint(request, result, roundedLocalPoint.x(), roundedLocalPoint.y(), 0, 0, hitTestAction); +} + +bool RenderForeignObject::nodeAtPoint(const HitTestRequest&, HitTestResult&, int, int, int, int, HitTestAction) +{ + ASSERT_NOT_REACHED(); + return false; +} + +void RenderForeignObject::mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool fixed, bool useTransforms, TransformState& transformState) const +{ + // When crawling up the hierachy starting from foreignObject child content, useTransforms may not be set to true. + if (!useTransforms) + useTransforms = true; + SVGRenderSupport::mapLocalToContainer(this, repaintContainer, fixed, useTransforms, transformState); +} + +} + +#endif diff --git a/Source/WebCore/rendering/RenderForeignObject.h b/Source/WebCore/rendering/RenderForeignObject.h new file mode 100644 index 0000000..bdf96ce --- /dev/null +++ b/Source/WebCore/rendering/RenderForeignObject.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. + * Copyright (C) 2009 Google, Inc. + * + * 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. + * + */ + +#ifndef RenderForeignObject_h +#define RenderForeignObject_h + +#if ENABLE(SVG) && ENABLE(SVG_FOREIGN_OBJECT) +#include "AffineTransform.h" +#include "FloatPoint.h" +#include "RenderSVGBlock.h" + +namespace WebCore { + +class SVGForeignObjectElement; + +class RenderForeignObject : public RenderSVGBlock { +public: + explicit RenderForeignObject(SVGForeignObjectElement*); + virtual ~RenderForeignObject(); + + virtual const char* renderName() const { return "RenderForeignObject"; } + + virtual void paint(PaintInfo&, int parentX, int parentY); + + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer); + virtual void computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect&, bool fixed = false); + + virtual bool requiresLayer() const { return false; } + virtual void layout(); + + virtual FloatRect objectBoundingBox() const { return m_viewport; } + virtual FloatRect strokeBoundingBox() const { return m_viewport; } + virtual FloatRect repaintRectInLocalCoordinates() const { return m_viewport; } + + virtual bool nodeAtFloatPoint(const HitTestRequest&, HitTestResult&, const FloatPoint& pointInParent, HitTestAction); + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + virtual bool isSVGForeignObject() const { return true; } + + virtual void mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool fixed , bool useTransforms, TransformState& transformState) const; + virtual void setNeedsTransformUpdate() { m_needsTransformUpdate = true; } + + private: + virtual void computeLogicalWidth(); + virtual void computeLogicalHeight(); + + virtual const AffineTransform& localToParentTransform() const; + virtual AffineTransform localTransform() const { return m_localTransform; } + + bool m_needsTransformUpdate : 1; + FloatRect m_viewport; + AffineTransform m_localTransform; + mutable AffineTransform m_localToParentTransform; +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/RenderFrame.cpp b/Source/WebCore/rendering/RenderFrame.cpp new file mode 100644 index 0000000..4b1444b --- /dev/null +++ b/Source/WebCore/rendering/RenderFrame.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Simon Hausmann <hausmann@kde.org> + * (C) 2000 Stefan Schimanski (1Stein@gmx.de) + * Copyright (C) 2004, 2005, 2006, 2009 Apple Inc. All rights reserved. + * + * 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 "RenderFrame.h" + +#include "FrameView.h" +#include "HTMLFrameElement.h" +#include "RenderView.h" + +namespace WebCore { + +RenderFrame::RenderFrame(HTMLFrameElement* frame) + : RenderFrameBase(frame) +{ + setInline(false); +} + +FrameEdgeInfo RenderFrame::edgeInfo() const +{ + HTMLFrameElement* element = static_cast<HTMLFrameElement*>(node()); + return FrameEdgeInfo(element->noResize(), element->hasFrameBorder()); +} + +void RenderFrame::viewCleared() +{ + HTMLFrameElement* element = static_cast<HTMLFrameElement*>(node()); + if (!element || !widget() || !widget()->isFrameView()) + return; + + FrameView* view = static_cast<FrameView*>(widget()); + + int marginWidth = element->marginWidth(); + int marginHeight = element->marginHeight(); + + if (marginWidth != -1) + view->setMarginWidth(marginWidth); + if (marginHeight != -1) + view->setMarginHeight(marginHeight); +} + +#ifdef ANDROID_FLATTEN_FRAMESET +void RenderFrame::layout() +{ + FrameView* view = static_cast<FrameView*>(widget()); + RenderView* root = view ? view->frame()->contentRenderer() : 0; + if (!width() || !height() || !root) { + setNeedsLayout(false); + return; + } + + HTMLFrameElementBase* element = static_cast<HTMLFrameElementBase*>(node()); + if (element->scrollingMode() == ScrollbarAlwaysOff && !root->isFrameSet()) { + setNeedsLayout(false); + return; + } + + int layoutWidth = width(); + + setWidth(max(view->contentsWidth() + borderAndPaddingWidth(), width())); + setHeight(max(view->contentsHeight() + borderAndPaddingHeight(), height())); + + // Trigger a layout of the FrameView which will schedule a relayout of this RenderFrame. + if (layoutWidth < width()) + view->layout(); + + setNeedsLayout(false); +} +#endif + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderFrame.h b/Source/WebCore/rendering/RenderFrame.h new file mode 100644 index 0000000..a7a859f --- /dev/null +++ b/Source/WebCore/rendering/RenderFrame.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Simon Hausmann <hausmann@kde.org> + * Copyright (C) 2006, 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderFrame_h +#define RenderFrame_h + +#include "RenderFrameBase.h" +#include "RenderFrameSet.h" + +namespace WebCore { + +class HTMLFrameElement; + +class RenderFrame : public RenderFrameBase { +public: + explicit RenderFrame(HTMLFrameElement*); + + FrameEdgeInfo edgeInfo() const; + +private: + virtual const char* renderName() const { return "RenderFrame"; } + virtual bool isFrame() const { return true; } + +#ifdef ANDROID_FLATTEN_FRAMESET + virtual void layout(); +#endif + virtual void viewCleared(); +}; + +inline RenderFrame* toRenderFrame(RenderObject* object) +{ + ASSERT(!object || object->isFrame()); + return static_cast<RenderFrame*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderFrame(const RenderFrame*); + +} // namespace WebCore + +#endif // RenderFrame_h diff --git a/Source/WebCore/rendering/RenderFrameBase.cpp b/Source/WebCore/rendering/RenderFrameBase.cpp new file mode 100644 index 0000000..b36ad3a --- /dev/null +++ b/Source/WebCore/rendering/RenderFrameBase.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderFrameBase.h" + +#include "FrameView.h" +#include "HTMLFrameElementBase.h" +#include "RenderView.h" + +namespace WebCore { + +RenderFrameBase::RenderFrameBase(Element* element) + : RenderPart(element) +{ +} + +void RenderFrameBase::layoutWithFlattening(bool fixedWidth, bool fixedHeight) +{ + FrameView* childFrameView = static_cast<FrameView*>(widget()); + RenderView* childRoot = childFrameView ? static_cast<RenderView*>(childFrameView->frame()->contentRenderer()) : 0; + + // Do not expand frames which has zero width or height + if (!width() || !height() || !childRoot) { + updateWidgetPosition(); + if (childFrameView) + childFrameView->layout(); + setNeedsLayout(false); + return; + } + + // need to update to calculate min/max correctly + updateWidgetPosition(); + if (childRoot->preferredLogicalWidthsDirty()) + childRoot->computePreferredLogicalWidths(); + + // if scrollbars are off, and the width or height are fixed + // we obey them and do not expand. With frame flattening + // no subframe much ever become scrollable. + + HTMLFrameElementBase* element = static_cast<HTMLFrameElementBase*>(node()); + bool isScrollable = element->scrollingMode() != ScrollbarAlwaysOff; + + // consider iframe inset border + int hBorder = borderLeft() + borderRight(); + int vBorder = borderTop() + borderBottom(); + + // make sure minimum preferred width is enforced + if (isScrollable || !fixedWidth) { + setWidth(max(width(), childRoot->minPreferredLogicalWidth() + hBorder)); + // update again to pass the new width to the child frame + updateWidgetPosition(); + childFrameView->layout(); + } + + // expand the frame by setting frame height = content height + if (isScrollable || !fixedHeight || childRoot->isFrameSet()) + setHeight(max(height(), childFrameView->contentsHeight() + vBorder)); + if (isScrollable || !fixedWidth || childRoot->isFrameSet()) + setWidth(max(width(), childFrameView->contentsWidth() + hBorder)); + + updateWidgetPosition(); + + ASSERT(!childFrameView->layoutPending()); + ASSERT(!childRoot->needsLayout()); + ASSERT(!childRoot->firstChild() || !childRoot->firstChild()->firstChild() || !childRoot->firstChild()->firstChild()->needsLayout()); + + setNeedsLayout(false); +} + +} diff --git a/Source/WebCore/rendering/RenderFrameBase.h b/Source/WebCore/rendering/RenderFrameBase.h new file mode 100644 index 0000000..4fad560 --- /dev/null +++ b/Source/WebCore/rendering/RenderFrameBase.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderFrameBase_h +#define RenderFrameBase_h + +#include "RenderPart.h" + +namespace WebCore { + +// Base class for RenderFrame and RenderIFrame +class RenderFrameBase : public RenderPart { +protected: + explicit RenderFrameBase(Element*); + +public: + void layoutWithFlattening(bool fixedWidth, bool fixedHeight); +}; + +} // namespace WebCore + +#endif // RenderFrameBase_h diff --git a/Source/WebCore/rendering/RenderFrameSet.cpp b/Source/WebCore/rendering/RenderFrameSet.cpp new file mode 100644 index 0000000..600d65e --- /dev/null +++ b/Source/WebCore/rendering/RenderFrameSet.cpp @@ -0,0 +1,890 @@ +/** + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Simon Hausmann <hausmann@kde.org> + * (C) 2000 Stefan Schimanski (1Stein@gmx.de) + * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + * + * 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 "RenderFrameSet.h" + +#include "Document.h" +#include "EventHandler.h" +#include "EventNames.h" +#include "Frame.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "HTMLFrameSetElement.h" +#include "HitTestRequest.h" +#include "HitTestResult.h" +#include "MouseEvent.h" +#include "RenderFrame.h" +#include "RenderView.h" +#include "Settings.h" + +namespace WebCore { + +RenderFrameSet::RenderFrameSet(HTMLFrameSetElement* frameSet) + : RenderBox(frameSet) + , m_isResizing(false) + , m_isChildResizing(false) +#ifdef ANDROID_FLATTEN_FRAMESET + , m_gridCalculated(false) +#endif +{ + setInline(false); +} + +RenderFrameSet::~RenderFrameSet() +{ +} + +RenderFrameSet::GridAxis::GridAxis() + : m_splitBeingResized(noSplit) +{ +} + +inline HTMLFrameSetElement* RenderFrameSet::frameSet() const +{ + return static_cast<HTMLFrameSetElement*>(node()); +} + +static Color borderStartEdgeColor() +{ + return Color(170, 170, 170); +} + +static Color borderEndEdgeColor() +{ + return Color::black; +} + +static Color borderFillColor() +{ + return Color(208, 208, 208); +} + +void RenderFrameSet::paintColumnBorder(const PaintInfo& paintInfo, const IntRect& borderRect) +{ + if (!paintInfo.rect.intersects(borderRect)) + return; + + // FIXME: We should do something clever when borders from distinct framesets meet at a join. + + // Fill first. + GraphicsContext* context = paintInfo.context; + ColorSpace colorSpace = style()->colorSpace(); + context->fillRect(borderRect, frameSet()->hasBorderColor() ? style()->visitedDependentColor(CSSPropertyBorderLeftColor) : borderFillColor(), colorSpace); + + // Now stroke the edges but only if we have enough room to paint both edges with a little + // bit of the fill color showing through. + if (borderRect.width() >= 3) { + context->fillRect(IntRect(borderRect.topLeft(), IntSize(1, height())), borderStartEdgeColor(), colorSpace); + context->fillRect(IntRect(borderRect.topRight(), IntSize(1, height())), borderEndEdgeColor(), colorSpace); + } +} + +void RenderFrameSet::paintRowBorder(const PaintInfo& paintInfo, const IntRect& borderRect) +{ + if (!paintInfo.rect.intersects(borderRect)) + return; + + // FIXME: We should do something clever when borders from distinct framesets meet at a join. + + // Fill first. + GraphicsContext* context = paintInfo.context; + ColorSpace colorSpace = style()->colorSpace(); + context->fillRect(borderRect, frameSet()->hasBorderColor() ? style()->visitedDependentColor(CSSPropertyBorderLeftColor) : borderFillColor(), colorSpace); + + // Now stroke the edges but only if we have enough room to paint both edges with a little + // bit of the fill color showing through. + if (borderRect.height() >= 3) { + context->fillRect(IntRect(borderRect.topLeft(), IntSize(width(), 1)), borderStartEdgeColor(), colorSpace); + context->fillRect(IntRect(borderRect.bottomLeft(), IntSize(width(), 1)), borderEndEdgeColor(), colorSpace); + } +} + +void RenderFrameSet::paint(PaintInfo& paintInfo, int tx, int ty) +{ + if (paintInfo.phase != PaintPhaseForeground) + return; + + RenderObject* child = firstChild(); + if (!child) + return; + + // Add in our offsets. + tx += x(); + ty += y(); + + int rows = frameSet()->totalRows(); + int cols = frameSet()->totalCols(); + int borderThickness = frameSet()->border(); + + int yPos = 0; + for (int r = 0; r < rows; r++) { + int xPos = 0; + for (int c = 0; c < cols; c++) { + child->paint(paintInfo, tx, ty); + xPos += m_cols.m_sizes[c]; + if (borderThickness && m_cols.m_allowBorder[c + 1]) { + paintColumnBorder(paintInfo, IntRect(tx + xPos, ty + yPos, borderThickness, height())); + xPos += borderThickness; + } + child = child->nextSibling(); + if (!child) + return; + } + yPos += m_rows.m_sizes[r]; + if (borderThickness && m_rows.m_allowBorder[r + 1]) { + paintRowBorder(paintInfo, IntRect(tx, ty + yPos, width(), borderThickness)); + yPos += borderThickness; + } + } +} + +bool RenderFrameSet::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, + int x, int y, int tx, int ty, HitTestAction action) +{ + if (action != HitTestForeground) + return false; + + bool inside = RenderBox::nodeAtPoint(request, result, x, y, tx, ty, action) + || m_isResizing; + + if (inside && frameSet()->noResize() + && !request.readOnly() && !result.innerNode()) { + result.setInnerNode(node()); + result.setInnerNonSharedNode(node()); + } + + return inside || m_isChildResizing; +} + +void RenderFrameSet::GridAxis::resize(int size) +{ + m_sizes.resize(size); + m_deltas.resize(size); + m_deltas.fill(0); + + // To track edges for resizability and borders, we need to be (size + 1). This is because a parent frameset + // may ask us for information about our left/top/right/bottom edges in order to make its own decisions about + // what to do. We are capable of tainting that parent frameset's borders, so we have to cache this info. + m_preventResize.resize(size + 1); + m_allowBorder.resize(size + 1); +} + +void RenderFrameSet::layOutAxis(GridAxis& axis, const Length* grid, int availableLen) +{ + availableLen = max(availableLen, 0); + + int* gridLayout = axis.m_sizes.data(); + + if (!grid) { + gridLayout[0] = availableLen; + return; + } + + int gridLen = axis.m_sizes.size(); + ASSERT(gridLen); + + int totalRelative = 0; + int totalFixed = 0; + int totalPercent = 0; + int countRelative = 0; + int countFixed = 0; + int countPercent = 0; + + // First we need to investigate how many columns of each type we have and + // how much space these columns are going to require. + for (int i = 0; i < gridLen; ++i) { + // Count the total length of all of the fixed columns/rows -> totalFixed + // Count the number of columns/rows which are fixed -> countFixed + if (grid[i].isFixed()) { + gridLayout[i] = max(grid[i].value(), 0); + totalFixed += gridLayout[i]; + countFixed++; + } + + // Count the total percentage of all of the percentage columns/rows -> totalPercent + // Count the number of columns/rows which are percentages -> countPercent + if (grid[i].isPercent()) { + gridLayout[i] = max(grid[i].calcValue(availableLen), 0); + totalPercent += gridLayout[i]; + countPercent++; + } + + // Count the total relative of all the relative columns/rows -> totalRelative + // Count the number of columns/rows which are relative -> countRelative + if (grid[i].isRelative()) { + totalRelative += max(grid[i].value(), 1); + countRelative++; + } + } + + int remainingLen = availableLen; + + // Fixed columns/rows are our first priority. If there is not enough space to fit all fixed + // columns/rows we need to proportionally adjust their size. + if (totalFixed > remainingLen) { + int remainingFixed = remainingLen; + + for (int i = 0; i < gridLen; ++i) { + if (grid[i].isFixed()) { + gridLayout[i] = (gridLayout[i] * remainingFixed) / totalFixed; + remainingLen -= gridLayout[i]; + } + } + } else + remainingLen -= totalFixed; + + // Percentage columns/rows are our second priority. Divide the remaining space proportionally + // over all percentage columns/rows. IMPORTANT: the size of each column/row is not relative + // to 100%, but to the total percentage. For example, if there are three columns, each of 75%, + // and the available space is 300px, each column will become 100px in width. + if (totalPercent > remainingLen) { + int remainingPercent = remainingLen; + + for (int i = 0; i < gridLen; ++i) { + if (grid[i].isPercent()) { + gridLayout[i] = (gridLayout[i] * remainingPercent) / totalPercent; + remainingLen -= gridLayout[i]; + } + } + } else + remainingLen -= totalPercent; + + // Relative columns/rows are our last priority. Divide the remaining space proportionally + // over all relative columns/rows. IMPORTANT: the relative value of 0* is treated as 1*. + if (countRelative) { + int lastRelative = 0; + int remainingRelative = remainingLen; + + for (int i = 0; i < gridLen; ++i) { + if (grid[i].isRelative()) { + gridLayout[i] = (max(grid[i].value(), 1) * remainingRelative) / totalRelative; + remainingLen -= gridLayout[i]; + lastRelative = i; + } + } + + // If we could not evenly distribute the available space of all of the relative + // columns/rows, the remainder will be added to the last column/row. + // For example: if we have a space of 100px and three columns (*,*,*), the remainder will + // be 1px and will be added to the last column: 33px, 33px, 34px. + if (remainingLen) { + gridLayout[lastRelative] += remainingLen; + remainingLen = 0; + } + } + + // If we still have some left over space we need to divide it over the already existing + // columns/rows + if (remainingLen) { + // Our first priority is to spread if over the percentage columns. The remaining + // space is spread evenly, for example: if we have a space of 100px, the columns + // definition of 25%,25% used to result in two columns of 25px. After this the + // columns will each be 50px in width. + if (countPercent && totalPercent) { + int remainingPercent = remainingLen; + int changePercent = 0; + + for (int i = 0; i < gridLen; ++i) { + if (grid[i].isPercent()) { + changePercent = (remainingPercent * gridLayout[i]) / totalPercent; + gridLayout[i] += changePercent; + remainingLen -= changePercent; + } + } + } else if (totalFixed) { + // Our last priority is to spread the remaining space over the fixed columns. + // For example if we have 100px of space and two column of each 40px, both + // columns will become exactly 50px. + int remainingFixed = remainingLen; + int changeFixed = 0; + + for (int i = 0; i < gridLen; ++i) { + if (grid[i].isFixed()) { + changeFixed = (remainingFixed * gridLayout[i]) / totalFixed; + gridLayout[i] += changeFixed; + remainingLen -= changeFixed; + } + } + } + } + + // If we still have some left over space we probably ended up with a remainder of + // a division. We cannot spread it evenly anymore. If we have any percentage + // columns/rows simply spread the remainder equally over all available percentage columns, + // regardless of their size. + if (remainingLen && countPercent) { + int remainingPercent = remainingLen; + int changePercent = 0; + + for (int i = 0; i < gridLen; ++i) { + if (grid[i].isPercent()) { + changePercent = remainingPercent / countPercent; + gridLayout[i] += changePercent; + remainingLen -= changePercent; + } + } + } + + // If we don't have any percentage columns/rows we only have fixed columns. Spread + // the remainder equally over all fixed columns/rows. + else if (remainingLen && countFixed) { + int remainingFixed = remainingLen; + int changeFixed = 0; + + for (int i = 0; i < gridLen; ++i) { + if (grid[i].isFixed()) { + changeFixed = remainingFixed / countFixed; + gridLayout[i] += changeFixed; + remainingLen -= changeFixed; + } + } + } + + // Still some left over. Add it to the last column, because it is impossible + // spread it evenly or equally. + if (remainingLen) + gridLayout[gridLen - 1] += remainingLen; + + // now we have the final layout, distribute the delta over it + bool worked = true; + int* gridDelta = axis.m_deltas.data(); + for (int i = 0; i < gridLen; ++i) { + if (gridLayout[i] && gridLayout[i] + gridDelta[i] <= 0) + worked = false; + gridLayout[i] += gridDelta[i]; + } + // if the deltas broke something, undo them + if (!worked) { + for (int i = 0; i < gridLen; ++i) + gridLayout[i] -= gridDelta[i]; + axis.m_deltas.fill(0); + } +} + +void RenderFrameSet::fillFromEdgeInfo(const FrameEdgeInfo& edgeInfo, int r, int c) +{ + if (edgeInfo.allowBorder(LeftFrameEdge)) + m_cols.m_allowBorder[c] = true; + if (edgeInfo.allowBorder(RightFrameEdge)) + m_cols.m_allowBorder[c + 1] = true; + if (edgeInfo.preventResize(LeftFrameEdge)) + m_cols.m_preventResize[c] = true; + if (edgeInfo.preventResize(RightFrameEdge)) + m_cols.m_preventResize[c + 1] = true; + + if (edgeInfo.allowBorder(TopFrameEdge)) + m_rows.m_allowBorder[r] = true; + if (edgeInfo.allowBorder(BottomFrameEdge)) + m_rows.m_allowBorder[r + 1] = true; + if (edgeInfo.preventResize(TopFrameEdge)) + m_rows.m_preventResize[r] = true; + if (edgeInfo.preventResize(BottomFrameEdge)) + m_rows.m_preventResize[r + 1] = true; +} + +void RenderFrameSet::computeEdgeInfo() +{ + m_rows.m_preventResize.fill(frameSet()->noResize()); + m_rows.m_allowBorder.fill(false); + m_cols.m_preventResize.fill(frameSet()->noResize()); + m_cols.m_allowBorder.fill(false); + + RenderObject* child = firstChild(); + if (!child) + return; + + int rows = frameSet()->totalRows(); + int cols = frameSet()->totalCols(); + for (int r = 0; r < rows; ++r) { + for (int c = 0; c < cols; ++c) { + FrameEdgeInfo edgeInfo; + if (child->isFrameSet()) + edgeInfo = toRenderFrameSet(child)->edgeInfo(); + else + edgeInfo = toRenderFrame(child)->edgeInfo(); + fillFromEdgeInfo(edgeInfo, r, c); + child = child->nextSibling(); + if (!child) + return; + } + } +} + +FrameEdgeInfo RenderFrameSet::edgeInfo() const +{ + FrameEdgeInfo result(frameSet()->noResize(), true); + + int rows = frameSet()->totalRows(); + int cols = frameSet()->totalCols(); + if (rows && cols) { + result.setPreventResize(LeftFrameEdge, m_cols.m_preventResize[0]); + result.setAllowBorder(LeftFrameEdge, m_cols.m_allowBorder[0]); + result.setPreventResize(RightFrameEdge, m_cols.m_preventResize[cols]); + result.setAllowBorder(RightFrameEdge, m_cols.m_allowBorder[cols]); + result.setPreventResize(TopFrameEdge, m_rows.m_preventResize[0]); + result.setAllowBorder(TopFrameEdge, m_rows.m_allowBorder[0]); + result.setPreventResize(BottomFrameEdge, m_rows.m_preventResize[rows]); + result.setAllowBorder(BottomFrameEdge, m_rows.m_allowBorder[rows]); + } + + return result; +} + +void RenderFrameSet::layout() +{ + ASSERT(needsLayout()); + + bool doFullRepaint = selfNeedsLayout() && checkForRepaintDuringLayout(); + IntRect oldBounds; + if (doFullRepaint) + oldBounds = absoluteClippedOverflowRect(); + + if (!parent()->isFrameSet() && !document()->printing()) { +#ifdef ANDROID_FLATTEN_FRAMESET + // Force a grid recalc. + m_gridCalculated = false; +#endif + setWidth(view()->viewWidth()); + setHeight(view()->viewHeight()); + } + + size_t cols = frameSet()->totalCols(); + size_t rows = frameSet()->totalRows(); + + if (m_rows.m_sizes.size() != rows || m_cols.m_sizes.size() != cols) { + m_rows.resize(rows); + m_cols.resize(cols); +#ifdef ANDROID_FLATTEN_FRAMESET + m_gridCalculated = false; +#endif + } + +#ifdef ANDROID_FLATTEN_FRAMESET + if (!m_gridCalculated) { + m_gridCalculated = true; + // Make all the child framesets recalculate their grid. + RenderObject* child = firstChild(); + for (; child; child = child->nextSibling()) { + if (child->isFrameSet()) + static_cast<RenderFrameSet*>(child)->setGridNeedsLayout(); + } +#endif + int borderThickness = frameSet()->border(); + layOutAxis(m_rows, frameSet()->rowLengths(), height() - (rows - 1) * borderThickness); + layOutAxis(m_cols, frameSet()->colLengths(), width() - (cols - 1) * borderThickness); +#ifdef ANDROID_FLATTEN_FRAMESET + } +#endif + + if (flattenFrameSet()) + positionFramesWithFlattening(); + else + positionFrames(); + + RenderBox::layout(); + + computeEdgeInfo(); + + if (doFullRepaint) { + view()->repaintViewRectangle(oldBounds); + IntRect newBounds = absoluteClippedOverflowRect(); + if (newBounds != oldBounds) + view()->repaintViewRectangle(newBounds); + } + + setNeedsLayout(false); +} + +void RenderFrameSet::positionFrames() +{ + RenderBox* child = firstChildBox(); + if (!child) + return; + + int rows = frameSet()->totalRows(); + int cols = frameSet()->totalCols(); + + int yPos = 0; + int borderThickness = frameSet()->border(); +#ifdef ANDROID_FLATTEN_FRAMESET + // Keep track of the maximum width of a row which will become the maximum width of the frameset. + int maxWidth = 0; + const Length* rowLengths = frameSet()->rowLengths(); + const Length* colLengths = frameSet()->colLengths(); + + for (int r = 0; r < rows && child; r++) { + int xPos = 0; + int height = m_rows.m_sizes[r]; + int rowHeight = -1; + if (rowLengths) { + Length l = rowLengths[r]; + if (l.isFixed()) + rowHeight = l.value(); + } + for (int c = 0; c < cols && child; c++) { + child->setX(xPos); + child->setY(yPos); + child->setWidth(m_cols.m_sizes[c]); + child->setHeight(height); + int colWidth = -1; + if (colLengths) { + Length l = colLengths[c]; + if (l.isFixed()) + colWidth = l.value(); + } + if (colWidth && rowHeight) { + child->setNeedsLayout(true); + child->layout(); + } else { + child->layoutIfNeeded(); + } + + ASSERT(child->width() >= m_cols.m_sizes[c]); + m_cols.m_sizes[c] = child->width(); + + height = max(child->height(), height); + xPos += child->width() + borderThickness; + child = (RenderBox*)child->nextSibling(); + } + ASSERT(height >= m_rows.m_sizes[r]); + m_rows.m_sizes[r] = height; + maxWidth = max(xPos, maxWidth); + yPos += height + borderThickness; + } + + // Compute a new width and height according to the positioning of each expanded child frame. + // Note: we subtract borderThickness because we only count borders between frames. + int newWidth = maxWidth - borderThickness; + int newHeight = yPos - borderThickness; + + // Distribute the extra width and height evenly across the grid. + int dWidth = (width() - newWidth) / cols; + int dHeight = (height() - newHeight) / rows; + if (dWidth > 0) { + int availableWidth = width() - (cols - 1) * borderThickness; + for (int c = 0; c < cols; c++) + availableWidth -= m_cols.m_sizes[c] += dWidth; + // If the extra width did not distribute evenly, add the remainder to + // the last column. + if (availableWidth) + m_cols.m_sizes[cols - 1] += availableWidth; + } + if (dHeight > 0) { + int availableHeight = height() - (rows - 1) * borderThickness; + for (int r = 0; r < rows; r++) + availableHeight -= m_rows.m_sizes[r] += dHeight; + // If the extra height did not distribute evenly, add the remainder to + // the last row. + if (availableHeight) + m_rows.m_sizes[rows - 1] += availableHeight; + } + // Ensure the rows and columns are filled by falling through to the normal + // layout + setHeight(max(height(), newHeight)); + setWidth(max(width(), newWidth)); + child = (RenderBox*)firstChild(); + yPos = 0; +#endif // ANDROID_FLATTEN_FRAMESET + + for (int r = 0; r < rows; r++) { + int xPos = 0; + int height = m_rows.m_sizes[r]; + for (int c = 0; c < cols; c++) { + child->setLocation(xPos, yPos); + int width = m_cols.m_sizes[c]; + + // has to be resized and itself resize its contents + if (width != child->width() || height != child->height()) { + child->setWidth(width); + child->setHeight(height); + child->setNeedsLayout(true); + child->layout(); + } + + xPos += width + borderThickness; + + child = child->nextSiblingBox(); + if (!child) + return; + } + yPos += height + borderThickness; + } + + // all the remaining frames are hidden to avoid ugly spurious unflowed frames + for (; child; child = child->nextSiblingBox()) { + child->setWidth(0); + child->setHeight(0); + child->setNeedsLayout(false); + } +} + +void RenderFrameSet::positionFramesWithFlattening() +{ + RenderBox* child = firstChildBox(); + if (!child) + return; + + int rows = frameSet()->totalRows(); + int cols = frameSet()->totalCols(); + + int borderThickness = frameSet()->border(); + bool repaintNeeded = false; + + // calculate frameset height based on actual content height to eliminate scrolling + bool out = false; + for (int r = 0; r < rows && !out; r++) { + int extra = 0; + int height = m_rows.m_sizes[r]; + + for (int c = 0; c < cols; c++) { + IntRect oldFrameRect = child->frameRect(); + + int width = m_cols.m_sizes[c]; + + bool fixedWidth = frameSet()->colLengths() && frameSet()->colLengths()[c].isFixed(); + bool fixedHeight = frameSet()->rowLengths() && frameSet()->rowLengths()[r].isFixed(); + + // has to be resized and itself resize its contents + if (!fixedWidth) + child->setWidth(width ? width + extra / (cols - c) : 0); + else + child->setWidth(width); + child->setHeight(height); + + child->setNeedsLayout(true); + + if (child->isFrameSet()) + toRenderFrameSet(child)->layout(); + else + toRenderFrame(child)->layoutWithFlattening(fixedWidth, fixedHeight); + + if (child->height() > m_rows.m_sizes[r]) + m_rows.m_sizes[r] = child->height(); + if (child->width() > m_cols.m_sizes[c]) + m_cols.m_sizes[c] = child->width(); + + if (child->frameRect() != oldFrameRect) + repaintNeeded = true; + + // difference between calculated frame width and the width it actually decides to have + extra += width - m_cols.m_sizes[c]; + + child = child->nextSiblingBox(); + if (!child) { + out = true; + break; + } + } + } + + int xPos = 0; + int yPos = 0; + out = false; + child = firstChildBox(); + for (int r = 0; r < rows && !out; r++) { + xPos = 0; + for (int c = 0; c < cols; c++) { + // ensure the rows and columns are filled + IntRect oldRect = child->frameRect(); + + child->setLocation(xPos, yPos); + child->setHeight(m_rows.m_sizes[r]); + child->setWidth(m_cols.m_sizes[c]); + + if (child->frameRect() != oldRect) { + repaintNeeded = true; + + // update to final size + child->setNeedsLayout(true); + if (child->isFrameSet()) + toRenderFrameSet(child)->layout(); + else + toRenderFrame(child)->layoutWithFlattening(true, true); + } + + xPos += m_cols.m_sizes[c] + borderThickness; + child = child->nextSiblingBox(); + if (!child) { + out = true; + break; + } + } + yPos += m_rows.m_sizes[r] + borderThickness; + } + + setWidth(xPos - borderThickness); + setHeight(yPos - borderThickness); + + if (repaintNeeded) + repaint(); + + // all the remaining frames are hidden to avoid ugly spurious unflowed frames + for (; child; child = child->nextSiblingBox()) { + child->setWidth(0); + child->setHeight(0); + child->setNeedsLayout(false); + } +} + +bool RenderFrameSet::flattenFrameSet() const +{ + return frame() && frame()->settings()->frameFlatteningEnabled(); +} + +void RenderFrameSet::startResizing(GridAxis& axis, int position) +{ + int split = hitTestSplit(axis, position); + if (split == noSplit || !axis.m_allowBorder[split] || axis.m_preventResize[split]) { + axis.m_splitBeingResized = noSplit; + return; + } + axis.m_splitBeingResized = split; + axis.m_splitResizeOffset = position - splitPosition(axis, split); +} + +void RenderFrameSet::continueResizing(GridAxis& axis, int position) +{ + if (needsLayout()) + return; + if (axis.m_splitBeingResized == noSplit) + return; + int currentSplitPosition = splitPosition(axis, axis.m_splitBeingResized); + int delta = (position - currentSplitPosition) - axis.m_splitResizeOffset; + if (delta == 0) + return; + axis.m_deltas[axis.m_splitBeingResized - 1] += delta; + axis.m_deltas[axis.m_splitBeingResized] -= delta; + setNeedsLayout(true); +} + +bool RenderFrameSet::userResize(MouseEvent* evt) +{ + if (flattenFrameSet()) + return false; + + if (!m_isResizing) { + if (needsLayout()) + return false; + if (evt->type() == eventNames().mousedownEvent && evt->button() == LeftButton) { + FloatPoint pos = localToAbsolute(); + startResizing(m_cols, evt->absoluteLocation().x() - pos.x()); + startResizing(m_rows, evt->absoluteLocation().y() - pos.y()); + if (m_cols.m_splitBeingResized != noSplit || m_rows.m_splitBeingResized != noSplit) { + setIsResizing(true); + return true; + } + } + } else { + if (evt->type() == eventNames().mousemoveEvent || (evt->type() == eventNames().mouseupEvent && evt->button() == LeftButton)) { + FloatPoint pos = localToAbsolute(); + continueResizing(m_cols, evt->absoluteLocation().x() - pos.x()); + continueResizing(m_rows, evt->absoluteLocation().y() - pos.y()); + if (evt->type() == eventNames().mouseupEvent && evt->button() == LeftButton) { + setIsResizing(false); + return true; + } + } + } + + return false; +} + +void RenderFrameSet::setIsResizing(bool isResizing) +{ + m_isResizing = isResizing; + for (RenderObject* ancestor = parent(); ancestor; ancestor = ancestor->parent()) { + if (ancestor->isFrameSet()) + toRenderFrameSet(ancestor)->m_isChildResizing = isResizing; + } + if (Frame* frame = this->frame()) + frame->eventHandler()->setResizingFrameSet(isResizing ? frameSet() : 0); +} + +bool RenderFrameSet::isResizingRow() const +{ + return m_isResizing && m_rows.m_splitBeingResized != noSplit; +} + +bool RenderFrameSet::isResizingColumn() const +{ + return m_isResizing && m_cols.m_splitBeingResized != noSplit; +} + +bool RenderFrameSet::canResizeRow(const IntPoint& p) const +{ + int r = hitTestSplit(m_rows, p.y()); + return r != noSplit && m_rows.m_allowBorder[r] && !m_rows.m_preventResize[r]; +} + +bool RenderFrameSet::canResizeColumn(const IntPoint& p) const +{ + int c = hitTestSplit(m_cols, p.x()); + return c != noSplit && m_cols.m_allowBorder[c] && !m_cols.m_preventResize[c]; +} + +int RenderFrameSet::splitPosition(const GridAxis& axis, int split) const +{ + if (needsLayout()) + return 0; + + int borderThickness = frameSet()->border(); + + int size = axis.m_sizes.size(); + if (!size) + return 0; + + int position = 0; + for (int i = 0; i < split && i < size; ++i) + position += axis.m_sizes[i] + borderThickness; + return position - borderThickness; +} + +int RenderFrameSet::hitTestSplit(const GridAxis& axis, int position) const +{ + if (needsLayout()) + return noSplit; + + int borderThickness = frameSet()->border(); + if (borderThickness <= 0) + return noSplit; + + size_t size = axis.m_sizes.size(); + if (!size) + return noSplit; + + int splitPosition = axis.m_sizes[0]; + for (size_t i = 1; i < size; ++i) { + if (position >= splitPosition && position < splitPosition + borderThickness) + return i; + splitPosition += borderThickness + axis.m_sizes[i]; + } + return noSplit; +} + +bool RenderFrameSet::isChildAllowed(RenderObject* child, RenderStyle*) const +{ + return child->isFrame() || child->isFrameSet(); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderFrameSet.h b/Source/WebCore/rendering/RenderFrameSet.h new file mode 100644 index 0000000..cdc7b5a --- /dev/null +++ b/Source/WebCore/rendering/RenderFrameSet.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Simon Hausmann <hausmann@kde.org> + * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderFrameSet_h +#define RenderFrameSet_h + +#include "RenderBox.h" + +namespace WebCore { + +class HTMLFrameSetElement; +class MouseEvent; + +enum FrameEdge { LeftFrameEdge, RightFrameEdge, TopFrameEdge, BottomFrameEdge }; + +struct FrameEdgeInfo { + FrameEdgeInfo(bool preventResize = false, bool allowBorder = true) + : m_preventResize(4) + , m_allowBorder(4) + { + m_preventResize.fill(preventResize); + m_allowBorder.fill(allowBorder); + } + + bool preventResize(FrameEdge edge) const { return m_preventResize[edge]; } + bool allowBorder(FrameEdge edge) const { return m_allowBorder[edge]; } + + void setPreventResize(FrameEdge edge, bool preventResize) { m_preventResize[edge] = preventResize; } + void setAllowBorder(FrameEdge edge, bool allowBorder) { m_allowBorder[edge] = allowBorder; } + +private: + Vector<bool> m_preventResize; + Vector<bool> m_allowBorder; +}; + +class RenderFrameSet : public RenderBox { +public: + RenderFrameSet(HTMLFrameSetElement*); + virtual ~RenderFrameSet(); + + const RenderObjectChildList* children() const { return &m_children; } + RenderObjectChildList* children() { return &m_children; } + + FrameEdgeInfo edgeInfo() const; + + bool userResize(MouseEvent*); + + bool isResizingRow() const; + bool isResizingColumn() const; + + bool canResizeRow(const IntPoint&) const; + bool canResizeColumn(const IntPoint&) const; + +#ifdef ANDROID_FLATTEN_FRAMESET + void setGridNeedsLayout() { m_gridCalculated = false; } +#endif + +private: + static const int noSplit = -1; + + class GridAxis : public Noncopyable { + public: + GridAxis(); + void resize(int); + Vector<int> m_sizes; + Vector<int> m_deltas; + Vector<bool> m_preventResize; + Vector<bool> m_allowBorder; + int m_splitBeingResized; + int m_splitResizeOffset; + }; + + virtual RenderObjectChildList* virtualChildren() { return children(); } + virtual const RenderObjectChildList* virtualChildren() const { return children(); } + + virtual const char* renderName() const { return "RenderFrameSet"; } + virtual bool isFrameSet() const { return true; } + + virtual void layout(); + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + virtual void paint(PaintInfo&, int tx, int ty); + virtual bool isChildAllowed(RenderObject*, RenderStyle*) const; + + inline HTMLFrameSetElement* frameSet() const; + + bool flattenFrameSet() const; + + void setIsResizing(bool); + + void layOutAxis(GridAxis&, const Length*, int availableSpace); + void computeEdgeInfo(); + void fillFromEdgeInfo(const FrameEdgeInfo& edgeInfo, int r, int c); + void positionFrames(); + void positionFramesWithFlattening(); + + int splitPosition(const GridAxis&, int split) const; + int hitTestSplit(const GridAxis&, int position) const; + + void startResizing(GridAxis&, int position); + void continueResizing(GridAxis&, int position); + + void paintRowBorder(const PaintInfo&, const IntRect&); + void paintColumnBorder(const PaintInfo&, const IntRect&); + + RenderObjectChildList m_children; + + GridAxis m_rows; + GridAxis m_cols; + + bool m_isResizing; + bool m_isChildResizing; +#ifdef ANDROID_FLATTEN_FRAMESET + bool m_gridCalculated; +#endif +}; + + +inline RenderFrameSet* toRenderFrameSet(RenderObject* object) +{ + ASSERT(!object || object->isFrameSet()); + return static_cast<RenderFrameSet*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderFrameSet(const RenderFrameSet*); + +} // namespace WebCore + +#endif // RenderFrameSet_h diff --git a/Source/WebCore/rendering/RenderFullScreen.cpp b/Source/WebCore/rendering/RenderFullScreen.cpp new file mode 100644 index 0000000..7cd452f --- /dev/null +++ b/Source/WebCore/rendering/RenderFullScreen.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if ENABLE(FULLSCREEN_API) + +#include "RenderFullScreen.h" + +#include "RenderLayer.h" + +using namespace WebCore; + +void RenderFullScreen::setAnimating(bool animating) +{ + m_isAnimating = animating; +#if ENABLE(ACCELERATED_COMPOSITING) + if (layer()) + layer()->contentChanged(RenderLayer::FullScreenChanged); +#endif +} + +PassRefPtr<RenderStyle> RenderFullScreen::createFullScreenStyle() +{ + RefPtr<RenderStyle> fullscreenStyle = RenderStyle::createDefaultStyle(); + + // Create a stacking context: + fullscreenStyle->setZIndex(0); + + fullscreenStyle->setFontDescription(FontDescription()); + fullscreenStyle->font().update(0); + + fullscreenStyle->setDisplay(BOX); + fullscreenStyle->setBoxPack(BCENTER); + fullscreenStyle->setBoxAlign(BCENTER); + fullscreenStyle->setBoxOrient(HORIZONTAL); + + fullscreenStyle->setPosition(FixedPosition); + fullscreenStyle->setWidth(Length(100.0, Percent)); + fullscreenStyle->setHeight(Length(100.0, Percent)); + fullscreenStyle->setLeft(Length(0, Fixed)); + fullscreenStyle->setTop(Length(0, Fixed)); + + fullscreenStyle->setBackgroundColor(Color::black); + + return fullscreenStyle; +} + +#endif diff --git a/Source/WebCore/rendering/RenderFullScreen.h b/Source/WebCore/rendering/RenderFullScreen.h new file mode 100644 index 0000000..52d4ee2 --- /dev/null +++ b/Source/WebCore/rendering/RenderFullScreen.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderFullScreen_h +#define RenderFullScreen_h + +#if ENABLE(FULLSCREEN_API) + +#include "RenderFlexibleBox.h" + +namespace WebCore { + +class RenderFullScreen : public RenderFlexibleBox { +public: + RenderFullScreen(Node* node) : RenderFlexibleBox(node) { setReplaced(false); setIsAnonymous(false); } + virtual bool isRenderFullScreen() const { return true; } + virtual const char* renderName() const { return "RenderFullScreen"; } + + bool isAnimating() const { return m_isAnimating; } + void setAnimating(bool); + + static PassRefPtr<RenderStyle> createFullScreenStyle(); + +protected: + bool m_isAnimating; +}; + +inline RenderFullScreen* toRenderFullScreen(RenderObject* object) +{ + ASSERT(object->isRenderFullScreen()); + return static_cast<RenderFullScreen*>(object); +} + +// This will catch anyone doing an unnecessary cast: +void toRenderFullScreen(RenderFullScreen*); +} + +#endif + +#endif diff --git a/Source/WebCore/rendering/RenderHTMLCanvas.cpp b/Source/WebCore/rendering/RenderHTMLCanvas.cpp new file mode 100644 index 0000000..68bb536 --- /dev/null +++ b/Source/WebCore/rendering/RenderHTMLCanvas.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderHTMLCanvas.h" + +#include "CanvasRenderingContext.h" +#include "Document.h" +#include "GraphicsContext.h" +#include "HTMLCanvasElement.h" +#include "HTMLNames.h" +#include "RenderView.h" +#include "FrameView.h" + +namespace WebCore { + +using namespace HTMLNames; + +RenderHTMLCanvas::RenderHTMLCanvas(HTMLCanvasElement* element) + : RenderReplaced(element, element->size()) +{ + view()->frameView()->setIsVisuallyNonEmpty(); +} + +bool RenderHTMLCanvas::requiresLayer() const +{ + if (RenderReplaced::requiresLayer()) + return true; + + HTMLCanvasElement* canvas = static_cast<HTMLCanvasElement*>(node()); + return canvas && canvas->renderingContext() && canvas->renderingContext()->isAccelerated(); +} + +void RenderHTMLCanvas::paintReplaced(PaintInfo& paintInfo, int tx, int ty) +{ + IntRect rect = contentBoxRect(); + rect.move(tx, ty); + static_cast<HTMLCanvasElement*>(node())->paint(paintInfo.context, rect); +} + +void RenderHTMLCanvas::canvasSizeChanged() +{ + IntSize canvasSize = static_cast<HTMLCanvasElement*>(node())->size(); + IntSize zoomedSize(canvasSize.width() * style()->effectiveZoom(), canvasSize.height() * style()->effectiveZoom()); + + if (zoomedSize == intrinsicSize()) + return; + + setIntrinsicSize(zoomedSize); + + if (!parent()) + return; + + if (!preferredLogicalWidthsDirty()) + setPreferredLogicalWidthsDirty(true); + + IntSize oldSize = size(); + computeLogicalWidth(); + computeLogicalHeight(); + if (oldSize == size()) + return; + + if (!selfNeedsLayout()) + setNeedsLayout(true); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderHTMLCanvas.h b/Source/WebCore/rendering/RenderHTMLCanvas.h new file mode 100644 index 0000000..2230b39 --- /dev/null +++ b/Source/WebCore/rendering/RenderHTMLCanvas.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2004, 2006, 2007, 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderHTMLCanvas_h +#define RenderHTMLCanvas_h + +#include "RenderReplaced.h" + +namespace WebCore { + +class HTMLCanvasElement; + +class RenderHTMLCanvas : public RenderReplaced { +public: + explicit RenderHTMLCanvas(HTMLCanvasElement*); + + virtual bool isCanvas() const { return true; } + virtual bool requiresLayer() const; + + void canvasSizeChanged(); + +private: + virtual const char* renderName() const { return "RenderHTMLCanvas"; } + virtual void paintReplaced(PaintInfo&, int tx, int ty); + virtual void intrinsicSizeChanged() { canvasSizeChanged(); } +}; + +inline RenderHTMLCanvas* toRenderHTMLCanvas(RenderObject* object) +{ + ASSERT(!object || !strcmp(object->renderName(), "RenderHTMLCanvas")); + return static_cast<RenderHTMLCanvas*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderHTMLCanvas(const RenderHTMLCanvas*); + +} // namespace WebCore + +#endif // RenderHTMLCanvas_h diff --git a/Source/WebCore/rendering/RenderIFrame.cpp b/Source/WebCore/rendering/RenderIFrame.cpp new file mode 100644 index 0000000..01b8a17 --- /dev/null +++ b/Source/WebCore/rendering/RenderIFrame.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderIFrame.h" + +#include "FrameView.h" +#include "HTMLNames.h" +#include "HTMLIFrameElement.h" +#include "RenderView.h" +#include "Settings.h" + +namespace WebCore { + +using namespace HTMLNames; + +RenderIFrame::RenderIFrame(Element* element) + : RenderFrameBase(element) +{ +} + +void RenderIFrame::computeLogicalHeight() +{ + RenderPart::computeLogicalHeight(); + if (!flattenFrame()) + return; + + HTMLIFrameElement* frame = static_cast<HTMLIFrameElement*>(node()); + bool isScrollable = frame->scrollingMode() != ScrollbarAlwaysOff; + + if (isScrollable || !style()->height().isFixed()) { + FrameView* view = static_cast<FrameView*>(widget()); + if (!view) + return; + int border = borderTop() + borderBottom(); + setHeight(max(height(), view->contentsHeight() + border)); + } +} + +void RenderIFrame::computeLogicalWidth() +{ + RenderPart::computeLogicalWidth(); + if (!flattenFrame()) + return; + + HTMLIFrameElement* frame = static_cast<HTMLIFrameElement*>(node()); + bool isScrollable = frame->scrollingMode() != ScrollbarAlwaysOff; + + if (isScrollable || !style()->width().isFixed()) { + FrameView* view = static_cast<FrameView*>(widget()); + if (!view) + return; + int border = borderLeft() + borderRight(); + setWidth(max(width(), view->contentsWidth() + border)); + } +} + +bool RenderIFrame::flattenFrame() +{ + if (!node() || !node()->hasTagName(iframeTag)) + return false; + + HTMLIFrameElement* element = static_cast<HTMLIFrameElement*>(node()); + bool isScrollable = element->scrollingMode() != ScrollbarAlwaysOff; + + if (!isScrollable && style()->width().isFixed() + && style()->height().isFixed()) + return false; + + Frame* frame = element->document()->frame(); + bool enabled = frame && frame->settings()->frameFlatteningEnabled(); + + if (!enabled || !frame->page()) + return false; + + FrameView* view = frame->page()->mainFrame()->view(); + if (!view) + return false; + + // Do not flatten offscreen inner frames during frame flattening. + return absoluteBoundingBoxRect().intersects(IntRect(IntPoint(0, 0), view->contentsSize())); +} + +void RenderIFrame::layout() +{ + ASSERT(needsLayout()); + + RenderPart::computeLogicalWidth(); + RenderPart::computeLogicalHeight(); + + if (flattenFrame()) { + layoutWithFlattening(style()->width().isFixed(), style()->height().isFixed()); + return; + } + + RenderPart::layout(); + + m_overflow.clear(); + addShadowOverflow(); + updateLayerTransform(); + + setNeedsLayout(false); +} + +#if USE(ACCELERATED_COMPOSITING) +bool RenderIFrame::requiresLayer() const +{ + if (RenderPart::requiresLayer()) + return true; + + return requiresAcceleratedCompositing(); +} + +bool RenderIFrame::requiresAcceleratedCompositing() const +{ + if (!node() || !node()->hasTagName(iframeTag)) + return false; + +#if PLATFORM(ANDROID) + // XXX: Bug submitted to webkit.org + // https://bugs.webkit.org/show_bug.cgi?id=52655 + if (style()->visibility() != VISIBLE) + return false; +#endif + + // If the contents of the iframe are composited, then we have to be as well. + HTMLIFrameElement* element = static_cast<HTMLIFrameElement*>(node()); + if (Document* contentDocument = element->contentDocument()) { + if (RenderView* view = contentDocument->renderView()) + return view->usesCompositing(); + } + + return false; +} +#endif + +} diff --git a/Source/WebCore/rendering/RenderIFrame.h b/Source/WebCore/rendering/RenderIFrame.h new file mode 100644 index 0000000..0bb3182 --- /dev/null +++ b/Source/WebCore/rendering/RenderIFrame.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderIFrame_h +#define RenderIFrame_h + +#include "RenderFrameBase.h" + +namespace WebCore { + +class RenderIFrame : public RenderFrameBase { +public: + explicit RenderIFrame(Element*); + +#if USE(ACCELERATED_COMPOSITING) + bool requiresAcceleratedCompositing() const; +#endif + +private: + virtual void computeLogicalHeight(); + virtual void computeLogicalWidth(); + + virtual void layout(); + +#if USE(ACCELERATED_COMPOSITING) + virtual bool requiresLayer() const; +#endif + virtual bool isRenderIFrame() const { return true; } + + virtual const char* renderName() const { return "RenderPartObject"; } // Lying for now to avoid breaking tests + + bool flattenFrame(); + +}; + +inline RenderIFrame* toRenderIFrame(RenderObject* object) +{ + ASSERT(!object || object->isRenderIFrame()); + return static_cast<RenderIFrame*>(object); +} + +inline const RenderIFrame* toRenderIFrame(const RenderObject* object) +{ + ASSERT(!object || object->isRenderIFrame()); + return static_cast<const RenderIFrame*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderIFrame(const RenderIFrame*); + + +} // namespace WebCore + +#endif // RenderIFrame_h diff --git a/Source/WebCore/rendering/RenderImage.cpp b/Source/WebCore/rendering/RenderImage.cpp new file mode 100644 index 0000000..7f4c41e --- /dev/null +++ b/Source/WebCore/rendering/RenderImage.cpp @@ -0,0 +1,572 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * (C) 2006 Allan Sandfeld Jensen (kde@carewolf.com) + * (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + * Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * 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 "RenderImage.h" + +#include "Frame.h" +#include "GraphicsContext.h" +#include "HTMLAreaElement.h" +#include "HTMLCollection.h" +#include "HTMLImageElement.h" +#include "HTMLInputElement.h" +#include "HTMLMapElement.h" +#include "HTMLNames.h" +#include "HitTestResult.h" +#include "Page.h" +#include "RenderLayer.h" +#include "RenderTheme.h" +#include "RenderView.h" +#include "SelectionController.h" +#include <wtf/CurrentTime.h> +#include <wtf/UnusedParam.h> + +#ifdef ANDROID_LAYOUT +#include "Settings.h" +#endif + +#if ENABLE(WML) +#include "WMLImageElement.h" +#include "WMLNames.h" +#endif + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +RenderImage::RenderImage(Node* node) + : RenderReplaced(node, IntSize(0, 0)) + , m_needsToSetSizeForAltText(false) +{ + updateAltText(); + + view()->frameView()->setIsVisuallyNonEmpty(); +} + +RenderImage::~RenderImage() +{ + ASSERT(m_imageResource); + m_imageResource->shutdown(); +} + +void RenderImage::setImageResource(PassOwnPtr<RenderImageResource> imageResource) +{ + ASSERT(!m_imageResource); + m_imageResource = imageResource; + m_imageResource->initialize(this); +} + +// If we'll be displaying either alt text or an image, add some padding. +static const unsigned short paddingWidth = 4; +static const unsigned short paddingHeight = 4; + +// Alt text is restricted to this maximum size, in pixels. These are +// signed integers because they are compared with other signed values. +static const int maxAltTextWidth = 1024; +static const int maxAltTextHeight = 256; + +IntSize RenderImage::imageSizeForError(CachedImage* newImage) const +{ + ASSERT_ARG(newImage, newImage); + ASSERT_ARG(newImage, newImage->image()); + + // imageSize() returns 0 for the error image. We need the true size of the + // error image, so we have to get it by grabbing image() directly. + return IntSize(paddingWidth + newImage->image()->width() * style()->effectiveZoom(), paddingHeight + newImage->image()->height() * style()->effectiveZoom()); +} + +// Sets the image height and width to fit the alt text. Returns true if the +// image size changed. +bool RenderImage::setImageSizeForAltText(CachedImage* newImage /* = 0 */) +{ + IntSize imageSize; + if (newImage && newImage->image()) + imageSize = imageSizeForError(newImage); + else if (!m_altText.isEmpty() || newImage) { + // If we'll be displaying either text or an image, add a little padding. + imageSize = IntSize(paddingWidth, paddingHeight); + } + + // we have an alt and the user meant it (its not a text we invented) + if (!m_altText.isEmpty()) { + const Font& font = style()->font(); + IntSize textSize(min(font.width(TextRun(m_altText.characters(), m_altText.length())), maxAltTextWidth), min(font.height(), maxAltTextHeight)); + imageSize = imageSize.expandedTo(textSize); + } + + if (imageSize == intrinsicSize()) + return false; + + setIntrinsicSize(imageSize); + return true; +} + +void RenderImage::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderReplaced::styleDidChange(diff, oldStyle); + if (m_needsToSetSizeForAltText) { + if (!m_altText.isEmpty() && setImageSizeForAltText(m_imageResource->cachedImage())) + imageDimensionsChanged(true /* imageSizeChanged */); + m_needsToSetSizeForAltText = false; + } +} + +void RenderImage::imageChanged(WrappedImagePtr newImage, const IntRect* rect) +{ + if (documentBeingDestroyed()) + return; + + if (hasBoxDecorations() || hasMask()) + RenderReplaced::imageChanged(newImage, rect); + + if (!m_imageResource) + return; + + if (newImage != m_imageResource->imagePtr() || !newImage) + return; + + bool imageSizeChanged = false; + + // Set image dimensions, taking into account the size of the alt text. + if (m_imageResource->errorOccurred()) { + if (!m_altText.isEmpty() && document()->isPendingStyleRecalc()) { + ASSERT(node()); + if (node()) { + m_needsToSetSizeForAltText = true; + node()->setNeedsStyleRecalc(SyntheticStyleChange); + } + return; + } + imageSizeChanged = setImageSizeForAltText(m_imageResource->cachedImage()); + } + + imageDimensionsChanged(imageSizeChanged, rect); +} + +void RenderImage::imageDimensionsChanged(bool imageSizeChanged, const IntRect* rect) +{ + bool shouldRepaint = true; + + if (m_imageResource->imageSize(style()->effectiveZoom()) != intrinsicSize() || imageSizeChanged) { + if (!m_imageResource->errorOccurred()) + setIntrinsicSize(m_imageResource->imageSize(style()->effectiveZoom())); + + // In the case of generated image content using :before/:after, we might not be in the + // render tree yet. In that case, we don't need to worry about check for layout, since we'll get a + // layout when we get added in to the render tree hierarchy later. + if (containingBlock()) { + // lets see if we need to relayout at all.. + int oldwidth = width(); + int oldheight = height(); + if (!preferredLogicalWidthsDirty()) + setPreferredLogicalWidthsDirty(true); + computeLogicalWidth(); + computeLogicalHeight(); + + if (imageSizeChanged || width() != oldwidth || height() != oldheight) { + shouldRepaint = false; + if (!selfNeedsLayout()) + setNeedsLayout(true); + } + + setWidth(oldwidth); + setHeight(oldheight); + } + } + + if (shouldRepaint) { + IntRect repaintRect; + if (rect) { + // The image changed rect is in source image coordinates (pre-zooming), + // so map from the bounds of the image to the contentsBox. + repaintRect = enclosingIntRect(mapRect(*rect, FloatRect(FloatPoint(), m_imageResource->imageSize(1.0f)), contentBoxRect())); + // Guard against too-large changed rects. + repaintRect.intersect(contentBoxRect()); + } else + repaintRect = contentBoxRect(); + + repaintRectangle(repaintRect); + +#if USE(ACCELERATED_COMPOSITING) + if (hasLayer()) { + // Tell any potential compositing layers that the image needs updating. + layer()->contentChanged(RenderLayer::ImageChanged); + } +#endif + } +} + +void RenderImage::notifyFinished(CachedResource* newImage) +{ + if (!m_imageResource) + return; + + if (documentBeingDestroyed()) + return; + +#if USE(ACCELERATED_COMPOSITING) + if (newImage == m_imageResource->cachedImage() && hasLayer()) { + // tell any potential compositing layers + // that the image is done and they can reference it directly. + layer()->contentChanged(RenderLayer::ImageChanged); + } +#else + UNUSED_PARAM(newImage); +#endif +} + +void RenderImage::paintReplaced(PaintInfo& paintInfo, int tx, int ty) +{ + int cWidth = contentWidth(); + int cHeight = contentHeight(); + int leftBorder = borderLeft(); + int topBorder = borderTop(); + int leftPad = paddingLeft(); + int topPad = paddingTop(); + + GraphicsContext* context = paintInfo.context; + + if (!m_imageResource->hasImage() || m_imageResource->errorOccurred()) { + if (paintInfo.phase == PaintPhaseSelection) + return; + + if (cWidth > 2 && cHeight > 2) { + // Draw an outline rect where the image should be. +#ifdef ANDROID_FIX // see http://b/issue?id=2052757 + context->save(); +#endif + context->setStrokeStyle(SolidStroke); + context->setStrokeColor(Color::lightGray, style()->colorSpace()); + context->setFillColor(Color::transparent, style()->colorSpace()); + context->drawRect(IntRect(tx + leftBorder + leftPad, ty + topBorder + topPad, cWidth, cHeight)); +#ifdef ANDROID_FIX // see http://b/issue?id=2052757 + context->restore(); +#endif + + bool errorPictureDrawn = false; + int imageX = 0; + int imageY = 0; + // When calculating the usable dimensions, exclude the pixels of + // the ouline rect so the error image/alt text doesn't draw on it. + int usableWidth = cWidth - 2; + int usableHeight = cHeight - 2; + + Image* image = m_imageResource->image(); + + if (m_imageResource->errorOccurred() && !image->isNull() && usableWidth >= image->width() && usableHeight >= image->height()) { + // Center the error image, accounting for border and padding. + int centerX = (usableWidth - image->width()) / 2; + if (centerX < 0) + centerX = 0; + int centerY = (usableHeight - image->height()) / 2; + if (centerY < 0) + centerY = 0; + imageX = leftBorder + leftPad + centerX + 1; + imageY = topBorder + topPad + centerY + 1; + context->drawImage(image, style()->colorSpace(), IntPoint(tx + imageX, ty + imageY)); + errorPictureDrawn = true; + } + + if (!m_altText.isEmpty()) { + String text = document()->displayStringModifiedByEncoding(m_altText); + context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace()); + int ax = tx + leftBorder + leftPad; + int ay = ty + topBorder + topPad; + const Font& font = style()->font(); + int ascent = font.ascent(); + + // Only draw the alt text if it'll fit within the content box, + // and only if it fits above the error image. + TextRun textRun(text.characters(), text.length()); + int textWidth = font.width(textRun); + if (errorPictureDrawn) { + if (usableWidth >= textWidth && font.height() <= imageY) + context->drawText(style()->font(), textRun, IntPoint(ax, ay + ascent)); + } else if (usableWidth >= textWidth && cHeight >= font.height()) + context->drawText(style()->font(), textRun, IntPoint(ax, ay + ascent)); + } + } + } else if (m_imageResource->hasImage() && cWidth > 0 && cHeight > 0) { + Image* img = m_imageResource->image(cWidth, cHeight); + if (!img || img->isNull()) + return; + +#if PLATFORM(MAC) + if (style()->highlight() != nullAtom && !paintInfo.context->paintingDisabled()) + paintCustomHighlight(tx - x(), ty - y(), style()->highlight(), true); +#endif + + IntSize contentSize(cWidth, cHeight); + IntRect rect(IntPoint(tx + leftBorder + leftPad, ty + topBorder + topPad), contentSize); + paintIntoRect(context, rect); + } +} + +void RenderImage::paint(PaintInfo& paintInfo, int tx, int ty) +{ + RenderReplaced::paint(paintInfo, tx, ty); + + if (paintInfo.phase == PaintPhaseOutline) + paintFocusRing(paintInfo, style()); +} + +void RenderImage::paintFocusRing(PaintInfo& paintInfo, const RenderStyle*) +{ + // Don't draw focus rings if printing. + if (document()->printing() || !frame()->selection()->isFocusedAndActive()) + return; + + if (paintInfo.context->paintingDisabled() && !paintInfo.context->updatingControlTints()) + return; + + HTMLMapElement* mapElement = imageMap(); + if (!mapElement) + return; + + Document* document = mapElement->document(); + if (!document) + return; + + Node* focusedNode = document->focusedNode(); + if (!focusedNode) + return; + + RefPtr<HTMLCollection> areas = mapElement->areas(); + unsigned numAreas = areas->length(); + + // FIXME: Clip the paths to the image bounding box. + for (unsigned k = 0; k < numAreas; ++k) { + HTMLAreaElement* areaElement = static_cast<HTMLAreaElement*>(areas->item(k)); + if (focusedNode != areaElement) + continue; + + RenderStyle* styleToUse = areaElement->computedStyle(); + if (theme()->supportsFocusRing(styleToUse)) + return; // The theme draws the focus ring. + paintInfo.context->drawFocusRing(areaElement->getPath(this), styleToUse->outlineWidth(), styleToUse->outlineOffset(), styleToUse->visitedDependentColor(CSSPropertyOutlineColor)); + break; + } +} + +void RenderImage::paintIntoRect(GraphicsContext* context, const IntRect& rect) +{ + if (!m_imageResource->hasImage() || m_imageResource->errorOccurred() || rect.width() <= 0 || rect.height() <= 0) + return; + + Image* img = m_imageResource->image(rect.width(), rect.height()); + if (!img || img->isNull()) + return; + + HTMLImageElement* imageElt = (node() && node()->hasTagName(imgTag)) ? static_cast<HTMLImageElement*>(node()) : 0; + CompositeOperator compositeOperator = imageElt ? imageElt->compositeOperator() : CompositeSourceOver; + bool useLowQualityScaling = shouldPaintAtLowQuality(context, m_imageResource->image(), 0, rect.size()); + context->drawImage(m_imageResource->image(rect.width(), rect.height()), style()->colorSpace(), rect, compositeOperator, useLowQualityScaling); +} + +int RenderImage::minimumReplacedHeight() const +{ + return m_imageResource->errorOccurred() ? intrinsicSize().height() : 0; +} + +HTMLMapElement* RenderImage::imageMap() const +{ + HTMLImageElement* i = node() && node()->hasTagName(imgTag) ? static_cast<HTMLImageElement*>(node()) : 0; + return i ? i->document()->getImageMap(i->fastGetAttribute(usemapAttr)) : 0; +} + +bool RenderImage::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction hitTestAction) +{ + HitTestResult tempResult(result.point(), result.topPadding(), result.rightPadding(), result.bottomPadding(), result.leftPadding()); + bool inside = RenderReplaced::nodeAtPoint(request, tempResult, x, y, tx, ty, hitTestAction); + + if (tempResult.innerNode() && node()) { + if (HTMLMapElement* map = imageMap()) { + IntRect contentBox = contentBoxRect(); + float zoom = style()->effectiveZoom(); + int mapX = lroundf((x - tx - this->x() - contentBox.x()) / zoom); + int mapY = lroundf((y - ty - this->y() - contentBox.y()) / zoom); + if (map->mapMouseEvent(mapX, mapY, contentBox.size(), tempResult)) + tempResult.setInnerNonSharedNode(node()); + } + } + + if (!inside && result.isRectBasedTest()) + result.append(tempResult); + if (inside) + result = tempResult; + return inside; +} + +void RenderImage::updateAltText() +{ + if (!node()) + return; + + if (node()->hasTagName(inputTag)) + m_altText = static_cast<HTMLInputElement*>(node())->altText(); + else if (node()->hasTagName(imgTag)) + m_altText = static_cast<HTMLImageElement*>(node())->altText(); +#if ENABLE(WML) + else if (node()->hasTagName(WMLNames::imgTag)) + m_altText = static_cast<WMLImageElement*>(node())->altText(); +#endif +} + +bool RenderImage::isLogicalWidthSpecified() const +{ + switch (style()->logicalWidth().type()) { + case Fixed: + case Percent: + return true; + case Auto: + case Relative: // FIXME: Shouldn't this case return true? + case Static: + case Intrinsic: + case MinIntrinsic: + return false; + } + ASSERT(false); + return false; +} + +bool RenderImage::isLogicalHeightSpecified() const +{ + switch (style()->logicalHeight().type()) { + case Fixed: + case Percent: + return true; + case Auto: + case Relative: // FIXME: Shouldn't this case return true? + case Static: + case Intrinsic: + case MinIntrinsic: + return false; + } + ASSERT(false); + return false; +} + +int RenderImage::computeReplacedLogicalWidth(bool includeMaxWidth) const +{ + if (m_imageResource->imageHasRelativeWidth()) + if (RenderObject* cb = isPositioned() ? container() : containingBlock()) { + if (cb->isBox()) + m_imageResource->setImageContainerSize(IntSize(toRenderBox(cb)->availableWidth(), toRenderBox(cb)->availableHeight())); + } + + int logicalWidth; + if (isLogicalWidthSpecified()) + logicalWidth = computeReplacedLogicalWidthUsing(style()->logicalWidth()); + else if (m_imageResource->usesImageContainerSize()) { + IntSize size = m_imageResource->imageSize(style()->effectiveZoom()); + logicalWidth = style()->isHorizontalWritingMode() ? size.width() : size.height(); + } else if (m_imageResource->imageHasRelativeWidth()) + logicalWidth = 0; // If the image is relatively-sized, set the width to 0 until there is a set container size. + else + logicalWidth = calcAspectRatioLogicalWidth(); + + int minLogicalWidth = computeReplacedLogicalWidthUsing(style()->logicalMinWidth()); + int maxLogicalWidth = !includeMaxWidth || style()->logicalMaxWidth().isUndefined() ? logicalWidth : computeReplacedLogicalWidthUsing(style()->logicalMaxWidth()); + +#ifdef ANDROID_LAYOUT + logicalWidth = max(minLogicalWidth, min(logicalWidth, maxLogicalWidth)); + // in SSR mode, we will fit the image to its container width + if (document()->settings()->layoutAlgorithm() == Settings::kLayoutSSR) { + int cw = containingBlockLogicalWidthForContent(); + if (cw && logicalWidth > cw) + logicalWidth = cw; + } + return logicalWidth; +#else + return max(minLogicalWidth, min(logicalWidth, maxLogicalWidth)); +#endif +} + +int RenderImage::computeReplacedLogicalHeight() const +{ + int logicalHeight; + if (isLogicalHeightSpecified()) + logicalHeight = computeReplacedLogicalHeightUsing(style()->logicalHeight()); + else if (m_imageResource->usesImageContainerSize()) { + IntSize size = m_imageResource->imageSize(style()->effectiveZoom()); + logicalHeight = style()->isHorizontalWritingMode() ? size.height() : size.width(); + } else if (m_imageResource->imageHasRelativeHeight()) + logicalHeight = 0; // If the image is relatively-sized, set the height to 0 until there is a set container size. + else + logicalHeight = calcAspectRatioLogicalHeight(); + + int minLogicalHeight = computeReplacedLogicalHeightUsing(style()->logicalMinHeight()); + int maxLogicalHeight = style()->logicalMaxHeight().isUndefined() ? logicalHeight : computeReplacedLogicalHeightUsing(style()->logicalMaxHeight()); + +#ifdef ANDROID_LAYOUT + logicalHeight = max(minLogicalHeight, min(logicalHeight, maxLogicalHeight)); + // in SSR mode, we will fit the image to its container width + if (logicalHeight && document()->settings()->layoutAlgorithm() == Settings::kLayoutSSR) { + int logicalWidth; + if (isLogicalWidthSpecified()) + logicalWidth = computeReplacedLogicalWidthUsing(style()->width()); + else + logicalWidth = calcAspectRatioLogicalWidth(); + int minLogicalWidth = computeReplacedLogicalWidthUsing(style()->minWidth()); + int maxLogicalWidth = style()->maxWidth().value() == undefinedLength ? logicalWidth : + computeReplacedLogicalWidthUsing(style()->maxWidth()); + logicalWidth = max(minLogicalWidth, min(logicalWidth, maxLogicalWidth)); + + int cw = containingBlockLogicalWidthForContent(); + if (cw && logicalWidth && logicalWidth > cw) + logicalHeight = cw * logicalHeight / logicalWidth; // preserve aspect ratio + } + return logicalHeight; +#else + return max(minLogicalHeight, min(logicalHeight, maxLogicalHeight)); +#endif +} + +int RenderImage::calcAspectRatioLogicalWidth() const +{ + int intrinsicWidth = intrinsicLogicalWidth(); + int intrinsicHeight = intrinsicLogicalHeight(); + if (!intrinsicHeight) + return 0; + if (!m_imageResource->hasImage() || m_imageResource->errorOccurred()) + return intrinsicWidth; // Don't bother scaling. + return RenderBox::computeReplacedLogicalHeight() * intrinsicWidth / intrinsicHeight; +} + +int RenderImage::calcAspectRatioLogicalHeight() const +{ + int intrinsicWidth = intrinsicLogicalWidth(); + int intrinsicHeight = intrinsicLogicalHeight(); + if (!intrinsicWidth) + return 0; + if (!m_imageResource->hasImage() || m_imageResource->errorOccurred()) + return intrinsicHeight; // Don't bother scaling. + return RenderBox::computeReplacedLogicalWidth() * intrinsicHeight / intrinsicWidth; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderImage.h b/Source/WebCore/rendering/RenderImage.h new file mode 100644 index 0000000..16ae7ec --- /dev/null +++ b/Source/WebCore/rendering/RenderImage.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2006 Allan Sandfeld Jensen (kde@carewolf.com) + * (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderImage_h +#define RenderImage_h + +#include "RenderImageResource.h" +#include "RenderReplaced.h" + +namespace WebCore { + +class HTMLMapElement; + +class RenderImage : public RenderReplaced { +public: + RenderImage(Node*); + virtual ~RenderImage(); + + void setImageResource(PassOwnPtr<RenderImageResource>); + + RenderImageResource* imageResource() { return m_imageResource.get(); } + const RenderImageResource* imageResource() const { return m_imageResource.get(); } + CachedImage* cachedImage() const { return m_imageResource ? m_imageResource->cachedImage() : 0; } + + bool setImageSizeForAltText(CachedImage* newImage = 0); + + void updateAltText(); + + HTMLMapElement* imageMap() const; + + void highQualityRepaintTimerFired(Timer<RenderImage>*); + +protected: + virtual void styleDidChange(StyleDifference, const RenderStyle*); + + virtual void imageChanged(WrappedImagePtr, const IntRect* = 0); + + virtual void paintIntoRect(GraphicsContext*, const IntRect&); + void paintFocusRing(PaintInfo&, const RenderStyle*); + virtual void paint(PaintInfo&, int tx, int ty); + + bool isLogicalWidthSpecified() const; + bool isLogicalHeightSpecified() const; + + virtual void intrinsicSizeChanged() + { + if (m_imageResource) + imageChanged(m_imageResource->imagePtr()); + } + +private: + virtual const char* renderName() const { return "RenderImage"; } + + virtual bool isImage() const { return true; } + virtual bool isRenderImage() const { return true; } + + virtual void paintReplaced(PaintInfo&, int tx, int ty); + + virtual int minimumReplacedHeight() const; + + virtual void notifyFinished(CachedResource*); + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + + virtual int computeReplacedLogicalWidth(bool includeMaxWidth = true) const; + virtual int computeReplacedLogicalHeight() const; + + IntSize imageSizeForError(CachedImage*) const; + void imageDimensionsChanged(bool imageSizeChanged, const IntRect* = 0); + + int calcAspectRatioLogicalWidth() const; + int calcAspectRatioLogicalHeight() const; + + // Text to display as long as the image isn't available. + String m_altText; + OwnPtr<RenderImageResource> m_imageResource; + bool m_needsToSetSizeForAltText; + + friend class RenderImageScaleObserver; +}; + +inline RenderImage* toRenderImage(RenderObject* object) +{ + ASSERT(!object || object->isRenderImage()); + return static_cast<RenderImage*>(object); +} + +inline const RenderImage* toRenderImage(const RenderObject* object) +{ + ASSERT(!object || object->isRenderImage()); + return static_cast<const RenderImage*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderImage(const RenderImage*); + +} // namespace WebCore + +#endif // RenderImage_h diff --git a/Source/WebCore/rendering/RenderImageResource.cpp b/Source/WebCore/rendering/RenderImageResource.cpp new file mode 100644 index 0000000..ea3ed2f --- /dev/null +++ b/Source/WebCore/rendering/RenderImageResource.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (C) 1999 Lars Knoll <knoll@kde.org> + * Copyright (C) 1999 Antti Koivisto <koivisto@kde.org> + * Copyright (C) 2000 Dirk Mueller <mueller@kde.org> + * Copyright (C) 2006 Allan Sandfeld Jensen <kde@carewolf.com> + * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com> + * Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2010 Patrick Gansterer <paroga@paroga.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 "RenderImageResource.h" + +#include "RenderImageResourceStyleImage.h" +#include "RenderObject.h" + +namespace WebCore { + +RenderImageResource::RenderImageResource() + : m_renderer(0) + , m_cachedImage(0) +{ +} + +RenderImageResource::~RenderImageResource() +{ +} + +void RenderImageResource::initialize(RenderObject* renderer) +{ + ASSERT(!m_renderer); + ASSERT(renderer); + m_renderer = renderer; +} + +void RenderImageResource::shutdown() +{ + ASSERT(m_renderer); + + if (m_cachedImage) + m_cachedImage->removeClient(m_renderer); +} + +void RenderImageResource::setCachedImage(CachedImage* newImage) +{ + ASSERT(m_renderer); + + if (m_cachedImage == newImage) + return; + + if (m_cachedImage) + m_cachedImage->removeClient(m_renderer); + m_cachedImage = newImage; + if (m_cachedImage) { + m_cachedImage->addClient(m_renderer); + if (m_cachedImage->errorOccurred()) + m_renderer->imageChanged(m_cachedImage.get()); + } +} + +void RenderImageResource::resetAnimation() +{ + ASSERT(m_renderer); + + if (!m_cachedImage) + return; + + image()->resetAnimation(); + + if (!m_renderer->needsLayout()) + m_renderer->repaint(); +} + +void RenderImageResource::setImageContainerSize(const IntSize& size) const +{ + ASSERT(m_renderer); + + if (!m_cachedImage) + return; + + m_cachedImage->setImageContainerSize(size); +} + +Image* RenderImageResource::nullImage() +{ + return Image::nullImage(); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderImageResource.h b/Source/WebCore/rendering/RenderImageResource.h new file mode 100644 index 0000000..2346712 --- /dev/null +++ b/Source/WebCore/rendering/RenderImageResource.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 1999 Lars Knoll <knoll@kde.org> + * Copyright (C) 1999 Antti Koivisto <koivisto@kde.org> + * Copyright (C) 2006 Allan Sandfeld Jensen <kde@carewolf.com> + * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com> + * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010 Patrick Gansterer <paroga@paroga.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. + * + */ + +#ifndef RenderImageResource_h +#define RenderImageResource_h + +#include "CachedImage.h" +#include "CachedResourceHandle.h" +#include "StyleImage.h" +#include <wtf/Noncopyable.h> + +namespace WebCore { + +class RenderObject; + +class RenderImageResource : public Noncopyable { +public: + virtual ~RenderImageResource(); + + static PassOwnPtr<RenderImageResource> create() + { + return adoptPtr(new RenderImageResource); + } + + virtual void initialize(RenderObject*); + virtual void shutdown(); + + void setCachedImage(CachedImage*); + CachedImage* cachedImage() const { return m_cachedImage.get(); } + virtual bool hasImage() const { return m_cachedImage; } + + void resetAnimation(); + + virtual Image* image(int /* width */ = 0, int /* height */ = 0) { return m_cachedImage ? m_cachedImage->image() : nullImage(); } + virtual bool errorOccurred() const { return m_cachedImage && m_cachedImage->errorOccurred(); } + + virtual void setImageContainerSize(const IntSize& size) const; + virtual bool usesImageContainerSize() const { return m_cachedImage ? m_cachedImage->usesImageContainerSize() : false; } + virtual bool imageHasRelativeWidth() const { return m_cachedImage ? m_cachedImage->imageHasRelativeWidth() : false; } + virtual bool imageHasRelativeHeight() const { return m_cachedImage ? m_cachedImage->imageHasRelativeHeight() : false; } + + virtual IntSize imageSize(float multiplier) const { return m_cachedImage ? m_cachedImage->imageSize(multiplier) : IntSize(); } + + virtual WrappedImagePtr imagePtr() const { return m_cachedImage.get(); } + +protected: + RenderImageResource(); + RenderObject* m_renderer; + CachedResourceHandle<CachedImage> m_cachedImage; + +private: + static Image* nullImage(); +}; + +} // namespace WebCore + +#endif // RenderImage_h diff --git a/Source/WebCore/rendering/RenderImageResourceStyleImage.cpp b/Source/WebCore/rendering/RenderImageResourceStyleImage.cpp new file mode 100644 index 0000000..7f41984 --- /dev/null +++ b/Source/WebCore/rendering/RenderImageResourceStyleImage.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 1999 Lars Knoll <knoll@kde.org> + * Copyright (C) 1999 Antti Koivisto <koivisto@kde.org> + * Copyright (C) 2000 Dirk Mueller <mueller@kde.org> + * Copyright (C) 2006 Allan Sandfeld Jensen <kde@carewolf.com> + * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com> + * Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2010 Patrick Gansterer <paroga@paroga.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 "RenderImageResourceStyleImage.h" + +#include "RenderObject.h" +#include "StyleCachedImage.h" + +namespace WebCore { + +RenderImageResourceStyleImage::RenderImageResourceStyleImage(StyleImage* styleImage) + : m_styleImage(styleImage) +{ + ASSERT(m_styleImage); +} + +RenderImageResourceStyleImage::~RenderImageResourceStyleImage() +{ +} + +void RenderImageResourceStyleImage::initialize(RenderObject* renderer) +{ + RenderImageResource::initialize(renderer); + + if (m_styleImage->isCachedImage()) + m_cachedImage = static_cast<StyleCachedImage*>(m_styleImage.get())->cachedImage(); + + m_styleImage->addClient(m_renderer); +} + +void RenderImageResourceStyleImage::shutdown() +{ + ASSERT(m_renderer); + m_styleImage->removeClient(m_renderer); + m_cachedImage = 0; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderImageResourceStyleImage.h b/Source/WebCore/rendering/RenderImageResourceStyleImage.h new file mode 100644 index 0000000..d91aaa8 --- /dev/null +++ b/Source/WebCore/rendering/RenderImageResourceStyleImage.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 1999 Lars Knoll <knoll@kde.org> + * Copyright (C) 1999 Antti Koivisto <koivisto@kde.org> + * Copyright (C) 2006 Allan Sandfeld Jensen <kde@carewolf.com> + * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com> + * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010 Patrick Gansterer <paroga@paroga.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. + * + */ + +#ifndef RenderImageResourceStyleImage_h +#define RenderImageResourceStyleImage_h + +#include "RenderImageResource.h" +#include "StyleImage.h" +#include <wtf/RefPtr.h> + +namespace WebCore { + +class RenderObject; + +class RenderImageResourceStyleImage : public RenderImageResource { +public: + virtual ~RenderImageResourceStyleImage(); + + static PassOwnPtr<RenderImageResource> create(StyleImage* styleImage) + { + return adoptPtr(new RenderImageResourceStyleImage(styleImage)); + } + virtual void initialize(RenderObject*); + virtual void shutdown(); + + virtual bool hasImage() const { return true; } + virtual Image* image(int width = 0, int height = 0) { return m_styleImage->image(m_renderer, IntSize(width, height)); } + virtual bool errorOccurred() const { return m_styleImage->errorOccurred(); } + + virtual void setImageContainerSize(const IntSize& size) const { m_styleImage->setImageContainerSize(size); } + virtual bool usesImageContainerSize() const { return m_styleImage->usesImageContainerSize(); } + virtual bool imageHasRelativeWidth() const { return m_styleImage->imageHasRelativeWidth(); } + virtual bool imageHasRelativeHeight() const { return m_styleImage->imageHasRelativeHeight(); } + + virtual IntSize imageSize(float multiplier) const { return m_styleImage->imageSize(m_renderer, multiplier); } + + virtual WrappedImagePtr imagePtr() const { return m_styleImage->data(); } + +private: + RenderImageResourceStyleImage(StyleImage*); + RefPtr<StyleImage> m_styleImage; +}; + +} // namespace WebCore + +#endif // RenderImageStyleImage_h diff --git a/Source/WebCore/rendering/RenderIndicator.cpp b/Source/WebCore/rendering/RenderIndicator.cpp new file mode 100644 index 0000000..b03dfba --- /dev/null +++ b/Source/WebCore/rendering/RenderIndicator.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * + * 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" + +#if ENABLE(PROGRESS_TAG) || ENABLE(METER_TAG) + +#include "RenderIndicator.h" + +#include "RenderTheme.h" +#include "ShadowElement.h" + +using namespace std; + +namespace WebCore { + +RenderIndicator::RenderIndicator(Node* node) + : RenderBlock(node) +{ +} + +RenderIndicator::~RenderIndicator() +{ +} + +void RenderIndicator::layout() +{ + ASSERT(needsLayout()); + + LayoutRepainter repainter(*this, checkForRepaintDuringLayout()); + computeLogicalWidth(); + computeLogicalHeight(); + layoutParts(); + repainter.repaintAfterLayout(); + setNeedsLayout(false); +} + +void RenderIndicator::updateFromElement() +{ + setNeedsLayout(true); + repaint(); +} + +} // namespace WebCore + +#endif diff --git a/Source/WebCore/rendering/RenderIndicator.h b/Source/WebCore/rendering/RenderIndicator.h new file mode 100644 index 0000000..50d819d --- /dev/null +++ b/Source/WebCore/rendering/RenderIndicator.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + * + * 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. + * + */ + +#ifndef RenderIndicator_h +#define RenderIndicator_h + +#if ENABLE(PROGRESS_TAG) || ENABLE(METER_TAG) +#include "RenderBlock.h" + +namespace WebCore { + +class RenderIndicator : public RenderBlock { +public: + RenderIndicator(Node*); + virtual ~RenderIndicator(); + +protected: + virtual void layout(); + virtual void updateFromElement(); + virtual bool requiresForcedStyleRecalcPropagation() const { return true; } + virtual bool canHaveChildren() const { return false; } + + virtual void layoutParts() = 0; +}; + +} // namespace WebCore + +#endif + +#endif // RenderIndicator_h + diff --git a/Source/WebCore/rendering/RenderInline.cpp b/Source/WebCore/rendering/RenderInline.cpp new file mode 100644 index 0000000..7466ace --- /dev/null +++ b/Source/WebCore/rendering/RenderInline.cpp @@ -0,0 +1,1155 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * + * 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 "RenderInline.h" + +#include "Chrome.h" +#include "FloatQuad.h" +#include "GraphicsContext.h" +#include "HitTestResult.h" +#include "Page.h" +#include "RenderArena.h" +#include "RenderBlock.h" +#include "RenderLayer.h" +#include "RenderTheme.h" +#include "RenderView.h" +#include "TransformState.h" +#include "VisiblePosition.h" + +#if ENABLE(DASHBOARD_SUPPORT) +#include "Frame.h" +#endif + +using namespace std; + +namespace WebCore { + +RenderInline::RenderInline(Node* node) + : RenderBoxModelObject(node) + , m_lineHeight(-1) +{ + setChildrenInline(true); +} + +void RenderInline::destroy() +{ + // Make sure to destroy anonymous children first while they are still connected to the rest of the tree, so that they will + // properly dirty line boxes that they are removed from. Effects that do :before/:after only on hover could crash otherwise. + children()->destroyLeftoverChildren(); + + // Destroy our continuation before anything other than anonymous children. + // The reason we don't destroy it before anonymous children is that they may + // have continuations of their own that are anonymous children of our continuation. + RenderBoxModelObject* continuation = this->continuation(); + if (continuation) { + continuation->destroy(); + setContinuation(0); + } + + if (!documentBeingDestroyed()) { + if (firstLineBox()) { + // We can't wait for RenderBoxModelObject::destroy to clear the selection, + // because by then we will have nuked the line boxes. + // FIXME: The SelectionController should be responsible for this when it + // is notified of DOM mutations. + if (isSelectionBorder()) + view()->clearSelection(); + + // If line boxes are contained inside a root, that means we're an inline. + // In that case, we need to remove all the line boxes so that the parent + // lines aren't pointing to deleted children. If the first line box does + // not have a parent that means they are either already disconnected or + // root lines that can just be destroyed without disconnecting. + if (firstLineBox()->parent()) { + for (InlineFlowBox* box = firstLineBox(); box; box = box->nextLineBox()) + box->remove(); + } + } else if (isInline() && parent()) + parent()->dirtyLinesFromChangedChild(this); + } + + m_lineBoxes.deleteLineBoxes(renderArena()); + + RenderBoxModelObject::destroy(); +} + +RenderInline* RenderInline::inlineElementContinuation() const +{ + RenderBoxModelObject* continuation = this->continuation(); + if (!continuation || continuation->isInline()) + return toRenderInline(continuation); + return toRenderBlock(continuation)->inlineElementContinuation(); +} + +void RenderInline::updateBoxModelInfoFromStyle() +{ + RenderBoxModelObject::updateBoxModelInfoFromStyle(); + + setInline(true); // Needed for run-ins, since run-in is considered a block display type. + + // FIXME: Support transforms and reflections on inline flows someday. + setHasTransform(false); + setHasReflection(false); +} + +void RenderInline::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBoxModelObject::styleDidChange(diff, oldStyle); + + // Ensure that all of the split inlines pick up the new style. We + // only do this if we're an inline, since we don't want to propagate + // a block's style to the other inlines. + // e.g., <font>foo <h4>goo</h4> moo</font>. The <font> inlines before + // and after the block share the same style, but the block doesn't + // need to pass its style on to anyone else. + for (RenderInline* currCont = inlineElementContinuation(); currCont; currCont = currCont->inlineElementContinuation()) { + RenderBoxModelObject* nextCont = currCont->continuation(); + currCont->setContinuation(0); + currCont->setStyle(style()); + currCont->setContinuation(nextCont); + } + + m_lineHeight = -1; + + // Update pseudos for :before and :after now. + if (!isAnonymous() && document()->usesBeforeAfterRules()) { + children()->updateBeforeAfterContent(this, BEFORE); + children()->updateBeforeAfterContent(this, AFTER); + } +} + +void RenderInline::addChild(RenderObject* newChild, RenderObject* beforeChild) +{ + if (continuation()) + return addChildToContinuation(newChild, beforeChild); + return addChildIgnoringContinuation(newChild, beforeChild); +} + +static RenderBoxModelObject* nextContinuation(RenderObject* renderer) +{ + if (renderer->isInline() && !renderer->isReplaced()) + return toRenderInline(renderer)->continuation(); + return toRenderBlock(renderer)->inlineElementContinuation(); +} + +RenderBoxModelObject* RenderInline::continuationBefore(RenderObject* beforeChild) +{ + if (beforeChild && beforeChild->parent() == this) + return this; + + RenderBoxModelObject* curr = nextContinuation(this); + RenderBoxModelObject* nextToLast = this; + RenderBoxModelObject* last = this; + while (curr) { + if (beforeChild && beforeChild->parent() == curr) { + if (curr->firstChild() == beforeChild) + return last; + return curr; + } + + nextToLast = last; + last = curr; + curr = nextContinuation(curr); + } + + if (!beforeChild && !last->firstChild()) + return nextToLast; + return last; +} + +void RenderInline::addChildIgnoringContinuation(RenderObject* newChild, RenderObject* beforeChild) +{ + // Make sure we don't append things after :after-generated content if we have it. + if (!beforeChild && isAfterContent(lastChild())) + beforeChild = lastChild(); + + if (!newChild->isInline() && !newChild->isFloatingOrPositioned()) { + // We are placing a block inside an inline. We have to perform a split of this + // inline into continuations. This involves creating an anonymous block box to hold + // |newChild|. We then make that block box a continuation of this inline. We take all of + // the children after |beforeChild| and put them in a clone of this object. + RefPtr<RenderStyle> newStyle = RenderStyle::create(); + newStyle->inheritFrom(style()); + newStyle->setDisplay(BLOCK); + + RenderBlock* newBox = new (renderArena()) RenderBlock(document() /* anonymous box */); + newBox->setStyle(newStyle.release()); + RenderBoxModelObject* oldContinuation = continuation(); + setContinuation(newBox); + + // Someone may have put a <p> inside a <q>, causing a split. When this happens, the :after content + // has to move into the inline continuation. Call updateBeforeAfterContent to ensure that our :after + // content gets properly destroyed. + bool isLastChild = (beforeChild == lastChild()); + if (document()->usesBeforeAfterRules()) + children()->updateBeforeAfterContent(this, AFTER); + if (isLastChild && beforeChild != lastChild()) + beforeChild = 0; // We destroyed the last child, so now we need to update our insertion + // point to be 0. It's just a straight append now. + + splitFlow(beforeChild, newBox, newChild, oldContinuation); + return; + } + + RenderBoxModelObject::addChild(newChild, beforeChild); + + newChild->setNeedsLayoutAndPrefWidthsRecalc(); +} + +RenderInline* RenderInline::cloneInline(RenderInline* src) +{ + RenderInline* o = new (src->renderArena()) RenderInline(src->node()); + o->setStyle(src->style()); + return o; +} + +void RenderInline::splitInlines(RenderBlock* fromBlock, RenderBlock* toBlock, + RenderBlock* middleBlock, + RenderObject* beforeChild, RenderBoxModelObject* oldCont) +{ + // Create a clone of this inline. + RenderInline* clone = cloneInline(this); + clone->setContinuation(oldCont); + + // Now take all of the children from beforeChild to the end and remove + // them from |this| and place them in the clone. + RenderObject* o = beforeChild; + while (o) { + RenderObject* tmp = o; + o = tmp->nextSibling(); + clone->addChildIgnoringContinuation(children()->removeChildNode(this, tmp), 0); + tmp->setNeedsLayoutAndPrefWidthsRecalc(); + } + + // Hook |clone| up as the continuation of the middle block. + middleBlock->setContinuation(clone); + + // We have been reparented and are now under the fromBlock. We need + // to walk up our inline parent chain until we hit the containing block. + // Once we hit the containing block we're done. + RenderBoxModelObject* curr = toRenderBoxModelObject(parent()); + RenderBoxModelObject* currChild = this; + + // FIXME: Because splitting is O(n^2) as tags nest pathologically, we cap the depth at which we're willing to clone. + // There will eventually be a better approach to this problem that will let us nest to a much + // greater depth (see bugzilla bug 13430) but for now we have a limit. This *will* result in + // incorrect rendering, but the alternative is to hang forever. + unsigned splitDepth = 1; + const unsigned cMaxSplitDepth = 200; + while (curr && curr != fromBlock) { + ASSERT(curr->isRenderInline()); + if (splitDepth < cMaxSplitDepth) { + // Create a new clone. + RenderInline* cloneChild = clone; + clone = cloneInline(toRenderInline(curr)); + + // Insert our child clone as the first child. + clone->addChildIgnoringContinuation(cloneChild, 0); + + // Hook the clone up as a continuation of |curr|. + RenderInline* inlineCurr = toRenderInline(curr); + oldCont = inlineCurr->continuation(); + inlineCurr->setContinuation(clone); + clone->setContinuation(oldCont); + + // Someone may have indirectly caused a <q> to split. When this happens, the :after content + // has to move into the inline continuation. Call updateBeforeAfterContent to ensure that the inline's :after + // content gets properly destroyed. + if (document()->usesBeforeAfterRules()) + inlineCurr->children()->updateBeforeAfterContent(inlineCurr, AFTER); + + // Now we need to take all of the children starting from the first child + // *after* currChild and append them all to the clone. + o = currChild->nextSibling(); + while (o) { + RenderObject* tmp = o; + o = tmp->nextSibling(); + clone->addChildIgnoringContinuation(inlineCurr->children()->removeChildNode(curr, tmp), 0); + tmp->setNeedsLayoutAndPrefWidthsRecalc(); + } + } + + // Keep walking up the chain. + currChild = curr; + curr = toRenderBoxModelObject(curr->parent()); + splitDepth++; + } + + // Now we are at the block level. We need to put the clone into the toBlock. + toBlock->children()->appendChildNode(toBlock, clone); + + // Now take all the children after currChild and remove them from the fromBlock + // and put them in the toBlock. + o = currChild->nextSibling(); + while (o) { + RenderObject* tmp = o; + o = tmp->nextSibling(); + toBlock->children()->appendChildNode(toBlock, fromBlock->children()->removeChildNode(fromBlock, tmp)); + } +} + +void RenderInline::splitFlow(RenderObject* beforeChild, RenderBlock* newBlockBox, + RenderObject* newChild, RenderBoxModelObject* oldCont) +{ + RenderBlock* pre = 0; + RenderBlock* block = containingBlock(); + + // Delete our line boxes before we do the inline split into continuations. + block->deleteLineBoxTree(); + + bool madeNewBeforeBlock = false; + if (block->isAnonymousBlock() && (!block->parent() || !block->parent()->createsAnonymousWrapper())) { + // We can reuse this block and make it the preBlock of the next continuation. + pre = block; + pre->removePositionedObjects(0); + block = block->containingBlock(); + } else { + // No anonymous block available for use. Make one. + pre = block->createAnonymousBlock(); + madeNewBeforeBlock = true; + } + + RenderBlock* post = block->createAnonymousBlock(); + + RenderObject* boxFirst = madeNewBeforeBlock ? block->firstChild() : pre->nextSibling(); + if (madeNewBeforeBlock) + block->children()->insertChildNode(block, pre, boxFirst); + block->children()->insertChildNode(block, newBlockBox, boxFirst); + block->children()->insertChildNode(block, post, boxFirst); + block->setChildrenInline(false); + + if (madeNewBeforeBlock) { + RenderObject* o = boxFirst; + while (o) { + RenderObject* no = o; + o = no->nextSibling(); + pre->children()->appendChildNode(pre, block->children()->removeChildNode(block, no)); + no->setNeedsLayoutAndPrefWidthsRecalc(); + } + } + + splitInlines(pre, post, newBlockBox, beforeChild, oldCont); + + // We already know the newBlockBox isn't going to contain inline kids, so avoid wasting + // time in makeChildrenNonInline by just setting this explicitly up front. + newBlockBox->setChildrenInline(false); + + // We delayed adding the newChild until now so that the |newBlockBox| would be fully + // connected, thus allowing newChild access to a renderArena should it need + // to wrap itself in additional boxes (e.g., table construction). + newBlockBox->addChild(newChild); + + // Always just do a full layout in order to ensure that line boxes (especially wrappers for images) + // get deleted properly. Because objects moves from the pre block into the post block, we want to + // make new line boxes instead of leaving the old line boxes around. + pre->setNeedsLayoutAndPrefWidthsRecalc(); + block->setNeedsLayoutAndPrefWidthsRecalc(); + post->setNeedsLayoutAndPrefWidthsRecalc(); +} + +void RenderInline::addChildToContinuation(RenderObject* newChild, RenderObject* beforeChild) +{ + RenderBoxModelObject* flow = continuationBefore(beforeChild); + ASSERT(!beforeChild || beforeChild->parent()->isRenderBlock() || beforeChild->parent()->isRenderInline()); + RenderBoxModelObject* beforeChildParent = 0; + if (beforeChild) + beforeChildParent = toRenderBoxModelObject(beforeChild->parent()); + else { + RenderBoxModelObject* cont = nextContinuation(flow); + if (cont) + beforeChildParent = cont; + else + beforeChildParent = flow; + } + + if (newChild->isFloatingOrPositioned()) + return beforeChildParent->addChildIgnoringContinuation(newChild, beforeChild); + + // A continuation always consists of two potential candidates: an inline or an anonymous + // block box holding block children. + bool childInline = newChild->isInline(); + bool bcpInline = beforeChildParent->isInline(); + bool flowInline = flow->isInline(); + + if (flow == beforeChildParent) + return flow->addChildIgnoringContinuation(newChild, beforeChild); + else { + // The goal here is to match up if we can, so that we can coalesce and create the + // minimal # of continuations needed for the inline. + if (childInline == bcpInline) + return beforeChildParent->addChildIgnoringContinuation(newChild, beforeChild); + else if (flowInline == childInline) + return flow->addChildIgnoringContinuation(newChild, 0); // Just treat like an append. + else + return beforeChildParent->addChildIgnoringContinuation(newChild, beforeChild); + } +} + +void RenderInline::paint(PaintInfo& paintInfo, int tx, int ty) +{ + m_lineBoxes.paint(this, paintInfo, tx, ty); +} + +void RenderInline::absoluteRects(Vector<IntRect>& rects, int tx, int ty) +{ + if (InlineFlowBox* curr = firstLineBox()) { + for (; curr; curr = curr->nextLineBox()) + rects.append(IntRect(tx + curr->x(), ty + curr->y(), curr->logicalWidth(), curr->logicalHeight())); + } else + rects.append(IntRect(tx, ty, 0, 0)); + + if (continuation()) { + if (continuation()->isBox()) { + RenderBox* box = toRenderBox(continuation()); + continuation()->absoluteRects(rects, + tx - containingBlock()->x() + box->x(), + ty - containingBlock()->y() + box->y()); + } else + continuation()->absoluteRects(rects, tx - containingBlock()->x(), ty - containingBlock()->y()); + } +} + +void RenderInline::absoluteQuads(Vector<FloatQuad>& quads) +{ + if (InlineFlowBox* curr = firstLineBox()) { + for (; curr; curr = curr->nextLineBox()) { + FloatRect localRect(curr->x(), curr->y(), curr->logicalWidth(), curr->logicalHeight()); + quads.append(localToAbsoluteQuad(localRect)); + } + } else + quads.append(localToAbsoluteQuad(FloatRect())); + + if (continuation()) + continuation()->absoluteQuads(quads); +} + +int RenderInline::offsetLeft() const +{ + int x = RenderBoxModelObject::offsetLeft(); + if (firstLineBox()) + x += firstLineBox()->x(); + return x; +} + +int RenderInline::offsetTop() const +{ + int y = RenderBoxModelObject::offsetTop(); + if (firstLineBox()) + y += firstLineBox()->y(); + return y; +} + +static int computeMargin(const RenderInline* renderer, const Length& margin) +{ + if (margin.isAuto()) + return 0; + if (margin.isFixed()) + return margin.value(); + if (margin.isPercent()) + return margin.calcMinValue(max(0, renderer->containingBlock()->availableLogicalWidth())); + return 0; +} + +int RenderInline::marginLeft() const +{ + if (!style()->isHorizontalWritingMode()) + return 0; + return computeMargin(this, style()->marginLeft()); +} + +int RenderInline::marginRight() const +{ + if (!style()->isHorizontalWritingMode()) + return 0; + return computeMargin(this, style()->marginRight()); +} + +int RenderInline::marginTop() const +{ + if (style()->isHorizontalWritingMode()) + return 0; + return computeMargin(this, style()->marginTop()); +} + +int RenderInline::marginBottom() const +{ + if (style()->isHorizontalWritingMode()) + return 0; + return computeMargin(this, style()->marginBottom()); +} + +int RenderInline::marginStart() const +{ + return computeMargin(this, style()->marginStart()); +} + +int RenderInline::marginEnd() const +{ + return computeMargin(this, style()->marginEnd()); +} + +const char* RenderInline::renderName() const +{ + if (isRelPositioned()) + return "RenderInline (relative positioned)"; + if (isAnonymous()) + return "RenderInline (generated)"; + if (isRunIn()) + return "RenderInline (run-in)"; + return "RenderInline"; +} + +bool RenderInline::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, + int x, int y, int tx, int ty, HitTestAction hitTestAction) +{ + return m_lineBoxes.hitTest(this, request, result, x, y, tx, ty, hitTestAction); +} + +VisiblePosition RenderInline::positionForPoint(const IntPoint& point) +{ + // FIXME: Does not deal with relative positioned inlines (should it?) + RenderBlock* cb = containingBlock(); + if (firstLineBox()) { + // This inline actually has a line box. We must have clicked in the border/padding of one of these boxes. We + // should try to find a result by asking our containing block. + return cb->positionForPoint(point); + } + + // Translate the coords from the pre-anonymous block to the post-anonymous block. + int parentBlockX = cb->x() + point.x(); + int parentBlockY = cb->y() + point.y(); + RenderBoxModelObject* c = continuation(); + while (c) { + RenderBox* contBlock = c->isInline() ? c->containingBlock() : toRenderBlock(c); + if (c->isInline() || c->firstChild()) + return c->positionForCoordinates(parentBlockX - contBlock->x(), parentBlockY - contBlock->y()); + c = toRenderBlock(c)->inlineElementContinuation(); + } + + return RenderBoxModelObject::positionForPoint(point); +} + +IntRect RenderInline::linesBoundingBox() const +{ + IntRect result; + + // See <rdar://problem/5289721>, for an unknown reason the linked list here is sometimes inconsistent, first is non-zero and last is zero. We have been + // unable to reproduce this at all (and consequently unable to figure ot why this is happening). The assert will hopefully catch the problem in debug + // builds and help us someday figure out why. We also put in a redundant check of lastLineBox() to avoid the crash for now. + ASSERT(!firstLineBox() == !lastLineBox()); // Either both are null or both exist. + if (firstLineBox() && lastLineBox()) { + // Return the width of the minimal left side and the maximal right side. + int logicalLeftSide = 0; + int logicalRightSide = 0; + for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) { + if (curr == firstLineBox() || curr->logicalLeft() < logicalLeftSide) + logicalLeftSide = curr->logicalLeft(); + if (curr == firstLineBox() || curr->logicalRight() > logicalRightSide) + logicalRightSide = curr->logicalRight(); + } + + bool isHorizontal = style()->isHorizontalWritingMode(); + + int x = isHorizontal ? logicalLeftSide : firstLineBox()->x(); + int y = isHorizontal ? firstLineBox()->y() : logicalLeftSide; + int width = isHorizontal ? logicalRightSide - logicalLeftSide : lastLineBox()->logicalBottom() - x; + int height = isHorizontal ? lastLineBox()->logicalBottom() - y : logicalRightSide - logicalLeftSide; + result = IntRect(x, y, width, height); + } + + return result; +} + +IntRect RenderInline::linesVisualOverflowBoundingBox() const +{ + if (!firstLineBox() || !lastLineBox()) + return IntRect(); + + // Return the width of the minimal left side and the maximal right side. + int logicalLeftSide = numeric_limits<int>::max(); + int logicalRightSide = numeric_limits<int>::min(); + for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) { + logicalLeftSide = min(logicalLeftSide, curr->logicalLeftVisualOverflow()); + logicalRightSide = max(logicalRightSide, curr->logicalRightVisualOverflow()); + } + + bool isHorizontal = style()->isHorizontalWritingMode(); + + int x = isHorizontal ? logicalLeftSide : firstLineBox()->leftVisualOverflow(); + int y = isHorizontal ? firstLineBox()->topVisualOverflow() : logicalLeftSide; + int width = isHorizontal ? logicalRightSide - logicalLeftSide : lastLineBox()->rightVisualOverflow() - firstLineBox()->leftVisualOverflow(); + int height = isHorizontal ? lastLineBox()->bottomVisualOverflow() - firstLineBox()->topVisualOverflow() : logicalRightSide - logicalLeftSide; + return IntRect(x, y, width, height); +} + +IntRect RenderInline::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) +{ + // Only run-ins are allowed in here during layout. + ASSERT(!view() || !view()->layoutStateEnabled() || isRunIn()); + + if (!firstLineBox() && !continuation()) + return IntRect(); + + // Find our leftmost position. + IntRect boundingBox(linesVisualOverflowBoundingBox()); + int left = boundingBox.x(); + int top = boundingBox.y(); + + // Now invalidate a rectangle. + int ow = style() ? style()->outlineSize() : 0; + + // We need to add in the relative position offsets of any inlines (including us) up to our + // containing block. + RenderBlock* cb = containingBlock(); + for (RenderObject* inlineFlow = this; inlineFlow && inlineFlow->isRenderInline() && inlineFlow != cb; + inlineFlow = inlineFlow->parent()) { + if (inlineFlow->style()->position() == RelativePosition && inlineFlow->hasLayer()) + toRenderInline(inlineFlow)->layer()->relativePositionOffset(left, top); + } + + IntRect r(-ow + left, -ow + top, boundingBox.width() + ow * 2, boundingBox.height() + ow * 2); + + if (cb->hasColumns()) + cb->adjustRectForColumns(r); + + if (cb->hasOverflowClip()) { + // cb->height() is inaccurate if we're in the middle of a layout of |cb|, so use the + // layer's size instead. Even if the layer's size is wrong, the layer itself will repaint + // anyway if its size does change. + IntRect repaintRect(r); + repaintRect.move(-cb->layer()->scrolledContentOffset()); // For overflow:auto/scroll/hidden. + + IntRect boxRect(0, 0, cb->layer()->width(), cb->layer()->height()); + r = intersection(repaintRect, boxRect); + } + + // FIXME: need to ensure that we compute the correct repaint rect when the repaint container + // is an inline. + if (repaintContainer != this) + cb->computeRectForRepaint(repaintContainer, r); + + if (ow) { + for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) { + if (!curr->isText()) { + IntRect childRect = curr->rectWithOutlineForRepaint(repaintContainer, ow); + r.unite(childRect); + } + } + + if (continuation() && !continuation()->isInline()) { + IntRect contRect = continuation()->rectWithOutlineForRepaint(repaintContainer, ow); + r.unite(contRect); + } + } + + return r; +} + +IntRect RenderInline::rectWithOutlineForRepaint(RenderBoxModelObject* repaintContainer, int outlineWidth) +{ + IntRect r(RenderBoxModelObject::rectWithOutlineForRepaint(repaintContainer, outlineWidth)); + for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) { + if (!curr->isText()) + r.unite(curr->rectWithOutlineForRepaint(repaintContainer, outlineWidth)); + } + return r; +} + +void RenderInline::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& rect, bool fixed) +{ + if (RenderView* v = view()) { + // LayoutState is only valid for root-relative repainting + if (v->layoutStateEnabled() && !repaintContainer) { + LayoutState* layoutState = v->layoutState(); + if (style()->position() == RelativePosition && layer()) + rect.move(layer()->relativePositionOffset()); + rect.move(layoutState->m_paintOffset); + if (layoutState->m_clipped) + rect.intersect(layoutState->m_clipRect); + return; + } + } + + if (repaintContainer == this) + return; + + bool containerSkipped; + RenderObject* o = container(repaintContainer, &containerSkipped); + if (!o) + return; + + IntPoint topLeft = rect.location(); + + if (o->isBlockFlow() && style()->position() != AbsolutePosition && style()->position() != FixedPosition) { + RenderBlock* cb = toRenderBlock(o); + if (cb->hasColumns()) { + IntRect repaintRect(topLeft, rect.size()); + cb->adjustRectForColumns(repaintRect); + topLeft = repaintRect.location(); + rect = repaintRect; + } + } + + if (style()->position() == RelativePosition && layer()) { + // Apply the relative position offset when invalidating a rectangle. The layer + // is translated, but the render box isn't, so we need to do this to get the + // right dirty rect. Since this is called from RenderObject::setStyle, the relative position + // flag on the RenderObject has been cleared, so use the one on the style(). + topLeft += layer()->relativePositionOffset(); + } + + // FIXME: We ignore the lightweight clipping rect that controls use, since if |o| is in mid-layout, + // its controlClipRect will be wrong. For overflow clip we use the values cached by the layer. + if (o->hasOverflowClip()) { + RenderBox* containerBox = toRenderBox(o); + + // o->height() is inaccurate if we're in the middle of a layout of |o|, so use the + // layer's size instead. Even if the layer's size is wrong, the layer itself will repaint + // anyway if its size does change. + topLeft -= containerBox->layer()->scrolledContentOffset(); // For overflow:auto/scroll/hidden. + + IntRect repaintRect(topLeft, rect.size()); + IntRect boxRect(0, 0, containerBox->layer()->width(), containerBox->layer()->height()); + rect = intersection(repaintRect, boxRect); + if (rect.isEmpty()) + return; + } else + rect.setLocation(topLeft); + + if (containerSkipped) { + // If the repaintContainer is below o, then we need to map the rect into repaintContainer's coordinates. + IntSize containerOffset = repaintContainer->offsetFromAncestorContainer(o); + rect.move(-containerOffset); + return; + } + + o->computeRectForRepaint(repaintContainer, rect, fixed); +} + +IntSize RenderInline::offsetFromContainer(RenderObject* container, const IntPoint& point) const +{ + ASSERT(container == this->container()); + + IntSize offset; + if (isRelPositioned()) + offset += relativePositionOffset(); + + container->adjustForColumns(offset, point); + + if (container->hasOverflowClip()) + offset -= toRenderBox(container)->layer()->scrolledContentOffset(); + + return offset; +} + +void RenderInline::mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool fixed, bool useTransforms, TransformState& transformState) const +{ + if (repaintContainer == this) + return; + + if (RenderView *v = view()) { + if (v->layoutStateEnabled() && !repaintContainer) { + LayoutState* layoutState = v->layoutState(); + IntSize offset = layoutState->m_paintOffset; + if (style()->position() == RelativePosition && layer()) + offset += layer()->relativePositionOffset(); + transformState.move(offset); + return; + } + } + + bool containerSkipped; + RenderObject* o = container(repaintContainer, &containerSkipped); + if (!o) + return; + + IntSize containerOffset = offsetFromContainer(o, roundedIntPoint(transformState.mappedPoint())); + + bool preserve3D = useTransforms && (o->style()->preserves3D() || style()->preserves3D()); + if (useTransforms && shouldUseTransformFromContainer(o)) { + TransformationMatrix t; + getTransformFromContainer(o, containerOffset, t); + transformState.applyTransform(t, preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); + } else + transformState.move(containerOffset.width(), containerOffset.height(), preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); + + if (containerSkipped) { + // There can't be a transform between repaintContainer and o, because transforms create containers, so it should be safe + // to just subtract the delta between the repaintContainer and o. + IntSize containerOffset = repaintContainer->offsetFromAncestorContainer(o); + transformState.move(-containerOffset.width(), -containerOffset.height(), preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); + return; + } + + o->mapLocalToContainer(repaintContainer, fixed, useTransforms, transformState); +} + +void RenderInline::mapAbsoluteToLocalPoint(bool fixed, bool useTransforms, TransformState& transformState) const +{ + // We don't expect this function to be called during layout. + ASSERT(!view() || !view()->layoutStateEnabled()); + + RenderObject* o = container(); + if (!o) + return; + + o->mapAbsoluteToLocalPoint(fixed, useTransforms, transformState); + + IntSize containerOffset = offsetFromContainer(o, IntPoint()); + + bool preserve3D = useTransforms && (o->style()->preserves3D() || style()->preserves3D()); + if (useTransforms && shouldUseTransformFromContainer(o)) { + TransformationMatrix t; + getTransformFromContainer(o, containerOffset, t); + transformState.applyTransform(t, preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); + } else + transformState.move(-containerOffset.width(), -containerOffset.height(), preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); +} + +void RenderInline::updateDragState(bool dragOn) +{ + RenderBoxModelObject::updateDragState(dragOn); + if (continuation()) + continuation()->updateDragState(dragOn); +} + +void RenderInline::childBecameNonInline(RenderObject* child) +{ + // We have to split the parent flow. + RenderBlock* newBox = containingBlock()->createAnonymousBlock(); + RenderBoxModelObject* oldContinuation = continuation(); + setContinuation(newBox); + RenderObject* beforeChild = child->nextSibling(); + children()->removeChildNode(this, child); + splitFlow(beforeChild, newBox, child, oldContinuation); +} + +void RenderInline::updateHitTestResult(HitTestResult& result, const IntPoint& point) +{ + if (result.innerNode()) + return; + + Node* n = node(); + IntPoint localPoint(point); + if (n) { + if (isInlineElementContinuation()) { + // We're in the continuation of a split inline. Adjust our local point to be in the coordinate space + // of the principal renderer's containing block. This will end up being the innerNonSharedNode. + RenderBlock* firstBlock = n->renderer()->containingBlock(); + + // Get our containing block. + RenderBox* block = containingBlock(); + localPoint.move(block->x() - firstBlock->x(), block->y() - firstBlock->y()); + } + + result.setInnerNode(n); + if (!result.innerNonSharedNode()) + result.setInnerNonSharedNode(n); + result.setLocalPoint(localPoint); + } +} + +void RenderInline::dirtyLineBoxes(bool fullLayout) +{ + if (fullLayout) + m_lineBoxes.deleteLineBoxes(renderArena()); + else + m_lineBoxes.dirtyLineBoxes(); +} + +InlineFlowBox* RenderInline::createInlineFlowBox() +{ + return new (renderArena()) InlineFlowBox(this); +} + +InlineFlowBox* RenderInline::createAndAppendInlineFlowBox() +{ + InlineFlowBox* flowBox = createInlineFlowBox(); + m_lineBoxes.appendLineBox(flowBox); + return flowBox; +} + +int RenderInline::lineHeight(bool firstLine, LineDirectionMode /*direction*/, LinePositionMode /*linePositionMode*/) const +{ + if (firstLine && document()->usesFirstLineRules()) { + RenderStyle* s = style(firstLine); + if (s != style()) + return s->computedLineHeight(); + } + + if (m_lineHeight == -1) + m_lineHeight = style()->computedLineHeight(); + + return m_lineHeight; +} + +int RenderInline::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const +{ + const Font& f = style(firstLine)->font(); + return f.ascent(baselineType) + (lineHeight(firstLine, direction, linePositionMode) - f.height()) / 2; +} + +IntSize RenderInline::relativePositionedInlineOffset(const RenderBox* child) const +{ + ASSERT(isRelPositioned()); + if (!isRelPositioned()) + return IntSize(); + + // When we have an enclosing relpositioned inline, we need to add in the offset of the first line + // box from the rest of the content, but only in the cases where we know we're positioned + // relative to the inline itself. + + IntSize offset; + int sx; + int sy; + if (firstLineBox()) { + sx = firstLineBox()->x(); + sy = firstLineBox()->y(); + } else { + sx = layer()->staticX(); + sy = layer()->staticY(); + } + + if (!child->style()->hasStaticX()) + offset.setWidth(sx); + // This is not terribly intuitive, but we have to match other browsers. Despite being a block display type inside + // an inline, we still keep our x locked to the left of the relative positioned inline. Arguably the correct + // behavior would be to go flush left to the block that contains the inline, but that isn't what other browsers + // do. + else if (!child->style()->isOriginalDisplayInlineType()) + // Avoid adding in the left border/padding of the containing block twice. Subtract it out. + offset.setWidth(sx - (child->containingBlock()->borderLeft() + child->containingBlock()->paddingLeft())); + + if (!child->style()->hasStaticY()) + offset.setHeight(sy); + + return offset; +} + +void RenderInline::imageChanged(WrappedImagePtr, const IntRect*) +{ + if (!parent()) + return; + + // FIXME: We can do better. + repaint(); +} + +void RenderInline::addFocusRingRects(Vector<IntRect>& rects, int tx, int ty) +{ + for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) { + RootInlineBox* root = curr->root(); + int top = max(root->lineTop(), curr->y()); + int bottom = min(root->lineBottom(), curr->y() + curr->logicalHeight()); + IntRect rect(tx + curr->x(), ty + top, curr->logicalWidth(), bottom - top); + if (!rect.isEmpty()) + rects.append(rect); + } + + for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) { + if (!curr->isText() && !curr->isListMarker()) { + FloatPoint pos(tx, ty); + // FIXME: This doesn't work correctly with transforms. + if (curr->hasLayer()) + pos = curr->localToAbsolute(); + else if (curr->isBox()) + pos.move(toRenderBox(curr)->x(), toRenderBox(curr)->y()); + curr->addFocusRingRects(rects, pos.x(), pos.y()); + } + } + + if (continuation()) { + if (continuation()->isInline()) + continuation()->addFocusRingRects(rects, + tx - containingBlock()->x() + continuation()->containingBlock()->x(), + ty - containingBlock()->y() + continuation()->containingBlock()->y()); + else + continuation()->addFocusRingRects(rects, + tx - containingBlock()->x() + toRenderBox(continuation())->x(), + ty - containingBlock()->y() + toRenderBox(continuation())->y()); + } +} + +void RenderInline::paintOutline(GraphicsContext* graphicsContext, int tx, int ty) +{ + if (!hasOutline()) + return; + + RenderStyle* styleToUse = style(); + if (styleToUse->outlineStyleIsAuto() || hasOutlineAnnotation()) { + if (!theme()->supportsFocusRing(styleToUse)) { + // Only paint the focus ring by hand if the theme isn't able to draw the focus ring. + paintFocusRing(graphicsContext, tx, ty, styleToUse); + } + } + + if (styleToUse->outlineStyleIsAuto() || styleToUse->outlineStyle() == BNONE) + return; + + Vector<IntRect> rects; + + rects.append(IntRect()); + for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) { + RootInlineBox* root = curr->root(); + int top = max(root->lineTop(), curr->y()); + int bottom = min(root->lineBottom(), curr->y() + curr->logicalHeight()); + rects.append(IntRect(curr->x(), top, curr->logicalWidth(), bottom - top)); + } + rects.append(IntRect()); + + for (unsigned i = 1; i < rects.size() - 1; i++) + paintOutlineForLine(graphicsContext, tx, ty, rects.at(i - 1), rects.at(i), rects.at(i + 1)); +} + +void RenderInline::paintOutlineForLine(GraphicsContext* graphicsContext, int tx, int ty, + const IntRect& lastline, const IntRect& thisline, const IntRect& nextline) +{ + RenderStyle* styleToUse = style(); + int ow = styleToUse->outlineWidth(); + EBorderStyle os = styleToUse->outlineStyle(); + Color oc = styleToUse->visitedDependentColor(CSSPropertyOutlineColor); + + int offset = style()->outlineOffset(); + + int t = ty + thisline.y() - offset; + int l = tx + thisline.x() - offset; + int b = ty + thisline.bottom() + offset; + int r = tx + thisline.right() + offset; + + // left edge + drawLineForBoxSide(graphicsContext, + l - ow, + t - (lastline.isEmpty() || thisline.x() < lastline.x() || (lastline.right() - 1) <= thisline.x() ? ow : 0), + l, + b + (nextline.isEmpty() || thisline.x() <= nextline.x() || (nextline.right() - 1) <= thisline.x() ? ow : 0), + BSLeft, + oc, os, + (lastline.isEmpty() || thisline.x() < lastline.x() || (lastline.right() - 1) <= thisline.x() ? ow : -ow), + (nextline.isEmpty() || thisline.x() <= nextline.x() || (nextline.right() - 1) <= thisline.x() ? ow : -ow)); + + // right edge + drawLineForBoxSide(graphicsContext, + r, + t - (lastline.isEmpty() || lastline.right() < thisline.right() || (thisline.right() - 1) <= lastline.x() ? ow : 0), + r + ow, + b + (nextline.isEmpty() || nextline.right() <= thisline.right() || (thisline.right() - 1) <= nextline.x() ? ow : 0), + BSRight, + oc, os, + (lastline.isEmpty() || lastline.right() < thisline.right() || (thisline.right() - 1) <= lastline.x() ? ow : -ow), + (nextline.isEmpty() || nextline.right() <= thisline.right() || (thisline.right() - 1) <= nextline.x() ? ow : -ow)); + // upper edge + if (thisline.x() < lastline.x()) + drawLineForBoxSide(graphicsContext, + l - ow, + t - ow, + min(r+ow, (lastline.isEmpty() ? 1000000 : tx + lastline.x())), + t , + BSTop, oc, os, + ow, + (!lastline.isEmpty() && tx + lastline.x() + 1 < r + ow) ? -ow : ow); + + if (lastline.right() < thisline.right()) + drawLineForBoxSide(graphicsContext, + max(lastline.isEmpty() ? -1000000 : tx + lastline.right(), l - ow), + t - ow, + r + ow, + t , + BSTop, oc, os, + (!lastline.isEmpty() && l - ow < tx + lastline.right()) ? -ow : ow, + ow); + + // lower edge + if (thisline.x() < nextline.x()) + drawLineForBoxSide(graphicsContext, + l - ow, + b, + min(r + ow, !nextline.isEmpty() ? tx + nextline.x() + 1 : 1000000), + b + ow, + BSBottom, oc, os, + ow, + (!nextline.isEmpty() && tx + nextline.x() + 1 < r + ow) ? -ow : ow); + + if (nextline.right() < thisline.right()) + drawLineForBoxSide(graphicsContext, + max(!nextline.isEmpty() ? tx + nextline.right() : -1000000, l - ow), + b, + r + ow, + b + ow, + BSBottom, oc, os, + (!nextline.isEmpty() && l - ow < tx + nextline.right()) ? -ow : ow, + ow); +} + +#if ENABLE(DASHBOARD_SUPPORT) +void RenderInline::addDashboardRegions(Vector<DashboardRegionValue>& regions) +{ + // Convert the style regions to absolute coordinates. + if (style()->visibility() != VISIBLE) + return; + + const Vector<StyleDashboardRegion>& styleRegions = style()->dashboardRegions(); + unsigned i, count = styleRegions.size(); + for (i = 0; i < count; i++) { + StyleDashboardRegion styleRegion = styleRegions[i]; + + IntRect linesBoundingBox = this->linesBoundingBox(); + int w = linesBoundingBox.width(); + int h = linesBoundingBox.height(); + + DashboardRegionValue region; + region.label = styleRegion.label; + region.bounds = IntRect(linesBoundingBox.x() + styleRegion.offset.left().value(), + linesBoundingBox.y() + styleRegion.offset.top().value(), + w - styleRegion.offset.left().value() - styleRegion.offset.right().value(), + h - styleRegion.offset.top().value() - styleRegion.offset.bottom().value()); + region.type = styleRegion.type; + + RenderObject* container = containingBlock(); + if (!container) + container = this; + + region.clip = region.bounds; + container->computeAbsoluteRepaintRect(region.clip); + if (region.clip.height() < 0) { + region.clip.setHeight(0); + region.clip.setWidth(0); + } + + FloatPoint absPos = container->localToAbsolute(); + region.bounds.setX(absPos.x() + region.bounds.x()); + region.bounds.setY(absPos.y() + region.bounds.y()); + + if (frame()) { + float pageScaleFactor = frame()->page()->chrome()->scaleFactor(); + if (pageScaleFactor != 1.0f) { + region.bounds.scale(pageScaleFactor); + region.clip.scale(pageScaleFactor); + } + } + + regions.append(region); + } +} +#endif + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderInline.h b/Source/WebCore/rendering/RenderInline.h new file mode 100644 index 0000000..18b4a3c --- /dev/null +++ b/Source/WebCore/rendering/RenderInline.h @@ -0,0 +1,177 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderInline_h +#define RenderInline_h + +#include "RenderBoxModelObject.h" +#include "RenderLineBoxList.h" + +namespace WebCore { + +class Position; + +class RenderInline : public RenderBoxModelObject { +public: + explicit RenderInline(Node*); + + virtual void destroy(); + + virtual void addChild(RenderObject* newChild, RenderObject* beforeChild = 0); + + virtual int marginLeft() const; + virtual int marginRight() const; + virtual int marginTop() const; + virtual int marginBottom() const; + virtual int marginBefore() const { return 0; } + virtual int marginAfter() const { return 0; } + virtual int marginStart() const; + virtual int marginEnd() const; + + virtual void absoluteRects(Vector<IntRect>&, int tx, int ty); + virtual void absoluteQuads(Vector<FloatQuad>&); + + virtual IntSize offsetFromContainer(RenderObject*, const IntPoint&) const; + + IntRect linesBoundingBox() const; + IntRect linesVisualOverflowBoundingBox() const; + + InlineFlowBox* createAndAppendInlineFlowBox(); + + void dirtyLineBoxes(bool fullLayout); + + RenderLineBoxList* lineBoxes() { return &m_lineBoxes; } + const RenderLineBoxList* lineBoxes() const { return &m_lineBoxes; } + + InlineFlowBox* firstLineBox() const { return m_lineBoxes.firstLineBox(); } + InlineFlowBox* lastLineBox() const { return m_lineBoxes.lastLineBox(); } + + virtual RenderBoxModelObject* virtualContinuation() const { return continuation(); } + RenderInline* inlineElementContinuation() const; + + virtual void updateDragState(bool dragOn); + + IntSize relativePositionedInlineOffset(const RenderBox* child) const; + + virtual void addFocusRingRects(Vector<IntRect>&, int tx, int ty); + void paintOutline(GraphicsContext*, int tx, int ty); + + using RenderBoxModelObject::continuation; + using RenderBoxModelObject::setContinuation; + +protected: + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + +private: + virtual RenderObjectChildList* virtualChildren() { return children(); } + virtual const RenderObjectChildList* virtualChildren() const { return children(); } + const RenderObjectChildList* children() const { return &m_children; } + RenderObjectChildList* children() { return &m_children; } + + virtual const char* renderName() const; + + virtual bool isRenderInline() const { return true; } + + void addChildToContinuation(RenderObject* newChild, RenderObject* beforeChild); + virtual void addChildIgnoringContinuation(RenderObject* newChild, RenderObject* beforeChild = 0); + + void splitInlines(RenderBlock* fromBlock, RenderBlock* toBlock, RenderBlock* middleBlock, + RenderObject* beforeChild, RenderBoxModelObject* oldCont); + void splitFlow(RenderObject* beforeChild, RenderBlock* newBlockBox, + RenderObject* newChild, RenderBoxModelObject* oldCont); + + virtual void layout() { ASSERT_NOT_REACHED(); } // Do nothing for layout() + + virtual void paint(PaintInfo&, int tx, int ty); + + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + + virtual bool requiresLayer() const { return isRelPositioned() || isTransparent() || hasMask(); } + + virtual int offsetLeft() const; + virtual int offsetTop() const; + virtual int offsetWidth() const { return linesBoundingBox().width(); } + virtual int offsetHeight() const { return linesBoundingBox().height(); } + + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer); + virtual IntRect rectWithOutlineForRepaint(RenderBoxModelObject* repaintContainer, int outlineWidth); + virtual void computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& rect, bool fixed); + + virtual void mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool fixed, bool useTransforms, TransformState&) const; + virtual void mapAbsoluteToLocalPoint(bool fixed, bool useTransforms, TransformState&) const; + + virtual VisiblePosition positionForPoint(const IntPoint&); + + virtual IntRect borderBoundingBox() const + { + IntRect boundingBox = linesBoundingBox(); + return IntRect(0, 0, boundingBox.width(), boundingBox.height()); + } + + virtual InlineFlowBox* createInlineFlowBox(); // Subclassed by SVG and Ruby + + virtual void dirtyLinesFromChangedChild(RenderObject* child) { m_lineBoxes.dirtyLinesFromChangedChild(this, child); } + + virtual int lineHeight(bool firstLine, LineDirectionMode, LinePositionMode = PositionOnContainingLine) const; + virtual int baselinePosition(FontBaseline, bool firstLine, LineDirectionMode, LinePositionMode = PositionOnContainingLine) const; + + virtual void childBecameNonInline(RenderObject* child); + + virtual void updateHitTestResult(HitTestResult&, const IntPoint&); + + virtual void imageChanged(WrappedImagePtr, const IntRect* = 0); + +#if ENABLE(DASHBOARD_SUPPORT) + virtual void addDashboardRegions(Vector<DashboardRegionValue>&); +#endif + + virtual void updateBoxModelInfoFromStyle(); + + static RenderInline* cloneInline(RenderInline* src); + + void paintOutlineForLine(GraphicsContext*, int tx, int ty, const IntRect& prevLine, const IntRect& thisLine, const IntRect& nextLine); + RenderBoxModelObject* continuationBefore(RenderObject* beforeChild); + + RenderObjectChildList m_children; + RenderLineBoxList m_lineBoxes; // All of the line boxes created for this inline flow. For example, <i>Hello<br>world.</i> will have two <i> line boxes. + + mutable int m_lineHeight; +}; + +inline RenderInline* toRenderInline(RenderObject* object) +{ + ASSERT(!object || object->isRenderInline()); + return static_cast<RenderInline*>(object); +} + +inline const RenderInline* toRenderInline(const RenderObject* object) +{ + ASSERT(!object || object->isRenderInline()); + return static_cast<const RenderInline*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderInline(const RenderInline*); + +} // namespace WebCore + +#endif // RenderInline_h diff --git a/Source/WebCore/rendering/RenderInputSpeech.cpp b/Source/WebCore/rendering/RenderInputSpeech.cpp new file mode 100644 index 0000000..5472025 --- /dev/null +++ b/Source/WebCore/rendering/RenderInputSpeech.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderInputSpeech.h" + +#if ENABLE(INPUT_SPEECH) + +#include "GraphicsContext.h" +#include "HTMLNames.h" +#include "RenderBox.h" +#include "TextControlInnerElements.h" + +namespace WebCore { + +static const float defaultControlFontPixelSize = 13; +static const float defaultSpeechButtonSize = 16; +static const float minSpeechButtonSize = 8; +static const float maxSpeechButtonSize = 40; + +void RenderInputSpeech::adjustInputFieldSpeechButtonStyle(CSSStyleSelector*, RenderStyle* style, Element*) +{ + // Scale the button size based on the font size. + float fontScale = style->fontSize() / defaultControlFontPixelSize; + int speechButtonSize = lroundf(std::min(std::max(minSpeechButtonSize, defaultSpeechButtonSize * fontScale), maxSpeechButtonSize)); + style->setWidth(Length(speechButtonSize, Fixed)); + style->setHeight(Length(speechButtonSize, Fixed)); +} + +bool RenderInputSpeech::paintInputFieldSpeechButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + // Get the renderer of <input> element. + Node* input = object->node()->shadowAncestorNode(); + if (!input->renderer()->isBox()) + return false; + RenderBox* inputRenderBox = toRenderBox(input->renderer()); + IntRect inputContentBox = inputRenderBox->contentBoxRect(); + + // Make sure the scaled button stays square and will fit in its parent's box. + int buttonSize = std::min(inputContentBox.width(), std::min(inputContentBox.height(), rect.height())); + // Calculate button's coordinates relative to the input element. + // Center the button vertically. Round up though, so if it has to be one pixel off-center, it will + // be one pixel closer to the bottom of the field. This tends to look better with the text. + IntRect buttonRect(object->offsetFromAncestorContainer(inputRenderBox).width(), + inputContentBox.y() + (inputContentBox.height() - buttonSize + 1) / 2, + buttonSize, buttonSize); + + // Compute an offset between the part renderer and the input renderer. + IntSize offsetFromInputRenderer = -(object->offsetFromAncestorContainer(inputRenderBox)); + // Move the rect into partRenderer's coords. + buttonRect.move(offsetFromInputRenderer); + // Account for the local drawing offset. + buttonRect.move(rect.x(), rect.y()); + + DEFINE_STATIC_LOCAL(RefPtr<Image>, imageStateNormal, (Image::loadPlatformResource("inputSpeech"))); + DEFINE_STATIC_LOCAL(RefPtr<Image>, imageStateRecording, (Image::loadPlatformResource("inputSpeechRecording"))); + DEFINE_STATIC_LOCAL(RefPtr<Image>, imageStateWaiting, (Image::loadPlatformResource("inputSpeechWaiting"))); + + InputFieldSpeechButtonElement* speechButton = reinterpret_cast<InputFieldSpeechButtonElement*>(object->node()); + Image* image = imageStateNormal.get(); + if (speechButton->state() == InputFieldSpeechButtonElement::Recording) + image = imageStateRecording.get(); + else if (speechButton->state() == InputFieldSpeechButtonElement::Recognizing) + image = imageStateWaiting.get(); + paintInfo.context->drawImage(image, object->style()->colorSpace(), buttonRect); + + return false; +} + +} // namespace WebCore + +#endif // ENABLE(INPUT_SPEECH) diff --git a/Source/WebCore/rendering/RenderInputSpeech.h b/Source/WebCore/rendering/RenderInputSpeech.h new file mode 100644 index 0000000..63ef8ae --- /dev/null +++ b/Source/WebCore/rendering/RenderInputSpeech.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderInputSpeech_h +#define RenderInputSpeech_h + +#include "RenderObject.h" + +#if ENABLE(INPUT_SPEECH) + +namespace WebCore { + +class RenderInputSpeech { +public: + static void adjustInputFieldSpeechButtonStyle(CSSStyleSelector*, RenderStyle*, Element*); + static bool paintInputFieldSpeechButton(RenderObject*, const PaintInfo&, const IntRect&); +}; + +} // namespace WebCore + +#endif +#endif // RenderInputSpeech_h diff --git a/Source/WebCore/rendering/RenderLayer.cpp b/Source/WebCore/rendering/RenderLayer.cpp new file mode 100644 index 0000000..5623662 --- /dev/null +++ b/Source/WebCore/rendering/RenderLayer.cpp @@ -0,0 +1,4058 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * Portions are Copyright (C) 1998 Netscape Communications Corporation. + * + * Other contributors: + * Robert O'Callahan <roc+@cs.cmu.edu> + * David Baron <dbaron@fas.harvard.edu> + * Christian Biesinger <cbiesinger@web.de> + * Randall Jesup <rjesup@wgate.com> + * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> + * Josh Soref <timeless@mac.com> + * Boris Zbarsky <bzbarsky@mit.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Alternatively, the contents of this file may be used under the terms + * of either the Mozilla Public License Version 1.1, found at + * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public + * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html + * (the "GPL"), in which case the provisions of the MPL or the GPL are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of one of those two + * licenses (the MPL or the GPL) and not to allow others to use your + * version of this file under the LGPL, indicate your decision by + * deletingthe provisions above and replace them with the notice and + * other provisions required by the MPL or the GPL, as the case may be. + * If you do not delete the provisions above, a recipient may use your + * version of this file under any of the LGPL, the MPL or the GPL. + */ + +#include "config.h" +#include "RenderLayer.h" + +#include "ColumnInfo.h" +#include "CSSPropertyNames.h" +#include "CSSStyleDeclaration.h" +#include "CSSStyleSelector.h" +#include "Chrome.h" +#include "Document.h" +#include "EventHandler.h" +#include "EventNames.h" +#include "FloatPoint3D.h" +#include "FloatRect.h" +#include "FocusController.h" +#include "Frame.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "Gradient.h" +#include "GraphicsContext.h" +#include "HTMLFrameOwnerElement.h" +#include "HTMLNames.h" +#if ENABLE(ANDROID_OVERFLOW_SCROLL) +#include "HTMLTextAreaElement.h" +#endif +#include "HitTestRequest.h" +#include "HitTestResult.h" +#include "OverflowEvent.h" +#include "OverlapTestRequestClient.h" +#include "Page.h" +#include "PlatformMouseEvent.h" +#include "RenderArena.h" +#include "RenderInline.h" +#include "RenderMarquee.h" +#include "RenderReplica.h" +#include "RenderScrollbar.h" +#include "RenderScrollbarPart.h" +#include "RenderTheme.h" +#include "RenderTreeAsText.h" +#include "RenderView.h" +#include "ScaleTransformOperation.h" +#include "Scrollbar.h" +#include "ScrollbarTheme.h" +#include "SelectionController.h" +#include "TextStream.h" +#include "TransformState.h" +#include "TransformationMatrix.h" +#include "TranslateTransformOperation.h" +#include <wtf/StdLibExtras.h> +#include <wtf/UnusedParam.h> +#include <wtf/text/CString.h> + +#if USE(ACCELERATED_COMPOSITING) +#include "RenderLayerBacking.h" +#include "RenderLayerCompositor.h" +#endif + +#if ENABLE(SVG) +#include "SVGNames.h" +#endif + +#define MIN_INTERSECT_FOR_REVEAL 32 + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +const int MinimumWidthWhileResizing = 100; +const int MinimumHeightWhileResizing = 40; + +void* ClipRects::operator new(size_t sz, RenderArena* renderArena) throw() +{ + return renderArena->allocate(sz); +} + +void ClipRects::operator delete(void* ptr, size_t sz) +{ + // Stash size where destroy can find it. + *(size_t *)ptr = sz; +} + +void ClipRects::destroy(RenderArena* renderArena) +{ + delete this; + + // Recover the size left there for us by operator delete and free the memory. + renderArena->free(*(size_t *)this, this); +} + +RenderLayer::RenderLayer(RenderBoxModelObject* renderer) + : m_renderer(renderer) + , m_parent(0) + , m_previous(0) + , m_next(0) + , m_first(0) + , m_last(0) + , m_relX(0) + , m_relY(0) + , m_x(0) + , m_y(0) + , m_width(0) + , m_height(0) + , m_scrollX(0) + , m_scrollY(0) + , m_scrollLeftOverflow(0) + , m_scrollTopOverflow(0) + , m_scrollWidth(0) + , m_scrollHeight(0) + , m_inResizeMode(false) + , m_posZOrderList(0) + , m_negZOrderList(0) + , m_normalFlowList(0) + , m_clipRects(0) +#ifndef NDEBUG + , m_clipRectsRoot(0) +#endif + , m_scrollDimensionsDirty(true) + , m_zOrderListsDirty(true) + , m_normalFlowListDirty(true) + , m_isNormalFlowOnly(shouldBeNormalFlowOnly()) + , m_usedTransparency(false) + , m_paintingInsideReflection(false) + , m_inOverflowRelayout(false) + , m_needsFullRepaint(false) + , m_overflowStatusDirty(true) + , m_visibleContentStatusDirty(true) + , m_hasVisibleContent(false) + , m_visibleDescendantStatusDirty(false) + , m_hasVisibleDescendant(false) + , m_isPaginated(false) + , m_3DTransformedDescendantStatusDirty(true) + , m_has3DTransformedDescendant(false) +#if USE(ACCELERATED_COMPOSITING) + , m_hasCompositingDescendant(false) + , m_mustOverlapCompositedLayers(false) +#endif +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + , m_hasOverflowScroll(false) +#endif + , m_marquee(0) + , m_staticX(0) + , m_staticY(0) + , m_reflection(0) + , m_scrollCorner(0) + , m_resizer(0) +{ + if (!renderer->firstChild() && renderer->style()) { + m_visibleContentStatusDirty = false; + m_hasVisibleContent = renderer->style()->visibility() == VISIBLE; + } +} + +RenderLayer::~RenderLayer() +{ + if (inResizeMode() && !renderer()->documentBeingDestroyed()) { + if (Frame* frame = renderer()->frame()) + frame->eventHandler()->resizeLayerDestroyed(); + } + + destroyScrollbar(HorizontalScrollbar); + destroyScrollbar(VerticalScrollbar); + + // Child layers will be deleted by their corresponding render objects, so + // we don't need to delete them ourselves. + + delete m_posZOrderList; + delete m_negZOrderList; + delete m_normalFlowList; + delete m_marquee; + +#if USE(ACCELERATED_COMPOSITING) + clearBacking(); +#endif + + // Make sure we have no lingering clip rects. + ASSERT(!m_clipRects); + + if (m_reflection) + removeReflection(); + + if (m_scrollCorner) + m_scrollCorner->destroy(); + if (m_resizer) + m_resizer->destroy(); +} + +#if USE(ACCELERATED_COMPOSITING) +RenderLayerCompositor* RenderLayer::compositor() const +{ + ASSERT(renderer()->view()); + return renderer()->view()->compositor(); +} + +void RenderLayer::contentChanged(ContentChangeType changeType) +{ + // This can get called when video becomes accelerated, so the layers may change. + if ((changeType == CanvasChanged || changeType == VideoChanged || changeType == FullScreenChanged) && compositor()->updateLayerCompositingState(this)) + compositor()->setCompositingLayersNeedRebuild(); + + if (m_backing) + m_backing->contentChanged(changeType); +} +#endif // USE(ACCELERATED_COMPOSITING) + +bool RenderLayer::hasAcceleratedCompositing() const +{ +#if USE(ACCELERATED_COMPOSITING) + return compositor()->hasAcceleratedCompositing(); +#else + return false; +#endif +} + +bool RenderLayer::canRender3DTransforms() const +{ +#if USE(ACCELERATED_COMPOSITING) + return compositor()->canRender3DTransforms(); +#else + return false; +#endif +} + +void RenderLayer::updateLayerPositions(UpdateLayerPositionsFlags flags, IntPoint* cachedOffset) +{ + if (flags & DoFullRepaint) { + renderer()->repaint(); +#if USE(ACCELERATED_COMPOSITING) + flags &= ~CheckForRepaint; + // We need the full repaint to propagate to child layers if we are hardware compositing. + if (!compositor()->inCompositingMode()) + flags &= ~DoFullRepaint; +#else + flags &= ~(CheckForRepaint | DoFullRepaint); +#endif + } + + + updateLayerPosition(); // For relpositioned layers or non-positioned layers, + // we need to keep in sync, since we may have shifted relative + // to our parent layer. + IntPoint oldCachedOffset; + if (cachedOffset) { + // We can't cache our offset to the repaint container if the mapping is anything more complex than a simple translation + bool disableOffsetCache = renderer()->hasColumns() || renderer()->hasTransform() || isComposited(); +#if ENABLE(SVG) + disableOffsetCache = disableOffsetCache || renderer()->isSVGRoot(); +#endif + if (disableOffsetCache) + cachedOffset = 0; // If our cached offset is invalid make sure it's not passed to any of our children + else { + oldCachedOffset = *cachedOffset; + // Frequently our parent layer's renderer will be the same as our renderer's containing block. In that case, + // we just update the cache using our offset to our parent (which is m_x / m_y). Otherwise, regenerated cached + // offsets to the root from the render tree. + if (!m_parent || m_parent->renderer() == renderer()->containingBlock()) + cachedOffset->move(m_x, m_y); // Fast case + else { + int x = 0; + int y = 0; + convertToLayerCoords(root(), x, y); + *cachedOffset = IntPoint(x, y); + } + } + } + + int x = 0; + int y = 0; + if (cachedOffset) { + x += cachedOffset->x(); + y += cachedOffset->y(); +#ifndef NDEBUG + int nonCachedX = 0; + int nonCachedY = 0; + convertToLayerCoords(root(), nonCachedX, nonCachedY); + ASSERT(x == nonCachedX); + ASSERT(y == nonCachedY); +#endif + } else + convertToLayerCoords(root(), x, y); + positionOverflowControls(x, y); + + updateVisibilityStatus(); + + if (flags & UpdatePagination) + updatePagination(); + else + m_isPaginated = false; + + if (m_hasVisibleContent) { + RenderView* view = renderer()->view(); + ASSERT(view); + // FIXME: Optimize using LayoutState and remove the disableLayoutState() call + // from updateScrollInfoAfterLayout(). + ASSERT(!view->layoutStateEnabled()); + + RenderBoxModelObject* repaintContainer = renderer()->containerForRepaint(); + IntRect newRect = renderer()->clippedOverflowRectForRepaint(repaintContainer); + IntRect newOutlineBox = renderer()->outlineBoundsForRepaint(repaintContainer, cachedOffset); + // FIXME: Should ASSERT that value calculated for newOutlineBox using the cached offset is the same + // as the value not using the cached offset, but we can't due to https://bugs.webkit.org/show_bug.cgi?id=37048 + if (flags & CheckForRepaint) { + if (view && !view->printing()) { + if (m_needsFullRepaint) { + renderer()->repaintUsingContainer(repaintContainer, m_repaintRect); + if (newRect != m_repaintRect) + renderer()->repaintUsingContainer(repaintContainer, newRect); + } else + renderer()->repaintAfterLayoutIfNeeded(repaintContainer, m_repaintRect, m_outlineBox, &newRect, &newOutlineBox); + } + } + m_repaintRect = newRect; + m_outlineBox = newOutlineBox; + } else { + m_repaintRect = IntRect(); + m_outlineBox = IntRect(); + } + + m_needsFullRepaint = false; + + // Go ahead and update the reflection's position and size. + if (m_reflection) + m_reflection->layout(); + +#if USE(ACCELERATED_COMPOSITING) + // Clear the IsCompositingUpdateRoot flag once we've found the first compositing layer in this update. + bool isUpdateRoot = (flags & IsCompositingUpdateRoot); + if (isComposited()) + flags &= ~IsCompositingUpdateRoot; +#endif + + if (renderer()->hasColumns()) + flags |= UpdatePagination; + + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) + child->updateLayerPositions(flags, cachedOffset); + +#if USE(ACCELERATED_COMPOSITING) + if ((flags & UpdateCompositingLayers) && isComposited()) + backing()->updateAfterLayout(RenderLayerBacking::CompositingChildren, isUpdateRoot); +#endif + + // With all our children positioned, now update our marquee if we need to. + if (m_marquee) + m_marquee->updateMarqueePosition(); + + if (cachedOffset) + *cachedOffset = oldCachedOffset; +} + +IntRect RenderLayer::repaintRectIncludingDescendants() const +{ + IntRect repaintRect = m_repaintRect; + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) + repaintRect.unite(child->repaintRectIncludingDescendants()); + return repaintRect; +} + +void RenderLayer::computeRepaintRects() +{ + RenderBoxModelObject* repaintContainer = renderer()->containerForRepaint(); + m_repaintRect = renderer()->clippedOverflowRectForRepaint(repaintContainer); + m_outlineBox = renderer()->outlineBoundsForRepaint(repaintContainer); +} + +void RenderLayer::updateRepaintRectsAfterScroll(bool fixed) +{ + if (fixed || renderer()->style()->position() == FixedPosition) { + computeRepaintRects(); + fixed = true; + } else if (renderer()->hasTransform()) { + // Transforms act as fixed position containers, so nothing inside a + // transformed element can be fixed relative to the viewport if the + // transformed element is not fixed itself or child of a fixed element. + return; + } + + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) + child->updateRepaintRectsAfterScroll(fixed); +} + +void RenderLayer::updateTransform() +{ + // hasTransform() on the renderer is also true when there is transform-style: preserve-3d or perspective set, + // so check style too. + bool hasTransform = renderer()->hasTransform() && renderer()->style()->hasTransform(); + bool had3DTransform = has3DTransform(); + + bool hadTransform = m_transform; + if (hasTransform != hadTransform) { + if (hasTransform) + m_transform.set(new TransformationMatrix); + else + m_transform.clear(); + } + + if (hasTransform) { + RenderBox* box = renderBox(); + ASSERT(box); + m_transform->makeIdentity(); + box->style()->applyTransform(*m_transform, box->borderBoxRect().size(), RenderStyle::IncludeTransformOrigin); + makeMatrixRenderable(*m_transform, canRender3DTransforms()); + } + + if (had3DTransform != has3DTransform()) + dirty3DTransformedDescendantStatus(); +} + +TransformationMatrix RenderLayer::currentTransform() const +{ + if (!m_transform) + return TransformationMatrix(); + +#if USE(ACCELERATED_COMPOSITING) + if (renderer()->style()->isRunningAcceleratedAnimation()) { + TransformationMatrix currTransform; + RefPtr<RenderStyle> style = renderer()->animation()->getAnimatedStyleForRenderer(renderer()); + style->applyTransform(currTransform, renderBox()->borderBoxRect().size(), RenderStyle::IncludeTransformOrigin); + makeMatrixRenderable(currTransform, canRender3DTransforms()); + return currTransform; + } +#endif + + return *m_transform; +} + +TransformationMatrix RenderLayer::renderableTransform(PaintBehavior paintBehavior) const +{ + if (!m_transform) + return TransformationMatrix(); + + if (paintBehavior & PaintBehaviorFlattenCompositingLayers) { + TransformationMatrix matrix = *m_transform; + makeMatrixRenderable(matrix, false /* flatten 3d */); + return matrix; + } + + return *m_transform; +} + +static bool checkContainingBlockChainForPagination(RenderBoxModelObject* renderer, RenderBox* ancestorColumnsRenderer) +{ + RenderView* view = renderer->view(); + RenderBoxModelObject* prevBlock = renderer; + RenderBlock* containingBlock; + for (containingBlock = renderer->containingBlock(); + containingBlock && containingBlock != view && containingBlock != ancestorColumnsRenderer; + containingBlock = containingBlock->containingBlock()) + prevBlock = containingBlock; + + // If the columns block wasn't in our containing block chain, then we aren't paginated by it. + if (containingBlock != ancestorColumnsRenderer) + return false; + + // If the previous block is absolutely positioned, then we can't be paginated by the columns block. + if (prevBlock->isPositioned()) + return false; + + // Otherwise we are paginated by the columns block. + return true; +} + +void RenderLayer::updatePagination() +{ + m_isPaginated = false; + if (isComposited() || !parent()) + return; // FIXME: We will have to deal with paginated compositing layers someday. + // FIXME: For now the RenderView can't be paginated. Eventually printing will move to a model where it is though. + + if (isNormalFlowOnly()) { + m_isPaginated = parent()->renderer()->hasColumns(); + return; + } + + // If we're not normal flow, then we need to look for a multi-column object between us and our stacking context. + RenderLayer* ancestorStackingContext = stackingContext(); + for (RenderLayer* curr = parent(); curr; curr = curr->parent()) { + if (curr->renderer()->hasColumns()) { + m_isPaginated = checkContainingBlockChainForPagination(renderer(), curr->renderBox()); + return; + } + if (curr == ancestorStackingContext) + return; + } +} + +void RenderLayer::setHasVisibleContent(bool b) +{ + if (m_hasVisibleContent == b && !m_visibleContentStatusDirty) + return; + m_visibleContentStatusDirty = false; + m_hasVisibleContent = b; + if (m_hasVisibleContent) { + RenderBoxModelObject* repaintContainer = renderer()->containerForRepaint(); + m_repaintRect = renderer()->clippedOverflowRectForRepaint(repaintContainer); + m_outlineBox = renderer()->outlineBoundsForRepaint(repaintContainer); + if (!isNormalFlowOnly()) { + for (RenderLayer* sc = stackingContext(); sc; sc = sc->stackingContext()) { + sc->dirtyZOrderLists(); + if (sc->hasVisibleContent()) + break; + } + } + } + if (parent()) + parent()->childVisibilityChanged(m_hasVisibleContent); +} + +void RenderLayer::dirtyVisibleContentStatus() +{ + m_visibleContentStatusDirty = true; + if (parent()) + parent()->dirtyVisibleDescendantStatus(); +} + +void RenderLayer::childVisibilityChanged(bool newVisibility) +{ + if (m_hasVisibleDescendant == newVisibility || m_visibleDescendantStatusDirty) + return; + if (newVisibility) { + RenderLayer* l = this; + while (l && !l->m_visibleDescendantStatusDirty && !l->m_hasVisibleDescendant) { + l->m_hasVisibleDescendant = true; + l = l->parent(); + } + } else + dirtyVisibleDescendantStatus(); +} + +void RenderLayer::dirtyVisibleDescendantStatus() +{ + RenderLayer* l = this; + while (l && !l->m_visibleDescendantStatusDirty) { + l->m_visibleDescendantStatusDirty = true; + l = l->parent(); + } +} + +void RenderLayer::updateVisibilityStatus() +{ + if (m_visibleDescendantStatusDirty) { + m_hasVisibleDescendant = false; + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) { + child->updateVisibilityStatus(); + if (child->m_hasVisibleContent || child->m_hasVisibleDescendant) { + m_hasVisibleDescendant = true; + break; + } + } + m_visibleDescendantStatusDirty = false; + } + + if (m_visibleContentStatusDirty) { + if (renderer()->style()->visibility() == VISIBLE) + m_hasVisibleContent = true; + else { + // layer may be hidden but still have some visible content, check for this + m_hasVisibleContent = false; + RenderObject* r = renderer()->firstChild(); + while (r) { + if (r->style()->visibility() == VISIBLE && !r->hasLayer()) { + m_hasVisibleContent = true; + break; + } + if (r->firstChild() && !r->hasLayer()) + r = r->firstChild(); + else if (r->nextSibling()) + r = r->nextSibling(); + else { + do { + r = r->parent(); + if (r == renderer()) + r = 0; + } while (r && !r->nextSibling()); + if (r) + r = r->nextSibling(); + } + } + } + m_visibleContentStatusDirty = false; + } +} + +void RenderLayer::dirty3DTransformedDescendantStatus() +{ + RenderLayer* curr = stackingContext(); + if (curr) + curr->m_3DTransformedDescendantStatusDirty = true; + + // This propagates up through preserve-3d hierarchies to the enclosing flattening layer. + // Note that preserves3D() creates stacking context, so we can just run up the stacking contexts. + while (curr && curr->preserves3D()) { + curr->m_3DTransformedDescendantStatusDirty = true; + curr = curr->stackingContext(); + } +} + +// Return true if this layer or any preserve-3d descendants have 3d. +bool RenderLayer::update3DTransformedDescendantStatus() +{ + if (m_3DTransformedDescendantStatusDirty) { + m_has3DTransformedDescendant = false; + + // Transformed or preserve-3d descendants can only be in the z-order lists, not + // in the normal flow list, so we only need to check those. + if (m_posZOrderList) { + for (unsigned i = 0; i < m_posZOrderList->size(); ++i) + m_has3DTransformedDescendant |= m_posZOrderList->at(i)->update3DTransformedDescendantStatus(); + } + + // Now check our negative z-index children. + if (m_negZOrderList) { + for (unsigned i = 0; i < m_negZOrderList->size(); ++i) + m_has3DTransformedDescendant |= m_negZOrderList->at(i)->update3DTransformedDescendantStatus(); + } + + m_3DTransformedDescendantStatusDirty = false; + } + + // If we live in a 3d hierarchy, then the layer at the root of that hierarchy needs + // the m_has3DTransformedDescendant set. + if (preserves3D()) + return has3DTransform() || m_has3DTransformedDescendant; + + return has3DTransform(); +} + +void RenderLayer::updateLayerPosition() +{ + IntPoint localPoint; + IntSize inlineBoundingBoxOffset; // We don't put this into the RenderLayer x/y for inlines, so we need to subtract it out when done. + if (renderer()->isRenderInline()) { + RenderInline* inlineFlow = toRenderInline(renderer()); + IntRect lineBox = inlineFlow->linesBoundingBox(); + setWidth(lineBox.width()); + setHeight(lineBox.height()); + inlineBoundingBoxOffset = IntSize(lineBox.x(), lineBox.y()); + localPoint += inlineBoundingBoxOffset; + } else if (RenderBox* box = renderBox()) { + setWidth(box->width()); + setHeight(box->height()); + localPoint += box->locationOffsetIncludingFlipping(); + } + + // Clear our cached clip rect information. + clearClipRects(); + + if (!renderer()->isPositioned() && renderer()->parent()) { + // We must adjust our position by walking up the render tree looking for the + // nearest enclosing object with a layer. + RenderObject* curr = renderer()->parent(); + while (curr && !curr->hasLayer()) { + if (curr->isBox() && !curr->isTableRow()) { + // Rows and cells share the same coordinate space (that of the section). + // Omit them when computing our xpos/ypos. + localPoint += toRenderBox(curr)->locationOffsetIncludingFlipping(); + } + curr = curr->parent(); + } + if (curr->isBox() && curr->isTableRow()) { + // Put ourselves into the row coordinate space. + localPoint -= toRenderBox(curr)->locationOffsetIncludingFlipping(); + } + } + + // Subtract our parent's scroll offset. + if (renderer()->isPositioned() && enclosingPositionedAncestor()) { + RenderLayer* positionedParent = enclosingPositionedAncestor(); + + // For positioned layers, we subtract out the enclosing positioned layer's scroll offset. + IntSize offset = positionedParent->scrolledContentOffset(); + localPoint -= offset; + + if (renderer()->isPositioned() && positionedParent->renderer()->isRelPositioned() && positionedParent->renderer()->isRenderInline()) { + IntSize offset = toRenderInline(positionedParent->renderer())->relativePositionedInlineOffset(toRenderBox(renderer())); + localPoint += offset; + } + } else if (parent()) { + if (isComposited()) { + // FIXME: Composited layers ignore pagination, so about the best we can do is make sure they're offset into the appropriate column. + // They won't split across columns properly. + IntSize columnOffset; + parent()->renderer()->adjustForColumns(columnOffset, localPoint); + localPoint += columnOffset; + } + + IntSize scrollOffset = parent()->scrolledContentOffset(); + localPoint -= scrollOffset; + } + + m_relX = m_relY = 0; + if (renderer()->isRelPositioned()) { + m_relX = renderer()->relativePositionOffsetX(); + m_relY = renderer()->relativePositionOffsetY(); + localPoint.move(m_relX, m_relY); + } + + // FIXME: We'd really like to just get rid of the concept of a layer rectangle and rely on the renderers. + localPoint -= inlineBoundingBoxOffset; + setLocation(localPoint.x(), localPoint.y()); +} + +TransformationMatrix RenderLayer::perspectiveTransform() const +{ + if (!renderer()->hasTransform()) + return TransformationMatrix(); + + RenderStyle* style = renderer()->style(); + if (!style->hasPerspective()) + return TransformationMatrix(); + + // Maybe fetch the perspective from the backing? + const IntRect borderBox = toRenderBox(renderer())->borderBoxRect(); + const float boxWidth = borderBox.width(); + const float boxHeight = borderBox.height(); + + float perspectiveOriginX = style->perspectiveOriginX().calcFloatValue(boxWidth); + float perspectiveOriginY = style->perspectiveOriginY().calcFloatValue(boxHeight); + + // A perspective origin of 0,0 makes the vanishing point in the center of the element. + // We want it to be in the top-left, so subtract half the height and width. + perspectiveOriginX -= boxWidth / 2.0f; + perspectiveOriginY -= boxHeight / 2.0f; + + TransformationMatrix t; + t.translate(perspectiveOriginX, perspectiveOriginY); + t.applyPerspective(style->perspective()); + t.translate(-perspectiveOriginX, -perspectiveOriginY); + + return t; +} + +FloatPoint RenderLayer::perspectiveOrigin() const +{ + if (!renderer()->hasTransform()) + return FloatPoint(); + + const IntRect borderBox = toRenderBox(renderer())->borderBoxRect(); + RenderStyle* style = renderer()->style(); + + return FloatPoint(style->perspectiveOriginX().calcFloatValue(borderBox.width()), + style->perspectiveOriginY().calcFloatValue(borderBox.height())); +} + +RenderLayer* RenderLayer::stackingContext() const +{ + RenderLayer* layer = parent(); +#if ENABLE(COMPOSITED_FIXED_ELEMENTS) || ENABLE(ANDROID_OVERFLOW_SCROLL) + // When using composited fixed elements, they are turned into a stacking + // context and we thus need to return them. + // We can simplify the while loop by using isStackingContext(); with + // composited fixed elements turned on, this will return true for them, + // and is otherwise equivalent to the replaced statements. + while (layer && !layer->renderer()->isRoot() && !layer->isStackingContext()) +#else + while (layer && !layer->renderer()->isRenderView() && !layer->renderer()->isRoot() && layer->renderer()->style()->hasAutoZIndex()) +#endif + layer = layer->parent(); + return layer; +} + +static inline bool isPositionedContainer(RenderLayer* layer) +{ + RenderObject* o = layer->renderer(); + return o->isRenderView() || o->isPositioned() || o->isRelPositioned() || layer->hasTransform(); +} + +static inline bool isFixedPositionedContainer(RenderLayer* layer) +{ + RenderObject* o = layer->renderer(); + return o->isRenderView() || layer->hasTransform(); +} + +RenderLayer* RenderLayer::enclosingPositionedAncestor() const +{ + RenderLayer* curr = parent(); + while (curr && !isPositionedContainer(curr)) + curr = curr->parent(); + + return curr; +} + +RenderLayer* RenderLayer::enclosingTransformedAncestor() const +{ + RenderLayer* curr = parent(); + while (curr && !curr->renderer()->isRenderView() && !curr->transform()) + curr = curr->parent(); + + return curr; +} + +static inline const RenderLayer* compositingContainer(const RenderLayer* layer) +{ + return layer->isNormalFlowOnly() ? layer->parent() : layer->stackingContext(); +} + +#if USE(ACCELERATED_COMPOSITING) +RenderLayer* RenderLayer::enclosingCompositingLayer(bool includeSelf) const +{ + if (includeSelf && isComposited()) + return const_cast<RenderLayer*>(this); + + for (const RenderLayer* curr = compositingContainer(this); curr; curr = compositingContainer(curr)) { + if (curr->isComposited()) + return const_cast<RenderLayer*>(curr); + } + + return 0; +} +#endif + +RenderLayer* RenderLayer::clippingRoot() const +{ +#if USE(ACCELERATED_COMPOSITING) + if (isComposited()) + return const_cast<RenderLayer*>(this); +#endif + + const RenderLayer* current = this; + while (current) { + if (current->renderer()->isRenderView()) + return const_cast<RenderLayer*>(current); + + current = compositingContainer(current); + ASSERT(current); + if (current->transform() +#if USE(ACCELERATED_COMPOSITING) + || current->isComposited() +#endif + ) + return const_cast<RenderLayer*>(current); + } + + ASSERT_NOT_REACHED(); + return 0; +} + +IntPoint RenderLayer::absoluteToContents(const IntPoint& absolutePoint) const +{ + // We don't use convertToLayerCoords because it doesn't know about transforms + return roundedIntPoint(renderer()->absoluteToLocal(absolutePoint, false, true)); +} + +bool RenderLayer::requiresSlowRepaints() const +{ + if (isTransparent() || hasReflection() || hasTransform()) + return true; + if (!parent()) + return false; + return parent()->requiresSlowRepaints(); +} + +bool RenderLayer::isTransparent() const +{ +#if ENABLE(SVG) + if (renderer()->node() && renderer()->node()->namespaceURI() == SVGNames::svgNamespaceURI) + return false; +#endif + return renderer()->isTransparent() || renderer()->hasMask(); +} + +RenderLayer* RenderLayer::transparentPaintingAncestor() +{ + if (isComposited()) + return 0; + + for (RenderLayer* curr = parent(); curr; curr = curr->parent()) { + if (curr->isComposited()) + return 0; + if (curr->isTransparent()) + return curr; + } + return 0; +} + +static IntRect transparencyClipBox(const RenderLayer* l, const RenderLayer* rootLayer, PaintBehavior paintBehavior); + +static void expandClipRectForDescendantsAndReflection(IntRect& clipRect, const RenderLayer* l, const RenderLayer* rootLayer, PaintBehavior paintBehavior) +{ + // If we have a mask, then the clip is limited to the border box area (and there is + // no need to examine child layers). + if (!l->renderer()->hasMask()) { + // Note: we don't have to walk z-order lists since transparent elements always establish + // a stacking context. This means we can just walk the layer tree directly. + for (RenderLayer* curr = l->firstChild(); curr; curr = curr->nextSibling()) { + if (!l->reflection() || l->reflectionLayer() != curr) + clipRect.unite(transparencyClipBox(curr, rootLayer, paintBehavior)); + } + } + + // If we have a reflection, then we need to account for that when we push the clip. Reflect our entire + // current transparencyClipBox to catch all child layers. + // FIXME: Accelerated compositing will eventually want to do something smart here to avoid incorporating this + // size into the parent layer. + if (l->renderer()->hasReflection()) { + int deltaX = 0; + int deltaY = 0; + l->convertToLayerCoords(rootLayer, deltaX, deltaY); + clipRect.move(-deltaX, -deltaY); + clipRect.unite(l->renderBox()->reflectedRect(clipRect)); + clipRect.move(deltaX, deltaY); + } +} + +static IntRect transparencyClipBox(const RenderLayer* l, const RenderLayer* rootLayer, PaintBehavior paintBehavior) +{ + // FIXME: Although this function completely ignores CSS-imposed clipping, we did already intersect with the + // paintDirtyRect, and that should cut down on the amount we have to paint. Still it + // would be better to respect clips. + + if (rootLayer != l && l->paintsWithTransform(paintBehavior)) { + // The best we can do here is to use enclosed bounding boxes to establish a "fuzzy" enough clip to encompass + // the transformed layer and all of its children. + int x = 0; + int y = 0; + l->convertToLayerCoords(rootLayer, x, y); + + TransformationMatrix transform; + transform.translate(x, y); + transform = *l->transform() * transform; + + IntRect clipRect = l->boundingBox(l); + expandClipRectForDescendantsAndReflection(clipRect, l, l, paintBehavior); + return transform.mapRect(clipRect); + } + + IntRect clipRect = l->boundingBox(rootLayer); + expandClipRectForDescendantsAndReflection(clipRect, l, rootLayer, paintBehavior); + return clipRect; +} + +void RenderLayer::beginTransparencyLayers(GraphicsContext* p, const RenderLayer* rootLayer, PaintBehavior paintBehavior) +{ + if (p->paintingDisabled() || (paintsWithTransparency(paintBehavior) && m_usedTransparency)) + return; + + RenderLayer* ancestor = transparentPaintingAncestor(); + if (ancestor) + ancestor->beginTransparencyLayers(p, rootLayer, paintBehavior); + + if (paintsWithTransparency(paintBehavior)) { + m_usedTransparency = true; + p->save(); + IntRect clipRect = transparencyClipBox(this, rootLayer, paintBehavior); + p->clip(clipRect); + p->beginTransparencyLayer(renderer()->opacity()); +#ifdef REVEAL_TRANSPARENCY_LAYERS + p->setFillColor(Color(0.0f, 0.0f, 0.5f, 0.2f), ColorSpaceDeviceRGB); + p->fillRect(clipRect); +#endif + } +} + +void* RenderLayer::operator new(size_t sz, RenderArena* renderArena) throw() +{ + return renderArena->allocate(sz); +} + +void RenderLayer::operator delete(void* ptr, size_t sz) +{ + // Stash size where destroy can find it. + *(size_t *)ptr = sz; +} + +void RenderLayer::destroy(RenderArena* renderArena) +{ + delete this; + + // Recover the size left there for us by operator delete and free the memory. + renderArena->free(*(size_t *)this, this); +} + +void RenderLayer::addChild(RenderLayer* child, RenderLayer* beforeChild) +{ + RenderLayer* prevSibling = beforeChild ? beforeChild->previousSibling() : lastChild(); + if (prevSibling) { + child->setPreviousSibling(prevSibling); + prevSibling->setNextSibling(child); + ASSERT(prevSibling != child); + } else + setFirstChild(child); + + if (beforeChild) { + beforeChild->setPreviousSibling(child); + child->setNextSibling(beforeChild); + ASSERT(beforeChild != child); + } else + setLastChild(child); + + child->setParent(this); + + if (child->isNormalFlowOnly()) + dirtyNormalFlowList(); + + if (!child->isNormalFlowOnly() || child->firstChild()) { + // Dirty the z-order list in which we are contained. The stackingContext() can be null in the + // case where we're building up generated content layers. This is ok, since the lists will start + // off dirty in that case anyway. + child->dirtyStackingContextZOrderLists(); + } + + child->updateVisibilityStatus(); + if (child->m_hasVisibleContent || child->m_hasVisibleDescendant) + childVisibilityChanged(true); + +#if USE(ACCELERATED_COMPOSITING) + compositor()->layerWasAdded(this, child); +#endif +} + +RenderLayer* RenderLayer::removeChild(RenderLayer* oldChild) +{ +#if USE(ACCELERATED_COMPOSITING) + if (!renderer()->documentBeingDestroyed()) + compositor()->layerWillBeRemoved(this, oldChild); +#endif + + // remove the child + if (oldChild->previousSibling()) + oldChild->previousSibling()->setNextSibling(oldChild->nextSibling()); + if (oldChild->nextSibling()) + oldChild->nextSibling()->setPreviousSibling(oldChild->previousSibling()); + + if (m_first == oldChild) + m_first = oldChild->nextSibling(); + if (m_last == oldChild) + m_last = oldChild->previousSibling(); + + if (oldChild->isNormalFlowOnly()) + dirtyNormalFlowList(); + if (!oldChild->isNormalFlowOnly() || oldChild->firstChild()) { + // Dirty the z-order list in which we are contained. When called via the + // reattachment process in removeOnlyThisLayer, the layer may already be disconnected + // from the main layer tree, so we need to null-check the |stackingContext| value. + oldChild->dirtyStackingContextZOrderLists(); + } + + oldChild->setPreviousSibling(0); + oldChild->setNextSibling(0); + oldChild->setParent(0); + + oldChild->updateVisibilityStatus(); + if (oldChild->m_hasVisibleContent || oldChild->m_hasVisibleDescendant) + childVisibilityChanged(false); + + return oldChild; +} + +void RenderLayer::removeOnlyThisLayer() +{ + if (!m_parent) + return; + + // Mark that we are about to lose our layer. This makes render tree + // walks ignore this layer while we're removing it. + m_renderer->setHasLayer(false); + +#if USE(ACCELERATED_COMPOSITING) + compositor()->layerWillBeRemoved(m_parent, this); +#endif + + // Dirty the clip rects. + clearClipRectsIncludingDescendants(); + + // Remove us from the parent. + RenderLayer* parent = m_parent; + RenderLayer* nextSib = nextSibling(); + parent->removeChild(this); + + if (reflection()) + removeChild(reflectionLayer()); + + // Now walk our kids and reattach them to our parent. + RenderLayer* current = m_first; + while (current) { + RenderLayer* next = current->nextSibling(); + removeChild(current); + parent->addChild(current, nextSib); + current->updateLayerPositions(); // Depends on hasLayer() already being false for proper layout. + current = next; + } + + m_renderer->destroyLayer(); +} + +void RenderLayer::insertOnlyThisLayer() +{ + if (!m_parent && renderer()->parent()) { + // We need to connect ourselves when our renderer() has a parent. + // Find our enclosingLayer and add ourselves. + RenderLayer* parentLayer = renderer()->parent()->enclosingLayer(); + ASSERT(parentLayer); + RenderLayer* beforeChild = parentLayer->reflectionLayer() != this ? renderer()->parent()->findNextLayer(parentLayer, renderer()) : 0; + parentLayer->addChild(this, beforeChild); + } + + // Remove all descendant layers from the hierarchy and add them to the new position. + for (RenderObject* curr = renderer()->firstChild(); curr; curr = curr->nextSibling()) + curr->moveLayers(m_parent, this); + + // Clear out all the clip rects. + clearClipRectsIncludingDescendants(); +} + +void +RenderLayer::convertToLayerCoords(const RenderLayer* ancestorLayer, int& xPos, int& yPos) const +{ + if (ancestorLayer == this) + return; + + EPosition position = renderer()->style()->position(); + if (position == FixedPosition && (!ancestorLayer || ancestorLayer == renderer()->view()->layer())) { + // If the fixed layer's container is the root, just add in the offset of the view. We can obtain this by calling + // localToAbsolute() on the RenderView. + FloatPoint absPos = renderer()->localToAbsolute(FloatPoint(), true); + xPos += absPos.x(); + yPos += absPos.y(); + return; + } + + if (position == FixedPosition) { + // For a fixed layers, we need to walk up to the root to see if there's a fixed position container + // (e.g. a transformed layer). It's an error to call convertToLayerCoords() across a layer with a transform, + // so we should always find the ancestor at or before we find the fixed position container. + RenderLayer* fixedPositionContainerLayer = 0; + bool foundAncestor = false; + for (RenderLayer* currLayer = parent(); currLayer; currLayer = currLayer->parent()) { + if (currLayer == ancestorLayer) + foundAncestor = true; + + if (isFixedPositionedContainer(currLayer)) { + fixedPositionContainerLayer = currLayer; + ASSERT(foundAncestor); + break; + } + } + + ASSERT(fixedPositionContainerLayer); // We should have hit the RenderView's layer at least. + + if (fixedPositionContainerLayer != ancestorLayer) { + int fixedContainerX = 0; + int fixedContainerY = 0; + convertToLayerCoords(fixedPositionContainerLayer, fixedContainerX, fixedContainerY); + + int ancestorX = 0; + int ancestorY = 0; + ancestorLayer->convertToLayerCoords(fixedPositionContainerLayer, ancestorX, ancestorY); + + xPos += (fixedContainerX - ancestorX); + yPos += (fixedContainerY - ancestorY); + return; + } + } + + + RenderLayer* parentLayer; + if (position == AbsolutePosition || position == FixedPosition) { + // Do what enclosingPositionedAncestor() does, but check for ancestorLayer along the way. + parentLayer = parent(); + bool foundAncestorFirst = false; + while (parentLayer) { + if (isPositionedContainer(parentLayer)) + break; + + if (parentLayer == ancestorLayer) { + foundAncestorFirst = true; + break; + } + + parentLayer = parentLayer->parent(); + } + + if (foundAncestorFirst) { + // Found ancestorLayer before the abs. positioned container, so compute offset of both relative + // to enclosingPositionedAncestor and subtract. + RenderLayer* positionedAncestor = parentLayer->enclosingPositionedAncestor(); + + int thisX = 0; + int thisY = 0; + convertToLayerCoords(positionedAncestor, thisX, thisY); + + int ancestorX = 0; + int ancestorY = 0; + ancestorLayer->convertToLayerCoords(positionedAncestor, ancestorX, ancestorY); + + xPos += (thisX - ancestorX); + yPos += (thisY - ancestorY); + return; + } + } else + parentLayer = parent(); + + if (!parentLayer) + return; + + parentLayer->convertToLayerCoords(ancestorLayer, xPos, yPos); + + xPos += x(); + yPos += y(); +} + +static inline int adjustedScrollDelta(int beginningDelta) { + // This implemention matches Firefox's. + // http://mxr.mozilla.org/firefox/source/toolkit/content/widgets/browser.xml#856. + const int speedReducer = 12; + + int adjustedDelta = beginningDelta / speedReducer; + if (adjustedDelta > 1) + adjustedDelta = static_cast<int>(adjustedDelta * sqrt(static_cast<double>(adjustedDelta))) - 1; + else if (adjustedDelta < -1) + adjustedDelta = static_cast<int>(adjustedDelta * sqrt(static_cast<double>(-adjustedDelta))) + 1; + + return adjustedDelta; +} + +void RenderLayer::panScrollFromPoint(const IntPoint& sourcePoint) +{ + Frame* frame = renderer()->frame(); + if (!frame) + return; + + IntPoint currentMousePosition = frame->eventHandler()->currentMousePosition(); + + // We need to check if the current mouse position is out of the window. When the mouse is out of the window, the position is incoherent + static IntPoint previousMousePosition; + if (currentMousePosition.x() < 0 || currentMousePosition.y() < 0) + currentMousePosition = previousMousePosition; + else + previousMousePosition = currentMousePosition; + + int xDelta = currentMousePosition.x() - sourcePoint.x(); + int yDelta = currentMousePosition.y() - sourcePoint.y(); + + if (abs(xDelta) <= ScrollView::noPanScrollRadius) // at the center we let the space for the icon + xDelta = 0; + if (abs(yDelta) <= ScrollView::noPanScrollRadius) + yDelta = 0; + + scrollByRecursively(adjustedScrollDelta(xDelta), adjustedScrollDelta(yDelta)); +} + +void RenderLayer::scrollByRecursively(int xDelta, int yDelta) +{ + if (!xDelta && !yDelta) + return; + + bool restrictedByLineClamp = false; + if (renderer()->parent()) + restrictedByLineClamp = !renderer()->parent()->style()->lineClamp().isNone(); + + if (renderer()->hasOverflowClip() && !restrictedByLineClamp) { + int newOffsetX = scrollXOffset() + xDelta; + int newOffsetY = scrollYOffset() + yDelta; + scrollToOffset(newOffsetX, newOffsetY); + + // If this layer can't do the scroll we ask the next layer up that can scroll to try + int leftToScrollX = newOffsetX - scrollXOffset(); + int leftToScrollY = newOffsetY - scrollYOffset(); + if ((leftToScrollX || leftToScrollY) && renderer()->parent()) { + RenderObject* nextRenderer = renderer()->parent(); + while (nextRenderer) { + if (nextRenderer->isBox() && toRenderBox(nextRenderer)->canBeScrolledAndHasScrollableArea()) { + nextRenderer->enclosingLayer()->scrollByRecursively(leftToScrollX, leftToScrollY); + break; + } + nextRenderer = nextRenderer->parent(); + } + + Frame* frame = renderer()->frame(); + if (frame) + frame->eventHandler()->updateAutoscrollRenderer(); + } + } else if (renderer()->view()->frameView()) { + // If we are here, we were called on a renderer that can be programmatically scrolled, but doesn't + // have an overflow clip. Which means that it is a document node that can be scrolled. + renderer()->view()->frameView()->scrollBy(IntSize(xDelta, yDelta)); + // FIXME: If we didn't scroll the whole way, do we want to try looking at the frames ownerElement? + // https://bugs.webkit.org/show_bug.cgi?id=28237 + } +} + +void RenderLayer::scrollToOffset(int x, int y, bool updateScrollbars, bool repaint) +{ + RenderBox* box = renderBox(); + if (!box) + return; + + if (box->style()->overflowX() != OMARQUEE) { + if (x < 0) x = 0; + if (y < 0) y = 0; + + // Call the scrollWidth/Height functions so that the dimensions will be computed if they need + // to be (for overflow:hidden blocks). + int maxX = scrollWidth() - box->clientWidth(); + int maxY = scrollHeight() - box->clientHeight(); + + if (x > maxX) x = maxX; + if (y > maxY) y = maxY; + } + + // FIXME: Eventually, we will want to perform a blit. For now never + // blit, since the check for blitting is going to be very + // complicated (since it will involve testing whether our layer + // is either occluded by another layer or clipped by an enclosing + // layer or contains fixed backgrounds, etc.). + int newScrollX = x - m_scrollOrigin.x(); + int newScrollY = y - m_scrollOrigin.y(); + if (m_scrollY == newScrollY && m_scrollX == newScrollX) + return; + m_scrollX = newScrollX; + m_scrollY = newScrollY; + + // Update the positions of our child layers. Don't have updateLayerPositions() update + // compositing layers, because we need to do a deep update from the compositing ancestor. + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) + child->updateLayerPositions(0); + + RenderView* view = renderer()->view(); + + // We should have a RenderView if we're trying to scroll. + ASSERT(view); + if (view) { +#if ENABLE(DASHBOARD_SUPPORT) + // Update dashboard regions, scrolling may change the clip of a + // particular region. + view->frameView()->updateDashboardRegions(); +#endif + + view->updateWidgetPositions(); + } + +#if USE(ACCELERATED_COMPOSITING) + if (compositor()->inCompositingMode()) { + // Our stacking context is guaranteed to contain all of our descendants that may need + // repositioning, so update compositing layers from there. + if (RenderLayer* compositingAncestor = stackingContext()->enclosingCompositingLayer()) { + if (compositor()->compositingConsultsOverlap()) + compositor()->updateCompositingLayers(CompositingUpdateOnScroll, compositingAncestor); + else { + bool isUpdateRoot = true; + compositingAncestor->backing()->updateAfterLayout(RenderLayerBacking::AllDescendants, isUpdateRoot); + } + } + } +#endif + + RenderBoxModelObject* repaintContainer = renderer()->containerForRepaint(); + IntRect rectForRepaint = renderer()->clippedOverflowRectForRepaint(repaintContainer); + + Frame* frame = renderer()->frame(); + if (frame) { + // The caret rect needs to be invalidated after scrolling + frame->selection()->setCaretRectNeedsUpdate(); + + FloatQuad quadForFakeMouseMoveEvent = FloatQuad(rectForRepaint); + if (repaintContainer) + quadForFakeMouseMoveEvent = repaintContainer->localToAbsoluteQuad(quadForFakeMouseMoveEvent); + frame->eventHandler()->dispatchFakeMouseMoveEventSoonInQuad(quadForFakeMouseMoveEvent); + } + + // Just schedule a full repaint of our object. + if (view && repaint) + renderer()->repaintUsingContainer(repaintContainer, rectForRepaint); + + if (updateScrollbars) { + if (m_hBar) + m_hBar->setValue(scrollXOffset(), Scrollbar::NotFromScrollAnimator); + if (m_vBar) + m_vBar->setValue(m_scrollY, Scrollbar::NotFromScrollAnimator); + } + + // Schedule the scroll DOM event. + if (view) { + if (FrameView* frameView = view->frameView()) + frameView->scheduleEvent(Event::create(eventNames().scrollEvent, false, false), renderer()->node()); + } +} + +void RenderLayer::scrollRectToVisible(const IntRect& rect, bool scrollToAnchor, const ScrollAlignment& alignX, const ScrollAlignment& alignY) +{ + RenderLayer* parentLayer = 0; + IntRect newRect = rect; + int xOffset = 0, yOffset = 0; + + // We may end up propagating a scroll event. It is important that we suspend events until + // the end of the function since they could delete the layer or the layer's renderer(). + FrameView* frameView = renderer()->document()->view(); + if (frameView) + frameView->pauseScheduledEvents(); + + bool restrictedByLineClamp = false; + if (renderer()->parent()) { + parentLayer = renderer()->parent()->enclosingLayer(); + restrictedByLineClamp = !renderer()->parent()->style()->lineClamp().isNone(); + } + + if (renderer()->hasOverflowClip() && !restrictedByLineClamp) { + // Don't scroll to reveal an overflow layer that is restricted by the -webkit-line-clamp property. + // This will prevent us from revealing text hidden by the slider in Safari RSS. + RenderBox* box = renderBox(); + ASSERT(box); + FloatPoint absPos = box->localToAbsolute(); + absPos.move(box->borderLeft(), box->borderTop()); + + IntRect layerBounds = IntRect(absPos.x() + scrollXOffset(), absPos.y() + scrollYOffset(), box->clientWidth(), box->clientHeight()); + IntRect exposeRect = IntRect(rect.x() + scrollXOffset(), rect.y() + scrollYOffset(), rect.width(), rect.height()); + IntRect r = getRectToExpose(layerBounds, exposeRect, alignX, alignY); + + xOffset = r.x() - absPos.x(); + yOffset = r.y() - absPos.y(); + // Adjust offsets if they're outside of the allowable range. + xOffset = max(0, min(scrollWidth() - layerBounds.width(), xOffset)); + yOffset = max(0, min(scrollHeight() - layerBounds.height(), yOffset)); + + if (xOffset != scrollXOffset() || yOffset != scrollYOffset()) { + int diffX = scrollXOffset(); + int diffY = scrollYOffset(); + scrollToOffset(xOffset, yOffset); + diffX = scrollXOffset() - diffX; + diffY = scrollYOffset() - diffY; + newRect.setX(rect.x() - diffX); + newRect.setY(rect.y() - diffY); + } + } else if (!parentLayer && renderer()->isBox() && renderBox()->canBeProgramaticallyScrolled(scrollToAnchor)) { + if (frameView) { + if (renderer()->document() && renderer()->document()->ownerElement() && renderer()->document()->ownerElement()->renderer()) { + IntRect viewRect = frameView->visibleContentRect(); + IntRect r = getRectToExpose(viewRect, rect, alignX, alignY); + + xOffset = r.x(); + yOffset = r.y(); + // Adjust offsets if they're outside of the allowable range. + xOffset = max(0, min(frameView->contentsWidth(), xOffset)); + yOffset = max(0, min(frameView->contentsHeight(), yOffset)); + + frameView->setScrollPosition(IntPoint(xOffset, yOffset)); + parentLayer = renderer()->document()->ownerElement()->renderer()->enclosingLayer(); + newRect.setX(rect.x() - frameView->scrollX() + frameView->x()); + newRect.setY(rect.y() - frameView->scrollY() + frameView->y()); + } else { + IntRect viewRect = frameView->visibleContentRect(); + IntRect r = getRectToExpose(viewRect, rect, alignX, alignY); + + frameView->setScrollPosition(r.location()); + + // This is the outermost view of a web page, so after scrolling this view we + // scroll its container by calling Page::scrollRectIntoView. + // This only has an effect on the Mac platform in applications + // that put web views into scrolling containers, such as Mac OS X Mail. + // The canAutoscroll function in EventHandler also knows about this. + if (Frame* frame = frameView->frame()) { + if (Page* page = frame->page()) + page->chrome()->scrollRectIntoView(rect); + } + } + } + } + + if (parentLayer) + parentLayer->scrollRectToVisible(newRect, scrollToAnchor, alignX, alignY); + + if (frameView) + frameView->resumeScheduledEvents(); +} + +IntRect RenderLayer::getRectToExpose(const IntRect &visibleRect, const IntRect &exposeRect, const ScrollAlignment& alignX, const ScrollAlignment& alignY) +{ + // Determine the appropriate X behavior. + ScrollBehavior scrollX; + IntRect exposeRectX(exposeRect.x(), visibleRect.y(), exposeRect.width(), visibleRect.height()); + int intersectWidth = intersection(visibleRect, exposeRectX).width(); + if (intersectWidth == exposeRect.width() || intersectWidth >= MIN_INTERSECT_FOR_REVEAL) + // If the rectangle is fully visible, use the specified visible behavior. + // If the rectangle is partially visible, but over a certain threshold, + // then treat it as fully visible to avoid unnecessary horizontal scrolling + scrollX = ScrollAlignment::getVisibleBehavior(alignX); + else if (intersectWidth == visibleRect.width()) { + // If the rect is bigger than the visible area, don't bother trying to center. Other alignments will work. + scrollX = ScrollAlignment::getVisibleBehavior(alignX); + if (scrollX == alignCenter) + scrollX = noScroll; + } else if (intersectWidth > 0) + // If the rectangle is partially visible, but not above the minimum threshold, use the specified partial behavior + scrollX = ScrollAlignment::getPartialBehavior(alignX); + else + scrollX = ScrollAlignment::getHiddenBehavior(alignX); + // If we're trying to align to the closest edge, and the exposeRect is further right + // than the visibleRect, and not bigger than the visible area, then align with the right. + if (scrollX == alignToClosestEdge && exposeRect.right() > visibleRect.right() && exposeRect.width() < visibleRect.width()) + scrollX = alignRight; + + // Given the X behavior, compute the X coordinate. + int x; + if (scrollX == noScroll) + x = visibleRect.x(); + else if (scrollX == alignRight) + x = exposeRect.right() - visibleRect.width(); + else if (scrollX == alignCenter) + x = exposeRect.x() + (exposeRect.width() - visibleRect.width()) / 2; + else + x = exposeRect.x(); + + // Determine the appropriate Y behavior. + ScrollBehavior scrollY; + IntRect exposeRectY(visibleRect.x(), exposeRect.y(), visibleRect.width(), exposeRect.height()); + int intersectHeight = intersection(visibleRect, exposeRectY).height(); + if (intersectHeight == exposeRect.height()) + // If the rectangle is fully visible, use the specified visible behavior. + scrollY = ScrollAlignment::getVisibleBehavior(alignY); + else if (intersectHeight == visibleRect.height()) { + // If the rect is bigger than the visible area, don't bother trying to center. Other alignments will work. + scrollY = ScrollAlignment::getVisibleBehavior(alignY); + if (scrollY == alignCenter) + scrollY = noScroll; + } else if (intersectHeight > 0) + // If the rectangle is partially visible, use the specified partial behavior + scrollY = ScrollAlignment::getPartialBehavior(alignY); + else + scrollY = ScrollAlignment::getHiddenBehavior(alignY); + // If we're trying to align to the closest edge, and the exposeRect is further down + // than the visibleRect, and not bigger than the visible area, then align with the bottom. + if (scrollY == alignToClosestEdge && exposeRect.bottom() > visibleRect.bottom() && exposeRect.height() < visibleRect.height()) + scrollY = alignBottom; + + // Given the Y behavior, compute the Y coordinate. + int y; + if (scrollY == noScroll) + y = visibleRect.y(); + else if (scrollY == alignBottom) + y = exposeRect.bottom() - visibleRect.height(); + else if (scrollY == alignCenter) + y = exposeRect.y() + (exposeRect.height() - visibleRect.height()) / 2; + else + y = exposeRect.y(); + + return IntRect(IntPoint(x, y), visibleRect.size()); +} + +void RenderLayer::autoscroll() +{ + Frame* frame = renderer()->frame(); + if (!frame) + return; + + FrameView* frameView = frame->view(); + if (!frameView) + return; + +#if ENABLE(DRAG_SUPPORT) + frame->eventHandler()->updateSelectionForMouseDrag(); +#endif + + IntPoint currentDocumentPosition = frameView->windowToContents(frame->eventHandler()->currentMousePosition()); + scrollRectToVisible(IntRect(currentDocumentPosition, IntSize(1, 1)), false, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded); +} + +void RenderLayer::resize(const PlatformMouseEvent& evt, const IntSize& oldOffset) +{ + // FIXME: This should be possible on generated content but is not right now. + if (!inResizeMode() || !renderer()->hasOverflowClip() || !renderer()->node()) + return; + + // Set the width and height of the shadow ancestor node if there is one. + // This is necessary for textarea elements since the resizable layer is in the shadow content. + Element* element = static_cast<Element*>(renderer()->node()->shadowAncestorNode()); + RenderBox* renderer = toRenderBox(element->renderer()); + + EResize resize = renderer->style()->resize(); + if (resize == RESIZE_NONE) + return; + + Document* document = element->document(); + if (!document->frame()->eventHandler()->mousePressed()) + return; + + float zoomFactor = renderer->style()->effectiveZoom(); + + IntSize newOffset = offsetFromResizeCorner(document->view()->windowToContents(evt.pos())); + newOffset.setWidth(newOffset.width() / zoomFactor); + newOffset.setHeight(newOffset.height() / zoomFactor); + + IntSize currentSize = IntSize(renderer->width() / zoomFactor, renderer->height() / zoomFactor); + IntSize minimumSize = element->minimumSizeForResizing().shrunkTo(currentSize); + element->setMinimumSizeForResizing(minimumSize); + + IntSize adjustedOldOffset = IntSize(oldOffset.width() / zoomFactor, oldOffset.height() / zoomFactor); + + IntSize difference = (currentSize + newOffset - adjustedOldOffset).expandedTo(minimumSize) - currentSize; + + CSSStyleDeclaration* style = element->style(); + bool isBoxSizingBorder = renderer->style()->boxSizing() == BORDER_BOX; + + ExceptionCode ec; + + if (resize != RESIZE_VERTICAL && difference.width()) { + if (element->isFormControlElement()) { + // Make implicit margins from the theme explicit (see <http://bugs.webkit.org/show_bug.cgi?id=9547>). + style->setProperty(CSSPropertyMarginLeft, String::number(renderer->marginLeft() / zoomFactor) + "px", false, ec); + style->setProperty(CSSPropertyMarginRight, String::number(renderer->marginRight() / zoomFactor) + "px", false, ec); + } + int baseWidth = renderer->width() - (isBoxSizingBorder ? 0 : renderer->borderAndPaddingWidth()); + baseWidth = baseWidth / zoomFactor; + style->setProperty(CSSPropertyWidth, String::number(baseWidth + difference.width()) + "px", false, ec); + } + + if (resize != RESIZE_HORIZONTAL && difference.height()) { + if (element->isFormControlElement()) { + // Make implicit margins from the theme explicit (see <http://bugs.webkit.org/show_bug.cgi?id=9547>). + style->setProperty(CSSPropertyMarginTop, String::number(renderer->marginTop() / zoomFactor) + "px", false, ec); + style->setProperty(CSSPropertyMarginBottom, String::number(renderer->marginBottom() / zoomFactor) + "px", false, ec); + } + int baseHeight = renderer->height() - (isBoxSizingBorder ? 0 : renderer->borderAndPaddingHeight()); + baseHeight = baseHeight / zoomFactor; + style->setProperty(CSSPropertyHeight, String::number(baseHeight + difference.height()) + "px", false, ec); + } + + document->updateLayout(); + + // FIXME (Radar 4118564): We should also autoscroll the window as necessary to keep the point under the cursor in view. +} + +int RenderLayer::scrollSize(ScrollbarOrientation orientation) const +{ + Scrollbar* scrollbar = ((orientation == HorizontalScrollbar) ? m_hBar : m_vBar).get(); + return scrollbar ? (scrollbar->totalSize() - scrollbar->visibleSize()) : 0; +} + +void RenderLayer::setScrollOffsetFromAnimation(const IntPoint& offset) +{ + if (m_hBar) + m_hBar->setValue(offset.x(), Scrollbar::FromScrollAnimator); + if (m_vBar) + m_vBar->setValue(offset.y(), Scrollbar::FromScrollAnimator); +} + +void RenderLayer::valueChanged(Scrollbar*) +{ + // Update scroll position from scrollbars. + + bool needUpdate = false; + int newX = scrollXOffset(); + int newY = m_scrollY; + + if (m_hBar) { + newX = m_hBar->value(); + if (newX != scrollXOffset()) + needUpdate = true; + } + + if (m_vBar) { + newY = m_vBar->value(); + if (newY != m_scrollY) + needUpdate = true; + } + + if (needUpdate) + scrollToOffset(newX, newY, false); +} + +bool RenderLayer::isActive() const +{ + Page* page = renderer()->frame()->page(); + return page && page->focusController()->isActive(); +} + + +static IntRect cornerRect(const RenderLayer* layer, const IntRect& bounds) +{ + int horizontalThickness; + int verticalThickness; + if (!layer->verticalScrollbar() && !layer->horizontalScrollbar()) { + // FIXME: This isn't right. We need to know the thickness of custom scrollbars + // even when they don't exist in order to set the resizer square size properly. + horizontalThickness = ScrollbarTheme::nativeTheme()->scrollbarThickness(); + verticalThickness = horizontalThickness; + } else if (layer->verticalScrollbar() && !layer->horizontalScrollbar()) { + horizontalThickness = layer->verticalScrollbar()->width(); + verticalThickness = horizontalThickness; + } else if (layer->horizontalScrollbar() && !layer->verticalScrollbar()) { + verticalThickness = layer->horizontalScrollbar()->height(); + horizontalThickness = verticalThickness; + } else { + horizontalThickness = layer->verticalScrollbar()->width(); + verticalThickness = layer->horizontalScrollbar()->height(); + } + return IntRect(bounds.right() - horizontalThickness - layer->renderer()->style()->borderRightWidth(), + bounds.bottom() - verticalThickness - layer->renderer()->style()->borderBottomWidth(), + horizontalThickness, verticalThickness); +} + +static IntRect scrollCornerRect(const RenderLayer* layer, const IntRect& bounds) +{ + // We have a scrollbar corner when a scrollbar is visible and not filling the entire length of the box. + // This happens when: + // (a) A resizer is present and at least one scrollbar is present + // (b) Both scrollbars are present. + bool hasHorizontalBar = layer->horizontalScrollbar(); + bool hasVerticalBar = layer->verticalScrollbar(); + bool hasResizer = layer->renderer()->style()->resize() != RESIZE_NONE; + if ((hasHorizontalBar && hasVerticalBar) || (hasResizer && (hasHorizontalBar || hasVerticalBar))) + return cornerRect(layer, bounds); + return IntRect(); +} + +static IntRect resizerCornerRect(const RenderLayer* layer, const IntRect& bounds) +{ + ASSERT(layer->renderer()->isBox()); + if (layer->renderer()->style()->resize() == RESIZE_NONE) + return IntRect(); + return cornerRect(layer, bounds); +} + +bool RenderLayer::scrollbarCornerPresent() const +{ + ASSERT(renderer()->isBox()); + return !scrollCornerRect(this, renderBox()->borderBoxRect()).isEmpty(); +} + +IntRect RenderLayer::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& scrollbarRect) const +{ + RenderView* view = renderer()->view(); + if (!view) + return scrollbarRect; + + IntRect rect = scrollbarRect; + rect.move(scrollbarOffset(scrollbar)); + + return view->frameView()->convertFromRenderer(renderer(), rect); +} + +IntRect RenderLayer::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const +{ + RenderView* view = renderer()->view(); + if (!view) + return parentRect; + + IntRect rect = view->frameView()->convertToRenderer(renderer(), parentRect); + rect.move(-scrollbarOffset(scrollbar)); + return rect; +} + +IntPoint RenderLayer::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& scrollbarPoint) const +{ + RenderView* view = renderer()->view(); + if (!view) + return scrollbarPoint; + + IntPoint point = scrollbarPoint; + point.move(scrollbarOffset(scrollbar)); + return view->frameView()->convertFromRenderer(renderer(), point); +} + +IntPoint RenderLayer::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const +{ + RenderView* view = renderer()->view(); + if (!view) + return parentPoint; + + IntPoint point = view->frameView()->convertToRenderer(renderer(), parentPoint); + + point.move(-scrollbarOffset(scrollbar)); + return point; +} + +IntSize RenderLayer::scrollbarOffset(const Scrollbar* scrollbar) const +{ + RenderBox* box = renderBox(); + + if (scrollbar == m_vBar.get()) + return IntSize(box->width() - box->borderRight() - scrollbar->width(), box->borderTop()); + + if (scrollbar == m_hBar.get()) + return IntSize(box->borderLeft(), box->height() - box->borderBottom() - scrollbar->height()); + + ASSERT_NOT_REACHED(); + return IntSize(); +} + +void RenderLayer::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect) +{ + IntRect scrollRect = rect; + RenderBox* box = renderBox(); + ASSERT(box); + if (scrollbar == m_vBar.get()) + scrollRect.move(box->width() - box->borderRight() - scrollbar->width(), box->borderTop()); + else + scrollRect.move(box->borderLeft(), box->height() - box->borderBottom() - scrollbar->height()); + renderer()->repaintRectangle(scrollRect); +} + +PassRefPtr<Scrollbar> RenderLayer::createScrollbar(ScrollbarOrientation orientation) +{ + RefPtr<Scrollbar> widget; + RenderObject* actualRenderer = renderer()->node() ? renderer()->node()->shadowAncestorNode()->renderer() : renderer(); + bool hasCustomScrollbarStyle = actualRenderer->isBox() && actualRenderer->style()->hasPseudoStyle(SCROLLBAR); + if (hasCustomScrollbarStyle) + widget = RenderScrollbar::createCustomScrollbar(this, orientation, toRenderBox(actualRenderer)); + else + widget = Scrollbar::createNativeScrollbar(this, orientation, RegularScrollbar); + renderer()->document()->view()->addChild(widget.get()); + return widget.release(); +} + +void RenderLayer::destroyScrollbar(ScrollbarOrientation orientation) +{ + RefPtr<Scrollbar>& scrollbar = orientation == HorizontalScrollbar ? m_hBar : m_vBar; + if (scrollbar) { + if (scrollbar->isCustomScrollbar()) + static_cast<RenderScrollbar*>(scrollbar.get())->clearOwningRenderer(); + + scrollbar->removeFromParent(); + scrollbar->setClient(0); + scrollbar = 0; + } +} + +void RenderLayer::setHasHorizontalScrollbar(bool hasScrollbar) +{ + if (hasScrollbar == (m_hBar != 0)) + return; + + if (hasScrollbar) + m_hBar = createScrollbar(HorizontalScrollbar); + else + destroyScrollbar(HorizontalScrollbar); + + // Destroying or creating one bar can cause our scrollbar corner to come and go. We need to update the opposite scrollbar's style. + if (m_hBar) + m_hBar->styleChanged(); + if (m_vBar) + m_vBar->styleChanged(); + +#if ENABLE(DASHBOARD_SUPPORT) + // Force an update since we know the scrollbars have changed things. + if (renderer()->document()->hasDashboardRegions()) + renderer()->document()->setDashboardRegionsDirty(true); +#endif +} + +void RenderLayer::setHasVerticalScrollbar(bool hasScrollbar) +{ + if (hasScrollbar == (m_vBar != 0)) + return; + + if (hasScrollbar) + m_vBar = createScrollbar(VerticalScrollbar); + else + destroyScrollbar(VerticalScrollbar); + + // Destroying or creating one bar can cause our scrollbar corner to come and go. We need to update the opposite scrollbar's style. + if (m_hBar) + m_hBar->styleChanged(); + if (m_vBar) + m_vBar->styleChanged(); + +#if ENABLE(DASHBOARD_SUPPORT) + // Force an update since we know the scrollbars have changed things. + if (renderer()->document()->hasDashboardRegions()) + renderer()->document()->setDashboardRegionsDirty(true); +#endif +} + +int RenderLayer::verticalScrollbarWidth() const +{ + if (!m_vBar) + return 0; + return m_vBar->width(); +} + +int RenderLayer::horizontalScrollbarHeight() const +{ + if (!m_hBar) + return 0; + return m_hBar->height(); +} + +IntSize RenderLayer::offsetFromResizeCorner(const IntPoint& absolutePoint) const +{ + // Currently the resize corner is always the bottom right corner + IntPoint bottomRight(width(), height()); + IntPoint localPoint = absoluteToContents(absolutePoint); + return localPoint - bottomRight; +} + +bool RenderLayer::hasOverflowControls() const +{ + return m_hBar || m_vBar || m_scrollCorner || renderer()->style()->resize() != RESIZE_NONE; +} +#if ENABLE(ANDROID_OVERFLOW_SCROLL) +bool RenderLayer::hasOverflowParent() const +{ + const RenderLayer* layer = this; + while (layer && !layer->hasOverflowScroll()) + layer = layer->parent(); + return layer; +} +#endif + +void RenderLayer::positionOverflowControls(int tx, int ty) +{ + if (!m_hBar && !m_vBar && (!renderer()->hasOverflowClip() || renderer()->style()->resize() == RESIZE_NONE)) + return; + + RenderBox* box = renderBox(); + if (!box) + return; + + IntRect borderBox = box->borderBoxRect(); + IntRect scrollCorner(scrollCornerRect(this, borderBox)); + IntRect absBounds(borderBox.x() + tx, borderBox.y() + ty, borderBox.width(), borderBox.height()); + if (m_vBar) + m_vBar->setFrameRect(IntRect(absBounds.right() - box->borderRight() - m_vBar->width(), + absBounds.y() + box->borderTop(), + m_vBar->width(), + absBounds.height() - (box->borderTop() + box->borderBottom()) - scrollCorner.height())); + + if (m_hBar) + m_hBar->setFrameRect(IntRect(absBounds.x() + box->borderLeft(), + absBounds.bottom() - box->borderBottom() - m_hBar->height(), + absBounds.width() - (box->borderLeft() + box->borderRight()) - scrollCorner.width(), + m_hBar->height())); + + if (m_scrollCorner) + m_scrollCorner->setFrameRect(scrollCorner); + if (m_resizer) + m_resizer->setFrameRect(resizerCornerRect(this, borderBox)); +} + +#if PLATFORM(ANDROID) +// When width/height change, the scrollWidth/scrollHeight should be dirty. +// And this should be upstreamed to webkit. +void RenderLayer::setWidth(int w) +{ + if (m_width != w) { + m_scrollDimensionsDirty = true; + m_width = w; + } +} + +void RenderLayer::setHeight(int h) +{ + if (m_height != h) { + m_scrollDimensionsDirty = true; + m_height = h; + } +} +#endif + +int RenderLayer::scrollWidth() +{ + if (m_scrollDimensionsDirty) + computeScrollDimensions(); + return m_scrollWidth; +} + +int RenderLayer::scrollHeight() +{ + if (m_scrollDimensionsDirty) + computeScrollDimensions(); + return m_scrollHeight; +} + +int RenderLayer::overflowTop() const +{ + RenderBox* box = renderBox(); + IntRect overflowRect(box->layoutOverflowRect()); + box->flipForWritingMode(overflowRect); + return overflowRect.y(); +} + +int RenderLayer::overflowBottom() const +{ + RenderBox* box = renderBox(); + IntRect overflowRect(box->layoutOverflowRect()); + box->flipForWritingMode(overflowRect); + return overflowRect.bottom(); +} + +int RenderLayer::overflowLeft() const +{ + RenderBox* box = renderBox(); + IntRect overflowRect(box->layoutOverflowRect()); + box->flipForWritingMode(overflowRect); + return overflowRect.x(); +} + +int RenderLayer::overflowRight() const +{ + RenderBox* box = renderBox(); + IntRect overflowRect(box->layoutOverflowRect()); + box->flipForWritingMode(overflowRect); + return overflowRect.right(); +} + +void RenderLayer::computeScrollDimensions(bool* needHBar, bool* needVBar) +{ + RenderBox* box = renderBox(); + ASSERT(box); + + m_scrollDimensionsDirty = false; + + m_scrollLeftOverflow = overflowLeft() - box->borderLeft(); + m_scrollTopOverflow = overflowTop() - box->borderTop(); + + m_scrollWidth = overflowRight() - overflowLeft(); + m_scrollHeight = overflowBottom() - overflowTop(); + + m_scrollOrigin = IntPoint(-m_scrollLeftOverflow, -m_scrollTopOverflow); + + if (needHBar) + *needHBar = m_scrollWidth > box->clientWidth(); + if (needVBar) + *needVBar = m_scrollHeight > box->clientHeight(); +} + +void RenderLayer::updateOverflowStatus(bool horizontalOverflow, bool verticalOverflow) +{ + if (m_overflowStatusDirty) { + m_horizontalOverflow = horizontalOverflow; + m_verticalOverflow = verticalOverflow; + m_overflowStatusDirty = false; + return; + } + + bool horizontalOverflowChanged = (m_horizontalOverflow != horizontalOverflow); + bool verticalOverflowChanged = (m_verticalOverflow != verticalOverflow); + + if (horizontalOverflowChanged || verticalOverflowChanged) { + m_horizontalOverflow = horizontalOverflow; + m_verticalOverflow = verticalOverflow; + + if (FrameView* frameView = renderer()->document()->view()) { + frameView->scheduleEvent(OverflowEvent::create(horizontalOverflowChanged, horizontalOverflow, verticalOverflowChanged, verticalOverflow), + renderer()->node()); + } + } +} + +void +RenderLayer::updateScrollInfoAfterLayout() +{ + RenderBox* box = renderBox(); + if (!box) + return; + + m_scrollDimensionsDirty = true; + + bool horizontalOverflow, verticalOverflow; + computeScrollDimensions(&horizontalOverflow, &verticalOverflow); + + if (box->style()->overflowX() != OMARQUEE) { + // Layout may cause us to be in an invalid scroll position. In this case we need + // to pull our scroll offsets back to the max (or push them up to the min). + int newX = max(0, min(scrollXOffset(), scrollWidth() - box->clientWidth())); + int newY = max(0, min(m_scrollY, scrollHeight() - box->clientHeight())); + if (newX != scrollXOffset() || newY != m_scrollY) { + RenderView* view = renderer()->view(); + ASSERT(view); + // scrollToOffset() may call updateLayerPositions(), which doesn't work + // with LayoutState. + // FIXME: Remove the disableLayoutState/enableLayoutState if the above changes. + if (view) + view->disableLayoutState(); + scrollToOffset(newX, newY); + if (view) + view->enableLayoutState(); + } + } + + bool haveHorizontalBar = m_hBar; + bool haveVerticalBar = m_vBar; + + // overflow:scroll should just enable/disable. + if (renderer()->style()->overflowX() == OSCROLL) + m_hBar->setEnabled(horizontalOverflow); + if (renderer()->style()->overflowY() == OSCROLL) + m_vBar->setEnabled(verticalOverflow); + + // A dynamic change from a scrolling overflow to overflow:hidden means we need to get rid of any + // scrollbars that may be present. + if (renderer()->style()->overflowX() == OHIDDEN && haveHorizontalBar) + setHasHorizontalScrollbar(false); + if (renderer()->style()->overflowY() == OHIDDEN && haveVerticalBar) + setHasVerticalScrollbar(false); + + // overflow:auto may need to lay out again if scrollbars got added/removed. + bool scrollbarsChanged = (box->hasAutoHorizontalScrollbar() && haveHorizontalBar != horizontalOverflow) || + (box->hasAutoVerticalScrollbar() && haveVerticalBar != verticalOverflow); + if (scrollbarsChanged) { + if (box->hasAutoHorizontalScrollbar()) + setHasHorizontalScrollbar(horizontalOverflow); + if (box->hasAutoVerticalScrollbar()) + setHasVerticalScrollbar(verticalOverflow); + +#if ENABLE(DASHBOARD_SUPPORT) + // Force an update since we know the scrollbars have changed things. + if (renderer()->document()->hasDashboardRegions()) + renderer()->document()->setDashboardRegionsDirty(true); +#endif + + renderer()->repaint(); + + if (renderer()->style()->overflowX() == OAUTO || renderer()->style()->overflowY() == OAUTO) { + if (!m_inOverflowRelayout) { + // Our proprietary overflow: overlay value doesn't trigger a layout. + m_inOverflowRelayout = true; + renderer()->setNeedsLayout(true, false); + if (renderer()->isRenderBlock()) { + RenderBlock* block = toRenderBlock(renderer()); + block->scrollbarsChanged(box->hasAutoHorizontalScrollbar() && haveHorizontalBar != horizontalOverflow, + box->hasAutoVerticalScrollbar() && haveVerticalBar != verticalOverflow); + block->layoutBlock(true); + } else + renderer()->layout(); + m_inOverflowRelayout = false; + } + } + } + + // If overflow:scroll is turned into overflow:auto a bar might still be disabled (Bug 11985). + if (m_hBar && box->hasAutoHorizontalScrollbar()) + m_hBar->setEnabled(true); + if (m_vBar && box->hasAutoVerticalScrollbar()) + m_vBar->setEnabled(true); + + // Set up the range (and page step/line step). + if (m_hBar) { + int clientWidth = box->clientWidth(); + int pageStep = max(max<int>(clientWidth * Scrollbar::minFractionToStepWhenPaging(), clientWidth - Scrollbar::maxOverlapBetweenPages()), 1); + m_hBar->setSteps(Scrollbar::pixelsPerLineStep(), pageStep); + m_hBar->setProportion(clientWidth, m_scrollWidth); + // Explicitly set the horizontal scroll value. This ensures that when a + // right-to-left scrollable area's width (or content width) changes, the + // top right corner of the content doesn't shift with respect to the top + // right corner of the area. Conceptually, right-to-left areas have + // their origin at the top-right, but RenderLayer is top-left oriented, + // so this is needed to keep everything working. + m_hBar->setValue(scrollXOffset(), Scrollbar::NotFromScrollAnimator); + } + if (m_vBar) { + int clientHeight = box->clientHeight(); + int pageStep = max(max<int>(clientHeight * Scrollbar::minFractionToStepWhenPaging(), clientHeight - Scrollbar::maxOverlapBetweenPages()), 1); + m_vBar->setSteps(Scrollbar::pixelsPerLineStep(), pageStep); + m_vBar->setProportion(clientHeight, m_scrollHeight); + // Explicitly set the vertical scroll value. This ensures that when a + // right-to-left vertical writing-mode scrollable area's height (or content height) changes, the + // bottom right corner of the content doesn't shift with respect to the bottom + // right corner of the area. Conceptually, right-to-left vertical writing-mode areas have + // their origin at the bottom-right, but RenderLayer is top-left oriented, + // so this is needed to keep everything working. + m_vBar->setValue(scrollYOffset(), Scrollbar::NotFromScrollAnimator); + } + + if (renderer()->node() && renderer()->document()->hasListenerType(Document::OVERFLOWCHANGED_LISTENER)) + updateOverflowStatus(horizontalOverflow, verticalOverflow); + +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + bool hasOverflowScroll = ((horizontalOverflow && m_hBar) || (verticalOverflow && m_vBar)) + // Disable UI side scrolling for textareas, unless they are readonly. + && (!renderer()->isTextArea() || (renderer()->node() + && static_cast<HTMLTextAreaElement*>(renderer()->node())->readOnly())); + if (hasOverflowScroll != m_hasOverflowScroll) { + m_hasOverflowScroll = hasOverflowScroll; + dirtyZOrderLists(); + dirtyStackingContextZOrderLists(); + if (renderer()->node()) + renderer()->node()->setNeedsStyleRecalc(SyntheticStyleChange); + } +#endif +} + +void RenderLayer::paintOverflowControls(GraphicsContext* context, int tx, int ty, const IntRect& damageRect) +{ + // Don't do anything if we have no overflow. + if (!renderer()->hasOverflowClip()) + return; + + // Move the scrollbar widgets if necessary. We normally move and resize widgets during layout, but sometimes + // widgets can move without layout occurring (most notably when you scroll a document that + // contains fixed positioned elements). + positionOverflowControls(tx, ty); + + // Now that we're sure the scrollbars are in the right place, paint them. + if (m_hBar) + m_hBar->paint(context, damageRect); + if (m_vBar) + m_vBar->paint(context, damageRect); + + // We fill our scroll corner with white if we have a scrollbar that doesn't run all the way up to the + // edge of the box. + paintScrollCorner(context, tx, ty, damageRect); + + // Paint our resizer last, since it sits on top of the scroll corner. + paintResizer(context, tx, ty, damageRect); +} + +void RenderLayer::paintScrollCorner(GraphicsContext* context, int tx, int ty, const IntRect& damageRect) +{ + RenderBox* box = renderBox(); + ASSERT(box); + + IntRect cornerRect = scrollCornerRect(this, box->borderBoxRect()); + IntRect absRect = IntRect(cornerRect.x() + tx, cornerRect.y() + ty, cornerRect.width(), cornerRect.height()); + if (!absRect.intersects(damageRect)) + return; + + if (context->updatingControlTints()) { + updateScrollCornerStyle(); + return; + } + + if (m_scrollCorner) { + m_scrollCorner->paintIntoRect(context, tx, ty, absRect); + return; + } + + context->fillRect(absRect, Color::white, box->style()->colorSpace()); +} + +void RenderLayer::paintResizer(GraphicsContext* context, int tx, int ty, const IntRect& damageRect) +{ + if (renderer()->style()->resize() == RESIZE_NONE) + return; + + RenderBox* box = renderBox(); + ASSERT(box); + + IntRect cornerRect = resizerCornerRect(this, box->borderBoxRect()); + IntRect absRect = IntRect(cornerRect.x() + tx, cornerRect.y() + ty, cornerRect.width(), cornerRect.height()); + if (!absRect.intersects(damageRect)) + return; + + if (context->updatingControlTints()) { + updateResizerStyle(); + return; + } + + if (m_resizer) { + m_resizer->paintIntoRect(context, tx, ty, absRect); + return; + } + + // Paint the resizer control. + DEFINE_STATIC_LOCAL(RefPtr<Image>, resizeCornerImage, (Image::loadPlatformResource("textAreaResizeCorner"))); + IntPoint imagePoint(absRect.right() - resizeCornerImage->width(), absRect.bottom() - resizeCornerImage->height()); + context->drawImage(resizeCornerImage.get(), box->style()->colorSpace(), imagePoint); + + // Draw a frame around the resizer (1px grey line) if there are any scrollbars present. + // Clipping will exclude the right and bottom edges of this frame. + if (m_hBar || m_vBar) { + context->save(); + context->clip(absRect); + IntRect largerCorner = absRect; + largerCorner.setSize(IntSize(largerCorner.width() + 1, largerCorner.height() + 1)); + context->setStrokeColor(Color(makeRGB(217, 217, 217)), ColorSpaceDeviceRGB); + context->setStrokeThickness(1.0f); + context->setFillColor(Color::transparent, ColorSpaceDeviceRGB); + context->drawRect(largerCorner); + context->restore(); + } +} + +bool RenderLayer::isPointInResizeControl(const IntPoint& absolutePoint) const +{ + if (!renderer()->hasOverflowClip() || renderer()->style()->resize() == RESIZE_NONE) + return false; + + RenderBox* box = renderBox(); + ASSERT(box); + + IntPoint localPoint = absoluteToContents(absolutePoint); + + IntRect localBounds(0, 0, box->width(), box->height()); + return resizerCornerRect(this, localBounds).contains(localPoint); +} + +bool RenderLayer::hitTestOverflowControls(HitTestResult& result, const IntPoint& localPoint) +{ + if (!m_hBar && !m_vBar && (!renderer()->hasOverflowClip() || renderer()->style()->resize() == RESIZE_NONE)) + return false; + + RenderBox* box = renderBox(); + ASSERT(box); + + IntRect resizeControlRect; + if (renderer()->style()->resize() != RESIZE_NONE) { + resizeControlRect = resizerCornerRect(this, box->borderBoxRect()); + if (resizeControlRect.contains(localPoint)) + return true; + } + + int resizeControlSize = max(resizeControlRect.height(), 0); + + if (m_vBar) { + IntRect vBarRect(box->width() - box->borderRight() - m_vBar->width(), + box->borderTop(), + m_vBar->width(), + box->height() - (box->borderTop() + box->borderBottom()) - (m_hBar ? m_hBar->height() : resizeControlSize)); + if (vBarRect.contains(localPoint)) { + result.setScrollbar(m_vBar.get()); + return true; + } + } + + resizeControlSize = max(resizeControlRect.width(), 0); + if (m_hBar) { + IntRect hBarRect(box->borderLeft(), + box->height() - box->borderBottom() - m_hBar->height(), + box->width() - (box->borderLeft() + box->borderRight()) - (m_vBar ? m_vBar->width() : resizeControlSize), + m_hBar->height()); + if (hBarRect.contains(localPoint)) { + result.setScrollbar(m_hBar.get()); + return true; + } + } + + return false; +} + +bool RenderLayer::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier) +{ + bool didHorizontalScroll = false; + bool didVerticalScroll = false; + + if (m_hBar) + didHorizontalScroll = m_hBar->scroll(direction, granularity, multiplier); + if (m_vBar) + didVerticalScroll = m_vBar->scroll(direction, granularity, multiplier); + + return (didHorizontalScroll || didVerticalScroll); +} + +void RenderLayer::paint(GraphicsContext* p, const IntRect& damageRect, PaintBehavior paintBehavior, RenderObject *paintingRoot) +{ + OverlapTestRequestMap overlapTestRequests; + paintLayer(this, p, damageRect, paintBehavior, paintingRoot, &overlapTestRequests); + OverlapTestRequestMap::iterator end = overlapTestRequests.end(); + for (OverlapTestRequestMap::iterator it = overlapTestRequests.begin(); it != end; ++it) + it->first->setOverlapTestResult(false); +} + +static void setClip(GraphicsContext* p, const IntRect& paintDirtyRect, const IntRect& clipRect) +{ + if (paintDirtyRect == clipRect) + return; + p->save(); + p->clip(clipRect); +} + +static void restoreClip(GraphicsContext* p, const IntRect& paintDirtyRect, const IntRect& clipRect) +{ + if (paintDirtyRect == clipRect) + return; + p->restore(); +} + +static void performOverlapTests(OverlapTestRequestMap& overlapTestRequests, const RenderLayer* rootLayer, const RenderLayer* layer) +{ + Vector<OverlapTestRequestClient*> overlappedRequestClients; + OverlapTestRequestMap::iterator end = overlapTestRequests.end(); + IntRect boundingBox = layer->boundingBox(rootLayer); + for (OverlapTestRequestMap::iterator it = overlapTestRequests.begin(); it != end; ++it) { + if (!boundingBox.intersects(it->second)) + continue; + + it->first->setOverlapTestResult(true); + overlappedRequestClients.append(it->first); + } + for (size_t i = 0; i < overlappedRequestClients.size(); ++i) + overlapTestRequests.remove(overlappedRequestClients[i]); +} + +#if USE(ACCELERATED_COMPOSITING) +static bool shouldDoSoftwarePaint(const RenderLayer* layer, bool paintingReflection) +{ + return paintingReflection && !layer->has3DTransform(); +} +#endif + +void RenderLayer::paintLayer(RenderLayer* rootLayer, GraphicsContext* p, + const IntRect& paintDirtyRect, PaintBehavior paintBehavior, + RenderObject* paintingRoot, OverlapTestRequestMap* overlapTestRequests, + PaintLayerFlags paintFlags) +{ +#if USE(ACCELERATED_COMPOSITING) + if (isComposited()) { + // The updatingControlTints() painting pass goes through compositing layers, + // but we need to ensure that we don't cache clip rects computed with the wrong root in this case. + if (p->updatingControlTints() || (paintBehavior & PaintBehaviorFlattenCompositingLayers)) + paintFlags |= PaintLayerTemporaryClipRects; + else if (!backing()->paintingGoesToWindow() && !shouldDoSoftwarePaint(this, paintFlags & PaintLayerPaintingReflection)) { + // If this RenderLayer should paint into its backing, that will be done via RenderLayerBacking::paintIntoLayer(). + return; + } + } +#endif + + // Avoid painting layers when stylesheets haven't loaded. This eliminates FOUC. + // It's ok not to draw, because later on, when all the stylesheets do load, updateStyleSelector on the Document + // will do a full repaint(). + if (renderer()->document()->mayCauseFlashOfUnstyledContent() && !renderer()->isRenderView() && !renderer()->isRoot()) + return; + + // If this layer is totally invisible then there is nothing to paint. + if (!renderer()->opacity()) + return; + + if (paintsWithTransparency(paintBehavior)) + paintFlags |= PaintLayerHaveTransparency; + + // Apply a transform if we have one. A reflection is considered to be a transform, since it is a flip and a translate. + if (paintsWithTransform(paintBehavior) && !(paintFlags & PaintLayerAppliedTransform)) { + TransformationMatrix layerTransform = renderableTransform(paintBehavior); + // If the transform can't be inverted, then don't paint anything. + if (!layerTransform.isInvertible()) + return; + + // If we have a transparency layer enclosing us and we are the root of a transform, then we need to establish the transparency + // layer from the parent now. + if (paintFlags & PaintLayerHaveTransparency) + parent()->beginTransparencyLayers(p, rootLayer, paintBehavior); + + // Make sure the parent's clip rects have been calculated. + IntRect clipRect = paintDirtyRect; + if (parent()) { + clipRect = backgroundClipRect(rootLayer, paintFlags & PaintLayerTemporaryClipRects); + clipRect.intersect(paintDirtyRect); + } + + // Push the parent coordinate space's clip. + setClip(p, paintDirtyRect, clipRect); + + // Adjust the transform such that the renderer's upper left corner will paint at (0,0) in user space. + // This involves subtracting out the position of the layer in our current coordinate space. + int x = 0; + int y = 0; + convertToLayerCoords(rootLayer, x, y); + TransformationMatrix transform(layerTransform); + transform.translateRight(x, y); + + // Apply the transform. + p->save(); + p->concatCTM(transform.toAffineTransform()); + + // Now do a paint with the root layer shifted to be us. + paintLayer(this, p, transform.inverse().mapRect(paintDirtyRect), paintBehavior, paintingRoot, overlapTestRequests, paintFlags | PaintLayerAppliedTransform); + + p->restore(); + + // Restore the clip. + restoreClip(p, paintDirtyRect, clipRect); + + return; + } + + PaintLayerFlags localPaintFlags = paintFlags & ~PaintLayerAppliedTransform; + bool haveTransparency = localPaintFlags & PaintLayerHaveTransparency; + + // Paint the reflection first if we have one. + if (m_reflection && !m_paintingInsideReflection) { + // Mark that we are now inside replica painting. + m_paintingInsideReflection = true; + reflectionLayer()->paintLayer(rootLayer, p, paintDirtyRect, paintBehavior, paintingRoot, overlapTestRequests, localPaintFlags | PaintLayerPaintingReflection); + m_paintingInsideReflection = false; + } + + // Calculate the clip rects we should use. + IntRect layerBounds, damageRect, clipRectToApply, outlineRect; + calculateRects(rootLayer, paintDirtyRect, layerBounds, damageRect, clipRectToApply, outlineRect, localPaintFlags & PaintLayerTemporaryClipRects); + int x = layerBounds.x(); + int y = layerBounds.y(); + int tx = x - renderBoxX(); + int ty = y - renderBoxY(); + + // Ensure our lists are up-to-date. + updateCompositingAndLayerListsIfNeeded(); + + bool forceBlackText = paintBehavior & PaintBehaviorForceBlackText; + bool selectionOnly = paintBehavior & PaintBehaviorSelectionOnly; + + // If this layer's renderer is a child of the paintingRoot, we render unconditionally, which + // is done by passing a nil paintingRoot down to our renderer (as if no paintingRoot was ever set). + // Else, our renderer tree may or may not contain the painting root, so we pass that root along + // so it will be tested against as we descend through the renderers. + RenderObject* paintingRootForRenderer = 0; + if (paintingRoot && !renderer()->isDescendantOf(paintingRoot)) + paintingRootForRenderer = paintingRoot; + + if (overlapTestRequests && isSelfPaintingLayer()) + performOverlapTests(*overlapTestRequests, rootLayer, this); + + // We want to paint our layer, but only if we intersect the damage rect. + bool shouldPaint = intersectsDamageRect(layerBounds, damageRect, rootLayer) && m_hasVisibleContent && isSelfPaintingLayer(); + if (shouldPaint && !selectionOnly && !damageRect.isEmpty()) { + // Begin transparency layers lazily now that we know we have to paint something. + if (haveTransparency) + beginTransparencyLayers(p, rootLayer, paintBehavior); + + // Paint our background first, before painting any child layers. + // Establish the clip used to paint our background. + setClip(p, paintDirtyRect, damageRect); + + // Paint the background. + PaintInfo paintInfo(p, damageRect, PaintPhaseBlockBackground, false, paintingRootForRenderer, 0); + renderer()->paint(paintInfo, tx, ty); + + // Restore the clip. + restoreClip(p, paintDirtyRect, damageRect); + } + + // Now walk the sorted list of children with negative z-indices. + paintList(m_negZOrderList, rootLayer, p, paintDirtyRect, paintBehavior, paintingRoot, overlapTestRequests, localPaintFlags); + + // Now establish the appropriate clip and paint our child RenderObjects. + if (shouldPaint && !clipRectToApply.isEmpty()) { + // Begin transparency layers lazily now that we know we have to paint something. + if (haveTransparency) + beginTransparencyLayers(p, rootLayer, paintBehavior); + + // Set up the clip used when painting our children. + setClip(p, paintDirtyRect, clipRectToApply); + PaintInfo paintInfo(p, clipRectToApply, + selectionOnly ? PaintPhaseSelection : PaintPhaseChildBlockBackgrounds, + forceBlackText, paintingRootForRenderer, 0); + renderer()->paint(paintInfo, tx, ty); + if (!selectionOnly) { + paintInfo.phase = PaintPhaseFloat; + renderer()->paint(paintInfo, tx, ty); + paintInfo.phase = PaintPhaseForeground; + paintInfo.overlapTestRequests = overlapTestRequests; + renderer()->paint(paintInfo, tx, ty); + paintInfo.phase = PaintPhaseChildOutlines; + renderer()->paint(paintInfo, tx, ty); + } + + // Now restore our clip. + restoreClip(p, paintDirtyRect, clipRectToApply); + } + + if (!outlineRect.isEmpty() && isSelfPaintingLayer()) { + // Paint our own outline + PaintInfo paintInfo(p, outlineRect, PaintPhaseSelfOutline, false, paintingRootForRenderer, 0); + setClip(p, paintDirtyRect, outlineRect); + renderer()->paint(paintInfo, tx, ty); + restoreClip(p, paintDirtyRect, outlineRect); + } + + // Paint any child layers that have overflow. + paintList(m_normalFlowList, rootLayer, p, paintDirtyRect, paintBehavior, paintingRoot, overlapTestRequests, localPaintFlags); + + // Now walk the sorted list of children with positive z-indices. + paintList(m_posZOrderList, rootLayer, p, paintDirtyRect, paintBehavior, paintingRoot, overlapTestRequests, localPaintFlags); + + if (renderer()->hasMask() && shouldPaint && !selectionOnly && !damageRect.isEmpty()) { + setClip(p, paintDirtyRect, damageRect); + + // Paint the mask. + PaintInfo paintInfo(p, damageRect, PaintPhaseMask, false, paintingRootForRenderer, 0); + renderer()->paint(paintInfo, tx, ty); + + // Restore the clip. + restoreClip(p, paintDirtyRect, damageRect); + } + + // End our transparency layer + if (haveTransparency && m_usedTransparency && !m_paintingInsideReflection) { + p->endTransparencyLayer(); + p->restore(); + m_usedTransparency = false; + } +} + +void RenderLayer::paintList(Vector<RenderLayer*>* list, RenderLayer* rootLayer, GraphicsContext* p, + const IntRect& paintDirtyRect, PaintBehavior paintBehavior, + RenderObject* paintingRoot, OverlapTestRequestMap* overlapTestRequests, + PaintLayerFlags paintFlags) +{ + if (!list) + return; + + for (size_t i = 0; i < list->size(); ++i) { + RenderLayer* childLayer = list->at(i); + if (!childLayer->isPaginated()) + childLayer->paintLayer(rootLayer, p, paintDirtyRect, paintBehavior, paintingRoot, overlapTestRequests, paintFlags); + else + paintPaginatedChildLayer(childLayer, rootLayer, p, paintDirtyRect, paintBehavior, paintingRoot, overlapTestRequests, paintFlags); + } +} + +void RenderLayer::paintPaginatedChildLayer(RenderLayer* childLayer, RenderLayer* rootLayer, GraphicsContext* context, + const IntRect& paintDirtyRect, PaintBehavior paintBehavior, + RenderObject* paintingRoot, OverlapTestRequestMap* overlapTestRequests, + PaintLayerFlags paintFlags) +{ + // We need to do multiple passes, breaking up our child layer into strips. + Vector<RenderLayer*> columnLayers; + RenderLayer* ancestorLayer = isNormalFlowOnly() ? parent() : stackingContext(); + for (RenderLayer* curr = childLayer->parent(); curr; curr = curr->parent()) { + if (curr->renderer()->hasColumns()) + columnLayers.append(curr); +#ifdef ANDROID + // Bug filed: 56107 + if (curr == ancestorLayer) + break; +#else + if (curr == ancestorLayer || (curr->parent() && curr->parent()->renderer()->isPositioned())) + break; +#endif + } + + ASSERT(columnLayers.size()); + + paintChildLayerIntoColumns(childLayer, rootLayer, context, paintDirtyRect, paintBehavior, paintingRoot, overlapTestRequests, paintFlags, columnLayers, columnLayers.size() - 1); +} + +void RenderLayer::paintChildLayerIntoColumns(RenderLayer* childLayer, RenderLayer* rootLayer, GraphicsContext* context, + const IntRect& paintDirtyRect, PaintBehavior paintBehavior, + RenderObject* paintingRoot, OverlapTestRequestMap* overlapTestRequests, + PaintLayerFlags paintFlags, const Vector<RenderLayer*>& columnLayers, size_t colIndex) +{ + RenderBlock* columnBlock = toRenderBlock(columnLayers[colIndex]->renderer()); + + ASSERT(columnBlock && columnBlock->hasColumns()); + if (!columnBlock || !columnBlock->hasColumns()) + return; + + int layerX = 0; + int layerY = 0; + columnBlock->layer()->convertToLayerCoords(rootLayer, layerX, layerY); + + ColumnInfo* colInfo = columnBlock->columnInfo(); + unsigned colCount = columnBlock->columnCount(colInfo); + int currYOffset = 0; + for (unsigned i = 0; i < colCount; i++) { + // For each rect, we clip to the rect, and then we adjust our coords. + IntRect colRect = columnBlock->columnRectAt(colInfo, i); + int currXOffset = colRect.x() - (columnBlock->borderLeft() + columnBlock->paddingLeft()); + colRect.move(layerX, layerY); + + IntRect localDirtyRect(paintDirtyRect); + localDirtyRect.intersect(colRect); + + if (!localDirtyRect.isEmpty()) { + context->save(); + + // Each strip pushes a clip, since column boxes are specified as being + // like overflow:hidden. + context->clip(colRect); + + if (!colIndex) { + // Apply a translation transform to change where the layer paints. + TransformationMatrix oldTransform; + bool oldHasTransform = childLayer->transform(); + if (oldHasTransform) + oldTransform = *childLayer->transform(); + TransformationMatrix newTransform(oldTransform); + newTransform.translateRight(currXOffset, currYOffset); + + childLayer->m_transform.set(new TransformationMatrix(newTransform)); + childLayer->paintLayer(rootLayer, context, localDirtyRect, paintBehavior, paintingRoot, overlapTestRequests, paintFlags); + if (oldHasTransform) + childLayer->m_transform.set(new TransformationMatrix(oldTransform)); + else + childLayer->m_transform.clear(); + } else { + // Adjust the transform such that the renderer's upper left corner will paint at (0,0) in user space. + // This involves subtracting out the position of the layer in our current coordinate space. + int childX = 0; + int childY = 0; + columnLayers[colIndex - 1]->convertToLayerCoords(rootLayer, childX, childY); + TransformationMatrix transform; + transform.translateRight(childX + currXOffset, childY + currYOffset); + + // Apply the transform. + context->concatCTM(transform.toAffineTransform()); + + // Now do a paint with the root layer shifted to be the next multicol block. + paintChildLayerIntoColumns(childLayer, columnLayers[colIndex - 1], context, transform.inverse().mapRect(localDirtyRect), paintBehavior, + paintingRoot, overlapTestRequests, paintFlags, + columnLayers, colIndex - 1); + } + + context->restore(); + } + + // Move to the next position. + currYOffset -= colRect.height(); + } +} + +static inline IntRect frameVisibleRect(RenderObject* renderer) +{ + FrameView* frameView = renderer->document()->view(); + if (!frameView) + return IntRect(); + + return frameView->visibleContentRect(); +} + +bool RenderLayer::hitTest(const HitTestRequest& request, HitTestResult& result) +{ + renderer()->document()->updateLayout(); + + IntRect hitTestArea = result.rectForPoint(result.point()); + if (!request.ignoreClipping()) + hitTestArea.intersect(frameVisibleRect(renderer())); + + RenderLayer* insideLayer = hitTestLayer(this, 0, request, result, hitTestArea, result.point(), false); + if (!insideLayer) { + // We didn't hit any layer. If we are the root layer and the mouse is -- or just was -- down, + // return ourselves. We do this so mouse events continue getting delivered after a drag has + // exited the WebView, and so hit testing over a scrollbar hits the content document. + if ((request.active() || request.mouseUp()) && renderer()->isRenderView()) { + renderer()->updateHitTestResult(result, result.point()); + insideLayer = this; + } + } + + // Now determine if the result is inside an anchor - if the urlElement isn't already set. + Node* node = result.innerNode(); + if (node && !result.URLElement()) + result.setURLElement(static_cast<Element*>(node->enclosingLinkEventParentOrSelf())); + + // Next set up the correct :hover/:active state along the new chain. + updateHoverActiveState(request, result); + + // Now return whether we were inside this layer (this will always be true for the root + // layer). + return insideLayer; +} + +Node* RenderLayer::enclosingElement() const +{ + for (RenderObject* r = renderer(); r; r = r->parent()) { + if (Node* e = r->node()) + return e; + } + ASSERT_NOT_REACHED(); + return 0; +} + +// Compute the z-offset of the point in the transformState. +// This is effectively projecting a ray normal to the plane of ancestor, finding where that +// ray intersects target, and computing the z delta between those two points. +static double computeZOffset(const HitTestingTransformState& transformState) +{ + // We got an affine transform, so no z-offset + if (transformState.m_accumulatedTransform.isAffine()) + return 0; + + // Flatten the point into the target plane + FloatPoint targetPoint = transformState.mappedPoint(); + + // Now map the point back through the transform, which computes Z. + FloatPoint3D backmappedPoint = transformState.m_accumulatedTransform.mapPoint(FloatPoint3D(targetPoint)); + return backmappedPoint.z(); +} + +PassRefPtr<HitTestingTransformState> RenderLayer::createLocalTransformState(RenderLayer* rootLayer, RenderLayer* containerLayer, + const IntRect& hitTestRect, const IntPoint& hitTestPoint, + const HitTestingTransformState* containerTransformState) const +{ + RefPtr<HitTestingTransformState> transformState; + int offsetX = 0; + int offsetY = 0; + if (containerTransformState) { + // If we're already computing transform state, then it's relative to the container (which we know is non-null). + transformState = HitTestingTransformState::create(*containerTransformState); + convertToLayerCoords(containerLayer, offsetX, offsetY); + } else { + // If this is the first time we need to make transform state, then base it off of hitTestPoint, + // which is relative to rootLayer. + transformState = HitTestingTransformState::create(hitTestPoint, FloatQuad(hitTestRect)); + convertToLayerCoords(rootLayer, offsetX, offsetY); + } + + RenderObject* containerRenderer = containerLayer ? containerLayer->renderer() : 0; + if (renderer()->shouldUseTransformFromContainer(containerRenderer)) { + TransformationMatrix containerTransform; + renderer()->getTransformFromContainer(containerRenderer, IntSize(offsetX, offsetY), containerTransform); + transformState->applyTransform(containerTransform, HitTestingTransformState::AccumulateTransform); + } else { + transformState->translate(offsetX, offsetY, HitTestingTransformState::AccumulateTransform); + } + + return transformState; +} + + +static bool isHitCandidate(const RenderLayer* hitLayer, bool canDepthSort, double* zOffset, const HitTestingTransformState* transformState) +{ + if (!hitLayer) + return false; + + // The hit layer is depth-sorting with other layers, so just say that it was hit. + if (canDepthSort) + return true; + + // We need to look at z-depth to decide if this layer was hit. + if (zOffset) { + ASSERT(transformState); + // This is actually computing our z, but that's OK because the hitLayer is coplanar with us. + double childZOffset = computeZOffset(*transformState); + if (childZOffset > *zOffset) { + *zOffset = childZOffset; + return true; + } + return false; + } + + return true; +} + +// hitTestPoint and hitTestRect are relative to rootLayer. +// A 'flattening' layer is one preserves3D() == false. +// transformState.m_accumulatedTransform holds the transform from the containing flattening layer. +// transformState.m_lastPlanarPoint is the hitTestPoint in the plane of the containing flattening layer. +// transformState.m_lastPlanarQuad is the hitTestRect as a quad in the plane of the containing flattening layer. +// +// If zOffset is non-null (which indicates that the caller wants z offset information), +// *zOffset on return is the z offset of the hit point relative to the containing flattening layer. +RenderLayer* RenderLayer::hitTestLayer(RenderLayer* rootLayer, RenderLayer* containerLayer, const HitTestRequest& request, HitTestResult& result, + const IntRect& hitTestRect, const IntPoint& hitTestPoint, bool appliedTransform, + const HitTestingTransformState* transformState, double* zOffset) +{ + // The natural thing would be to keep HitTestingTransformState on the stack, but it's big, so we heap-allocate. + + bool useTemporaryClipRects = false; +#if USE(ACCELERATED_COMPOSITING) + useTemporaryClipRects = compositor()->inCompositingMode(); +#endif + + IntRect hitTestArea = result.rectForPoint(hitTestPoint); + + // Apply a transform if we have one. + if (transform() && !appliedTransform) { + // Make sure the parent's clip rects have been calculated. + if (parent()) { + IntRect clipRect = backgroundClipRect(rootLayer, useTemporaryClipRects); + // Go ahead and test the enclosing clip now. + if (!clipRect.intersects(hitTestArea)) + return 0; + } + + // Create a transform state to accumulate this transform. + RefPtr<HitTestingTransformState> newTransformState = createLocalTransformState(rootLayer, containerLayer, hitTestRect, hitTestPoint, transformState); + + // If the transform can't be inverted, then don't hit test this layer at all. + if (!newTransformState->m_accumulatedTransform.isInvertible()) + return 0; + + // Compute the point and the hit test rect in the coords of this layer by using the values + // from the transformState, which store the point and quad in the coords of the last flattened + // layer, and the accumulated transform which lets up map through preserve-3d layers. + // + // We can't just map hitTestPoint and hitTestRect because they may have been flattened (losing z) + // by our container. + IntPoint localPoint = roundedIntPoint(newTransformState->mappedPoint()); + IntRect localHitTestRect; +#if USE(ACCELERATED_COMPOSITING) + if (isComposited()) { + // It doesn't make sense to project hitTestRect into the plane of this layer, so use the same bounds we use for painting. + localHitTestRect = backing()->compositedBounds(); + } else +#endif + localHitTestRect = newTransformState->mappedQuad().enclosingBoundingBox(); + + // Now do a hit test with the root layer shifted to be us. + return hitTestLayer(this, containerLayer, request, result, localHitTestRect, localPoint, true, newTransformState.get(), zOffset); + } + + // Ensure our lists and 3d status are up-to-date. + updateCompositingAndLayerListsIfNeeded(); + update3DTransformedDescendantStatus(); + + RefPtr<HitTestingTransformState> localTransformState; + if (appliedTransform) { + // We computed the correct state in the caller (above code), so just reference it. + ASSERT(transformState); + localTransformState = const_cast<HitTestingTransformState*>(transformState); + } else if (transformState || m_has3DTransformedDescendant || preserves3D()) { + // We need transform state for the first time, or to offset the container state, so create it here. + localTransformState = createLocalTransformState(rootLayer, containerLayer, hitTestRect, hitTestPoint, transformState); + } + + // Check for hit test on backface if backface-visibility is 'hidden' + if (localTransformState && renderer()->style()->backfaceVisibility() == BackfaceVisibilityHidden) { + TransformationMatrix invertedMatrix = localTransformState->m_accumulatedTransform.inverse(); + // If the z-vector of the matrix is negative, the back is facing towards the viewer. + if (invertedMatrix.m33() < 0) + return 0; + } + + RefPtr<HitTestingTransformState> unflattenedTransformState = localTransformState; + if (localTransformState && !preserves3D()) { + // Keep a copy of the pre-flattening state, for computing z-offsets for the container + unflattenedTransformState = HitTestingTransformState::create(*localTransformState); + // This layer is flattening, so flatten the state passed to descendants. + localTransformState->flatten(); + } + + // Calculate the clip rects we should use. + IntRect layerBounds; + IntRect bgRect; + IntRect fgRect; + IntRect outlineRect; + calculateRects(rootLayer, hitTestRect, layerBounds, bgRect, fgRect, outlineRect, useTemporaryClipRects); + + // The following are used for keeping track of the z-depth of the hit point of 3d-transformed + // descendants. + double localZOffset = -numeric_limits<double>::infinity(); + double* zOffsetForDescendantsPtr = 0; + double* zOffsetForContentsPtr = 0; + + bool depthSortDescendants = false; + if (preserves3D()) { + depthSortDescendants = true; + // Our layers can depth-test with our container, so share the z depth pointer with the container, if it passed one down. + zOffsetForDescendantsPtr = zOffset ? zOffset : &localZOffset; + zOffsetForContentsPtr = zOffset ? zOffset : &localZOffset; + } else if (m_has3DTransformedDescendant) { + // Flattening layer with 3d children; use a local zOffset pointer to depth-test children and foreground. + depthSortDescendants = true; + zOffsetForDescendantsPtr = zOffset ? zOffset : &localZOffset; + zOffsetForContentsPtr = zOffset ? zOffset : &localZOffset; + } else if (zOffset) { + zOffsetForDescendantsPtr = 0; + // Container needs us to give back a z offset for the hit layer. + zOffsetForContentsPtr = zOffset; + } + + // This variable tracks which layer the mouse ends up being inside. + RenderLayer* candidateLayer = 0; + + // Begin by walking our list of positive layers from highest z-index down to the lowest z-index. + RenderLayer* hitLayer = hitTestList(m_posZOrderList, rootLayer, request, result, hitTestRect, hitTestPoint, + localTransformState.get(), zOffsetForDescendantsPtr, zOffset, unflattenedTransformState.get(), depthSortDescendants); + if (hitLayer) { + if (!depthSortDescendants) + return hitLayer; + candidateLayer = hitLayer; + } + + // Now check our overflow objects. + hitLayer = hitTestList(m_normalFlowList, rootLayer, request, result, hitTestRect, hitTestPoint, + localTransformState.get(), zOffsetForDescendantsPtr, zOffset, unflattenedTransformState.get(), depthSortDescendants); + if (hitLayer) { + if (!depthSortDescendants) + return hitLayer; + candidateLayer = hitLayer; + } + +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + if (hasOverflowParent()) { + ClipRects clipRects; + calculateClipRects(rootLayer, clipRects, useTemporaryClipRects); + fgRect.intersect(clipRects.hitTestClip()); + bgRect.intersect(clipRects.hitTestClip()); + } +#endif + // Next we want to see if the mouse pos is inside the child RenderObjects of the layer. + if (fgRect.intersects(hitTestArea) && isSelfPaintingLayer()) { + // Hit test with a temporary HitTestResult, because we only want to commit to 'result' if we know we're frontmost. + HitTestResult tempResult(result.point(), result.topPadding(), result.rightPadding(), result.bottomPadding(), result.leftPadding()); + if (hitTestContents(request, tempResult, layerBounds, hitTestPoint, HitTestDescendants) && + isHitCandidate(this, false, zOffsetForContentsPtr, unflattenedTransformState.get())) { + if (result.isRectBasedTest()) + result.append(tempResult); + else + result = tempResult; + if (!depthSortDescendants) + return this; + // Foreground can depth-sort with descendant layers, so keep this as a candidate. + candidateLayer = this; + } else if (result.isRectBasedTest()) + result.append(tempResult); + } + + // Now check our negative z-index children. + hitLayer = hitTestList(m_negZOrderList, rootLayer, request, result, hitTestRect, hitTestPoint, + localTransformState.get(), zOffsetForDescendantsPtr, zOffset, unflattenedTransformState.get(), depthSortDescendants); + if (hitLayer) { + if (!depthSortDescendants) + return hitLayer; + candidateLayer = hitLayer; + } + + // If we found a layer, return. Child layers, and foreground always render in front of background. + if (candidateLayer) + return candidateLayer; + + if (bgRect.intersects(hitTestArea) && isSelfPaintingLayer()) { + HitTestResult tempResult(result.point(), result.topPadding(), result.rightPadding(), result.bottomPadding(), result.leftPadding()); + if (hitTestContents(request, tempResult, layerBounds, hitTestPoint, HitTestSelf) && + isHitCandidate(this, false, zOffsetForContentsPtr, unflattenedTransformState.get())) { + if (result.isRectBasedTest()) + result.append(tempResult); + else + result = tempResult; + return this; + } else if (result.isRectBasedTest()) + result.append(tempResult); + } + + return 0; +} + +bool RenderLayer::hitTestContents(const HitTestRequest& request, HitTestResult& result, const IntRect& layerBounds, const IntPoint& hitTestPoint, HitTestFilter hitTestFilter) const +{ + if (!renderer()->hitTest(request, result, hitTestPoint, + layerBounds.x() - renderBoxX(), + layerBounds.y() - renderBoxY(), + hitTestFilter)) { + // It's wrong to set innerNode, but then claim that you didn't hit anything, unless it is + // a rect-based test. + ASSERT(!result.innerNode() || (result.isRectBasedTest() && result.rectBasedTestResult().size())); + return false; + } + + // For positioned generated content, we might still not have a + // node by the time we get to the layer level, since none of + // the content in the layer has an element. So just walk up + // the tree. + if (!result.innerNode() || !result.innerNonSharedNode()) { + Node* e = enclosingElement(); + if (!result.innerNode()) + result.setInnerNode(e); + if (!result.innerNonSharedNode()) + result.setInnerNonSharedNode(e); + } + + return true; +} + +RenderLayer* RenderLayer::hitTestList(Vector<RenderLayer*>* list, RenderLayer* rootLayer, + const HitTestRequest& request, HitTestResult& result, + const IntRect& hitTestRect, const IntPoint& hitTestPoint, + const HitTestingTransformState* transformState, + double* zOffsetForDescendants, double* zOffset, + const HitTestingTransformState* unflattenedTransformState, + bool depthSortDescendants) +{ + if (!list) + return 0; + + RenderLayer* resultLayer = 0; + for (int i = list->size() - 1; i >= 0; --i) { + RenderLayer* childLayer = list->at(i); + RenderLayer* hitLayer = 0; + HitTestResult tempResult(result.point(), result.topPadding(), result.rightPadding(), result.bottomPadding(), result.leftPadding()); + if (childLayer->isPaginated()) + hitLayer = hitTestPaginatedChildLayer(childLayer, rootLayer, request, tempResult, hitTestRect, hitTestPoint, transformState, zOffsetForDescendants); + else + hitLayer = childLayer->hitTestLayer(rootLayer, this, request, tempResult, hitTestRect, hitTestPoint, false, transformState, zOffsetForDescendants); + + // If it a rect-based test, we can safely append the temporary result since it might had hit + // nodes but not necesserily had hitLayer set. + if (result.isRectBasedTest()) + result.append(tempResult); + + if (isHitCandidate(hitLayer, depthSortDescendants, zOffset, unflattenedTransformState)) { + resultLayer = hitLayer; + if (!result.isRectBasedTest()) + result = tempResult; + if (!depthSortDescendants) + break; + } + } + + return resultLayer; +} + +RenderLayer* RenderLayer::hitTestPaginatedChildLayer(RenderLayer* childLayer, RenderLayer* rootLayer, const HitTestRequest& request, HitTestResult& result, + const IntRect& hitTestRect, const IntPoint& hitTestPoint, const HitTestingTransformState* transformState, double* zOffset) +{ + Vector<RenderLayer*> columnLayers; + RenderLayer* ancestorLayer = isNormalFlowOnly() ? parent() : stackingContext(); + for (RenderLayer* curr = childLayer->parent(); curr; curr = curr->parent()) { + if (curr->renderer()->hasColumns()) + columnLayers.append(curr); + if (curr == ancestorLayer || (curr->parent() && curr->parent()->renderer()->isPositioned())) + break; + } + + ASSERT(columnLayers.size()); + return hitTestChildLayerColumns(childLayer, rootLayer, request, result, hitTestRect, hitTestPoint, transformState, zOffset, + columnLayers, columnLayers.size() - 1); +} + +RenderLayer* RenderLayer::hitTestChildLayerColumns(RenderLayer* childLayer, RenderLayer* rootLayer, const HitTestRequest& request, HitTestResult& result, + const IntRect& hitTestRect, const IntPoint& hitTestPoint, const HitTestingTransformState* transformState, double* zOffset, + const Vector<RenderLayer*>& columnLayers, size_t columnIndex) +{ + RenderBlock* columnBlock = toRenderBlock(columnLayers[columnIndex]->renderer()); + + ASSERT(columnBlock && columnBlock->hasColumns()); + if (!columnBlock || !columnBlock->hasColumns()) + return 0; + + int layerX = 0; + int layerY = 0; + columnBlock->layer()->convertToLayerCoords(rootLayer, layerX, layerY); + + ColumnInfo* colInfo = columnBlock->columnInfo(); + int colCount = columnBlock->columnCount(colInfo); + + // We have to go backwards from the last column to the first. + int left = columnBlock->borderLeft() + columnBlock->paddingLeft(); + int currYOffset = 0; + int i; + for (i = 0; i < colCount; i++) + currYOffset -= columnBlock->columnRectAt(colInfo, i).height(); + for (i = colCount - 1; i >= 0; i--) { + // For each rect, we clip to the rect, and then we adjust our coords. + IntRect colRect = columnBlock->columnRectAt(colInfo, i); + int currXOffset = colRect.x() - left; + currYOffset += colRect.height(); + colRect.move(layerX, layerY); + + IntRect localClipRect(hitTestRect); + localClipRect.intersect(colRect); + + if (!localClipRect.isEmpty() && localClipRect.intersects(result.rectForPoint(hitTestPoint))) { + RenderLayer* hitLayer = 0; + if (!columnIndex) { + // Apply a translation transform to change where the layer paints. + TransformationMatrix oldTransform; + bool oldHasTransform = childLayer->transform(); + if (oldHasTransform) + oldTransform = *childLayer->transform(); + TransformationMatrix newTransform(oldTransform); + newTransform.translateRight(currXOffset, currYOffset); + + childLayer->m_transform.set(new TransformationMatrix(newTransform)); + hitLayer = childLayer->hitTestLayer(rootLayer, columnLayers[0], request, result, localClipRect, hitTestPoint, false, transformState, zOffset); + if (oldHasTransform) + childLayer->m_transform.set(new TransformationMatrix(oldTransform)); + else + childLayer->m_transform.clear(); + } else { + // Adjust the transform such that the renderer's upper left corner will be at (0,0) in user space. + // This involves subtracting out the position of the layer in our current coordinate space. + RenderLayer* nextLayer = columnLayers[columnIndex - 1]; + RefPtr<HitTestingTransformState> newTransformState = nextLayer->createLocalTransformState(rootLayer, nextLayer, localClipRect, hitTestPoint, transformState); + newTransformState->translate(currXOffset, currYOffset, HitTestingTransformState::AccumulateTransform); + IntPoint localPoint = roundedIntPoint(newTransformState->mappedPoint()); + IntRect localHitTestRect = newTransformState->mappedQuad().enclosingBoundingBox(); + newTransformState->flatten(); + + hitLayer = hitTestChildLayerColumns(childLayer, columnLayers[columnIndex - 1], request, result, localHitTestRect, localPoint, + newTransformState.get(), zOffset, columnLayers, columnIndex - 1); + } + + if (hitLayer) + return hitLayer; + } + } + + return 0; +} + +void RenderLayer::updateClipRects(const RenderLayer* rootLayer) +{ + if (m_clipRects) { + ASSERT(rootLayer == m_clipRectsRoot); + return; // We have the correct cached value. + } + + // For transformed layers, the root layer was shifted to be us, so there is no need to + // examine the parent. We want to cache clip rects with us as the root. + RenderLayer* parentLayer = rootLayer != this ? parent() : 0; + if (parentLayer) + parentLayer->updateClipRects(rootLayer); + + ClipRects clipRects; + calculateClipRects(rootLayer, clipRects, true); + + if (parentLayer && parentLayer->clipRects() && clipRects == *parentLayer->clipRects()) + m_clipRects = parentLayer->clipRects(); + else + m_clipRects = new (renderer()->renderArena()) ClipRects(clipRects); + m_clipRects->ref(); +#ifndef NDEBUG + m_clipRectsRoot = rootLayer; +#endif +} + +void RenderLayer::calculateClipRects(const RenderLayer* rootLayer, ClipRects& clipRects, bool useCached) const +{ + if (!parent()) { + // The root layer's clip rect is always infinite. + clipRects.reset(PaintInfo::infiniteRect()); + return; + } + + // For transformed layers, the root layer was shifted to be us, so there is no need to + // examine the parent. We want to cache clip rects with us as the root. + RenderLayer* parentLayer = rootLayer != this ? parent() : 0; + + // Ensure that our parent's clip has been calculated so that we can examine the values. + if (parentLayer) { + if (useCached && parentLayer->clipRects()) + clipRects = *parentLayer->clipRects(); + else + parentLayer->calculateClipRects(rootLayer, clipRects); + } + else + clipRects.reset(PaintInfo::infiniteRect()); + + // A fixed object is essentially the root of its containing block hierarchy, so when + // we encounter such an object, we reset our clip rects to the fixedClipRect. + if (renderer()->style()->position() == FixedPosition) { + clipRects.setPosClipRect(clipRects.fixedClipRect()); + clipRects.setOverflowClipRect(clipRects.fixedClipRect()); + clipRects.setFixed(true); + } + else if (renderer()->style()->position() == RelativePosition) + clipRects.setPosClipRect(clipRects.overflowClipRect()); + else if (renderer()->style()->position() == AbsolutePosition) + clipRects.setOverflowClipRect(clipRects.posClipRect()); + + // Update the clip rects that will be passed to child layers. + if (renderer()->hasOverflowClip() || renderer()->hasClip()) { + // This layer establishes a clip of some kind. + int x = 0; + int y = 0; + convertToLayerCoords(rootLayer, x, y); + RenderView* view = renderer()->view(); + ASSERT(view); + if (view && clipRects.fixed() && rootLayer->renderer() == view) { + x -= view->frameView()->scrollX(); + y -= view->frameView()->scrollY(); + } + + if (renderer()->hasOverflowClip()) { + IntRect newOverflowClip = toRenderBox(renderer())->overflowClipRect(x, y); +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + clipRects.setHitTestClip(intersection(clipRects.fixed() ? clipRects.fixedClipRect() + : newOverflowClip, clipRects.hitTestClip())); + if (hasOverflowScroll()) { + RenderBox* box = toRenderBox(renderer()); + newOverflowClip = + IntRect(x + box->borderLeft(), y + box->borderTop(), + m_scrollWidth, m_scrollHeight); + } +#endif + clipRects.setOverflowClipRect(intersection(newOverflowClip, clipRects.overflowClipRect())); + if (renderer()->isPositioned() || renderer()->isRelPositioned()) + clipRects.setPosClipRect(intersection(newOverflowClip, clipRects.posClipRect())); + } + if (renderer()->hasClip()) { + IntRect newPosClip = toRenderBox(renderer())->clipRect(x, y); + clipRects.setPosClipRect(intersection(newPosClip, clipRects.posClipRect())); + clipRects.setOverflowClipRect(intersection(newPosClip, clipRects.overflowClipRect())); + clipRects.setFixedClipRect(intersection(newPosClip, clipRects.fixedClipRect())); + } + } +} + +void RenderLayer::parentClipRects(const RenderLayer* rootLayer, ClipRects& clipRects, bool temporaryClipRects) const +{ + ASSERT(parent()); + if (temporaryClipRects) { + parent()->calculateClipRects(rootLayer, clipRects); + return; + } + + parent()->updateClipRects(rootLayer); + clipRects = *parent()->clipRects(); +} + +IntRect RenderLayer::backgroundClipRect(const RenderLayer* rootLayer, bool temporaryClipRects) const +{ + IntRect backgroundRect; + if (parent()) { + ClipRects parentRects; + parentClipRects(rootLayer, parentRects, temporaryClipRects); + backgroundRect = renderer()->style()->position() == FixedPosition ? parentRects.fixedClipRect() : + (renderer()->isPositioned() ? parentRects.posClipRect() : + parentRects.overflowClipRect()); + RenderView* view = renderer()->view(); + ASSERT(view); + if (view && parentRects.fixed() && rootLayer->renderer() == view) + backgroundRect.move(view->frameView()->scrollX(), view->frameView()->scrollY()); + } + return backgroundRect; +} + +void RenderLayer::calculateRects(const RenderLayer* rootLayer, const IntRect& paintDirtyRect, IntRect& layerBounds, + IntRect& backgroundRect, IntRect& foregroundRect, IntRect& outlineRect, bool temporaryClipRects) const +{ + if (rootLayer != this && parent()) { + backgroundRect = backgroundClipRect(rootLayer, temporaryClipRects); + backgroundRect.intersect(paintDirtyRect); + } else + backgroundRect = paintDirtyRect; + + foregroundRect = backgroundRect; + outlineRect = backgroundRect; + + int x = 0; + int y = 0; + convertToLayerCoords(rootLayer, x, y); + layerBounds = IntRect(x, y, width(), height()); + + // Update the clip rects that will be passed to child layers. + if (renderer()->hasOverflowClip() || renderer()->hasClip()) { + // This layer establishes a clip of some kind. +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + if (hasOverflowScroll()) { + // Use the entire foreground rectangle to record the contents. + RenderBox* box = toRenderBox(renderer()); + foregroundRect = + IntRect(x + box->borderLeft(), y + box->borderTop(), + m_scrollWidth, m_scrollHeight); + } else +#endif + if (renderer()->hasOverflowClip()) + foregroundRect.intersect(toRenderBox(renderer())->overflowClipRect(x, y)); + if (renderer()->hasClip()) { + // Clip applies to *us* as well, so go ahead and update the damageRect. + IntRect newPosClip = toRenderBox(renderer())->clipRect(x, y); + backgroundRect.intersect(newPosClip); + foregroundRect.intersect(newPosClip); + outlineRect.intersect(newPosClip); + } + + // If we establish a clip at all, then go ahead and make sure our background + // rect is intersected with our layer's bounds. + // FIXME: This could be changed to just use generic visual overflow. + // See https://bugs.webkit.org/show_bug.cgi?id=37467 for more information. + if (const ShadowData* boxShadow = renderer()->style()->boxShadow()) { + IntRect overflow = layerBounds; + do { + if (boxShadow->style() == Normal) { + IntRect shadowRect = layerBounds; + shadowRect.move(boxShadow->x(), boxShadow->y()); + shadowRect.inflate(boxShadow->blur() + boxShadow->spread()); + overflow.unite(shadowRect); + } + + boxShadow = boxShadow->next(); + } while (boxShadow); + backgroundRect.intersect(overflow); + } else + backgroundRect.intersect(layerBounds); + } +} + +IntRect RenderLayer::childrenClipRect() const +{ + RenderView* renderView = renderer()->view(); + RenderLayer* clippingRootLayer = clippingRoot(); + IntRect layerBounds, backgroundRect, foregroundRect, outlineRect; + calculateRects(clippingRootLayer, renderView->documentRect(), layerBounds, backgroundRect, foregroundRect, outlineRect); + return clippingRootLayer->renderer()->localToAbsoluteQuad(FloatQuad(foregroundRect)).enclosingBoundingBox(); +} + +IntRect RenderLayer::selfClipRect() const +{ + RenderView* renderView = renderer()->view(); + RenderLayer* clippingRootLayer = clippingRoot(); + IntRect layerBounds, backgroundRect, foregroundRect, outlineRect; + calculateRects(clippingRootLayer, renderView->documentRect(), layerBounds, backgroundRect, foregroundRect, outlineRect); + return clippingRootLayer->renderer()->localToAbsoluteQuad(FloatQuad(backgroundRect)).enclosingBoundingBox(); +} + +void RenderLayer::addBlockSelectionGapsBounds(const IntRect& bounds) +{ + m_blockSelectionGapsBounds.unite(bounds); +} + +void RenderLayer::clearBlockSelectionGapsBounds() +{ + m_blockSelectionGapsBounds = IntRect(); + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) + child->clearBlockSelectionGapsBounds(); +} + +void RenderLayer::repaintBlockSelectionGaps() +{ + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) + child->repaintBlockSelectionGaps(); + + if (m_blockSelectionGapsBounds.isEmpty()) + return; + + IntRect rect = m_blockSelectionGapsBounds; + rect.move(-scrolledContentOffset()); + if (renderer()->hasOverflowClip()) + rect.intersect(toRenderBox(renderer())->overflowClipRect(0, 0)); + if (renderer()->hasClip()) + rect.intersect(toRenderBox(renderer())->clipRect(0, 0)); + if (!rect.isEmpty()) + renderer()->repaintRectangle(rect); +} + +bool RenderLayer::intersectsDamageRect(const IntRect& layerBounds, const IntRect& damageRect, const RenderLayer* rootLayer) const +{ + // Always examine the canvas and the root. + // FIXME: Could eliminate the isRoot() check if we fix background painting so that the RenderView + // paints the root's background. + if (renderer()->isRenderView() || renderer()->isRoot()) + return true; + + // If we aren't an inline flow, and our layer bounds do intersect the damage rect, then we + // can go ahead and return true. + RenderView* view = renderer()->view(); + ASSERT(view); + if (view && !renderer()->isRenderInline()) { + IntRect b = layerBounds; + b.inflate(view->maximalOutlineSize()); + if (b.intersects(damageRect)) + return true; + } + + // Otherwise we need to compute the bounding box of this single layer and see if it intersects + // the damage rect. + return boundingBox(rootLayer).intersects(damageRect); +} + +IntRect RenderLayer::localBoundingBox() const +{ + // There are three special cases we need to consider. + // (1) Inline Flows. For inline flows we will create a bounding box that fully encompasses all of the lines occupied by the + // inline. In other words, if some <span> wraps to three lines, we'll create a bounding box that fully encloses the + // line boxes of all three lines (including overflow on those lines). + // (2) Left/Top Overflow. The width/height of layers already includes right/bottom overflow. However, in the case of left/top + // overflow, we have to create a bounding box that will extend to include this overflow. + // (3) Floats. When a layer has overhanging floats that it paints, we need to make sure to include these overhanging floats + // as part of our bounding box. We do this because we are the responsible layer for both hit testing and painting those + // floats. + IntRect result; + if (renderer()->isRenderInline()) { + // Go from our first line box to our last line box. + RenderInline* inlineFlow = toRenderInline(renderer()); + InlineFlowBox* firstBox = inlineFlow->firstLineBox(); + if (!firstBox) + return result; + int top = firstBox->topVisualOverflow(); + int bottom = inlineFlow->lastLineBox()->bottomVisualOverflow(); + int left = firstBox->x(); + for (InlineFlowBox* curr = firstBox->nextLineBox(); curr; curr = curr->nextLineBox()) + left = min(left, curr->x()); + result = IntRect(left, top, width(), bottom - top); + } else if (renderer()->isTableRow()) { + // Our bounding box is just the union of all of our cells' border/overflow rects. + for (RenderObject* child = renderer()->firstChild(); child; child = child->nextSibling()) { + if (child->isTableCell()) { + IntRect bbox = toRenderBox(child)->borderBoxRect(); + result.unite(bbox); + IntRect overflowRect = renderBox()->visualOverflowRect(); + if (bbox != overflowRect) + result.unite(overflowRect); + } + } + } else { + RenderBox* box = renderBox(); + ASSERT(box); + if (box->hasMask()) + result = box->maskClipRect(); + else { + IntRect bbox = box->borderBoxRect(); + result = bbox; + IntRect overflowRect = box->visualOverflowRect(); + if (bbox != overflowRect) + result.unite(overflowRect); + } + } + + RenderView* view = renderer()->view(); + ASSERT(view); + if (view) + result.inflate(view->maximalOutlineSize()); // Used to apply a fudge factor to dirty-rect checks on blocks/tables. + + return result; +} + +IntRect RenderLayer::boundingBox(const RenderLayer* ancestorLayer) const +{ + IntRect result = localBoundingBox(); + + int deltaX = 0, deltaY = 0; + convertToLayerCoords(ancestorLayer, deltaX, deltaY); + result.move(deltaX, deltaY); + return result; +} + +IntRect RenderLayer::absoluteBoundingBox() const +{ + return boundingBox(root()); +} + +void RenderLayer::clearClipRectsIncludingDescendants() +{ + if (!m_clipRects) + return; + + clearClipRects(); + + for (RenderLayer* l = firstChild(); l; l = l->nextSibling()) + l->clearClipRectsIncludingDescendants(); +} + +void RenderLayer::clearClipRects() +{ + if (m_clipRects) { + m_clipRects->deref(renderer()->renderArena()); + m_clipRects = 0; +#ifndef NDEBUG + m_clipRectsRoot = 0; +#endif + } +} + +#if USE(ACCELERATED_COMPOSITING) +RenderLayerBacking* RenderLayer::ensureBacking() +{ + if (!m_backing) + m_backing.set(new RenderLayerBacking(this)); + return m_backing.get(); +} + +void RenderLayer::clearBacking() +{ + m_backing.clear(); +} + +bool RenderLayer::hasCompositedMask() const +{ + return m_backing && m_backing->hasMaskLayer(); +} +#endif + +void RenderLayer::setParent(RenderLayer* parent) +{ + if (parent == m_parent) + return; + +#if USE(ACCELERATED_COMPOSITING) + if (m_parent && !renderer()->documentBeingDestroyed()) + compositor()->layerWillBeRemoved(m_parent, this); +#endif + + m_parent = parent; + +#if USE(ACCELERATED_COMPOSITING) + if (m_parent && !renderer()->documentBeingDestroyed()) + compositor()->layerWasAdded(m_parent, this); +#endif +} + +static RenderObject* commonAncestor(RenderObject* obj1, RenderObject* obj2) +{ + if (!obj1 || !obj2) + return 0; + + for (RenderObject* currObj1 = obj1; currObj1; currObj1 = currObj1->hoverAncestor()) + for (RenderObject* currObj2 = obj2; currObj2; currObj2 = currObj2->hoverAncestor()) + if (currObj1 == currObj2) + return currObj1; + + return 0; +} + +void RenderLayer::updateHoverActiveState(const HitTestRequest& request, HitTestResult& result) +{ + // We don't update :hover/:active state when the result is marked as readOnly. + if (request.readOnly()) + return; + + Document* doc = renderer()->document(); + + Node* activeNode = doc->activeNode(); + if (activeNode && !request.active()) { + // We are clearing the :active chain because the mouse has been released. + for (RenderObject* curr = activeNode->renderer(); curr; curr = curr->parent()) { + if (curr->node() && !curr->isText()) + curr->node()->clearInActiveChain(); + } + doc->setActiveNode(0); + } else { + Node* newActiveNode = result.innerNode(); + if (!activeNode && newActiveNode && request.active()) { + // We are setting the :active chain and freezing it. If future moves happen, they + // will need to reference this chain. + for (RenderObject* curr = newActiveNode->renderer(); curr; curr = curr->parent()) { + if (curr->node() && !curr->isText()) { + curr->node()->setInActiveChain(); + } + } + doc->setActiveNode(newActiveNode); + } + } + + // If the mouse is down and if this is a mouse move event, we want to restrict changes in + // :hover/:active to only apply to elements that are in the :active chain that we froze + // at the time the mouse went down. + bool mustBeInActiveChain = request.active() && request.mouseMove(); + + // Check to see if the hovered node has changed. If not, then we don't need to + // do anything. + RefPtr<Node> oldHoverNode = doc->hoverNode(); + Node* newHoverNode = result.innerNode(); + + // Update our current hover node. + doc->setHoverNode(newHoverNode); + + // We have two different objects. Fetch their renderers. + RenderObject* oldHoverObj = oldHoverNode ? oldHoverNode->renderer() : 0; + RenderObject* newHoverObj = newHoverNode ? newHoverNode->renderer() : 0; + + // Locate the common ancestor render object for the two renderers. + RenderObject* ancestor = commonAncestor(oldHoverObj, newHoverObj); + + Vector<RefPtr<Node>, 32> nodesToRemoveFromChain; + Vector<RefPtr<Node>, 32> nodesToAddToChain; + + if (oldHoverObj != newHoverObj) { + // The old hover path only needs to be cleared up to (and not including) the common ancestor; + for (RenderObject* curr = oldHoverObj; curr && curr != ancestor; curr = curr->hoverAncestor()) { + if (curr->node() && !curr->isText() && (!mustBeInActiveChain || curr->node()->inActiveChain())) + nodesToRemoveFromChain.append(curr->node()); + } + } + + // Now set the hover state for our new object up to the root. + for (RenderObject* curr = newHoverObj; curr; curr = curr->hoverAncestor()) { + if (curr->node() && !curr->isText() && (!mustBeInActiveChain || curr->node()->inActiveChain())) + nodesToAddToChain.append(curr->node()); + } + + size_t removeCount = nodesToRemoveFromChain.size(); + for (size_t i = 0; i < removeCount; ++i) { + nodesToRemoveFromChain[i]->setActive(false); + nodesToRemoveFromChain[i]->setHovered(false); + } + + size_t addCount = nodesToAddToChain.size(); + for (size_t i = 0; i < addCount; ++i) { + nodesToAddToChain[i]->setActive(request.active()); + nodesToAddToChain[i]->setHovered(true); + } +} + +// Helper for the sorting of layers by z-index. +static inline bool compareZIndex(RenderLayer* first, RenderLayer* second) +{ + return first->zIndex() < second->zIndex(); +} + +void RenderLayer::dirtyZOrderLists() +{ + if (m_posZOrderList) + m_posZOrderList->clear(); + if (m_negZOrderList) + m_negZOrderList->clear(); + m_zOrderListsDirty = true; + +#if USE(ACCELERATED_COMPOSITING) + if (!renderer()->documentBeingDestroyed()) + compositor()->setCompositingLayersNeedRebuild(); +#endif +} + +void RenderLayer::dirtyStackingContextZOrderLists() +{ + RenderLayer* sc = stackingContext(); + if (sc) + sc->dirtyZOrderLists(); +} + +void RenderLayer::dirtyNormalFlowList() +{ + if (m_normalFlowList) + m_normalFlowList->clear(); + m_normalFlowListDirty = true; + +#if USE(ACCELERATED_COMPOSITING) + if (!renderer()->documentBeingDestroyed()) + compositor()->setCompositingLayersNeedRebuild(); +#endif +} + +void RenderLayer::updateZOrderLists() +{ + if (!isStackingContext() || !m_zOrderListsDirty) + return; + + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) + if (!m_reflection || reflectionLayer() != child) + child->collectLayers(m_posZOrderList, m_negZOrderList); + + // Sort the two lists. + if (m_posZOrderList) + std::stable_sort(m_posZOrderList->begin(), m_posZOrderList->end(), compareZIndex); + + if (m_negZOrderList) + std::stable_sort(m_negZOrderList->begin(), m_negZOrderList->end(), compareZIndex); + + m_zOrderListsDirty = false; +} + +void RenderLayer::updateNormalFlowList() +{ + if (!m_normalFlowListDirty) + return; + + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) { + // Ignore non-overflow layers and reflections. + if (child->isNormalFlowOnly() && (!m_reflection || reflectionLayer() != child)) { + if (!m_normalFlowList) + m_normalFlowList = new Vector<RenderLayer*>; + m_normalFlowList->append(child); + } + } + + m_normalFlowListDirty = false; +} + +void RenderLayer::collectLayers(Vector<RenderLayer*>*& posBuffer, Vector<RenderLayer*>*& negBuffer) +{ + updateVisibilityStatus(); + + // Overflow layers are just painted by their enclosing layers, so they don't get put in zorder lists. + if ((m_hasVisibleContent || (m_hasVisibleDescendant && isStackingContext())) && !isNormalFlowOnly()) { + // Determine which buffer the child should be in. + Vector<RenderLayer*>*& buffer = (zIndex() >= 0) ? posBuffer : negBuffer; + + // Create the buffer if it doesn't exist yet. + if (!buffer) + buffer = new Vector<RenderLayer*>; + + // Append ourselves at the end of the appropriate buffer. + buffer->append(this); + } + + // Recur into our children to collect more layers, but only if we don't establish + // a stacking context. + if (m_hasVisibleDescendant && !isStackingContext()) { + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) { + // Ignore reflections. + if (!m_reflection || reflectionLayer() != child) + child->collectLayers(posBuffer, negBuffer); + } + } +} + +void RenderLayer::updateLayerListsIfNeeded() +{ + updateZOrderLists(); + updateNormalFlowList(); +} + +void RenderLayer::updateCompositingAndLayerListsIfNeeded() +{ +#if USE(ACCELERATED_COMPOSITING) + if (compositor()->inCompositingMode()) { + if ((isStackingContext() && m_zOrderListsDirty) || m_normalFlowListDirty) + compositor()->updateCompositingLayers(CompositingUpdateOnPaitingOrHitTest, this); + return; + } +#endif + updateLayerListsIfNeeded(); +} + +void RenderLayer::repaintIncludingDescendants() +{ + renderer()->repaint(); + for (RenderLayer* curr = firstChild(); curr; curr = curr->nextSibling()) + curr->repaintIncludingDescendants(); +} + +#if USE(ACCELERATED_COMPOSITING) +void RenderLayer::setBackingNeedsRepaint() +{ + ASSERT(isComposited()); + if (backing()->paintingGoesToWindow()) { + // If we're trying to repaint the placeholder document layer, propagate the + // repaint to the native view system. + RenderView* view = renderer()->view(); + if (view) + view->repaintViewRectangle(absoluteBoundingBox()); + } else + backing()->setContentsNeedDisplay(); +} + +void RenderLayer::setBackingNeedsRepaintInRect(const IntRect& r) +{ + ASSERT(isComposited()); + if (backing()->paintingGoesToWindow()) { + // If we're trying to repaint the placeholder document layer, propagate the + // repaint to the native view system. + IntRect absRect(r); + int x = 0; + int y = 0; + convertToLayerCoords(root(), x, y); + absRect.move(x, y); + + RenderView* view = renderer()->view(); + if (view) + view->repaintViewRectangle(absRect); + } else + backing()->setContentsNeedDisplayInRect(r); +} + +// Since we're only painting non-composited layers, we know that they all share the same repaintContainer. +void RenderLayer::repaintIncludingNonCompositingDescendants(RenderBoxModelObject* repaintContainer) +{ + renderer()->repaintUsingContainer(repaintContainer, renderer()->clippedOverflowRectForRepaint(repaintContainer)); + + for (RenderLayer* curr = firstChild(); curr; curr = curr->nextSibling()) { + if (!curr->isComposited()) + curr->repaintIncludingNonCompositingDescendants(repaintContainer); + } +} +#endif + +bool RenderLayer::shouldBeNormalFlowOnly() const +{ + return (renderer()->hasOverflowClip() + || renderer()->hasReflection() + || renderer()->hasMask() + || renderer()->isVideo() + || renderer()->isEmbeddedObject() + || renderer()->isApplet() + || renderer()->isRenderIFrame() + || renderer()->style()->specifiesColumns()) + && !renderer()->isPositioned() + && !renderer()->isRelPositioned() + && !renderer()->hasTransform() + && !isTransparent(); +} + +bool RenderLayer::isSelfPaintingLayer() const +{ +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + if (hasOverflowScroll()) + return true; +#endif + return !isNormalFlowOnly() + || renderer()->hasReflection() + || renderer()->hasMask() + || renderer()->isTableRow() + || renderer()->isVideo() + || renderer()->isEmbeddedObject() + || renderer()->isApplet() + || renderer()->isRenderIFrame(); +} + +void RenderLayer::styleChanged(StyleDifference diff, const RenderStyle*) +{ + bool isNormalFlowOnly = shouldBeNormalFlowOnly(); + if (isNormalFlowOnly != m_isNormalFlowOnly) { + m_isNormalFlowOnly = isNormalFlowOnly; + RenderLayer* p = parent(); + if (p) + p->dirtyNormalFlowList(); + dirtyStackingContextZOrderLists(); + } + + if (renderer()->style()->overflowX() == OMARQUEE && renderer()->style()->marqueeBehavior() != MNONE && renderer()->isBox()) { + if (!m_marquee) + m_marquee = new RenderMarquee(this); + m_marquee->updateMarqueeStyle(); + } + else if (m_marquee) { + delete m_marquee; + m_marquee = 0; + } + + if (!hasReflection() && m_reflection) + removeReflection(); + else if (hasReflection()) { + if (!m_reflection) + createReflection(); + updateReflectionStyle(); + } + + // FIXME: Need to detect a swap from custom to native scrollbars (and vice versa). + if (m_hBar) + m_hBar->styleChanged(); + if (m_vBar) + m_vBar->styleChanged(); + + updateScrollCornerStyle(); + updateResizerStyle(); + +#if USE(ACCELERATED_COMPOSITING) + updateTransform(); + + if (compositor()->updateLayerCompositingState(this)) + compositor()->setCompositingLayersNeedRebuild(); + else if (m_backing) + m_backing->updateGraphicsLayerGeometry(); + + if (m_backing && diff >= StyleDifferenceRepaint) + m_backing->setContentsNeedDisplay(); +#else + UNUSED_PARAM(diff); +#endif +} + +void RenderLayer::updateScrollCornerStyle() +{ + RenderObject* actualRenderer = renderer()->node() ? renderer()->node()->shadowAncestorNode()->renderer() : renderer(); + RefPtr<RenderStyle> corner = renderer()->hasOverflowClip() ? actualRenderer->getUncachedPseudoStyle(SCROLLBAR_CORNER, actualRenderer->style()) : 0; + if (corner) { + if (!m_scrollCorner) { + m_scrollCorner = new (renderer()->renderArena()) RenderScrollbarPart(renderer()->document()); + m_scrollCorner->setParent(renderer()); + } + m_scrollCorner->setStyle(corner.release()); + } else if (m_scrollCorner) { + m_scrollCorner->destroy(); + m_scrollCorner = 0; + } +} + +void RenderLayer::updateResizerStyle() +{ + RenderObject* actualRenderer = renderer()->node() ? renderer()->node()->shadowAncestorNode()->renderer() : renderer(); + RefPtr<RenderStyle> resizer = renderer()->hasOverflowClip() ? actualRenderer->getUncachedPseudoStyle(RESIZER, actualRenderer->style()) : 0; + if (resizer) { + if (!m_resizer) { + m_resizer = new (renderer()->renderArena()) RenderScrollbarPart(renderer()->document()); + m_resizer->setParent(renderer()); + } + m_resizer->setStyle(resizer.release()); + } else if (m_resizer) { + m_resizer->destroy(); + m_resizer = 0; + } +} + +RenderLayer* RenderLayer::reflectionLayer() const +{ + return m_reflection ? m_reflection->layer() : 0; +} + +void RenderLayer::createReflection() +{ + ASSERT(!m_reflection); + m_reflection = new (renderer()->renderArena()) RenderReplica(renderer()->document()); + m_reflection->setParent(renderer()); // We create a 1-way connection. +} + +void RenderLayer::removeReflection() +{ + if (!m_reflection->documentBeingDestroyed()) + m_reflection->removeLayers(this); + + m_reflection->setParent(0); + m_reflection->destroy(); + m_reflection = 0; +} + +void RenderLayer::updateReflectionStyle() +{ + RefPtr<RenderStyle> newStyle = RenderStyle::create(); + newStyle->inheritFrom(renderer()->style()); + + // Map in our transform. + TransformOperations transform; + switch (renderer()->style()->boxReflect()->direction()) { + case ReflectionBelow: + transform.operations().append(TranslateTransformOperation::create(Length(0, Fixed), Length(100., Percent), TransformOperation::TRANSLATE)); + transform.operations().append(TranslateTransformOperation::create(Length(0, Fixed), renderer()->style()->boxReflect()->offset(), TransformOperation::TRANSLATE)); + transform.operations().append(ScaleTransformOperation::create(1.0, -1.0, ScaleTransformOperation::SCALE)); + break; + case ReflectionAbove: + transform.operations().append(ScaleTransformOperation::create(1.0, -1.0, ScaleTransformOperation::SCALE)); + transform.operations().append(TranslateTransformOperation::create(Length(0, Fixed), Length(100., Percent), TransformOperation::TRANSLATE)); + transform.operations().append(TranslateTransformOperation::create(Length(0, Fixed), renderer()->style()->boxReflect()->offset(), TransformOperation::TRANSLATE)); + break; + case ReflectionRight: + transform.operations().append(TranslateTransformOperation::create(Length(100., Percent), Length(0, Fixed), TransformOperation::TRANSLATE)); + transform.operations().append(TranslateTransformOperation::create(renderer()->style()->boxReflect()->offset(), Length(0, Fixed), TransformOperation::TRANSLATE)); + transform.operations().append(ScaleTransformOperation::create(-1.0, 1.0, ScaleTransformOperation::SCALE)); + break; + case ReflectionLeft: + transform.operations().append(ScaleTransformOperation::create(-1.0, 1.0, ScaleTransformOperation::SCALE)); + transform.operations().append(TranslateTransformOperation::create(Length(100., Percent), Length(0, Fixed), TransformOperation::TRANSLATE)); + transform.operations().append(TranslateTransformOperation::create(renderer()->style()->boxReflect()->offset(), Length(0, Fixed), TransformOperation::TRANSLATE)); + break; + } + newStyle->setTransform(transform); + + // Map in our mask. + newStyle->setMaskBoxImage(renderer()->style()->boxReflect()->mask()); + + m_reflection->setStyle(newStyle.release()); +} + +} // namespace WebCore + +#ifndef NDEBUG +void showLayerTree(const WebCore::RenderLayer* layer) +{ + if (!layer) + return; + + if (WebCore::Frame* frame = layer->renderer()->frame()) { + WTF::String output = externalRepresentation(frame, WebCore::RenderAsTextShowAllLayers | WebCore::RenderAsTextShowLayerNesting | WebCore::RenderAsTextShowCompositedLayers | WebCore::RenderAsTextShowAddresses | WebCore::RenderAsTextShowIDAndClass | WebCore::RenderAsTextDontUpdateLayout | WebCore::RenderAsTextShowLayoutState); + fprintf(stderr, "%s\n", output.utf8().data()); + } +} + +void showLayerTree(const WebCore::RenderObject* renderer) +{ + if (!renderer) + return; + showLayerTree(renderer->enclosingLayer()); +} +#endif diff --git a/Source/WebCore/rendering/RenderLayer.h b/Source/WebCore/rendering/RenderLayer.h new file mode 100644 index 0000000..cc3b21c --- /dev/null +++ b/Source/WebCore/rendering/RenderLayer.h @@ -0,0 +1,771 @@ +/* + * Copyright (C) 2003, 2009 Apple Inc. All rights reserved. + * + * Portions are Copyright (C) 1998 Netscape Communications Corporation. + * + * Other contributors: + * Robert O'Callahan <roc+@cs.cmu.edu> + * David Baron <dbaron@fas.harvard.edu> + * Christian Biesinger <cbiesinger@web.de> + * Randall Jesup <rjesup@wgate.com> + * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> + * Josh Soref <timeless@mac.com> + * Boris Zbarsky <bzbarsky@mit.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Alternatively, the contents of this file may be used under the terms + * of either the Mozilla Public License Version 1.1, found at + * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public + * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html + * (the "GPL"), in which case the provisions of the MPL or the GPL are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of one of those two + * licenses (the MPL or the GPL) and not to allow others to use your + * version of this file under the LGPL, indicate your decision by + * deletingthe provisions above and replace them with the notice and + * other provisions required by the MPL or the GPL, as the case may be. + * If you do not delete the provisions above, a recipient may use your + * version of this file under any of the LGPL, the MPL or the GPL. + */ + +#ifndef RenderLayer_h +#define RenderLayer_h + +#include "RenderBox.h" +#include "ScrollBehavior.h" +#include "ScrollbarClient.h" +#include <wtf/OwnPtr.h> + +namespace WebCore { + +class HitTestRequest; +class HitTestResult; +class HitTestingTransformState; +class RenderMarquee; +class RenderReplica; +class RenderScrollbarPart; +class RenderStyle; +class RenderView; +class Scrollbar; +class TransformationMatrix; + +#if USE(ACCELERATED_COMPOSITING) +class RenderLayerBacking; +class RenderLayerCompositor; +#endif + +class ClipRects { +public: + ClipRects() + : m_refCnt(0) + , m_fixed(false) + { + } + + ClipRects(const IntRect& r) + : m_overflowClipRect(r) + , m_fixedClipRect(r) + , m_posClipRect(r) +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + , m_hitTestClip(r) +#endif + , m_refCnt(0) + , m_fixed(false) + { + } + + ClipRects(const ClipRects& other) + : m_overflowClipRect(other.overflowClipRect()) + , m_fixedClipRect(other.fixedClipRect()) + , m_posClipRect(other.posClipRect()) +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + , m_hitTestClip(other.hitTestClip()) +#endif + , m_refCnt(0) + , m_fixed(other.fixed()) + { + } + + void reset(const IntRect& r) + { + m_overflowClipRect = r; + m_fixedClipRect = r; + m_posClipRect = r; +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + m_hitTestClip = r; +#endif + m_fixed = false; + } + + const IntRect& overflowClipRect() const { return m_overflowClipRect; } + void setOverflowClipRect(const IntRect& r) { m_overflowClipRect = r; } + + const IntRect& fixedClipRect() const { return m_fixedClipRect; } + void setFixedClipRect(const IntRect&r) { m_fixedClipRect = r; } + + const IntRect& posClipRect() const { return m_posClipRect; } + void setPosClipRect(const IntRect& r) { m_posClipRect = r; } +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + const IntRect& hitTestClip() const { return m_hitTestClip; } + void setHitTestClip(const IntRect& r) { m_hitTestClip = r; } +#endif + + bool fixed() const { return m_fixed; } + void setFixed(bool fixed) { m_fixed = fixed; } + + void ref() { m_refCnt++; } + void deref(RenderArena* renderArena) { if (--m_refCnt == 0) destroy(renderArena); } + + void destroy(RenderArena*); + + // Overloaded new operator. + void* operator new(size_t, RenderArena*) throw(); + + // Overridden to prevent the normal delete from being called. + void operator delete(void*, size_t); + + bool operator==(const ClipRects& other) const + { + return m_overflowClipRect == other.overflowClipRect() && + m_fixedClipRect == other.fixedClipRect() && + m_posClipRect == other.posClipRect() && +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + m_hitTestClip == other.hitTestClip() && +#endif + m_fixed == other.fixed(); + } + + ClipRects& operator=(const ClipRects& other) + { + m_overflowClipRect = other.overflowClipRect(); + m_fixedClipRect = other.fixedClipRect(); + m_posClipRect = other.posClipRect(); +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + m_hitTestClip = other.hitTestClip(); +#endif + m_fixed = other.fixed(); + return *this; + } + +private: + // The normal operator new is disallowed on all render objects. + void* operator new(size_t) throw(); + +private: + IntRect m_overflowClipRect; + IntRect m_fixedClipRect; + IntRect m_posClipRect; +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + IntRect m_hitTestClip; +#endif + unsigned m_refCnt : 31; + bool m_fixed : 1; +}; + +class RenderLayer : public ScrollbarClient { +public: + friend class RenderReplica; + + RenderLayer(RenderBoxModelObject*); + ~RenderLayer(); + + RenderBoxModelObject* renderer() const { return m_renderer; } + RenderBox* renderBox() const { return m_renderer && m_renderer->isBox() ? toRenderBox(m_renderer) : 0; } + RenderLayer* parent() const { return m_parent; } + RenderLayer* previousSibling() const { return m_previous; } + RenderLayer* nextSibling() const { return m_next; } + RenderLayer* firstChild() const { return m_first; } + RenderLayer* lastChild() const { return m_last; } + + void addChild(RenderLayer* newChild, RenderLayer* beforeChild = 0); + RenderLayer* removeChild(RenderLayer*); + + void removeOnlyThisLayer(); + void insertOnlyThisLayer(); + + void repaintIncludingDescendants(); + +#if USE(ACCELERATED_COMPOSITING) + // Indicate that the layer contents need to be repainted. Only has an effect + // if layer compositing is being used, + void setBackingNeedsRepaint(); + void setBackingNeedsRepaintInRect(const IntRect& r); // r is in the coordinate space of the layer's render object + void repaintIncludingNonCompositingDescendants(RenderBoxModelObject* repaintContainer); +#endif + + void styleChanged(StyleDifference, const RenderStyle*); + + RenderMarquee* marquee() const { return m_marquee; } + + bool isNormalFlowOnly() const { return m_isNormalFlowOnly; } + bool isSelfPaintingLayer() const; + + bool requiresSlowRepaints() const; + + bool isTransparent() const; + RenderLayer* transparentPaintingAncestor(); + void beginTransparencyLayers(GraphicsContext*, const RenderLayer* rootLayer, PaintBehavior); + + bool hasReflection() const { return renderer()->hasReflection(); } + bool isReflection() const { return renderer()->isReplica(); } + RenderReplica* reflection() const { return m_reflection; } + RenderLayer* reflectionLayer() const; + + const RenderLayer* root() const + { + const RenderLayer* curr = this; + while (curr->parent()) + curr = curr->parent(); + return curr; + } + + int x() const { return m_x; } + int y() const { return m_y; } + void setLocation(int x, int y) + { + m_x = x; + m_y = y; + } + + int width() const { return m_width; } + int height() const { return m_height; } + IntSize size() const { return IntSize(m_width, m_height); } + +#if PLATFORM(ANDROID) + void setWidth(int w); + void setHeight(int h); +#else + void setWidth(int w) { m_width = w; } + void setHeight(int h) { m_height = h; } +#endif + + int scrollWidth(); + int scrollHeight(); + + void panScrollFromPoint(const IntPoint&); + + // Scrolling methods for layers that can scroll their overflow. + void scrollByRecursively(int xDelta, int yDelta); + + IntSize scrolledContentOffset() const { return IntSize(scrollXOffset() + m_scrollLeftOverflow, scrollYOffset() + m_scrollTopOverflow); } + + int scrollXOffset() const { return m_scrollX + m_scrollOrigin.x(); } + int scrollYOffset() const { return m_scrollY + m_scrollOrigin.y(); } + + void scrollToOffset(int x, int y, bool updateScrollbars = true, bool repaint = true); + void scrollToXOffset(int x) { scrollToOffset(x, m_scrollY + m_scrollOrigin.y()); } + void scrollToYOffset(int y) { scrollToOffset(m_scrollX + m_scrollOrigin.x(), y); } + void scrollRectToVisible(const IntRect&, bool scrollToAnchor = false, const ScrollAlignment& alignX = ScrollAlignment::alignCenterIfNeeded, const ScrollAlignment& alignY = ScrollAlignment::alignCenterIfNeeded); + + IntRect getRectToExpose(const IntRect& visibleRect, const IntRect& exposeRect, const ScrollAlignment& alignX, const ScrollAlignment& alignY); + + void setHasHorizontalScrollbar(bool); + void setHasVerticalScrollbar(bool); + + PassRefPtr<Scrollbar> createScrollbar(ScrollbarOrientation); + void destroyScrollbar(ScrollbarOrientation); + + Scrollbar* horizontalScrollbar() const { return m_hBar.get(); } + Scrollbar* verticalScrollbar() const { return m_vBar.get(); } + + int verticalScrollbarWidth() const; + int horizontalScrollbarHeight() const; + + bool hasOverflowControls() const; +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + bool hasOverflowScroll() const { return m_hasOverflowScroll; } + bool hasOverflowParent() const; +#endif + void positionOverflowControls(int tx, int ty); + bool isPointInResizeControl(const IntPoint& absolutePoint) const; + bool hitTestOverflowControls(HitTestResult&, const IntPoint& localPoint); + IntSize offsetFromResizeCorner(const IntPoint& absolutePoint) const; + + void paintOverflowControls(GraphicsContext*, int tx, int ty, const IntRect& damageRect); + void paintScrollCorner(GraphicsContext*, int tx, int ty, const IntRect& damageRect); + void paintResizer(GraphicsContext*, int tx, int ty, const IntRect& damageRect); + + void updateScrollInfoAfterLayout(); + + bool scroll(ScrollDirection, ScrollGranularity, float multiplier = 1); + void autoscroll(); + + void resize(const PlatformMouseEvent&, const IntSize&); + bool inResizeMode() const { return m_inResizeMode; } + void setInResizeMode(bool b) { m_inResizeMode = b; } + + bool isRootLayer() const { return renderer()->isRenderView(); } + +#if USE(ACCELERATED_COMPOSITING) + RenderLayerCompositor* compositor() const; + + // Notification from the renderer that its content changed (e.g. current frame of image changed). + // Allows updates of layer content without repainting. + enum ContentChangeType { ImageChanged, MaskImageChanged, CanvasChanged, VideoChanged, FullScreenChanged }; + void contentChanged(ContentChangeType); +#endif + + // Returns true if the accelerated compositing is enabled + bool hasAcceleratedCompositing() const; + + bool canRender3DTransforms() const; + + void updateLayerPosition(); + + enum UpdateLayerPositionsFlag { + DoFullRepaint = 1, + CheckForRepaint = 1 << 1, + IsCompositingUpdateRoot = 1 << 2, + UpdateCompositingLayers = 1 << 3, + UpdatePagination = 1 << 4 + }; + typedef unsigned UpdateLayerPositionsFlags; + void updateLayerPositions(UpdateLayerPositionsFlags = DoFullRepaint | IsCompositingUpdateRoot | UpdateCompositingLayers, IntPoint* cachedOffset = 0); + + void updateTransform(); + + void relativePositionOffset(int& relX, int& relY) const { relX += m_relX; relY += m_relY; } + IntSize relativePositionOffset() const { return IntSize(m_relX, m_relY); } + + void clearClipRectsIncludingDescendants(); + void clearClipRects(); + + void addBlockSelectionGapsBounds(const IntRect&); + void clearBlockSelectionGapsBounds(); + void repaintBlockSelectionGaps(); + + // Get the enclosing stacking context for this layer. A stacking context is a layer + // that has a non-auto z-index. + RenderLayer* stackingContext() const; +#if ENABLE(COMPOSITED_FIXED_ELEMENTS) + bool isFixed() const { return renderer()->isPositioned() && renderer()->style()->position() == FixedPosition; } + // If fixed elements are composited, they will be containing children + bool isStackingContext() const { +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + if (hasOverflowScroll()) + return true; +#endif + return !hasAutoZIndex() || renderer()->isRenderView() || (isComposited() && isFixed()); + } +#else +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + bool isStackingContext() const { return !hasAutoZIndex() || renderer()->isRenderView() || hasOverflowScroll(); } +#else + bool isStackingContext() const { return !hasAutoZIndex() || renderer()->isRenderView() ; } +#endif +#endif + + void dirtyZOrderLists(); + void dirtyStackingContextZOrderLists(); + void updateZOrderLists(); + Vector<RenderLayer*>* posZOrderList() const { return m_posZOrderList; } + Vector<RenderLayer*>* negZOrderList() const { return m_negZOrderList; } + + void dirtyNormalFlowList(); + void updateNormalFlowList(); + Vector<RenderLayer*>* normalFlowList() const { return m_normalFlowList; } + + bool hasVisibleContent() const { return m_hasVisibleContent; } + bool hasVisibleDescendant() const { return m_hasVisibleDescendant; } + void setHasVisibleContent(bool); + void dirtyVisibleContentStatus(); + + // Gets the nearest enclosing positioned ancestor layer (also includes + // the <html> layer and the root layer). + RenderLayer* enclosingPositionedAncestor() const; + + // The layer relative to which clipping rects for this layer are computed. + RenderLayer* clippingRoot() const; + +#if USE(ACCELERATED_COMPOSITING) + // Enclosing compositing layer; if includeSelf is true, may return this. + RenderLayer* enclosingCompositingLayer(bool includeSelf = true) const; + // Ancestor compositing layer, excluding this. + RenderLayer* ancestorCompositingLayer() const { return enclosingCompositingLayer(false); } +#endif + + void convertToLayerCoords(const RenderLayer* ancestorLayer, int& x, int& y) const; + + bool hasAutoZIndex() const { return renderer()->style()->hasAutoZIndex(); } + int zIndex() const { return renderer()->style()->zIndex(); } + + // The two main functions that use the layer system. The paint method + // paints the layers that intersect the damage rect from back to + // front. The hitTest method looks for mouse events by walking + // layers that intersect the point from front to back. + void paint(GraphicsContext*, const IntRect& damageRect, PaintBehavior = PaintBehaviorNormal, RenderObject* paintingRoot = 0); + bool hitTest(const HitTestRequest&, HitTestResult&); + + // This method figures out our layerBounds in coordinates relative to + // |rootLayer}. It also computes our background and foreground clip rects + // for painting/event handling. + void calculateRects(const RenderLayer* rootLayer, const IntRect& paintDirtyRect, IntRect& layerBounds, + IntRect& backgroundRect, IntRect& foregroundRect, IntRect& outlineRect, bool temporaryClipRects = false) const; + + // Compute and cache clip rects computed with the given layer as the root + void updateClipRects(const RenderLayer* rootLayer); + // Compute and return the clip rects. If useCached is true, will used previously computed clip rects on ancestors + // (rather than computing them all from scratch up the parent chain). + void calculateClipRects(const RenderLayer* rootLayer, ClipRects&, bool useCached = false) const; + ClipRects* clipRects() const { return m_clipRects; } + + IntRect childrenClipRect() const; // Returns the foreground clip rect of the layer in the document's coordinate space. + IntRect selfClipRect() const; // Returns the background clip rect of the layer in the document's coordinate space. + + bool intersectsDamageRect(const IntRect& layerBounds, const IntRect& damageRect, const RenderLayer* rootLayer) const; + + // Bounding box relative to some ancestor layer. + IntRect boundingBox(const RenderLayer* rootLayer) const; + // Bounding box in the coordinates of this layer. + IntRect localBoundingBox() const; + // Bounding box relative to the root. + IntRect absoluteBoundingBox() const; + + void updateHoverActiveState(const HitTestRequest&, HitTestResult&); + + // Return a cached repaint rect, computed relative to the layer renderer's containerForRepaint. + IntRect repaintRect() const { return m_repaintRect; } + IntRect repaintRectIncludingDescendants() const; + void computeRepaintRects(); + void updateRepaintRectsAfterScroll(bool fixed = false); + void setNeedsFullRepaint(bool f = true) { m_needsFullRepaint = f; } + + int staticX() const { return m_staticX; } + int staticY() const { return m_staticY; } + void setStaticX(int staticX) { m_staticX = staticX; } + void setStaticY(int staticY) { m_staticY = staticY; } + + bool hasTransform() const { return renderer()->hasTransform(); } + // Note that this transform has the transform-origin baked in. + TransformationMatrix* transform() const { return m_transform.get(); } + // currentTransform computes a transform which takes accelerated animations into account. The + // resulting transform has transform-origin baked in. If the layer does not have a transform, + // returns the identity matrix. + TransformationMatrix currentTransform() const; + TransformationMatrix renderableTransform(PaintBehavior) const; + + // Get the perspective transform, which is applied to transformed sublayers. + // Returns true if the layer has a -webkit-perspective. + // Note that this transform has the perspective-origin baked in. + TransformationMatrix perspectiveTransform() const; + FloatPoint perspectiveOrigin() const; + bool preserves3D() const { return renderer()->style()->transformStyle3D() == TransformStyle3DPreserve3D; } + bool has3DTransform() const { return m_transform && !m_transform->isAffine(); } + + // Overloaded new operator. Derived classes must override operator new + // in order to allocate out of the RenderArena. + void* operator new(size_t, RenderArena*) throw(); + + // Overridden to prevent the normal delete from being called. + void operator delete(void*, size_t); + +#if USE(ACCELERATED_COMPOSITING) + bool isComposited() const { return m_backing != 0; } + bool hasCompositedMask() const; + RenderLayerBacking* backing() const { return m_backing.get(); } + RenderLayerBacking* ensureBacking(); + void clearBacking(); +#else + bool isComposited() const { return false; } + bool hasCompositedMask() const { return false; } +#endif + + bool paintsWithTransparency(PaintBehavior paintBehavior) const + { + return isTransparent() && ((paintBehavior & PaintBehaviorFlattenCompositingLayers) || !isComposited()); + } + + bool paintsWithTransform(PaintBehavior paintBehavior) const + { + return transform() && ((paintBehavior & PaintBehaviorFlattenCompositingLayers) || !isComposited()); + } + +private: + // The normal operator new is disallowed on all render objects. + void* operator new(size_t) throw(); + +private: + void setNextSibling(RenderLayer* next) { m_next = next; } + void setPreviousSibling(RenderLayer* prev) { m_previous = prev; } + void setParent(RenderLayer* parent); + void setFirstChild(RenderLayer* first) { m_first = first; } + void setLastChild(RenderLayer* last) { m_last = last; } + + int renderBoxX() const { return renderer()->isBox() ? toRenderBox(renderer())->x() : 0; } + int renderBoxY() const { return renderer()->isBox() ? toRenderBox(renderer())->y() : 0; } + + void collectLayers(Vector<RenderLayer*>*&, Vector<RenderLayer*>*&); + + void updateLayerListsIfNeeded(); + void updateCompositingAndLayerListsIfNeeded(); + + enum PaintLayerFlag { + PaintLayerHaveTransparency = 1, + PaintLayerAppliedTransform = 1 << 1, + PaintLayerTemporaryClipRects = 1 << 2, + PaintLayerPaintingReflection = 1 << 3 + }; + + typedef unsigned PaintLayerFlags; + + void paintLayer(RenderLayer* rootLayer, GraphicsContext*, const IntRect& paintDirtyRect, + PaintBehavior, RenderObject* paintingRoot, OverlapTestRequestMap* = 0, + PaintLayerFlags = 0); + void paintList(Vector<RenderLayer*>*, RenderLayer* rootLayer, GraphicsContext* p, + const IntRect& paintDirtyRect, PaintBehavior, + RenderObject* paintingRoot, OverlapTestRequestMap*, + PaintLayerFlags); + void paintPaginatedChildLayer(RenderLayer* childLayer, RenderLayer* rootLayer, GraphicsContext*, + const IntRect& paintDirtyRect, PaintBehavior, + RenderObject* paintingRoot, OverlapTestRequestMap*, + PaintLayerFlags); + void paintChildLayerIntoColumns(RenderLayer* childLayer, RenderLayer* rootLayer, GraphicsContext*, + const IntRect& paintDirtyRect, PaintBehavior, + RenderObject* paintingRoot, OverlapTestRequestMap*, + PaintLayerFlags, const Vector<RenderLayer*>& columnLayers, size_t columnIndex); + + RenderLayer* hitTestLayer(RenderLayer* rootLayer, RenderLayer* containerLayer, const HitTestRequest& request, HitTestResult& result, + const IntRect& hitTestRect, const IntPoint& hitTestPoint, bool appliedTransform, + const HitTestingTransformState* transformState = 0, double* zOffset = 0); + RenderLayer* hitTestList(Vector<RenderLayer*>*, RenderLayer* rootLayer, const HitTestRequest& request, HitTestResult& result, + const IntRect& hitTestRect, const IntPoint& hitTestPoint, + const HitTestingTransformState* transformState, double* zOffsetForDescendants, double* zOffset, + const HitTestingTransformState* unflattenedTransformState, bool depthSortDescendants); + RenderLayer* hitTestPaginatedChildLayer(RenderLayer* childLayer, RenderLayer* rootLayer, const HitTestRequest& request, HitTestResult& result, + const IntRect& hitTestRect, const IntPoint& hitTestPoint, + const HitTestingTransformState* transformState, double* zOffset); + RenderLayer* hitTestChildLayerColumns(RenderLayer* childLayer, RenderLayer* rootLayer, const HitTestRequest& request, HitTestResult& result, + const IntRect& hitTestRect, const IntPoint& hitTestPoint, + const HitTestingTransformState* transformState, double* zOffset, + const Vector<RenderLayer*>& columnLayers, size_t columnIndex); + + PassRefPtr<HitTestingTransformState> createLocalTransformState(RenderLayer* rootLayer, RenderLayer* containerLayer, + const IntRect& hitTestRect, const IntPoint& hitTestPoint, + const HitTestingTransformState* containerTransformState) const; + + bool hitTestContents(const HitTestRequest&, HitTestResult&, const IntRect& layerBounds, const IntPoint& hitTestPoint, HitTestFilter) const; + + void computeScrollDimensions(bool* needHBar = 0, bool* needVBar = 0); + + bool shouldBeNormalFlowOnly() const; + + // ScrollBarClient interface + virtual int scrollSize(ScrollbarOrientation orientation) const; + virtual void setScrollOffsetFromAnimation(const IntPoint&); + virtual void valueChanged(Scrollbar*); + virtual void invalidateScrollbarRect(Scrollbar*, const IntRect&); + virtual bool isActive() const; + virtual bool scrollbarCornerPresent() const; + virtual IntRect convertFromScrollbarToContainingView(const Scrollbar*, const IntRect&) const; + virtual IntRect convertFromContainingViewToScrollbar(const Scrollbar*, const IntRect&) const; + virtual IntPoint convertFromScrollbarToContainingView(const Scrollbar*, const IntPoint&) const; + virtual IntPoint convertFromContainingViewToScrollbar(const Scrollbar*, const IntPoint&) const; + + IntSize scrollbarOffset(const Scrollbar*) const; + + void updateOverflowStatus(bool horizontalOverflow, bool verticalOverflow); + + void childVisibilityChanged(bool newVisibility); + void dirtyVisibleDescendantStatus(); + void updateVisibilityStatus(); + + // This flag is computed by RenderLayerCompositor, which knows more about 3d hierarchies than we do. + void setHas3DTransformedDescendant(bool b) { m_has3DTransformedDescendant = b; } + bool has3DTransformedDescendant() const { return m_has3DTransformedDescendant; } + + void dirty3DTransformedDescendantStatus(); + // Both updates the status, and returns true if descendants of this have 3d. + bool update3DTransformedDescendantStatus(); + + Node* enclosingElement() const; + + void createReflection(); + void removeReflection(); + + void updateReflectionStyle(); + bool paintingInsideReflection() const { return m_paintingInsideReflection; } + void setPaintingInsideReflection(bool b) { m_paintingInsideReflection = b; } + + void parentClipRects(const RenderLayer* rootLayer, ClipRects&, bool temporaryClipRects = false) const; + IntRect backgroundClipRect(const RenderLayer* rootLayer, bool temporaryClipRects) const; + + RenderLayer* enclosingTransformedAncestor() const; + + // Convert a point in absolute coords into layer coords, taking transforms into account + IntPoint absoluteToContents(const IntPoint&) const; + + void updateScrollCornerStyle(); + void updateResizerStyle(); + + void updatePagination(); + bool isPaginated() const { return m_isPaginated; } + +#if USE(ACCELERATED_COMPOSITING) + bool hasCompositingDescendant() const { return m_hasCompositingDescendant; } + void setHasCompositingDescendant(bool b) { m_hasCompositingDescendant = b; } + + bool mustOverlapCompositedLayers() const { return m_mustOverlapCompositedLayers; } + void setMustOverlapCompositedLayers(bool b) { m_mustOverlapCompositedLayers = b; } +#endif + +private: + friend class RenderLayerBacking; + friend class RenderLayerCompositor; + friend class RenderBoxModelObject; + + // Only safe to call from RenderBoxModelObject::destroyLayer(RenderArena*) + void destroy(RenderArena*); + + int overflowTop() const; + int overflowBottom() const; + int overflowLeft() const; + int overflowRight() const; + +protected: + RenderBoxModelObject* m_renderer; + + RenderLayer* m_parent; + RenderLayer* m_previous; + RenderLayer* m_next; + RenderLayer* m_first; + RenderLayer* m_last; + + IntRect m_repaintRect; // Cached repaint rects. Used by layout. + IntRect m_outlineBox; + + // Our current relative position offset. + int m_relX; + int m_relY; + + // Our (x,y) coordinates are in our parent layer's coordinate space. + int m_x; + int m_y; + + // The layer's width/height + int m_width; + int m_height; + + // Our scroll offsets if the view is scrolled. + int m_scrollX; + int m_scrollY; + + // There are 8 possible combinations of writing mode and direction. Scroll origin (and its corresponding left/top overflow) + // will be non-zero in the x or y axis if there is any reversed direction or writing-mode. The combinations are: + // writing-mode / direction scrollOrigin.x() set scrollOrigin.y() set + // horizontal-tb / ltr NO NO + // horizontal-tb / rtl YES NO + // horizontal-bt / ltr NO YES + // horizontal-bt / rtl YES YES + // vertical-lr / ltr NO NO + // vertical-lr / rtl NO YES + // vertical-rl / ltr YES NO + // vertical-rl / rtl YES YES + IntPoint m_scrollOrigin; + int m_scrollLeftOverflow; + int m_scrollTopOverflow; + + // The width/height of our scrolled area. + int m_scrollWidth; + int m_scrollHeight; + + // For layers with overflow, we have a pair of scrollbars. + RefPtr<Scrollbar> m_hBar; + RefPtr<Scrollbar> m_vBar; + + // Keeps track of whether the layer is currently resizing, so events can cause resizing to start and stop. + bool m_inResizeMode; + + // For layers that establish stacking contexts, m_posZOrderList holds a sorted list of all the + // descendant layers within the stacking context that have z-indices of 0 or greater + // (auto will count as 0). m_negZOrderList holds descendants within our stacking context with negative + // z-indices. + Vector<RenderLayer*>* m_posZOrderList; + Vector<RenderLayer*>* m_negZOrderList; + + // This list contains child layers that cannot create stacking contexts. For now it is just + // overflow layers, but that may change in the future. + Vector<RenderLayer*>* m_normalFlowList; + + ClipRects* m_clipRects; // Cached clip rects used when painting and hit testing. +#ifndef NDEBUG + const RenderLayer* m_clipRectsRoot; // Root layer used to compute clip rects. +#endif + + bool m_scrollDimensionsDirty : 1; + bool m_zOrderListsDirty : 1; + bool m_normalFlowListDirty: 1; + bool m_isNormalFlowOnly : 1; + + bool m_usedTransparency : 1; // Tracks whether we need to close a transparent layer, i.e., whether + // we ended up painting this layer or any descendants (and therefore need to + // blend). + bool m_paintingInsideReflection : 1; // A state bit tracking if we are painting inside a replica. + bool m_inOverflowRelayout : 1; + bool m_needsFullRepaint : 1; + + bool m_overflowStatusDirty : 1; + bool m_horizontalOverflow : 1; + bool m_verticalOverflow : 1; + bool m_visibleContentStatusDirty : 1; + bool m_hasVisibleContent : 1; + bool m_visibleDescendantStatusDirty : 1; + bool m_hasVisibleDescendant : 1; + + bool m_isPaginated : 1; // If we think this layer is split by a multi-column ancestor, then this bit will be set. + + bool m_3DTransformedDescendantStatusDirty : 1; + bool m_has3DTransformedDescendant : 1; // Set on a stacking context layer that has 3D descendants anywhere + // in a preserves3D hierarchy. Hint to do 3D-aware hit testing. +#if USE(ACCELERATED_COMPOSITING) + bool m_hasCompositingDescendant : 1; + bool m_mustOverlapCompositedLayers : 1; +#endif +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + bool m_hasOverflowScroll : 1; +#endif + + RenderMarquee* m_marquee; // Used by layers with overflow:marquee + + // Cached normal flow values for absolute positioned elements with static left/top values. + int m_staticX; + int m_staticY; + + OwnPtr<TransformationMatrix> m_transform; + + // May ultimately be extended to many replicas (with their own paint order). + RenderReplica* m_reflection; + + // Renderers to hold our custom scroll corner and resizer. + RenderScrollbarPart* m_scrollCorner; + RenderScrollbarPart* m_resizer; + +private: + IntRect m_blockSelectionGapsBounds; + +#if USE(ACCELERATED_COMPOSITING) + OwnPtr<RenderLayerBacking> m_backing; +#endif +}; + +} // namespace WebCore + +#ifndef NDEBUG +// Outside the WebCore namespace for ease of invocation from gdb. +void showLayerTree(const WebCore::RenderLayer*); +void showLayerTree(const WebCore::RenderObject*); +#endif + +#endif // RenderLayer_h diff --git a/Source/WebCore/rendering/RenderLayerBacking.cpp b/Source/WebCore/rendering/RenderLayerBacking.cpp new file mode 100644 index 0000000..c430bae --- /dev/null +++ b/Source/WebCore/rendering/RenderLayerBacking.cpp @@ -0,0 +1,1366 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "RenderLayerBacking.h" + +#include "AnimationController.h" +#include "CanvasRenderingContext.h" +#include "CanvasRenderingContext2D.h" +#include "CSSPropertyNames.h" +#include "CSSStyleSelector.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "GraphicsContext3D.h" +#include "GraphicsLayer.h" +#include "HTMLCanvasElement.h" +#include "HTMLElement.h" +#include "HTMLIFrameElement.h" +#include "HTMLMediaElement.h" +#include "HTMLNames.h" +#include "InspectorInstrumentation.h" +#include "KeyframeList.h" +#include "PluginViewBase.h" +#include "RenderApplet.h" +#include "RenderBox.h" +#include "RenderIFrame.h" +#include "RenderImage.h" +#include "RenderLayerCompositor.h" +#include "RenderEmbeddedObject.h" +#include "RenderVideo.h" +#include "RenderView.h" +#include "Settings.h" +#include "WebGLRenderingContext.h" + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +static bool hasBorderOutlineOrShadow(const RenderStyle*); +static bool hasBoxDecorationsOrBackground(const RenderObject*); +static bool hasBoxDecorationsOrBackgroundImage(const RenderStyle*); +static IntRect clipBox(RenderBox* renderer); + +static inline bool isAcceleratedCanvas(RenderObject* renderer) +{ +#if ENABLE(3D_CANVAS) || ENABLE(ACCELERATED_2D_CANVAS) + if (renderer->isCanvas()) { + HTMLCanvasElement* canvas = static_cast<HTMLCanvasElement*>(renderer->node()); + if (CanvasRenderingContext* context = canvas->renderingContext()) + return context->isAccelerated(); + } +#else + UNUSED_PARAM(renderer); +#endif + return false; +} + +RenderLayerBacking::RenderLayerBacking(RenderLayer* layer) + : m_owningLayer(layer) + , m_artificiallyInflatedBounds(false) +{ + createGraphicsLayer(); +} + +RenderLayerBacking::~RenderLayerBacking() +{ + updateClippingLayers(false, false); + updateForegroundLayer(false); + updateMaskLayer(false); + destroyGraphicsLayer(); +} + +void RenderLayerBacking::createGraphicsLayer() +{ + m_graphicsLayer = GraphicsLayer::create(this); + +#ifndef NDEBUG + m_graphicsLayer->setName(nameForLayer()); +#endif // NDEBUG + + updateLayerOpacity(renderer()->style()); + updateLayerTransform(renderer()->style()); +} + +void RenderLayerBacking::destroyGraphicsLayer() +{ + if (m_graphicsLayer) + m_graphicsLayer->removeFromParent(); + + m_graphicsLayer = 0; + m_foregroundLayer = 0; + m_clippingLayer = 0; + m_maskLayer = 0; +} + +void RenderLayerBacking::updateLayerOpacity(const RenderStyle* style) +{ + m_graphicsLayer->setOpacity(compositingOpacity(style->opacity())); +} + +void RenderLayerBacking::updateLayerTransform(const RenderStyle* style) +{ + // FIXME: This could use m_owningLayer->transform(), but that currently has transform-origin + // baked into it, and we don't want that. + TransformationMatrix t; + if (m_owningLayer->hasTransform()) { + style->applyTransform(t, toRenderBox(renderer())->borderBoxRect().size(), RenderStyle::ExcludeTransformOrigin); + makeMatrixRenderable(t, compositor()->canRender3DTransforms()); + } + + m_graphicsLayer->setTransform(t); +} + +static bool hasNonZeroTransformOrigin(const RenderObject* renderer) +{ + RenderStyle* style = renderer->style(); + return (style->transformOriginX().type() == Fixed && style->transformOriginX().value()) + || (style->transformOriginY().type() == Fixed && style->transformOriginY().value()); +} + +static bool layerOrAncestorIsTransformed(RenderLayer* layer) +{ + for (RenderLayer* curr = layer; curr; curr = curr->parent()) { + if (curr->hasTransform()) + return true; + } + + return false; +} + +#if ENABLE(FULLSCREEN_API) +static bool layerOrAncestorIsFullScreen(RenderLayer* layer) +{ + // Don't traverse through the render layer tree if we do not yet have a full screen renderer. + if (!layer->renderer()->document()->fullScreenRenderer()) + return false; + + for (RenderLayer* curr = layer; curr; curr = curr->parent()) { + if (curr->renderer()->isRenderFullScreen()) + return true; + } + + return false; +} +#endif + +void RenderLayerBacking::updateCompositedBounds() +{ + IntRect layerBounds = compositor()->calculateCompositedBounds(m_owningLayer, m_owningLayer); + + // Clip to the size of the document or enclosing overflow-scroll layer. + // If this or an ancestor is transformed, we can't currently compute the correct rect to intersect with. + // We'd need RenderObject::convertContainerToLocalQuad(), which doesn't yet exist. If this + // is a fullscreen renderer, don't clip to the viewport, as the renderer will be asked to + // display outside of the viewport bounds. + if (compositor()->compositingConsultsOverlap() && !layerOrAncestorIsTransformed(m_owningLayer) +#if ENABLE(FULLSCREEN_API) + && !layerOrAncestorIsFullScreen(m_owningLayer) +#endif + ) { + RenderView* view = m_owningLayer->renderer()->view(); + RenderLayer* rootLayer = view->layer(); + + // Start by clipping to the view's bounds. + IntRect clippingBounds = view->layoutOverflowRect(); + + if (m_owningLayer != rootLayer) + clippingBounds.intersect(m_owningLayer->backgroundClipRect(rootLayer, true)); + + int deltaX = 0; + int deltaY = 0; + m_owningLayer->convertToLayerCoords(rootLayer, deltaX, deltaY); + clippingBounds.move(-deltaX, -deltaY); + + layerBounds.intersect(clippingBounds); + } + + // If the element has a transform-origin that has fixed lengths, and the renderer has zero size, + // then we need to ensure that the compositing layer has non-zero size so that we can apply + // the transform-origin via the GraphicsLayer anchorPoint (which is expressed as a fractional value). + if (layerBounds.isEmpty() && hasNonZeroTransformOrigin(renderer())) { + layerBounds.setWidth(1); + layerBounds.setHeight(1); + m_artificiallyInflatedBounds = true; + } else + m_artificiallyInflatedBounds = false; + + setCompositedBounds(layerBounds); +} + +void RenderLayerBacking::updateAfterWidgetResize() +{ + if (renderer()->isRenderIFrame()) { + if (RenderLayerCompositor* innerCompositor = RenderLayerCompositor::iframeContentsCompositor(toRenderIFrame(renderer()))) + innerCompositor->frameViewDidChangeSize(contentsBox().location()); + } +} + +void RenderLayerBacking::updateAfterLayout(UpdateDepth updateDepth, bool isUpdateRoot) +{ + RenderLayerCompositor* layerCompositor = compositor(); + if (!layerCompositor->compositingLayersNeedRebuild()) { + // Calling updateGraphicsLayerGeometry() here gives incorrect results, because the + // position of this layer's GraphicsLayer depends on the position of our compositing + // ancestor's GraphicsLayer. That cannot be determined until all the descendant + // RenderLayers of that ancestor have been processed via updateLayerPositions(). + // + // The solution is to update compositing children of this layer here, + // via updateCompositingChildrenGeometry(). + updateCompositedBounds(); + layerCompositor->updateCompositingDescendantGeometry(m_owningLayer, m_owningLayer, updateDepth); + + if (isUpdateRoot) { + updateGraphicsLayerGeometry(); + layerCompositor->updateRootLayerPosition(); + } + } +} + +bool RenderLayerBacking::updateGraphicsLayerConfiguration() +{ + RenderLayerCompositor* compositor = this->compositor(); + RenderObject* renderer = this->renderer(); + + bool layerConfigChanged = false; + if (updateForegroundLayer(compositor->needsContentsCompositingLayer(m_owningLayer))) + layerConfigChanged = true; + + if (updateClippingLayers(compositor->clippedByAncestor(m_owningLayer), compositor->clipsCompositingDescendants(m_owningLayer))) + layerConfigChanged = true; + + if (updateMaskLayer(renderer->hasMask())) + m_graphicsLayer->setMaskLayer(m_maskLayer.get()); + + if (m_owningLayer->hasReflection()) { + if (m_owningLayer->reflectionLayer()->backing()) { + GraphicsLayer* reflectionLayer = m_owningLayer->reflectionLayer()->backing()->graphicsLayer(); + m_graphicsLayer->setReplicatedByLayer(reflectionLayer); + } + } else + m_graphicsLayer->setReplicatedByLayer(0); + + if (isDirectlyCompositedImage()) + updateImageContents(); + + if ((renderer->isEmbeddedObject() && toRenderEmbeddedObject(renderer)->allowsAcceleratedCompositing()) + || (renderer->isApplet() && toRenderApplet(renderer)->allowsAcceleratedCompositing())) { + PluginViewBase* pluginViewBase = static_cast<PluginViewBase*>(toRenderWidget(renderer)->widget()); + m_graphicsLayer->setContentsToMedia(pluginViewBase->platformLayer()); + } +#if ENABLE(VIDEO) + else if (renderer->isVideo()) { + HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(renderer->node()); + m_graphicsLayer->setContentsToMedia(mediaElement->platformLayer()); + } +#endif +#if ENABLE(3D_CANVAS) || ENABLE(ACCELERATED_2D_CANVAS) + else if (isAcceleratedCanvas(renderer)) { + HTMLCanvasElement* canvas = static_cast<HTMLCanvasElement*>(renderer->node()); + if (CanvasRenderingContext* context = canvas->renderingContext()) + m_graphicsLayer->setContentsToCanvas(context->platformLayer()); + layerConfigChanged = true; + } +#endif + + if (renderer->isRenderIFrame()) + layerConfigChanged = RenderLayerCompositor::parentIFrameContentLayers(toRenderIFrame(renderer)); + + return layerConfigChanged; +} + +static IntRect clipBox(RenderBox* renderer) +{ + IntRect result = PaintInfo::infiniteRect(); + if (renderer->hasOverflowClip()) + result = renderer->overflowClipRect(0, 0); + + if (renderer->hasClip()) + result.intersect(renderer->clipRect(0, 0)); + + return result; +} + +void RenderLayerBacking::updateGraphicsLayerGeometry() +{ + // If we haven't built z-order lists yet, wait until later. + if (m_owningLayer->isStackingContext() && m_owningLayer->m_zOrderListsDirty) + return; + + // Set transform property, if it is not animating. We have to do this here because the transform + // is affected by the layer dimensions. + if (!renderer()->animation()->isRunningAcceleratedAnimationOnRenderer(renderer(), CSSPropertyWebkitTransform)) + updateLayerTransform(renderer()->style()); + + // Set opacity, if it is not animating. + if (!renderer()->animation()->isRunningAcceleratedAnimationOnRenderer(renderer(), CSSPropertyOpacity)) + updateLayerOpacity(renderer()->style()); + + RenderStyle* style = renderer()->style(); + m_graphicsLayer->setPreserves3D(style->transformStyle3D() == TransformStyle3DPreserve3D && !renderer()->hasReflection()); + m_graphicsLayer->setBackfaceVisibility(style->backfaceVisibility() == BackfaceVisibilityVisible); + + RenderLayer* compAncestor = m_owningLayer->ancestorCompositingLayer(); + + // We compute everything relative to the enclosing compositing layer. + IntRect ancestorCompositingBounds; + if (compAncestor) { + ASSERT(compAncestor->backing()); + ancestorCompositingBounds = compAncestor->backing()->compositedBounds(); + } + + IntRect localCompositingBounds = compositedBounds(); + + IntRect relativeCompositingBounds(localCompositingBounds); + int deltaX = 0, deltaY = 0; + m_owningLayer->convertToLayerCoords(compAncestor, deltaX, deltaY); + relativeCompositingBounds.move(deltaX, deltaY); + + IntPoint graphicsLayerParentLocation; + if (compAncestor && compAncestor->backing()->hasClippingLayer()) { + // If the compositing ancestor has a layer to clip children, we parent in that, and therefore + // position relative to it. + IntRect clippingBox = clipBox(toRenderBox(compAncestor->renderer())); + graphicsLayerParentLocation = clippingBox.location(); + } else + graphicsLayerParentLocation = ancestorCompositingBounds.location(); + + if (compAncestor && m_ancestorClippingLayer) { + // Call calculateRects to get the backgroundRect which is what is used to clip the contents of this + // layer. Note that we call it with temporaryClipRects = true because normally when computing clip rects + // for a compositing layer, rootLayer is the layer itself. + IntRect parentClipRect = m_owningLayer->backgroundClipRect(compAncestor, true); + m_ancestorClippingLayer->setPosition(FloatPoint() + (parentClipRect.location() - graphicsLayerParentLocation)); + m_ancestorClippingLayer->setSize(parentClipRect.size()); + + // backgroundRect is relative to compAncestor, so subtract deltaX/deltaY to get back to local coords. + IntSize rendererOffset(parentClipRect.location().x() - deltaX, parentClipRect.location().y() - deltaY); + m_ancestorClippingLayer->setOffsetFromRenderer(rendererOffset); + + // The primary layer is then parented in, and positioned relative to this clipping layer. + graphicsLayerParentLocation = parentClipRect.location(); + } + + m_graphicsLayer->setPosition(FloatPoint() + (relativeCompositingBounds.location() - graphicsLayerParentLocation)); + + IntSize oldOffsetFromRenderer = m_graphicsLayer->offsetFromRenderer(); + m_graphicsLayer->setOffsetFromRenderer(localCompositingBounds.location() - IntPoint()); + // If the compositing layer offset changes, we need to repaint. + if (oldOffsetFromRenderer != m_graphicsLayer->offsetFromRenderer()) + m_graphicsLayer->setNeedsDisplay(); + + FloatSize oldSize = m_graphicsLayer->size(); + FloatSize newSize = relativeCompositingBounds.size(); + if (oldSize != newSize) { + m_graphicsLayer->setSize(newSize); + // A bounds change will almost always require redisplay. Usually that redisplay + // will happen because of a repaint elsewhere, but not always: + // e.g. see RenderView::setMaximalOutlineSize() + m_graphicsLayer->setNeedsDisplay(); + } + + // If we have a layer that clips children, position it. + IntRect clippingBox; + if (m_clippingLayer) { + clippingBox = clipBox(toRenderBox(renderer())); + m_clippingLayer->setPosition(FloatPoint() + (clippingBox.location() - localCompositingBounds.location())); + m_clippingLayer->setSize(clippingBox.size()); + m_clippingLayer->setOffsetFromRenderer(clippingBox.location() - IntPoint()); + } + + if (m_maskLayer) { + if (m_maskLayer->size() != m_graphicsLayer->size()) { + m_maskLayer->setSize(m_graphicsLayer->size()); + m_maskLayer->setNeedsDisplay(); + } + m_maskLayer->setPosition(FloatPoint()); + } + + if (m_owningLayer->hasTransform()) { + const IntRect borderBox = toRenderBox(renderer())->borderBoxRect(); + + // Get layout bounds in the coords of compAncestor to match relativeCompositingBounds. + IntRect layerBounds = IntRect(deltaX, deltaY, borderBox.width(), borderBox.height()); + + // Update properties that depend on layer dimensions + FloatPoint3D transformOrigin = computeTransformOrigin(borderBox); + // Compute the anchor point, which is in the center of the renderer box unless transform-origin is set. + FloatPoint3D anchor(relativeCompositingBounds.width() != 0.0f ? ((layerBounds.x() - relativeCompositingBounds.x()) + transformOrigin.x()) / relativeCompositingBounds.width() : 0.5f, + relativeCompositingBounds.height() != 0.0f ? ((layerBounds.y() - relativeCompositingBounds.y()) + transformOrigin.y()) / relativeCompositingBounds.height() : 0.5f, + transformOrigin.z()); + m_graphicsLayer->setAnchorPoint(anchor); + + RenderStyle* style = renderer()->style(); + if (style->hasPerspective()) { + TransformationMatrix t = owningLayer()->perspectiveTransform(); + + if (m_clippingLayer) { + m_clippingLayer->setChildrenTransform(t); + m_graphicsLayer->setChildrenTransform(TransformationMatrix()); + } + else + m_graphicsLayer->setChildrenTransform(t); + } else { + if (m_clippingLayer) + m_clippingLayer->setChildrenTransform(TransformationMatrix()); + else + m_graphicsLayer->setChildrenTransform(TransformationMatrix()); + } + } else { + m_graphicsLayer->setAnchorPoint(FloatPoint3D(0.5f, 0.5f, 0)); + } + + if (m_foregroundLayer) { + FloatPoint foregroundPosition; + FloatSize foregroundSize = newSize; + IntSize foregroundOffset = m_graphicsLayer->offsetFromRenderer(); + // If we have a clipping layer (which clips descendants), then the foreground layer is a child of it, + // so that it gets correctly sorted with children. In that case, position relative to the clipping layer. + if (m_clippingLayer) { + foregroundPosition = FloatPoint() + (localCompositingBounds.location() - clippingBox.location()); + foregroundSize = FloatSize(clippingBox.size()); + foregroundOffset = clippingBox.location() - IntPoint(); + } + + m_foregroundLayer->setPosition(foregroundPosition); + m_foregroundLayer->setSize(foregroundSize); + m_foregroundLayer->setOffsetFromRenderer(foregroundOffset); + } + + if (m_owningLayer->reflectionLayer() && m_owningLayer->reflectionLayer()->isComposited()) { + RenderLayerBacking* reflectionBacking = m_owningLayer->reflectionLayer()->backing(); + reflectionBacking->updateGraphicsLayerGeometry(); + + // The reflection layer has the bounds of m_owningLayer->reflectionLayer(), + // but the reflected layer is the bounds of this layer, so we need to position it appropriately. + FloatRect layerBounds = compositedBounds(); + FloatRect reflectionLayerBounds = reflectionBacking->compositedBounds(); + reflectionBacking->graphicsLayer()->setReplicatedLayerPosition(FloatPoint() + (layerBounds.location() - reflectionLayerBounds.location())); + } + + m_graphicsLayer->setContentsRect(contentsBox()); + updateDrawsContent(); + updateAfterWidgetResize(); +} + +void RenderLayerBacking::updateInternalHierarchy() +{ + // m_foregroundLayer has to be inserted in the correct order with child layers, + // so it's not inserted here. + if (m_ancestorClippingLayer) { + m_ancestorClippingLayer->removeAllChildren(); + m_graphicsLayer->removeFromParent(); + m_ancestorClippingLayer->addChild(m_graphicsLayer.get()); + } + + if (m_clippingLayer) { + m_clippingLayer->removeFromParent(); + m_graphicsLayer->addChild(m_clippingLayer.get()); + } +} + +void RenderLayerBacking::updateDrawsContent() +{ + m_graphicsLayer->setDrawsContent(containsPaintedContent()); +} + +// Return true if the layers changed. +bool RenderLayerBacking::updateClippingLayers(bool needsAncestorClip, bool needsDescendantClip) +{ + bool layersChanged = false; + + if (needsAncestorClip) { + if (!m_ancestorClippingLayer) { + m_ancestorClippingLayer = GraphicsLayer::create(this); +#ifndef NDEBUG + m_ancestorClippingLayer->setName("Ancestor clipping Layer"); +#endif + m_ancestorClippingLayer->setMasksToBounds(true); + layersChanged = true; + } + } else if (m_ancestorClippingLayer) { + m_ancestorClippingLayer->removeFromParent(); + m_ancestorClippingLayer = 0; + layersChanged = true; + } + + if (needsDescendantClip) { + if (!m_clippingLayer) { + m_clippingLayer = GraphicsLayer::create(this); +#ifndef NDEBUG + m_clippingLayer->setName("Child clipping Layer"); +#endif + m_clippingLayer->setMasksToBounds(true); + layersChanged = true; + } + } else if (m_clippingLayer) { + m_clippingLayer->removeFromParent(); + m_clippingLayer = 0; + layersChanged = true; + } + + if (layersChanged) + updateInternalHierarchy(); + + return layersChanged; +} + +bool RenderLayerBacking::updateForegroundLayer(bool needsForegroundLayer) +{ + bool layerChanged = false; + if (needsForegroundLayer) { + if (!m_foregroundLayer) { + m_foregroundLayer = GraphicsLayer::create(this); +#ifndef NDEBUG + m_foregroundLayer->setName(nameForLayer() + " (foreground)"); +#endif + m_foregroundLayer->setDrawsContent(true); + m_foregroundLayer->setPaintingPhase(GraphicsLayerPaintForeground); + layerChanged = true; + } + } else if (m_foregroundLayer) { + m_foregroundLayer->removeFromParent(); + m_foregroundLayer = 0; + layerChanged = true; + } + + if (layerChanged) + m_graphicsLayer->setPaintingPhase(paintingPhaseForPrimaryLayer()); + + return layerChanged; +} + +bool RenderLayerBacking::updateMaskLayer(bool needsMaskLayer) +{ + bool layerChanged = false; + if (needsMaskLayer) { + if (!m_maskLayer) { + m_maskLayer = GraphicsLayer::create(this); +#ifndef NDEBUG + m_maskLayer->setName("Mask"); +#endif + m_maskLayer->setDrawsContent(true); + m_maskLayer->setPaintingPhase(GraphicsLayerPaintMask); + layerChanged = true; + } + } else if (m_maskLayer) { + m_maskLayer = 0; + layerChanged = true; + } + + if (layerChanged) + m_graphicsLayer->setPaintingPhase(paintingPhaseForPrimaryLayer()); + + return layerChanged; +} + +GraphicsLayerPaintingPhase RenderLayerBacking::paintingPhaseForPrimaryLayer() const +{ + unsigned phase = GraphicsLayerPaintBackground; + if (!m_foregroundLayer) + phase |= GraphicsLayerPaintForeground; + if (!m_maskLayer) + phase |= GraphicsLayerPaintMask; + + return static_cast<GraphicsLayerPaintingPhase>(phase); +} + +float RenderLayerBacking::compositingOpacity(float rendererOpacity) const +{ + float finalOpacity = rendererOpacity; + + for (RenderLayer* curr = m_owningLayer->parent(); curr; curr = curr->parent()) { + // We only care about parents that are stacking contexts. + // Recall that opacity creates stacking context. + if (!curr->isStackingContext()) + continue; + + // If we found a compositing layer, we want to compute opacity + // relative to it. So we can break here. + if (curr->isComposited()) + break; + + finalOpacity *= curr->renderer()->opacity(); + } + + return finalOpacity; +} + +static bool hasBorderOutlineOrShadow(const RenderStyle* style) +{ + return style->hasBorder() || style->hasBorderRadius() || style->hasOutline() || style->hasAppearance() || style->boxShadow(); +} + +static bool hasBoxDecorationsOrBackground(const RenderObject* renderer) +{ + return hasBorderOutlineOrShadow(renderer->style()) || renderer->hasBackground(); +} + +static bool hasBoxDecorationsOrBackgroundImage(const RenderStyle* style) +{ + return hasBorderOutlineOrShadow(style) || style->hasBackgroundImage(); +} + +bool RenderLayerBacking::rendererHasBackground() const +{ + // FIXME: share more code here + if (renderer()->node() && renderer()->node()->isDocumentNode()) { + RenderObject* htmlObject = renderer()->firstChild(); + if (!htmlObject) + return false; + + if (htmlObject->hasBackground()) + return true; + + RenderObject* bodyObject = htmlObject->firstChild(); + if (!bodyObject) + return false; + + return bodyObject->hasBackground(); + } + + return renderer()->hasBackground(); +} + +const Color RenderLayerBacking::rendererBackgroundColor() const +{ + // FIXME: share more code here + if (renderer()->node() && renderer()->node()->isDocumentNode()) { + RenderObject* htmlObject = renderer()->firstChild(); + if (htmlObject->hasBackground()) + return htmlObject->style()->visitedDependentColor(CSSPropertyBackgroundColor); + + RenderObject* bodyObject = htmlObject->firstChild(); + return bodyObject->style()->visitedDependentColor(CSSPropertyBackgroundColor); + } + + return renderer()->style()->visitedDependentColor(CSSPropertyBackgroundColor); +} + +// A "simple container layer" is a RenderLayer which has no visible content to render. +// It may have no children, or all its children may be themselves composited. +// This is a useful optimization, because it allows us to avoid allocating backing store. +bool RenderLayerBacking::isSimpleContainerCompositingLayer() const +{ + RenderObject* renderObject = renderer(); + if (renderObject->isReplaced() || // replaced objects are not containers + renderObject->hasMask()) // masks require special treatment + return false; + + RenderStyle* style = renderObject->style(); + + // Reject anything that has a border, a border-radius or outline, + // or any background (color or image). + // FIXME: we could optimize layers for simple backgrounds. + if (hasBoxDecorationsOrBackground(renderObject)) + return false; + + if (m_owningLayer->hasOverflowControls()) + return false; + + // If we have got this far and the renderer has no children, then we're ok. + if (!renderObject->firstChild()) + return true; + + if (renderObject->node() && renderObject->node()->isDocumentNode()) { + // Look to see if the root object has a non-simple backgound + RenderObject* rootObject = renderObject->document()->documentElement()->renderer(); + if (!rootObject) + return false; + + style = rootObject->style(); + + // Reject anything that has a border, a border-radius or outline, + // or is not a simple background (no background, or solid color). + if (hasBoxDecorationsOrBackgroundImage(style)) + return false; + + // Now look at the body's renderer. + HTMLElement* body = renderObject->document()->body(); + RenderObject* bodyObject = (body && body->hasLocalName(bodyTag)) ? body->renderer() : 0; + if (!bodyObject) + return false; + + style = bodyObject->style(); + + if (hasBoxDecorationsOrBackgroundImage(style)) + return false; + + // Check to see if all the body's children are compositing layers. + if (hasNonCompositingDescendants()) + return false; + + return true; + } + + // Check to see if all the renderer's children are compositing layers. + if (hasNonCompositingDescendants()) + return false; + + return true; +} + +// Conservative test for having no rendered children. +bool RenderLayerBacking::hasNonCompositingDescendants() const +{ + // Some HTML can cause whitespace text nodes to have renderers, like: + // <div> + // <img src=...> + // </div> + // so test for 0x0 RenderTexts here + for (RenderObject* child = renderer()->firstChild(); child; child = child->nextSibling()) { + if (!child->hasLayer()) { + if (child->isRenderInline() || !child->isBox()) + return true; + + if (toRenderBox(child)->width() > 0 || toRenderBox(child)->height() > 0) + return true; + } + } + + if (m_owningLayer->isStackingContext()) { + // Use the m_hasCompositingDescendant bit to optimize? + if (Vector<RenderLayer*>* negZOrderList = m_owningLayer->negZOrderList()) { + size_t listSize = negZOrderList->size(); + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = negZOrderList->at(i); + if (!curLayer->isComposited()) + return true; + } + } + + if (Vector<RenderLayer*>* posZOrderList = m_owningLayer->posZOrderList()) { + size_t listSize = posZOrderList->size(); + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = posZOrderList->at(i); + if (!curLayer->isComposited()) + return true; + } + } + } + + if (Vector<RenderLayer*>* normalFlowList = m_owningLayer->normalFlowList()) { + size_t listSize = normalFlowList->size(); + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = normalFlowList->at(i); + if (!curLayer->isComposited()) + return true; + } + } + + return false; +} + +bool RenderLayerBacking::containsPaintedContent() const +{ + if (isSimpleContainerCompositingLayer() || paintingGoesToWindow() || m_artificiallyInflatedBounds || m_owningLayer->isReflection()) + return false; + + if (isDirectlyCompositedImage()) + return false; + + // FIXME: we could optimize cases where the image, video or canvas is known to fill the border box entirely, + // and set background color on the layer in that case, instead of allocating backing store and painting. +#if ENABLE(VIDEO) + if (renderer()->isVideo() && toRenderVideo(renderer())->shouldDisplayVideo()) + return hasBoxDecorationsOrBackground(renderer()); +#endif +#if PLATFORM(MAC) && PLATFORM(CA) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) +#elif ENABLE(3D_CANVAS) || ENABLE(ACCELERATED_2D_CANVAS) + if (isAcceleratedCanvas(renderer())) + return hasBoxDecorationsOrBackground(renderer()); +#endif + + return true; +} + +// An image can be directly compositing if it's the sole content of the layer, and has no box decorations +// that require painting. Direct compositing saves backing store. +bool RenderLayerBacking::isDirectlyCompositedImage() const +{ + RenderObject* renderObject = renderer(); + + if (!renderObject->isImage() || hasBoxDecorationsOrBackground(renderObject) || renderObject->hasClip()) + return false; + + RenderImage* imageRenderer = toRenderImage(renderObject); + if (CachedImage* cachedImage = imageRenderer->cachedImage()) { + if (cachedImage->hasImage()) + return cachedImage->image()->isBitmapImage(); + } + + return false; +} + +void RenderLayerBacking::contentChanged(RenderLayer::ContentChangeType changeType) +{ + if ((changeType == RenderLayer::ImageChanged) && isDirectlyCompositedImage()) { + updateImageContents(); + return; + } + + if ((changeType == RenderLayer::MaskImageChanged) && m_maskLayer) { + // The composited layer bounds relies on box->maskClipRect(), which changes + // when the mask image becomes available. + bool isUpdateRoot = true; + updateAfterLayout(CompositingChildren, isUpdateRoot); + } + +#if ENABLE(3D_CANVAS) || ENABLE(ACCELERATED_2D_CANVAS) + if ((changeType == RenderLayer::CanvasChanged) && isAcceleratedCanvas(renderer())) { + m_graphicsLayer->setContentsNeedsDisplay(); + return; + } +#endif +} + +void RenderLayerBacking::updateImageContents() +{ + ASSERT(renderer()->isImage()); + RenderImage* imageRenderer = toRenderImage(renderer()); + + CachedImage* cachedImage = imageRenderer->cachedImage(); + if (!cachedImage) + return; + + Image* image = cachedImage->image(); + if (!image) + return; + + // We have to wait until the image is fully loaded before setting it on the layer. + if (!cachedImage->isLoaded()) + return; + + // This is a no-op if the layer doesn't have an inner layer for the image. + m_graphicsLayer->setContentsToImage(image); + + // Image animation is "lazy", in that it automatically stops unless someone is drawing + // the image. So we have to kick the animation each time; this has the downside that the + // image will keep animating, even if its layer is not visible. + image->startAnimation(); +} + +FloatPoint3D RenderLayerBacking::computeTransformOrigin(const IntRect& borderBox) const +{ + RenderStyle* style = renderer()->style(); + + FloatPoint3D origin; + origin.setX(style->transformOriginX().calcFloatValue(borderBox.width())); + origin.setY(style->transformOriginY().calcFloatValue(borderBox.height())); + origin.setZ(style->transformOriginZ()); + + return origin; +} + +FloatPoint RenderLayerBacking::computePerspectiveOrigin(const IntRect& borderBox) const +{ + RenderStyle* style = renderer()->style(); + + float boxWidth = borderBox.width(); + float boxHeight = borderBox.height(); + + FloatPoint origin; + origin.setX(style->perspectiveOriginX().calcFloatValue(boxWidth)); + origin.setY(style->perspectiveOriginY().calcFloatValue(boxHeight)); + + return origin; +} + +// Return the offset from the top-left of this compositing layer at which the renderer's contents are painted. +IntSize RenderLayerBacking::contentOffsetInCompostingLayer() const +{ + return IntSize(-m_compositedBounds.x(), -m_compositedBounds.y()); +} + +IntRect RenderLayerBacking::contentsBox() const +{ + if (!renderer()->isBox()) + return IntRect(); + + IntRect contentsRect; +#if ENABLE(VIDEO) + if (renderer()->isVideo()) { + RenderVideo* videoRenderer = toRenderVideo(renderer()); + contentsRect = videoRenderer->videoBox(); + } else +#endif + contentsRect = toRenderBox(renderer())->contentBoxRect(); + + IntSize contentOffset = contentOffsetInCompostingLayer(); + contentsRect.move(contentOffset); + return contentsRect; +} + +// Map the given point from coordinates in the GraphicsLayer to RenderLayer coordinates. +FloatPoint RenderLayerBacking::graphicsLayerToContentsCoordinates(const GraphicsLayer* graphicsLayer, const FloatPoint& point) +{ + return point + FloatSize(graphicsLayer->offsetFromRenderer()); +} + +// Map the given point from coordinates in the RenderLayer to GraphicsLayer coordinates. +FloatPoint RenderLayerBacking::contentsToGraphicsLayerCoordinates(const GraphicsLayer* graphicsLayer, const FloatPoint& point) +{ + return point - FloatSize(graphicsLayer->offsetFromRenderer()); +} + +bool RenderLayerBacking::paintingGoesToWindow() const +{ + if (m_owningLayer->isRootLayer()) + return !m_owningLayer->hasTransform() && (compositor()->rootLayerAttachment() != RenderLayerCompositor::RootLayerAttachedViaEnclosingIframe); + + return false; +} + +void RenderLayerBacking::setContentsNeedDisplay() +{ + if (m_graphicsLayer && m_graphicsLayer->drawsContent()) + m_graphicsLayer->setNeedsDisplay(); + + if (m_foregroundLayer && m_foregroundLayer->drawsContent()) + m_foregroundLayer->setNeedsDisplay(); + + if (m_maskLayer && m_maskLayer->drawsContent()) + m_maskLayer->setNeedsDisplay(); +} + +// r is in the coordinate space of the layer's render object +void RenderLayerBacking::setContentsNeedDisplayInRect(const IntRect& r) +{ + if (m_graphicsLayer && m_graphicsLayer->drawsContent()) { + FloatPoint dirtyOrigin = contentsToGraphicsLayerCoordinates(m_graphicsLayer.get(), FloatPoint(r.x(), r.y())); + FloatRect dirtyRect(dirtyOrigin, r.size()); + FloatRect bounds(FloatPoint(), m_graphicsLayer->size()); + if (bounds.intersects(dirtyRect)) + m_graphicsLayer->setNeedsDisplayInRect(dirtyRect); + } + + if (m_foregroundLayer && m_foregroundLayer->drawsContent()) { + // FIXME: do incremental repaint + m_foregroundLayer->setNeedsDisplay(); + } + + if (m_maskLayer && m_maskLayer->drawsContent()) { + // FIXME: do incremental repaint + m_maskLayer->setNeedsDisplay(); + } +} + +static void setClip(GraphicsContext* p, const IntRect& paintDirtyRect, const IntRect& clipRect) +{ + if (paintDirtyRect == clipRect) + return; + p->save(); + p->clip(clipRect); +} + +static void restoreClip(GraphicsContext* p, const IntRect& paintDirtyRect, const IntRect& clipRect) +{ + if (paintDirtyRect == clipRect) + return; + p->restore(); +} + +// Share this with RenderLayer::paintLayer, which would have to be educated about GraphicsLayerPaintingPhase? +void RenderLayerBacking::paintIntoLayer(RenderLayer* rootLayer, GraphicsContext* context, + const IntRect& paintDirtyRect, // in the coords of rootLayer + PaintBehavior paintBehavior, GraphicsLayerPaintingPhase paintingPhase, + RenderObject* paintingRoot) +{ + if (paintingGoesToWindow()) { + ASSERT_NOT_REACHED(); + return; + } + + m_owningLayer->updateLayerListsIfNeeded(); + + // Calculate the clip rects we should use. + IntRect layerBounds, damageRect, clipRectToApply, outlineRect; + m_owningLayer->calculateRects(rootLayer, paintDirtyRect, layerBounds, damageRect, clipRectToApply, outlineRect); + + int x = layerBounds.x(); // layerBounds is computed relative to rootLayer + int y = layerBounds.y(); + int tx = x - m_owningLayer->renderBoxX(); + int ty = y - m_owningLayer->renderBoxY(); + + // If this layer's renderer is a child of the paintingRoot, we render unconditionally, which + // is done by passing a nil paintingRoot down to our renderer (as if no paintingRoot was ever set). + // Else, our renderer tree may or may not contain the painting root, so we pass that root along + // so it will be tested against as we decend through the renderers. + RenderObject *paintingRootForRenderer = 0; + if (paintingRoot && !renderer()->isDescendantOf(paintingRoot)) + paintingRootForRenderer = paintingRoot; + + bool shouldPaint = (m_owningLayer->hasVisibleContent() || m_owningLayer->hasVisibleDescendant()) && m_owningLayer->isSelfPaintingLayer(); + + if (shouldPaint && (paintingPhase & GraphicsLayerPaintBackground)) { + // Paint our background first, before painting any child layers. + // Establish the clip used to paint our background. + setClip(context, paintDirtyRect, damageRect); + + PaintInfo info(context, damageRect, PaintPhaseBlockBackground, false, paintingRootForRenderer, 0); + renderer()->paint(info, tx, ty); + + // Our scrollbar widgets paint exactly when we tell them to, so that they work properly with + // z-index. We paint after we painted the background/border, so that the scrollbars will + // sit above the background/border. + m_owningLayer->paintOverflowControls(context, x, y, damageRect); + + // Restore the clip. + restoreClip(context, paintDirtyRect, damageRect); +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + // Paint the outline as part of the background phase in order for the + // outline to not be a part of the scrollable content. + if (!outlineRect.isEmpty()) { + // Paint our own outline + PaintInfo paintInfo(context, outlineRect, PaintPhaseSelfOutline, false, paintingRootForRenderer, 0); + setClip(context, paintDirtyRect, outlineRect); + renderer()->paint(paintInfo, tx, ty); + restoreClip(context, paintDirtyRect, outlineRect); + } +#endif + + // Now walk the sorted list of children with negative z-indices. Only RenderLayers without compositing layers will paint. + m_owningLayer->paintList(m_owningLayer->negZOrderList(), rootLayer, context, paintDirtyRect, paintBehavior, paintingRoot, 0, 0); + } + + bool forceBlackText = paintBehavior & PaintBehaviorForceBlackText; + bool selectionOnly = paintBehavior & PaintBehaviorSelectionOnly; + + if (shouldPaint && (paintingPhase & GraphicsLayerPaintForeground)) { + // Set up the clip used when painting our children. + setClip(context, paintDirtyRect, clipRectToApply); + PaintInfo paintInfo(context, clipRectToApply, + selectionOnly ? PaintPhaseSelection : PaintPhaseChildBlockBackgrounds, + forceBlackText, paintingRootForRenderer, 0); + renderer()->paint(paintInfo, tx, ty); + + if (!selectionOnly) { + paintInfo.phase = PaintPhaseFloat; + renderer()->paint(paintInfo, tx, ty); + + paintInfo.phase = PaintPhaseForeground; + renderer()->paint(paintInfo, tx, ty); + + paintInfo.phase = PaintPhaseChildOutlines; + renderer()->paint(paintInfo, tx, ty); + } + + // Now restore our clip. + restoreClip(context, paintDirtyRect, clipRectToApply); + +#if !ENABLE(ANDROID_OVERFLOW_SCROLL) + // Do not paint the outline as part of the foreground since it will + // appear inside the scrollable content. + if (!outlineRect.isEmpty()) { + // Paint our own outline + PaintInfo paintInfo(context, outlineRect, PaintPhaseSelfOutline, false, paintingRootForRenderer, 0); + setClip(context, paintDirtyRect, outlineRect); + renderer()->paint(paintInfo, tx, ty); + restoreClip(context, paintDirtyRect, outlineRect); + } +#endif + + // Paint any child layers that have overflow. + m_owningLayer->paintList(m_owningLayer->normalFlowList(), rootLayer, context, paintDirtyRect, paintBehavior, paintingRoot, 0, 0); + + // Now walk the sorted list of children with positive z-indices. + m_owningLayer->paintList(m_owningLayer->posZOrderList(), rootLayer, context, paintDirtyRect, paintBehavior, paintingRoot, 0, 0); + } + + if (shouldPaint && (paintingPhase & GraphicsLayerPaintMask)) { + if (renderer()->hasMask() && !selectionOnly && !damageRect.isEmpty()) { + setClip(context, paintDirtyRect, damageRect); + + // Paint the mask. + PaintInfo paintInfo(context, damageRect, PaintPhaseMask, false, paintingRootForRenderer, 0); + renderer()->paint(paintInfo, tx, ty); + + // Restore the clip. + restoreClip(context, paintDirtyRect, damageRect); + } + } + + ASSERT(!m_owningLayer->m_usedTransparency); +} + +// Up-call from compositing layer drawing callback. +void RenderLayerBacking::paintContents(const GraphicsLayer*, GraphicsContext& context, GraphicsLayerPaintingPhase paintingPhase, const IntRect& clip) +{ + InspectorInstrumentationCookie cookie = InspectorInstrumentation::willPaint(m_owningLayer->renderer()->frame(), clip); + + // We have to use the same root as for hit testing, because both methods + // can compute and cache clipRects. + IntRect enclosingBBox = compositedBounds(); +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + // If we encounter a scrollable layer, layers inside the scrollable layer + // will need their entire content recorded. + if (m_owningLayer->hasOverflowParent()) + enclosingBBox.setSize(clip.size()); +#endif + + IntRect clipRect(clip); + + // Set up the coordinate space to be in the layer's rendering coordinates. + context.translate(-enclosingBBox.x(), -enclosingBBox.y()); + + // Offset the clip. + clipRect.move(enclosingBBox.x(), enclosingBBox.y()); + + // The dirtyRect is in the coords of the painting root. + IntRect dirtyRect = enclosingBBox; + dirtyRect.intersect(clipRect); + + paintIntoLayer(m_owningLayer, &context, dirtyRect, PaintBehaviorNormal, paintingPhase, renderer()); + + InspectorInstrumentation::didPaint(cookie); +} + +bool RenderLayerBacking::showDebugBorders() const +{ + return compositor() ? compositor()->compositorShowDebugBorders() : false; +} + +bool RenderLayerBacking::showRepaintCounter() const +{ + return compositor() ? compositor()->compositorShowRepaintCounter() : false; +} + +bool RenderLayerBacking::startAnimation(double timeOffset, const Animation* anim, const KeyframeList& keyframes) +{ + bool hasOpacity = keyframes.containsProperty(CSSPropertyOpacity); + bool hasTransform = renderer()->isBox() && keyframes.containsProperty(CSSPropertyWebkitTransform); + + if (!hasOpacity && !hasTransform) + return false; + + KeyframeValueList transformVector(AnimatedPropertyWebkitTransform); + KeyframeValueList opacityVector(AnimatedPropertyOpacity); + + size_t numKeyframes = keyframes.size(); + for (size_t i = 0; i < numKeyframes; ++i) { + const KeyframeValue& currentKeyframe = keyframes[i]; + const RenderStyle* keyframeStyle = currentKeyframe.style(); + float key = currentKeyframe.key(); + + if (!keyframeStyle) + continue; + + // Get timing function. + RefPtr<TimingFunction> tf = keyframeStyle->hasAnimations() ? (*keyframeStyle->animations()).animation(0)->timingFunction() : 0; + + bool isFirstOrLastKeyframe = key == 0 || key == 1; + if ((hasTransform && isFirstOrLastKeyframe) || currentKeyframe.containsProperty(CSSPropertyWebkitTransform)) + transformVector.insert(new TransformAnimationValue(key, &(keyframeStyle->transform()), tf)); + + if ((hasOpacity && isFirstOrLastKeyframe) || currentKeyframe.containsProperty(CSSPropertyOpacity)) + opacityVector.insert(new FloatAnimationValue(key, keyframeStyle->opacity(), tf)); + } + + bool didAnimateTransform = false; + bool didAnimateOpacity = false; + + if (hasTransform && m_graphicsLayer->addAnimation(transformVector, toRenderBox(renderer())->borderBoxRect().size(), anim, keyframes.animationName(), timeOffset)) { + didAnimateTransform = true; + compositor()->didStartAcceleratedAnimation(CSSPropertyWebkitTransform); + } + + if (hasOpacity && m_graphicsLayer->addAnimation(opacityVector, IntSize(), anim, keyframes.animationName(), timeOffset)) { + didAnimateOpacity = true; + compositor()->didStartAcceleratedAnimation(CSSPropertyOpacity); + } + + return didAnimateTransform || didAnimateOpacity; +} + +void RenderLayerBacking::animationPaused(double timeOffset, const String& animationName) +{ + m_graphicsLayer->pauseAnimation(animationName, timeOffset); +} + +void RenderLayerBacking::animationFinished(const String& animationName) +{ + m_graphicsLayer->removeAnimation(animationName); +} + +bool RenderLayerBacking::startTransition(double timeOffset, int property, const RenderStyle* fromStyle, const RenderStyle* toStyle) +{ + bool didAnimateOpacity = false; + bool didAnimateTransform = false; + ASSERT(property != cAnimateAll); + + if (property == (int)CSSPropertyOpacity) { + const Animation* opacityAnim = toStyle->transitionForProperty(CSSPropertyOpacity); + if (opacityAnim && !opacityAnim->isEmptyOrZeroDuration()) { + KeyframeValueList opacityVector(AnimatedPropertyOpacity); + opacityVector.insert(new FloatAnimationValue(0, compositingOpacity(fromStyle->opacity()))); + opacityVector.insert(new FloatAnimationValue(1, compositingOpacity(toStyle->opacity()))); + // The boxSize param is only used for transform animations (which can only run on RenderBoxes), so we pass an empty size here. + if (m_graphicsLayer->addAnimation(opacityVector, IntSize(), opacityAnim, GraphicsLayer::animationNameForTransition(AnimatedPropertyOpacity), timeOffset)) { + // To ensure that the correct opacity is visible when the animation ends, also set the final opacity. + updateLayerOpacity(toStyle); + didAnimateOpacity = true; + } + } + } + + if (property == (int)CSSPropertyWebkitTransform && m_owningLayer->hasTransform()) { + const Animation* transformAnim = toStyle->transitionForProperty(CSSPropertyWebkitTransform); + if (transformAnim && !transformAnim->isEmptyOrZeroDuration()) { + KeyframeValueList transformVector(AnimatedPropertyWebkitTransform); + transformVector.insert(new TransformAnimationValue(0, &fromStyle->transform())); + transformVector.insert(new TransformAnimationValue(1, &toStyle->transform())); + if (m_graphicsLayer->addAnimation(transformVector, toRenderBox(renderer())->borderBoxRect().size(), transformAnim, GraphicsLayer::animationNameForTransition(AnimatedPropertyWebkitTransform), timeOffset)) { + // To ensure that the correct transform is visible when the animation ends, also set the final opacity. + updateLayerTransform(toStyle); + didAnimateTransform = true; + } + } + } + + if (didAnimateOpacity) + compositor()->didStartAcceleratedAnimation(CSSPropertyOpacity); + + if (didAnimateTransform) + compositor()->didStartAcceleratedAnimation(CSSPropertyWebkitTransform); + + return didAnimateOpacity || didAnimateTransform; +} + +void RenderLayerBacking::transitionPaused(double timeOffset, int property) +{ + AnimatedPropertyID animatedProperty = cssToGraphicsLayerProperty(property); + if (animatedProperty != AnimatedPropertyInvalid) + m_graphicsLayer->pauseAnimation(GraphicsLayer::animationNameForTransition(animatedProperty), timeOffset); +} + +void RenderLayerBacking::transitionFinished(int property) +{ + AnimatedPropertyID animatedProperty = cssToGraphicsLayerProperty(property); + if (animatedProperty != AnimatedPropertyInvalid) + m_graphicsLayer->removeAnimation(GraphicsLayer::animationNameForTransition(animatedProperty)); +} + +void RenderLayerBacking::notifyAnimationStarted(const GraphicsLayer*, double time) +{ + renderer()->animation()->notifyAnimationStarted(renderer(), time); +} + +void RenderLayerBacking::notifySyncRequired(const GraphicsLayer*) +{ + if (!renderer()->documentBeingDestroyed()) + compositor()->scheduleSync(); +} + +// This is used for the 'freeze' API, for testing only. +void RenderLayerBacking::suspendAnimations(double time) +{ + m_graphicsLayer->suspendAnimations(time); +} + +void RenderLayerBacking::resumeAnimations() +{ + m_graphicsLayer->resumeAnimations(); +} + +IntRect RenderLayerBacking::compositedBounds() const +{ + return m_compositedBounds; +} + +void RenderLayerBacking::setCompositedBounds(const IntRect& bounds) +{ + m_compositedBounds = bounds; + +} +int RenderLayerBacking::graphicsLayerToCSSProperty(AnimatedPropertyID property) +{ + int cssProperty = CSSPropertyInvalid; + switch (property) { + case AnimatedPropertyWebkitTransform: + cssProperty = CSSPropertyWebkitTransform; + break; + case AnimatedPropertyOpacity: + cssProperty = CSSPropertyOpacity; + break; + case AnimatedPropertyBackgroundColor: + cssProperty = CSSPropertyBackgroundColor; + break; + case AnimatedPropertyInvalid: + ASSERT_NOT_REACHED(); + } + return cssProperty; +} + +AnimatedPropertyID RenderLayerBacking::cssToGraphicsLayerProperty(int cssProperty) +{ + switch (cssProperty) { + case CSSPropertyWebkitTransform: + return AnimatedPropertyWebkitTransform; + case CSSPropertyOpacity: + return AnimatedPropertyOpacity; + case CSSPropertyBackgroundColor: + return AnimatedPropertyBackgroundColor; + // It's fine if we see other css properties here; they are just not accelerated. + } + return AnimatedPropertyInvalid; +} + +#ifndef NDEBUG +String RenderLayerBacking::nameForLayer() const +{ + String name = renderer()->renderName(); + if (Node* node = renderer()->node()) { + if (node->isElementNode()) + name += " " + static_cast<Element*>(node)->tagName(); + if (node->hasID()) + name += " \'" + static_cast<Element*>(node)->getIdAttribute() + "\'"; + } + + if (m_owningLayer->isReflection()) + name += " (reflection)"; + + return name; +} +#endif + +CompositingLayerType RenderLayerBacking::compositingLayerType() const +{ + if (m_graphicsLayer->hasContentsLayer()) + return MediaCompositingLayer; + + if (m_graphicsLayer->drawsContent()) + return m_graphicsLayer->usingTiledLayer() ? TiledCompositingLayer : NormalCompositingLayer; + + return ContainerCompositingLayer; +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/Source/WebCore/rendering/RenderLayerBacking.h b/Source/WebCore/rendering/RenderLayerBacking.h new file mode 100644 index 0000000..c5489f3 --- /dev/null +++ b/Source/WebCore/rendering/RenderLayerBacking.h @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderLayerBacking_h +#define RenderLayerBacking_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "FloatPoint.h" +#include "FloatPoint3D.h" +#include "GraphicsLayer.h" +#include "GraphicsLayerClient.h" +#include "RenderLayer.h" +#include "TransformationMatrix.h" + +namespace WebCore { + +class KeyframeList; +class RenderLayerCompositor; + +enum CompositingLayerType { + NormalCompositingLayer, // non-tiled layer with backing store + TiledCompositingLayer, // tiled layer (always has backing store) + MediaCompositingLayer, // layer that contains an image, video, webGL or plugin + ContainerCompositingLayer // layer with no backing store +}; + +// RenderLayerBacking controls the compositing behavior for a single RenderLayer. +// It holds the various GraphicsLayers, and makes decisions about intra-layer rendering +// optimizations. +// +// There is one RenderLayerBacking for each RenderLayer that is composited. + +class RenderLayerBacking : public GraphicsLayerClient, public Noncopyable { +public: + RenderLayerBacking(RenderLayer*); + ~RenderLayerBacking(); + + RenderLayer* owningLayer() const { return m_owningLayer; } + + enum UpdateDepth { CompositingChildren, AllDescendants }; + void updateAfterLayout(UpdateDepth, bool isUpdateRoot); + + // Returns true if layer configuration changed. + bool updateGraphicsLayerConfiguration(); + // Update graphics layer position and bounds. + void updateGraphicsLayerGeometry(); // make private + // Update contents and clipping structure. + void updateInternalHierarchy(); // make private + void updateDrawsContent(); + + GraphicsLayer* graphicsLayer() const { return m_graphicsLayer.get(); } + + // Layer to clip children + bool hasClippingLayer() const { return m_clippingLayer != 0; } + GraphicsLayer* clippingLayer() const { return m_clippingLayer.get(); } + + // Layer to get clipped by ancestor + bool hasAncestorClippingLayer() const { return m_ancestorClippingLayer != 0; } + GraphicsLayer* ancestorClippingLayer() const { return m_ancestorClippingLayer.get(); } + + bool hasContentsLayer() const { return m_foregroundLayer != 0; } + GraphicsLayer* foregroundLayer() const { return m_foregroundLayer.get(); } + + bool hasMaskLayer() const { return m_maskLayer != 0; } + + GraphicsLayer* parentForSublayers() const { return m_clippingLayer ? m_clippingLayer.get() : m_graphicsLayer.get(); } + GraphicsLayer* childForSuperlayers() const { return m_ancestorClippingLayer ? m_ancestorClippingLayer.get() : m_graphicsLayer.get(); } + + // RenderLayers with backing normally short-circuit paintLayer() because + // their content is rendered via callbacks from GraphicsLayer. However, the document + // layer is special, because it has a GraphicsLayer to act as a container for the GraphicsLayers + // for descendants, but its contents usually render into the window (in which case this returns true). + // This returns false for other layers, and when the document layer actually needs to paint into its backing store + // for some reason. + bool paintingGoesToWindow() const; + + void setContentsNeedDisplay(); + // r is in the coordinate space of the layer's render object + void setContentsNeedDisplayInRect(const IntRect& r); + + // Notification from the renderer that its content changed. + void contentChanged(RenderLayer::ContentChangeType); + + // Interface to start, finish, suspend and resume animations and transitions + bool startTransition(double timeOffset, int property, const RenderStyle* fromStyle, const RenderStyle* toStyle); + void transitionPaused(double timeOffset, int property); + void transitionFinished(int property); + + bool startAnimation(double timeOffset, const Animation* anim, const KeyframeList& keyframes); + void animationPaused(double timeOffset, const String& name); + void animationFinished(const String& name); + + void suspendAnimations(double time = 0); + void resumeAnimations(); + + IntRect compositedBounds() const; + void setCompositedBounds(const IntRect&); + void updateCompositedBounds(); + + void updateAfterWidgetResize(); + + FloatPoint graphicsLayerToContentsCoordinates(const GraphicsLayer*, const FloatPoint&); + FloatPoint contentsToGraphicsLayerCoordinates(const GraphicsLayer*, const FloatPoint&); + + // GraphicsLayerClient interface + virtual void notifyAnimationStarted(const GraphicsLayer*, double startTime); + virtual void notifySyncRequired(const GraphicsLayer*); + + virtual void paintContents(const GraphicsLayer*, GraphicsContext&, GraphicsLayerPaintingPhase, const IntRect& clip); + + virtual bool showDebugBorders() const; + virtual bool showRepaintCounter() const; + + IntRect contentsBox() const; + + // For informative purposes only. + CompositingLayerType compositingLayerType() const; + +private: + void createGraphicsLayer(); + void destroyGraphicsLayer(); + + RenderBoxModelObject* renderer() const { return m_owningLayer->renderer(); } + RenderLayerCompositor* compositor() const { return m_owningLayer->compositor(); } + + bool updateClippingLayers(bool needsAncestorClip, bool needsDescendantClip); + bool updateForegroundLayer(bool needsForegroundLayer); + bool updateMaskLayer(bool needsMaskLayer); + + GraphicsLayerPaintingPhase paintingPhaseForPrimaryLayer() const; + + IntSize contentOffsetInCompostingLayer() const; + // Result is transform origin in pixels. + FloatPoint3D computeTransformOrigin(const IntRect& borderBox) const; + // Result is perspective origin in pixels. + FloatPoint computePerspectiveOrigin(const IntRect& borderBox) const; + + void updateLayerOpacity(const RenderStyle*); + void updateLayerTransform(const RenderStyle*); + + // Return the opacity value that this layer should use for compositing. + float compositingOpacity(float rendererOpacity) const; + + // Returns true if this compositing layer has no visible content. + bool isSimpleContainerCompositingLayer() const; + // Returns true if this layer has content that needs to be rendered by painting into the backing store. + bool containsPaintedContent() const; + // Returns true if the RenderLayer just contains an image that we can composite directly. + bool isDirectlyCompositedImage() const; + void updateImageContents(); + + bool rendererHasBackground() const; + const Color rendererBackgroundColor() const; + + bool hasNonCompositingDescendants() const; + + void paintIntoLayer(RenderLayer* rootLayer, GraphicsContext*, const IntRect& paintDirtyRect, + PaintBehavior paintBehavior, GraphicsLayerPaintingPhase, RenderObject* paintingRoot); + + static int graphicsLayerToCSSProperty(AnimatedPropertyID); + static AnimatedPropertyID cssToGraphicsLayerProperty(int); + +#ifndef NDEBUG + String nameForLayer() const; +#endif + +private: + RenderLayer* m_owningLayer; + + OwnPtr<GraphicsLayer> m_ancestorClippingLayer; // only used if we are clipped by an ancestor which is not a stacking context + OwnPtr<GraphicsLayer> m_graphicsLayer; + OwnPtr<GraphicsLayer> m_foregroundLayer; // only used in cases where we need to draw the foreground separately + OwnPtr<GraphicsLayer> m_clippingLayer; // only used if we have clipping on a stacking context, with compositing children + OwnPtr<GraphicsLayer> m_maskLayer; // only used if we have a mask + + IntRect m_compositedBounds; + + bool m_artificiallyInflatedBounds; // bounds had to be made non-zero to make transform-origin work +}; + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) + +#endif // RenderLayerBacking_h diff --git a/Source/WebCore/rendering/RenderLayerCompositor.cpp b/Source/WebCore/rendering/RenderLayerCompositor.cpp new file mode 100644 index 0000000..960aa37 --- /dev/null +++ b/Source/WebCore/rendering/RenderLayerCompositor.cpp @@ -0,0 +1,1668 @@ +/* + * Copyright (C) 2009, 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if USE(ACCELERATED_COMPOSITING) +#include "RenderLayerCompositor.h" + +#include "AnimationController.h" +#include "CanvasRenderingContext.h" +#include "CSSPropertyNames.h" +#include "Chrome.h" +#include "ChromeClient.h" +#include "Frame.h" +#include "FrameView.h" +#include "GraphicsLayer.h" +#include "HTMLCanvasElement.h" +#include "HTMLIFrameElement.h" +#include "HTMLNames.h" +#include "HitTestResult.h" +#include "NodeList.h" +#include "Page.h" +#include "RenderApplet.h" +#include "RenderEmbeddedObject.h" +#include "RenderFullScreen.h" +#include "RenderIFrame.h" +#include "RenderLayerBacking.h" +#include "RenderReplica.h" +#include "RenderVideo.h" +#include "RenderView.h" +#include "Settings.h" + +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) +#include "HTMLMediaElement.h" +#endif + +#if PROFILE_LAYER_REBUILD +#include <wtf/CurrentTime.h> +#endif + +#ifndef NDEBUG +#include "RenderTreeAsText.h" +#endif + +#if ENABLE(3D_RENDERING) +// This symbol is used to determine from a script whether 3D rendering is enabled (via 'nm'). +bool WebCoreHas3DRendering = true; +#endif + +namespace WebCore { + +using namespace HTMLNames; + +struct CompositingState { + CompositingState(RenderLayer* compAncestor) + : m_compositingAncestor(compAncestor) + , m_subtreeIsCompositing(false) +#if ENABLE(COMPOSITED_FIXED_ELEMENTS) + , m_fixedSibling(false) +#endif +#ifndef NDEBUG + , m_depth(0) +#endif + { + } + + RenderLayer* m_compositingAncestor; + bool m_subtreeIsCompositing; +#if ENABLE(COMPOSITED_FIXED_ELEMENTS) + bool m_fixedSibling; +#endif +#ifndef NDEBUG + int m_depth; +#endif +}; + +RenderLayerCompositor::RenderLayerCompositor(RenderView* renderView) + : m_renderView(renderView) + , m_rootPlatformLayer(0) + , m_updateCompositingLayersTimer(this, &RenderLayerCompositor::updateCompositingLayersTimerFired) + , m_hasAcceleratedCompositing(true) + , m_compositingTriggers(static_cast<ChromeClient::CompositingTriggerFlags>(ChromeClient::AllTriggers)) + , m_showDebugBorders(false) + , m_showRepaintCounter(false) + , m_compositingConsultsOverlap(true) + , m_compositingDependsOnGeometry(false) + , m_compositing(false) + , m_compositingLayersNeedRebuild(false) + , m_rootLayerAttachment(RootLayerUnattached) +#if PROFILE_LAYER_REBUILD + , m_rootLayerUpdateCount(0) +#endif // PROFILE_LAYER_REBUILD +{ +} + +RenderLayerCompositor::~RenderLayerCompositor() +{ + ASSERT(m_rootLayerAttachment == RootLayerUnattached); +} + +void RenderLayerCompositor::enableCompositingMode(bool enable /* = true */) +{ + if (enable != m_compositing) { + m_compositing = enable; + + if (m_compositing) { + ensureRootPlatformLayer(); + notifyIFramesOfCompositingChange(); + } else + destroyRootPlatformLayer(); + } +} + +void RenderLayerCompositor::cacheAcceleratedCompositingFlags() +{ + bool hasAcceleratedCompositing = false; + bool showDebugBorders = false; + bool showRepaintCounter = false; + + if (Settings* settings = m_renderView->document()->settings()) { + hasAcceleratedCompositing = settings->acceleratedCompositingEnabled(); + showDebugBorders = settings->showDebugBorders(); + showRepaintCounter = settings->showRepaintCounter(); + } + + // We allow the chrome to override the settings, in case the page is rendered + // on a chrome that doesn't allow accelerated compositing. + if (hasAcceleratedCompositing) { + Frame* frame = m_renderView->frameView()->frame(); + Page* page = frame ? frame->page() : 0; + if (page) { + ChromeClient* chromeClient = page->chrome()->client(); + m_compositingTriggers = chromeClient->allowedCompositingTriggers(); + hasAcceleratedCompositing = m_compositingTriggers; + } + } + + if (hasAcceleratedCompositing != m_hasAcceleratedCompositing || showDebugBorders != m_showDebugBorders || showRepaintCounter != m_showRepaintCounter) + setCompositingLayersNeedRebuild(); + + m_hasAcceleratedCompositing = hasAcceleratedCompositing; + m_showDebugBorders = showDebugBorders; + m_showRepaintCounter = showRepaintCounter; +} + +bool RenderLayerCompositor::canRender3DTransforms() const +{ + return hasAcceleratedCompositing() && (m_compositingTriggers & ChromeClient::ThreeDTransformTrigger); +} + +void RenderLayerCompositor::setCompositingLayersNeedRebuild(bool needRebuild) +{ + if (inCompositingMode()) + m_compositingLayersNeedRebuild = needRebuild; +} + +void RenderLayerCompositor::scheduleSync() +{ + Frame* frame = m_renderView->frameView()->frame(); + Page* page = frame ? frame->page() : 0; + if (!page) + return; + + page->chrome()->client()->scheduleCompositingLayerSync(); +} + +void RenderLayerCompositor::scheduleCompositingLayerUpdate() +{ + if (!m_updateCompositingLayersTimer.isActive()) + m_updateCompositingLayersTimer.startOneShot(0); +} + +bool RenderLayerCompositor::compositingLayerUpdatePending() const +{ + return m_updateCompositingLayersTimer.isActive(); +} + +void RenderLayerCompositor::updateCompositingLayersTimerFired(Timer<RenderLayerCompositor>*) +{ + updateCompositingLayers(); +} + +void RenderLayerCompositor::updateCompositingLayers(CompositingUpdateType updateType, RenderLayer* updateRoot) +{ + m_updateCompositingLayersTimer.stop(); + + if (!m_compositingDependsOnGeometry && !m_compositing) + return; + + bool checkForHierarchyUpdate = m_compositingDependsOnGeometry; + bool needGeometryUpdate = false; + + switch (updateType) { + case CompositingUpdateAfterLayoutOrStyleChange: + case CompositingUpdateOnPaitingOrHitTest: + checkForHierarchyUpdate = true; + break; + case CompositingUpdateOnScroll: + if (m_compositingConsultsOverlap) + checkForHierarchyUpdate = true; // Overlap can change with scrolling, so need to check for hierarchy updates. + + needGeometryUpdate = true; + break; + } + + if (!checkForHierarchyUpdate && !needGeometryUpdate) + return; + + bool needHierarchyUpdate = m_compositingLayersNeedRebuild; + if (!updateRoot || m_compositingConsultsOverlap) { + // Only clear the flag if we're updating the entire hierarchy. + m_compositingLayersNeedRebuild = false; + updateRoot = rootRenderLayer(); + } + +#if PROFILE_LAYER_REBUILD + ++m_rootLayerUpdateCount; + + double startTime = WTF::currentTime(); +#endif + + if (checkForHierarchyUpdate) { + // Go through the layers in presentation order, so that we can compute which RenderLayers need compositing layers. + // FIXME: we could maybe do this and the hierarchy udpate in one pass, but the parenting logic would be more complex. + CompositingState compState(updateRoot); + bool layersChanged = false; + if (m_compositingConsultsOverlap) { + OverlapMap overlapTestRequestMap; + computeCompositingRequirements(updateRoot, &overlapTestRequestMap, compState, layersChanged); + } else + computeCompositingRequirements(updateRoot, 0, compState, layersChanged); + + needHierarchyUpdate |= layersChanged; + } + + if (needHierarchyUpdate) { + // Update the hierarchy of the compositing layers. + CompositingState compState(updateRoot); + Vector<GraphicsLayer*> childList; + rebuildCompositingLayerTree(updateRoot, compState, childList); + + // Host the document layer in the RenderView's root layer. + if (updateRoot == rootRenderLayer()) { + if (childList.isEmpty()) + destroyRootPlatformLayer(); + else + m_rootPlatformLayer->setChildren(childList); + } + } else if (needGeometryUpdate) { + // We just need to do a geometry update. This is only used for position:fixed scrolling; + // most of the time, geometry is updated via RenderLayer::styleChanged(). + updateLayerTreeGeometry(updateRoot); + } + +#if PROFILE_LAYER_REBUILD + double endTime = WTF::currentTime(); + if (updateRoot == rootRenderLayer()) + fprintf(stderr, "Update %d: computeCompositingRequirements for the world took %fms\n", + m_rootLayerUpdateCount, 1000.0 * (endTime - startTime)); +#endif + ASSERT(updateRoot || !m_compositingLayersNeedRebuild); + + if (!hasAcceleratedCompositing()) + enableCompositingMode(false); +} + +bool RenderLayerCompositor::updateBacking(RenderLayer* layer, CompositingChangeRepaint shouldRepaint) +{ + bool layerChanged = false; + + if (needsToBeComposited(layer)) { + enableCompositingMode(); + + // 3D transforms turn off the testing of overlap. + if (requiresCompositingForTransform(layer->renderer())) + setCompositingConsultsOverlap(false); +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + // If we are a child of a scrollable layer, ignore the overlap from the + // scrollable layer as it can cause child layers to become composited + // siblings and will not scroll with the main content layer. + if (layer->hasOverflowParent()) + setCompositingConsultsOverlap(false); +#endif + + if (!layer->backing()) { + + // If we need to repaint, do so before making backing + if (shouldRepaint == CompositingChangeRepaintNow) + repaintOnCompositingChange(layer); + + layer->ensureBacking(); + +#if PLATFORM(MAC) && PLATFORM(CA) + if (layer->renderer()->isCanvas()) { + HTMLCanvasElement* canvas = static_cast<HTMLCanvasElement*>(layer->renderer()->node()); + if (canvas->renderingContext() && canvas->renderingContext()->isAccelerated()) + layer->backing()->graphicsLayer()->setAcceleratesDrawing(true); + } +#endif + layerChanged = true; + } + } else { + if (layer->backing()) { + // If we're removing backing on a reflection, clear the source GraphicsLayer's pointer to + // its replica GraphicsLayer. In practice this should never happen because reflectee and reflection + // are both either composited, or not composited. + if (layer->isReflection()) { + RenderLayer* sourceLayer = toRenderBoxModelObject(layer->renderer()->parent())->layer(); + if (RenderLayerBacking* backing = sourceLayer->backing()) { + ASSERT(backing->graphicsLayer()->replicaLayer() == layer->backing()->graphicsLayer()); + backing->graphicsLayer()->setReplicatedByLayer(0); + } + } + + layer->clearBacking(); + layerChanged = true; + + // The layer's cached repaints rects are relative to the repaint container, so change when + // compositing changes; we need to update them here. + layer->computeRepaintRects(); + + // If we need to repaint, do so now that we've removed the backing + if (shouldRepaint == CompositingChangeRepaintNow) + repaintOnCompositingChange(layer); + } + } + +#if ENABLE(VIDEO) + if (layerChanged && layer->renderer()->isVideo()) { + // If it's a video, give the media player a chance to hook up to the layer. + RenderVideo* video = toRenderVideo(layer->renderer()); + video->acceleratedRenderingStateChanged(); + } +#endif + + if (layerChanged && layer->renderer()->isRenderIFrame()) { + RenderLayerCompositor* innerCompositor = iframeContentsCompositor(toRenderIFrame(layer->renderer())); + if (innerCompositor && innerCompositor->inCompositingMode()) + innerCompositor->updateRootLayerAttachment(); + } + + return layerChanged; +} + +bool RenderLayerCompositor::updateLayerCompositingState(RenderLayer* layer, CompositingChangeRepaint shouldRepaint) +{ + bool layerChanged = updateBacking(layer, shouldRepaint); + + // See if we need content or clipping layers. Methods called here should assume + // that the compositing state of descendant layers has not been updated yet. + if (layer->backing() && layer->backing()->updateGraphicsLayerConfiguration()) + layerChanged = true; + + return layerChanged; +} + +void RenderLayerCompositor::repaintOnCompositingChange(RenderLayer* layer) +{ + // If the renderer is not attached yet, no need to repaint. + if (layer->renderer() != m_renderView && !layer->renderer()->parent()) + return; + + RenderBoxModelObject* repaintContainer = layer->renderer()->containerForRepaint(); + if (!repaintContainer) + repaintContainer = m_renderView; + + layer->repaintIncludingNonCompositingDescendants(repaintContainer); + if (repaintContainer == m_renderView) { + // The contents of this layer may be moving between the window + // and a GraphicsLayer, so we need to make sure the window system + // synchronizes those changes on the screen. + m_renderView->frameView()->setNeedsOneShotDrawingSynchronization(); + } +} + +// The bounds of the GraphicsLayer created for a compositing layer is the union of the bounds of all the descendant +// RenderLayers that are rendered by the composited RenderLayer. +IntRect RenderLayerCompositor::calculateCompositedBounds(const RenderLayer* layer, const RenderLayer* ancestorLayer) +{ + if (!canBeComposited(layer)) + return IntRect(); + + IntRect boundingBoxRect = layer->localBoundingBox(); + if (layer->renderer()->isRoot()) { + // If the root layer becomes composited (e.g. because some descendant with negative z-index is composited), + // then it has to be big enough to cover the viewport in order to display the background. This is akin + // to the code in RenderBox::paintRootBoxDecorations(). + if (m_renderView->frameView()) { + int rw = m_renderView->frameView()->contentsWidth(); + int rh = m_renderView->frameView()->contentsHeight(); + + boundingBoxRect.setWidth(max(boundingBoxRect.width(), rw - boundingBoxRect.x())); + boundingBoxRect.setHeight(max(boundingBoxRect.height(), rh - boundingBoxRect.y())); + } + } + + IntRect unionBounds = boundingBoxRect; + + if (layer->renderer()->hasOverflowClip() || layer->renderer()->hasMask()) { + int ancestorRelX = 0, ancestorRelY = 0; + layer->convertToLayerCoords(ancestorLayer, ancestorRelX, ancestorRelY); + boundingBoxRect.move(ancestorRelX, ancestorRelY); + return boundingBoxRect; + } + + if (RenderLayer* reflection = layer->reflectionLayer()) { + if (!reflection->isComposited()) { + IntRect childUnionBounds = calculateCompositedBounds(reflection, layer); + unionBounds.unite(childUnionBounds); + } + } + + ASSERT(layer->isStackingContext() || (!layer->m_posZOrderList || layer->m_posZOrderList->size() == 0)); + + if (Vector<RenderLayer*>* negZOrderList = layer->negZOrderList()) { + size_t listSize = negZOrderList->size(); + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = negZOrderList->at(i); + if (!curLayer->isComposited()) { + IntRect childUnionBounds = calculateCompositedBounds(curLayer, layer); + unionBounds.unite(childUnionBounds); + } + } + } + + if (Vector<RenderLayer*>* posZOrderList = layer->posZOrderList()) { + size_t listSize = posZOrderList->size(); + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = posZOrderList->at(i); + if (!curLayer->isComposited()) { + IntRect childUnionBounds = calculateCompositedBounds(curLayer, layer); + unionBounds.unite(childUnionBounds); + } + } + } + + if (Vector<RenderLayer*>* normalFlowList = layer->normalFlowList()) { + size_t listSize = normalFlowList->size(); + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = normalFlowList->at(i); + if (!curLayer->isComposited()) { + IntRect curAbsBounds = calculateCompositedBounds(curLayer, layer); + unionBounds.unite(curAbsBounds); + } + } + } + + if (layer->paintsWithTransform(PaintBehaviorNormal)) { + TransformationMatrix* affineTrans = layer->transform(); + boundingBoxRect = affineTrans->mapRect(boundingBoxRect); + unionBounds = affineTrans->mapRect(unionBounds); + } + + int ancestorRelX = 0, ancestorRelY = 0; + layer->convertToLayerCoords(ancestorLayer, ancestorRelX, ancestorRelY); + unionBounds.move(ancestorRelX, ancestorRelY); + + return unionBounds; +} + +void RenderLayerCompositor::layerWasAdded(RenderLayer* /*parent*/, RenderLayer* /*child*/) +{ + setCompositingLayersNeedRebuild(); +} + +void RenderLayerCompositor::layerWillBeRemoved(RenderLayer* parent, RenderLayer* child) +{ + if (!child->isComposited() || parent->renderer()->documentBeingDestroyed()) + return; + + setCompositingParent(child, 0); + + RenderLayer* compLayer = parent->enclosingCompositingLayer(); + if (compLayer) { + ASSERT(compLayer->backing()); + IntRect compBounds = child->backing()->compositedBounds(); + + int offsetX = 0, offsetY = 0; + child->convertToLayerCoords(compLayer, offsetX, offsetY); + compBounds.move(offsetX, offsetY); + + compLayer->setBackingNeedsRepaintInRect(compBounds); + + // The contents of this layer may be moving from a GraphicsLayer to the window, + // so we need to make sure the window system synchronizes those changes on the screen. + m_renderView->frameView()->setNeedsOneShotDrawingSynchronization(); + } + + setCompositingLayersNeedRebuild(); +} + +RenderLayer* RenderLayerCompositor::enclosingNonStackingClippingLayer(const RenderLayer* layer) const +{ + for (RenderLayer* curr = layer->parent(); curr != 0; curr = curr->parent()) { + if (curr->isStackingContext()) + return 0; + + if (curr->renderer()->hasOverflowClip() || curr->renderer()->hasClip()) + return curr; + } + return 0; +} + +void RenderLayerCompositor::addToOverlapMap(OverlapMap& overlapMap, RenderLayer* layer, IntRect& layerBounds, bool& boundsComputed) +{ + if (layer->isRootLayer()) + return; + + if (!boundsComputed) { + layerBounds = layer->renderer()->localToAbsoluteQuad(FloatRect(layer->localBoundingBox())).enclosingBoundingBox(); + // Empty rects never intersect, but we need them to for the purposes of overlap testing. + if (layerBounds.isEmpty()) + layerBounds.setSize(IntSize(1, 1)); + boundsComputed = true; + } + + overlapMap.add(layer, layerBounds); +} + +bool RenderLayerCompositor::overlapsCompositedLayers(OverlapMap& overlapMap, const IntRect& layerBounds) +{ + RenderLayerCompositor::OverlapMap::const_iterator end = overlapMap.end(); + for (RenderLayerCompositor::OverlapMap::const_iterator it = overlapMap.begin(); it != end; ++it) { + const IntRect& bounds = it->second; + if (layerBounds.intersects(bounds)) { +#if ENABLE(COMPOSITED_FIXED_ELEMENTS) + RenderLayer* intersectedLayer = it->first; + if (intersectedLayer && intersectedLayer->isFixed()) { + if (bounds.contains(layerBounds)) { + continue; + } + } +#endif + return true; + } + } + + return false; +} + +// Recurse through the layers in z-index and overflow order (which is equivalent to painting order) +// For the z-order children of a compositing layer: +// If a child layers has a compositing layer, then all subsequent layers must +// be compositing in order to render above that layer. +// +// If a child in the negative z-order list is compositing, then the layer itself +// must be compositing so that its contents render over that child. +// This implies that its positive z-index children must also be compositing. +// +void RenderLayerCompositor::computeCompositingRequirements(RenderLayer* layer, OverlapMap* overlapMap, struct CompositingState& compositingState, bool& layersChanged) +{ + layer->updateLayerPosition(); + layer->updateZOrderLists(); + layer->updateNormalFlowList(); + + // Clear the flag + layer->setHasCompositingDescendant(false); + + bool mustOverlapCompositedLayers = compositingState.m_subtreeIsCompositing; + + bool haveComputedBounds = false; + IntRect absBounds; + if (overlapMap && !overlapMap->isEmpty()) { + // If we're testing for overlap, we only need to composite if we overlap something that is already composited. + absBounds = layer->renderer()->localToAbsoluteQuad(FloatRect(layer->localBoundingBox())).enclosingBoundingBox(); + // Empty rects never intersect, but we need them to for the purposes of overlap testing. + if (absBounds.isEmpty()) + absBounds.setSize(IntSize(1, 1)); + haveComputedBounds = true; + mustOverlapCompositedLayers = overlapsCompositedLayers(*overlapMap, absBounds); + } + + layer->setMustOverlapCompositedLayers(mustOverlapCompositedLayers); + + // The children of this layer don't need to composite, unless there is + // a compositing layer among them, so start by inheriting the compositing + // ancestor with m_subtreeIsCompositing set to false. + CompositingState childState(compositingState.m_compositingAncestor); +#ifndef NDEBUG + ++childState.m_depth; +#endif + + bool willBeComposited = needsToBeComposited(layer); + +#if ENABLE(COMPOSITED_FIXED_ELEMENTS) + // If we are a fixed layer, signal it to our siblings + if (willBeComposited && layer->isFixed()) + compositingState.m_fixedSibling = true; + + if (!willBeComposited && compositingState.m_fixedSibling) { + layer->setMustOverlapCompositedLayers(true); + willBeComposited = true; + } +#endif + if (willBeComposited) { + // Tell the parent it has compositing descendants. + compositingState.m_subtreeIsCompositing = true; + // This layer now acts as the ancestor for kids. + childState.m_compositingAncestor = layer; + if (overlapMap) + addToOverlapMap(*overlapMap, layer, absBounds, haveComputedBounds); + } + +#if ENABLE(VIDEO) + // Video is special. It's a replaced element with a content layer, but has shadow content + // for the controller that must render in front. Without this, the controls fail to show + // when the video element is a stacking context (e.g. due to opacity or transform). + if (willBeComposited && layer->renderer()->isVideo()) + childState.m_subtreeIsCompositing = true; +#endif + + if (layer->isStackingContext()) { + ASSERT(!layer->m_zOrderListsDirty); + if (Vector<RenderLayer*>* negZOrderList = layer->negZOrderList()) { + size_t listSize = negZOrderList->size(); +#if ENABLE(COMPOSITED_FIXED_ELEMENTS) + childState.m_fixedSibling = false; + + // For the negative z-order, if we have a fixed layer + // we need to make all the siblings composited layers. + // Otherwise a negative layer (below the fixed layer) could + // still be drawn onto a higher z-order layer (e.g. the body) + // if not immediately intersecting with our fixed layer. + // So it's not enough here to only set m_fixedSibling for + // subsequent siblings as we do for the normal flow + // and positive z-order. + for (size_t j = 0; j < listSize; ++j) { + if ((negZOrderList->at(j))->isFixed() && + needsToBeComposited(negZOrderList->at(j))) { + childState.m_fixedSibling = true; + break; + } + } +#endif + + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = negZOrderList->at(i); + computeCompositingRequirements(curLayer, overlapMap, childState, layersChanged); + + // If we have to make a layer for this child, make one now so we can have a contents layer + // (since we need to ensure that the -ve z-order child renders underneath our contents). + if (!willBeComposited && childState.m_subtreeIsCompositing) { + // make layer compositing + layer->setMustOverlapCompositedLayers(true); + childState.m_compositingAncestor = layer; + if (overlapMap) + addToOverlapMap(*overlapMap, layer, absBounds, haveComputedBounds); + willBeComposited = true; + } + } + } + } + + ASSERT(!layer->m_normalFlowListDirty); + if (Vector<RenderLayer*>* normalFlowList = layer->normalFlowList()) { + size_t listSize = normalFlowList->size(); +#if ENABLE(COMPOSITED_FIXED_ELEMENTS) + childState.m_fixedSibling = false; +#endif + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = normalFlowList->at(i); + computeCompositingRequirements(curLayer, overlapMap, childState, layersChanged); + } + } + + if (layer->isStackingContext()) { + if (Vector<RenderLayer*>* posZOrderList = layer->posZOrderList()) { + size_t listSize = posZOrderList->size(); +#if ENABLE(COMPOSITED_FIXED_ELEMENTS) + childState.m_fixedSibling = false; +#endif + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = posZOrderList->at(i); + computeCompositingRequirements(curLayer, overlapMap, childState, layersChanged); + } + } + } + + // If we just entered compositing mode, the root will have become composited (as long as accelerated compositing is enabled). + if (layer->isRootLayer()) { + if (inCompositingMode() && m_hasAcceleratedCompositing) + willBeComposited = true; + } + + ASSERT(willBeComposited == needsToBeComposited(layer)); + + // If we have a software transform, and we have layers under us, we need to also + // be composited. Also, if we have opacity < 1, then we need to be a layer so that + // the child layers are opaque, then rendered with opacity on this layer. + if (!willBeComposited && canBeComposited(layer) && childState.m_subtreeIsCompositing && requiresCompositingWhenDescendantsAreCompositing(layer->renderer())) { + layer->setMustOverlapCompositedLayers(true); + if (overlapMap) + addToOverlapMap(*overlapMap, layer, absBounds, haveComputedBounds); + willBeComposited = true; + } + + ASSERT(willBeComposited == needsToBeComposited(layer)); + if (layer->reflectionLayer()) + layer->reflectionLayer()->setMustOverlapCompositedLayers(willBeComposited); + + // Subsequent layers in the parent stacking context also need to composite. + if (childState.m_subtreeIsCompositing) + compositingState.m_subtreeIsCompositing = true; + + // Set the flag to say that this SC has compositing children. + layer->setHasCompositingDescendant(childState.m_subtreeIsCompositing); + + // setHasCompositingDescendant() may have changed the answer to needsToBeComposited() when clipping, + // so test that again. + if (!willBeComposited && canBeComposited(layer) && clipsCompositingDescendants(layer)) { + if (overlapMap) + addToOverlapMap(*overlapMap, layer, absBounds, haveComputedBounds); + willBeComposited = true; + } + + // If we're back at the root, and no other layers need to be composited, and the root layer itself doesn't need + // to be composited, then we can drop out of compositing mode altogether. + if (layer->isRootLayer() && !childState.m_subtreeIsCompositing && !requiresCompositingLayer(layer)) { + enableCompositingMode(false); + willBeComposited = false; + } + + // If the layer is going into compositing mode, repaint its old location. + ASSERT(willBeComposited == needsToBeComposited(layer)); + if (!layer->isComposited() && willBeComposited) + repaintOnCompositingChange(layer); + + // Update backing now, so that we can use isComposited() reliably during tree traversal in rebuildCompositingLayerTree(). + if (updateBacking(layer, CompositingChangeRepaintNow)) + layersChanged = true; + + if (layer->reflectionLayer() && updateLayerCompositingState(layer->reflectionLayer(), CompositingChangeRepaintNow)) + layersChanged = true; +} + +void RenderLayerCompositor::setCompositingParent(RenderLayer* childLayer, RenderLayer* parentLayer) +{ + ASSERT(!parentLayer || childLayer->ancestorCompositingLayer() == parentLayer); + ASSERT(childLayer->isComposited()); + + // It's possible to be called with a parent that isn't yet composited when we're doing + // partial updates as required by painting or hit testing. Just bail in that case; + // we'll do a full layer update soon. + if (!parentLayer || !parentLayer->isComposited()) + return; + + if (parentLayer) { + GraphicsLayer* hostingLayer = parentLayer->backing()->parentForSublayers(); + GraphicsLayer* hostedLayer = childLayer->backing()->childForSuperlayers(); + + hostingLayer->addChild(hostedLayer); + } else + childLayer->backing()->childForSuperlayers()->removeFromParent(); +} + +void RenderLayerCompositor::removeCompositedChildren(RenderLayer* layer) +{ + ASSERT(layer->isComposited()); + + GraphicsLayer* hostingLayer = layer->backing()->parentForSublayers(); + hostingLayer->removeAllChildren(); +} + +#if ENABLE(VIDEO) +bool RenderLayerCompositor::canAccelerateVideoRendering(RenderVideo* o) const +{ + if (!m_hasAcceleratedCompositing) + return false; + + return o->supportsAcceleratedRendering(); +} +#endif + +void RenderLayerCompositor::rebuildCompositingLayerTree(RenderLayer* layer, const CompositingState& compositingState, Vector<GraphicsLayer*>& childLayersOfEnclosingLayer) +{ + // Make the layer compositing if necessary, and set up clipping and content layers. + // Note that we can only do work here that is independent of whether the descendant layers + // have been processed. computeCompositingRequirements() will already have done the repaint if necessary. + + RenderLayerBacking* layerBacking = layer->backing(); + if (layerBacking) { + // The compositing state of all our children has been updated already, so now + // we can compute and cache the composited bounds for this layer. + layerBacking->updateCompositedBounds(); + + if (RenderLayer* reflection = layer->reflectionLayer()) { + if (reflection->backing()) + reflection->backing()->updateCompositedBounds(); + } + + layerBacking->updateGraphicsLayerConfiguration(); + layerBacking->updateGraphicsLayerGeometry(); + + if (!layer->parent()) + updateRootLayerPosition(); + } + + // If this layer has backing, then we are collecting its children, otherwise appending + // to the compositing child list of an enclosing layer. + Vector<GraphicsLayer*> layerChildren; + Vector<GraphicsLayer*>& childList = layerBacking ? layerChildren : childLayersOfEnclosingLayer; + + CompositingState childState = compositingState; + if (layer->isComposited()) + childState.m_compositingAncestor = layer; + +#ifndef NDEBUG + ++childState.m_depth; +#endif + + // The children of this stacking context don't need to composite, unless there is + // a compositing layer among them, so start by assuming false. + childState.m_subtreeIsCompositing = false; + + if (layer->isStackingContext()) { + ASSERT(!layer->m_zOrderListsDirty); + + if (Vector<RenderLayer*>* negZOrderList = layer->negZOrderList()) { + size_t listSize = negZOrderList->size(); + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = negZOrderList->at(i); + rebuildCompositingLayerTree(curLayer, childState, childList); + } + } + + // If a negative z-order child is compositing, we get a foreground layer which needs to get parented. + if (layerBacking && layerBacking->foregroundLayer()) + childList.append(layerBacking->foregroundLayer()); + } + + ASSERT(!layer->m_normalFlowListDirty); + if (Vector<RenderLayer*>* normalFlowList = layer->normalFlowList()) { + size_t listSize = normalFlowList->size(); + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = normalFlowList->at(i); + rebuildCompositingLayerTree(curLayer, childState, childList); + } + } + + if (layer->isStackingContext()) { + if (Vector<RenderLayer*>* posZOrderList = layer->posZOrderList()) { + size_t listSize = posZOrderList->size(); + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = posZOrderList->at(i); + rebuildCompositingLayerTree(curLayer, childState, childList); + } + } + } + + if (layerBacking) { + bool parented = false; + if (layer->renderer()->isRenderIFrame()) + parented = parentIFrameContentLayers(toRenderIFrame(layer->renderer())); + + if (!parented) + layerBacking->parentForSublayers()->setChildren(layerChildren); + +#if ENABLE(FULLSCREEN_API) + // For the sake of clients of the full screen renderer, don't reparent + // the full screen layer out from under them if they're in the middle of + // animating. + if (layer->renderer()->isRenderFullScreen() && toRenderFullScreen(layer->renderer())->isAnimating()) + return; +#endif + childLayersOfEnclosingLayer.append(layerBacking->childForSuperlayers()); + } +} + +void RenderLayerCompositor::frameViewDidChangeSize(const IntPoint& contentsOffset) +{ + if (m_clipLayer) { + FrameView* frameView = m_renderView->frameView(); + m_clipLayer->setPosition(contentsOffset); + m_clipLayer->setSize(FloatSize(frameView->layoutWidth(), frameView->layoutHeight())); + + IntPoint scrollPosition = frameView->scrollPosition(); + m_scrollLayer->setPosition(FloatPoint(-scrollPosition.x(), -scrollPosition.y())); + } +} + +void RenderLayerCompositor::frameViewDidScroll(const IntPoint& scrollPosition) +{ + if (m_scrollLayer) + m_scrollLayer->setPosition(FloatPoint(-scrollPosition.x(), -scrollPosition.y())); +} + +String RenderLayerCompositor::layerTreeAsText() +{ + if (compositingLayerUpdatePending()) + updateCompositingLayers(); + + if (!m_rootPlatformLayer) + return String(); + + // We skip dumping the scroll and clip layers to keep layerTreeAsText output + // similar between platforms. + return m_rootPlatformLayer->layerTreeAsText(); +} + +RenderLayerCompositor* RenderLayerCompositor::iframeContentsCompositor(RenderIFrame* renderer) +{ + HTMLIFrameElement* element = static_cast<HTMLIFrameElement*>(renderer->node()); + if (Document* contentDocument = element->contentDocument()) { + if (RenderView* view = contentDocument->renderView()) + return view->compositor(); + } + return 0; +} + +bool RenderLayerCompositor::parentIFrameContentLayers(RenderIFrame* renderer) +{ + RenderLayerCompositor* innerCompositor = iframeContentsCompositor(renderer); + if (!innerCompositor || !innerCompositor->inCompositingMode() || innerCompositor->rootLayerAttachment() != RootLayerAttachedViaEnclosingIframe) + return false; + + RenderLayer* layer = renderer->layer(); + if (!layer->isComposited()) + return false; + + RenderLayerBacking* backing = layer->backing(); + GraphicsLayer* hostingLayer = backing->parentForSublayers(); + GraphicsLayer* rootLayer = innerCompositor->rootPlatformLayer(); + if (hostingLayer->children().size() != 1 || hostingLayer->children()[0] != rootLayer) { + hostingLayer->removeAllChildren(); + hostingLayer->addChild(rootLayer); + } + return true; +} + +// This just updates layer geometry without changing the hierarchy. +void RenderLayerCompositor::updateLayerTreeGeometry(RenderLayer* layer) +{ + if (RenderLayerBacking* layerBacking = layer->backing()) { + // The compositing state of all our children has been updated already, so now + // we can compute and cache the composited bounds for this layer. + layerBacking->updateCompositedBounds(); + + if (RenderLayer* reflection = layer->reflectionLayer()) { + if (reflection->backing()) + reflection->backing()->updateCompositedBounds(); + } + + layerBacking->updateGraphicsLayerConfiguration(); + layerBacking->updateGraphicsLayerGeometry(); + + if (!layer->parent()) + updateRootLayerPosition(); + } + + if (layer->isStackingContext()) { + ASSERT(!layer->m_zOrderListsDirty); + + if (Vector<RenderLayer*>* negZOrderList = layer->negZOrderList()) { + size_t listSize = negZOrderList->size(); + for (size_t i = 0; i < listSize; ++i) + updateLayerTreeGeometry(negZOrderList->at(i)); + } + } + + ASSERT(!layer->m_normalFlowListDirty); + if (Vector<RenderLayer*>* normalFlowList = layer->normalFlowList()) { + size_t listSize = normalFlowList->size(); + for (size_t i = 0; i < listSize; ++i) + updateLayerTreeGeometry(normalFlowList->at(i)); + } + + if (layer->isStackingContext()) { + if (Vector<RenderLayer*>* posZOrderList = layer->posZOrderList()) { + size_t listSize = posZOrderList->size(); + for (size_t i = 0; i < listSize; ++i) + updateLayerTreeGeometry(posZOrderList->at(i)); + } + } +} + +// Recurs down the RenderLayer tree until its finds the compositing descendants of compositingAncestor and updates their geometry. +void RenderLayerCompositor::updateCompositingDescendantGeometry(RenderLayer* compositingAncestor, RenderLayer* layer, RenderLayerBacking::UpdateDepth updateDepth) +{ + if (layer != compositingAncestor) { + if (RenderLayerBacking* layerBacking = layer->backing()) { + layerBacking->updateCompositedBounds(); + + if (RenderLayer* reflection = layer->reflectionLayer()) { + if (reflection->backing()) + reflection->backing()->updateCompositedBounds(); + } + + layerBacking->updateGraphicsLayerGeometry(); + if (updateDepth == RenderLayerBacking::CompositingChildren) + return; + } + } + + if (layer->reflectionLayer()) + updateCompositingDescendantGeometry(compositingAncestor, layer->reflectionLayer(), updateDepth); + + if (!layer->hasCompositingDescendant()) + return; + + if (layer->isStackingContext()) { + if (Vector<RenderLayer*>* negZOrderList = layer->negZOrderList()) { + size_t listSize = negZOrderList->size(); + for (size_t i = 0; i < listSize; ++i) + updateCompositingDescendantGeometry(compositingAncestor, negZOrderList->at(i), updateDepth); + } + } + + if (Vector<RenderLayer*>* normalFlowList = layer->normalFlowList()) { + size_t listSize = normalFlowList->size(); + for (size_t i = 0; i < listSize; ++i) + updateCompositingDescendantGeometry(compositingAncestor, normalFlowList->at(i), updateDepth); + } + + if (layer->isStackingContext()) { + if (Vector<RenderLayer*>* posZOrderList = layer->posZOrderList()) { + size_t listSize = posZOrderList->size(); + for (size_t i = 0; i < listSize; ++i) + updateCompositingDescendantGeometry(compositingAncestor, posZOrderList->at(i), updateDepth); + } + } +} + + +void RenderLayerCompositor::repaintCompositedLayersAbsoluteRect(const IntRect& absRect) +{ + recursiveRepaintLayerRect(rootRenderLayer(), absRect); +} + +void RenderLayerCompositor::recursiveRepaintLayerRect(RenderLayer* layer, const IntRect& rect) +{ + // FIXME: This method does not work correctly with transforms. + if (layer->isComposited()) + layer->setBackingNeedsRepaintInRect(rect); + + if (layer->hasCompositingDescendant()) { + if (Vector<RenderLayer*>* negZOrderList = layer->negZOrderList()) { + size_t listSize = negZOrderList->size(); + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = negZOrderList->at(i); + int x = 0; + int y = 0; + curLayer->convertToLayerCoords(layer, x, y); + IntRect childRect(rect); + childRect.move(-x, -y); + recursiveRepaintLayerRect(curLayer, childRect); + } + } + + if (Vector<RenderLayer*>* posZOrderList = layer->posZOrderList()) { + size_t listSize = posZOrderList->size(); + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = posZOrderList->at(i); + int x = 0; + int y = 0; + curLayer->convertToLayerCoords(layer, x, y); + IntRect childRect(rect); + childRect.move(-x, -y); + recursiveRepaintLayerRect(curLayer, childRect); + } + } + } + if (Vector<RenderLayer*>* normalFlowList = layer->normalFlowList()) { + size_t listSize = normalFlowList->size(); + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = normalFlowList->at(i); + int x = 0; + int y = 0; + curLayer->convertToLayerCoords(layer, x, y); + IntRect childRect(rect); + childRect.move(-x, -y); + recursiveRepaintLayerRect(curLayer, childRect); + } + } +} + +RenderLayer* RenderLayerCompositor::rootRenderLayer() const +{ + return m_renderView->layer(); +} + +GraphicsLayer* RenderLayerCompositor::rootPlatformLayer() const +{ + return m_clipLayer ? m_clipLayer.get() : m_rootPlatformLayer.get(); +} + +void RenderLayerCompositor::didMoveOnscreen() +{ + if (!inCompositingMode() || m_rootLayerAttachment != RootLayerUnattached) + return; + + RootLayerAttachment attachment = shouldPropagateCompositingToEnclosingIFrame() ? RootLayerAttachedViaEnclosingIframe : RootLayerAttachedViaChromeClient; + attachRootPlatformLayer(attachment); +} + +void RenderLayerCompositor::willMoveOffscreen() +{ + if (!inCompositingMode() || m_rootLayerAttachment == RootLayerUnattached) + return; + + detachRootPlatformLayer(); +} + +void RenderLayerCompositor::updateRootLayerPosition() +{ + if (m_rootPlatformLayer) { + m_rootPlatformLayer->setSize(FloatSize(m_renderView->docWidth(), m_renderView->docHeight())); + m_rootPlatformLayer->setPosition(FloatPoint(m_renderView->docLeft(), m_renderView->docTop())); + } +} + +void RenderLayerCompositor::didStartAcceleratedAnimation(CSSPropertyID property) +{ + // If an accelerated animation or transition runs, we have to turn off overlap checking because + // we don't do layout for every frame, but we have to ensure that the layering is + // correct between the animating object and other objects on the page. + if (property == CSSPropertyWebkitTransform) + setCompositingConsultsOverlap(false); +} + +bool RenderLayerCompositor::has3DContent() const +{ + return layerHas3DContent(rootRenderLayer()); +} + +bool RenderLayerCompositor::allowsIndependentlyCompositedIFrames(const FrameView* view) +{ +#if PLATFORM(MAC) + // iframes are only independently composited in Mac pre-WebKit2. + return view->platformWidget(); +#endif + return false; +} + +bool RenderLayerCompositor::shouldPropagateCompositingToEnclosingIFrame() const +{ +#if PLATFORM(ANDROID) + if (enclosingIFrameElement() && !allowsIndependentlyCompositedIFrames(m_renderView->frameView())) + return true; +#endif + // Parent document content needs to be able to render on top of a composited iframe, so correct behavior + // is to have the parent document become composited too. However, this can cause problems on platforms that + // use native views for frames (like Mac), so disable that behavior on those platforms for now. + HTMLFrameOwnerElement* ownerElement = enclosingIFrameElement(); + RenderObject* renderer = ownerElement ? ownerElement->renderer() : 0; + if (!renderer || !renderer->isRenderIFrame()) + return false; + + if (!allowsIndependentlyCompositedIFrames(m_renderView->frameView())) + return true; + + // On Mac, only propagate compositing if the iframe is overlapped in the parent + // document, or the parent is already compositing. + RenderIFrame* iframeRenderer = toRenderIFrame(renderer); + if (iframeRenderer->widget()) { + ASSERT(iframeRenderer->widget()->isFrameView()); + FrameView* view = static_cast<FrameView*>(iframeRenderer->widget()); + if (view->isOverlappedIncludingAncestors() || view->hasCompositingAncestor()) + return true; + } + + return false; +} + +HTMLFrameOwnerElement* RenderLayerCompositor::enclosingIFrameElement() const +{ + if (HTMLFrameOwnerElement* ownerElement = m_renderView->document()->ownerElement()) + return ownerElement->hasTagName(iframeTag) ? ownerElement : 0; + + return 0; +} + +bool RenderLayerCompositor::needsToBeComposited(const RenderLayer* layer) const +{ + if (!canBeComposited(layer)) + return false; + + // The root layer always has a compositing layer, but it may not have backing. + return requiresCompositingLayer(layer) || layer->mustOverlapCompositedLayers() || (inCompositingMode() && layer->isRootLayer()); +} + +#if PLATFORM(ANDROID) +bool RenderLayerCompositor::requiresCompositingForAndroidLayers(const RenderLayer* layer) const +{ +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + if (layer->hasOverflowScroll()) + return true; + if (layer->isRootLayer() && m_renderView->frameView()->hasOverflowScroll()) + return true; +#endif +#if ENABLE(COMPOSITED_FIXED_ELEMENTS) + + // Enable composited layers (for fixed elements) + if (layer->isFixed()) + return true; +#endif + return false; +} +#endif + +// Note: this specifies whether the RL needs a compositing layer for intrinsic reasons. +// Use needsToBeComposited() to determine if a RL actually needs a compositing layer. +// static +bool RenderLayerCompositor::requiresCompositingLayer(const RenderLayer* layer) const +{ + RenderObject* renderer = layer->renderer(); + // The compositing state of a reflection should match that of its reflected layer. + if (layer->isReflection()) { + renderer = renderer->parent(); // The RenderReplica's parent is the object being reflected. + layer = toRenderBoxModelObject(renderer)->layer(); + } + return requiresCompositingForTransform(renderer) +#if PLATFORM(ANDROID) + || requiresCompositingForAndroidLayers(layer) +#endif + || requiresCompositingForVideo(renderer) + || requiresCompositingForCanvas(renderer) + || requiresCompositingForPlugin(renderer) + || requiresCompositingForIFrame(renderer) + || (canRender3DTransforms() && renderer->style()->backfaceVisibility() == BackfaceVisibilityHidden) + || clipsCompositingDescendants(layer) + || requiresCompositingForAnimation(renderer) + || requiresCompositingForFullScreen(renderer); +} + +bool RenderLayerCompositor::canBeComposited(const RenderLayer* layer) const +{ + return m_hasAcceleratedCompositing && layer->isSelfPaintingLayer(); +} + +// Return true if the given layer has some ancestor in the RenderLayer hierarchy that clips, +// up to the enclosing compositing ancestor. This is required because compositing layers are parented +// according to the z-order hierarchy, yet clipping goes down the renderer hierarchy. +// Thus, a RenderLayer can be clipped by a RenderLayer that is an ancestor in the renderer hierarchy, +// but a sibling in the z-order hierarchy. +bool RenderLayerCompositor::clippedByAncestor(RenderLayer* layer) const +{ + if (!layer->isComposited() || !layer->parent()) + return false; + + RenderLayer* compositingAncestor = layer->ancestorCompositingLayer(); + if (!compositingAncestor) + return false; + + // If the compositingAncestor clips, that will be taken care of by clipsCompositingDescendants(), + // so we only care about clipping between its first child that is our ancestor (the computeClipRoot), + // and layer. + RenderLayer* computeClipRoot = 0; + RenderLayer* curr = layer; + while (curr) { + RenderLayer* next = curr->parent(); + if (next == compositingAncestor) { + computeClipRoot = curr; + break; + } + curr = next; + } + + if (!computeClipRoot || computeClipRoot == layer) + return false; + + IntRect backgroundRect = layer->backgroundClipRect(computeClipRoot, true); + return backgroundRect != PaintInfo::infiniteRect(); +} + +// Return true if the given layer is a stacking context and has compositing child +// layers that it needs to clip. In this case we insert a clipping GraphicsLayer +// into the hierarchy between this layer and its children in the z-order hierarchy. +bool RenderLayerCompositor::clipsCompositingDescendants(const RenderLayer* layer) const +{ +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + if (layer->hasOverflowScroll()) + return false; +#endif + return layer->hasCompositingDescendant() && + (layer->renderer()->hasOverflowClip() || layer->renderer()->hasClip()); +} + +bool RenderLayerCompositor::requiresCompositingForTransform(RenderObject* renderer) const +{ + if (!(m_compositingTriggers & ChromeClient::ThreeDTransformTrigger)) + return false; + + RenderStyle* style = renderer->style(); + // Note that we ask the renderer if it has a transform, because the style may have transforms, + // but the renderer may be an inline that doesn't suppport them. + return renderer->hasTransform() && (style->transform().has3DOperation() || style->transformStyle3D() == TransformStyle3DPreserve3D || style->hasPerspective()); +} + +bool RenderLayerCompositor::requiresCompositingForVideo(RenderObject* renderer) const +{ + if (!(m_compositingTriggers & ChromeClient::VideoTrigger)) + return false; +#if ENABLE(VIDEO) + if (renderer->isVideo()) { + RenderVideo* video = toRenderVideo(renderer); + return video->shouldDisplayVideo() && canAccelerateVideoRendering(video); + } +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + else if (renderer->isRenderPart()) { + if (!m_hasAcceleratedCompositing) + return false; + + Node* node = renderer->node(); + if (!node || (!node->hasTagName(HTMLNames::videoTag) && !node->hasTagName(HTMLNames::audioTag))) + return false; + + HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(node); + return mediaElement->player() ? mediaElement->player()->supportsAcceleratedRendering() : false; + } +#endif // ENABLE(PLUGIN_PROXY_FOR_VIDEO) +#else + UNUSED_PARAM(renderer); +#endif + return false; +} + +bool RenderLayerCompositor::requiresCompositingForCanvas(RenderObject* renderer) const +{ + if (!(m_compositingTriggers & ChromeClient::CanvasTrigger)) + return false; + + if (renderer->isCanvas()) { + HTMLCanvasElement* canvas = static_cast<HTMLCanvasElement*>(renderer->node()); + return canvas->renderingContext() && canvas->renderingContext()->isAccelerated(); + } + return false; +} + +bool RenderLayerCompositor::requiresCompositingForPlugin(RenderObject* renderer) const +{ + if (!(m_compositingTriggers & ChromeClient::PluginTrigger)) + return false; + + bool composite = (renderer->isEmbeddedObject() && toRenderEmbeddedObject(renderer)->allowsAcceleratedCompositing()) + || (renderer->isApplet() && toRenderApplet(renderer)->allowsAcceleratedCompositing()); + if (!composite) + return false; + + m_compositingDependsOnGeometry = true; + + RenderWidget* pluginRenderer = toRenderWidget(renderer); + // If we can't reliably know the size of the plugin yet, don't change compositing state. + if (pluginRenderer->needsLayout()) + return pluginRenderer->hasLayer() && pluginRenderer->layer()->isComposited(); + + // Don't go into compositing mode if height or width are zero, or size is 1x1. + IntRect contentBox = pluginRenderer->contentBoxRect(); + return contentBox.height() * contentBox.width() > 1; +} + +bool RenderLayerCompositor::requiresCompositingForIFrame(RenderObject* renderer) const +{ + if (!renderer->isRenderIFrame()) + return false; + + RenderIFrame* iframeRenderer = toRenderIFrame(renderer); + + if (!iframeRenderer->requiresAcceleratedCompositing()) + return false; + + m_compositingDependsOnGeometry = true; + + RenderLayerCompositor* innerCompositor = iframeContentsCompositor(iframeRenderer); + if (!innerCompositor->shouldPropagateCompositingToEnclosingIFrame()) + return false; + + // If we can't reliably know the size of the iframe yet, don't change compositing state. + if (renderer->needsLayout()) + return iframeRenderer->hasLayer() && iframeRenderer->layer()->isComposited(); + + // Don't go into compositing mode if height or width are zero. + IntRect contentBox = iframeRenderer->contentBoxRect(); + return contentBox.height() * contentBox.width() > 0; +} + +bool RenderLayerCompositor::requiresCompositingForAnimation(RenderObject* renderer) const +{ + if (!(m_compositingTriggers & ChromeClient::AnimationTrigger)) + return false; + + if (AnimationController* animController = renderer->animation()) { + return (animController->isRunningAnimationOnRenderer(renderer, CSSPropertyOpacity) && inCompositingMode()) + || animController->isRunningAnimationOnRenderer(renderer, CSSPropertyWebkitTransform); + } + return false; +} + +bool RenderLayerCompositor::requiresCompositingWhenDescendantsAreCompositing(RenderObject* renderer) const +{ + return renderer->hasTransform() || renderer->isTransparent() || renderer->hasMask() || renderer->hasReflection(); +} + +bool RenderLayerCompositor::requiresCompositingForFullScreen(RenderObject* renderer) const +{ +#if ENABLE(FULLSCREEN_API) + return renderer->isRenderFullScreen() && toRenderFullScreen(renderer)->isAnimating(); +#else + return false; +#endif +} + +// If an element has negative z-index children, those children render in front of the +// layer background, so we need an extra 'contents' layer for the foreground of the layer +// object. +bool RenderLayerCompositor::needsContentsCompositingLayer(const RenderLayer* layer) const +{ + return (layer->m_negZOrderList && layer->m_negZOrderList->size() > 0); +} + +bool RenderLayerCompositor::requiresScrollLayer(RootLayerAttachment attachment) const +{ + // We need to handle our own scrolling if we're: + return !m_renderView->frameView()->platformWidget() // viewless (i.e. non-Mac, or Mac in WebKit2) + || attachment == RootLayerAttachedViaEnclosingIframe; // a composited iframe on Mac +} + +void RenderLayerCompositor::ensureRootPlatformLayer() +{ + RootLayerAttachment expectedAttachment = shouldPropagateCompositingToEnclosingIFrame() ? RootLayerAttachedViaEnclosingIframe : RootLayerAttachedViaChromeClient; + if (expectedAttachment == m_rootLayerAttachment) + return; + + if (!m_rootPlatformLayer) { + m_rootPlatformLayer = GraphicsLayer::create(0); +#ifndef NDEBUG + m_rootPlatformLayer->setName("Root platform"); +#endif + m_rootPlatformLayer->setSize(FloatSize(m_renderView->rightLayoutOverflow(), m_renderView->bottomLayoutOverflow())); + m_rootPlatformLayer->setPosition(FloatPoint()); + + // Need to clip to prevent transformed content showing outside this frame + m_rootPlatformLayer->setMasksToBounds(true); + } + + if (requiresScrollLayer(expectedAttachment)) { + if (!m_clipLayer) { + ASSERT(!m_scrollLayer); + // Create a clipping layer if this is an iframe + m_clipLayer = GraphicsLayer::create(this); +#ifndef NDEBUG + m_clipLayer->setName("iframe Clipping"); +#endif + m_clipLayer->setMasksToBounds(true); + + m_scrollLayer = GraphicsLayer::create(this); +#ifndef NDEBUG + m_scrollLayer->setName("iframe scrolling"); +#endif + // Hook them up + m_clipLayer->addChild(m_scrollLayer.get()); + m_scrollLayer->addChild(m_rootPlatformLayer.get()); + + frameViewDidChangeSize(); + frameViewDidScroll(m_renderView->frameView()->scrollPosition()); + } + } else { + if (m_clipLayer) { + m_clipLayer->removeAllChildren(); + m_clipLayer->removeFromParent(); + m_clipLayer = 0; + + m_scrollLayer->removeAllChildren(); + m_scrollLayer = 0; + } + } + + // Check to see if we have to change the attachment + if (m_rootLayerAttachment != RootLayerUnattached) + detachRootPlatformLayer(); + + attachRootPlatformLayer(expectedAttachment); +} + +void RenderLayerCompositor::destroyRootPlatformLayer() +{ + if (!m_rootPlatformLayer) + return; + + detachRootPlatformLayer(); + if (m_clipLayer) { + m_clipLayer->removeAllChildren(); + m_clipLayer = 0; + + m_scrollLayer->removeAllChildren(); + m_scrollLayer = 0; + } + ASSERT(!m_scrollLayer); + m_rootPlatformLayer = 0; +} + +void RenderLayerCompositor::attachRootPlatformLayer(RootLayerAttachment attachment) +{ + if (!m_rootPlatformLayer) + return; + + switch (attachment) { + case RootLayerUnattached: + ASSERT_NOT_REACHED(); + break; + case RootLayerAttachedViaChromeClient: { + Frame* frame = m_renderView->frameView()->frame(); + Page* page = frame ? frame->page() : 0; + if (!page) + return; + + page->chrome()->client()->attachRootGraphicsLayer(frame, rootPlatformLayer()); + break; + } + case RootLayerAttachedViaEnclosingIframe: { + // The layer will get hooked up via RenderLayerBacking::updateGraphicsLayerConfiguration() + // for the iframe's renderer in the parent document. + scheduleNeedsStyleRecalc(m_renderView->document()->ownerElement()); + break; + } + } + + m_rootLayerAttachment = attachment; + rootLayerAttachmentChanged(); +} + +void RenderLayerCompositor::detachRootPlatformLayer() +{ + if (!m_rootPlatformLayer || m_rootLayerAttachment == RootLayerUnattached) + return; + + switch (m_rootLayerAttachment) { + case RootLayerAttachedViaEnclosingIframe: { + // The layer will get unhooked up via RenderLayerBacking::updateGraphicsLayerConfiguration() + // for the iframe's renderer in the parent document. + if (m_clipLayer) + m_clipLayer->removeFromParent(); + else + m_rootPlatformLayer->removeFromParent(); + + if (HTMLFrameOwnerElement* ownerElement = m_renderView->document()->ownerElement()) + scheduleNeedsStyleRecalc(ownerElement); + break; + } + case RootLayerAttachedViaChromeClient: { + Frame* frame = m_renderView->frameView()->frame(); + Page* page = frame ? frame->page() : 0; + if (!page) + return; + + page->chrome()->client()->attachRootGraphicsLayer(frame, 0); + } + break; + case RootLayerUnattached: + break; + } + + m_rootLayerAttachment = RootLayerUnattached; + rootLayerAttachmentChanged(); +} + +void RenderLayerCompositor::updateRootLayerAttachment() +{ + ensureRootPlatformLayer(); +} + +void RenderLayerCompositor::rootLayerAttachmentChanged() +{ + // The attachment can affect whether the RenderView layer's paintingGoesToWindow() behavior, + // so call updateGraphicsLayerGeometry() to udpate that. + RenderLayer* layer = m_renderView->layer(); + if (RenderLayerBacking* backing = layer ? layer->backing() : 0) + backing->updateDrawsContent(); +} + +static void needsStyleRecalcCallback(Node* node) +{ + node->setNeedsStyleRecalc(SyntheticStyleChange); +} + +void RenderLayerCompositor::scheduleNeedsStyleRecalc(Element* element) +{ + if (ContainerNode::postAttachCallbacksAreSuspended()) + ContainerNode::queuePostAttachCallback(needsStyleRecalcCallback, element); + else + element->setNeedsStyleRecalc(SyntheticStyleChange); +} + +// IFrames are special, because we hook compositing layers together across iframe boundaries +// when both parent and iframe content are composited. So when this frame becomes composited, we have +// to use a synthetic style change to get the iframes into RenderLayers in order to allow them to composite. +void RenderLayerCompositor::notifyIFramesOfCompositingChange() +{ + Frame* frame = m_renderView->frameView() ? m_renderView->frameView()->frame() : 0; + if (!frame) + return; + + for (Frame* child = frame->tree()->firstChild(); child; child = child->tree()->traverseNext(frame)) { + if (child->document() && child->document()->ownerElement()) + scheduleNeedsStyleRecalc(child->document()->ownerElement()); + } + + // Compositing also affects the answer to RenderIFrame::requiresAcceleratedCompositing(), so + // we need to schedule a style recalc in our parent document. + if (HTMLFrameOwnerElement* ownerElement = m_renderView->document()->ownerElement()) + scheduleNeedsStyleRecalc(ownerElement); +} + +bool RenderLayerCompositor::layerHas3DContent(const RenderLayer* layer) const +{ + const RenderStyle* style = layer->renderer()->style(); + + if (style && + (style->transformStyle3D() == TransformStyle3DPreserve3D || + style->hasPerspective() || + style->transform().has3DOperation())) + return true; + + if (layer->isStackingContext()) { + if (Vector<RenderLayer*>* negZOrderList = layer->negZOrderList()) { + size_t listSize = negZOrderList->size(); + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = negZOrderList->at(i); + if (layerHas3DContent(curLayer)) + return true; + } + } + + if (Vector<RenderLayer*>* posZOrderList = layer->posZOrderList()) { + size_t listSize = posZOrderList->size(); + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = posZOrderList->at(i); + if (layerHas3DContent(curLayer)) + return true; + } + } + } + + if (Vector<RenderLayer*>* normalFlowList = layer->normalFlowList()) { + size_t listSize = normalFlowList->size(); + for (size_t i = 0; i < listSize; ++i) { + RenderLayer* curLayer = normalFlowList->at(i); + if (layerHas3DContent(curLayer)) + return true; + } + } + return false; +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/Source/WebCore/rendering/RenderLayerCompositor.h b/Source/WebCore/rendering/RenderLayerCompositor.h new file mode 100644 index 0000000..53a0f9a --- /dev/null +++ b/Source/WebCore/rendering/RenderLayerCompositor.h @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderLayerCompositor_h +#define RenderLayerCompositor_h + +#include "ChromeClient.h" +#include "RenderLayer.h" +#include "RenderLayerBacking.h" + +namespace WebCore { + +#define PROFILE_LAYER_REBUILD 0 + +class GraphicsLayer; +class RenderEmbeddedObject; +class RenderIFrame; +#if ENABLE(VIDEO) +class RenderVideo; +#endif + +enum CompositingUpdateType { + CompositingUpdateAfterLayoutOrStyleChange, + CompositingUpdateOnPaitingOrHitTest, + CompositingUpdateOnScroll +}; + +// RenderLayerCompositor manages the hierarchy of +// composited RenderLayers. It determines which RenderLayers +// become compositing, and creates and maintains a hierarchy of +// GraphicsLayers based on the RenderLayer painting order. +// +// There is one RenderLayerCompositor per RenderView. + +class RenderLayerCompositor : public GraphicsLayerClient { +public: + RenderLayerCompositor(RenderView*); + ~RenderLayerCompositor(); + + // Return true if this RenderView is in "compositing mode" (i.e. has one or more + // composited RenderLayers) + bool inCompositingMode() const { return m_compositing; } + // This will make a compositing layer at the root automatically, and hook up to + // the native view/window system. + void enableCompositingMode(bool enable = true); + + // Returns true if the accelerated compositing is enabled + bool hasAcceleratedCompositing() const { return m_hasAcceleratedCompositing; } + + bool canRender3DTransforms() const; + + // Copy the accelerated compositing related flags from Settings + void cacheAcceleratedCompositingFlags(); + + // Called when the layer hierarchy needs to be updated (compositing layers have been + // created, destroyed or re-parented). + void setCompositingLayersNeedRebuild(bool needRebuild = true); + bool compositingLayersNeedRebuild() const { return m_compositingLayersNeedRebuild; } + + // Controls whether or not to consult geometry when deciding which layers need + // to be composited. Defaults to true. + void setCompositingConsultsOverlap(bool b) { m_compositingConsultsOverlap = b; } + bool compositingConsultsOverlap() const { return m_compositingConsultsOverlap; } + + void scheduleSync(); + + // Rebuild the tree of compositing layers + void updateCompositingLayers(CompositingUpdateType = CompositingUpdateAfterLayoutOrStyleChange, RenderLayer* updateRoot = 0); + // This is only used when state changes and we do not exepect a style update or layout to happen soon (e.g. when + // we discover that an iframe is overlapped during painting). + void scheduleCompositingLayerUpdate(); + bool compositingLayerUpdatePending() const; + + // Update the compositing state of the given layer. Returns true if that state changed. + enum CompositingChangeRepaint { CompositingChangeRepaintNow, CompositingChangeWillRepaintLater }; + bool updateLayerCompositingState(RenderLayer*, CompositingChangeRepaint = CompositingChangeRepaintNow); + + // Update the geometry for compositing children of compositingAncestor. + void updateCompositingDescendantGeometry(RenderLayer* compositingAncestor, RenderLayer* layer, RenderLayerBacking::UpdateDepth); + + // Whether layer's backing needs a graphics layer to do clipping by an ancestor (non-stacking-context parent with overflow). + bool clippedByAncestor(RenderLayer*) const; + // Whether layer's backing needs a graphics layer to clip z-order children of the given layer. + bool clipsCompositingDescendants(const RenderLayer*) const; + + // Whether the given layer needs an extra 'contents' layer. + bool needsContentsCompositingLayer(const RenderLayer*) const; + // Return the bounding box required for compositing layer and its childern, relative to ancestorLayer. + // If layerBoundingBox is not 0, on return it contains the bounding box of this layer only. + IntRect calculateCompositedBounds(const RenderLayer* layer, const RenderLayer* ancestorLayer); + + // Repaint the appropriate layers when the given RenderLayer starts or stops being composited. + void repaintOnCompositingChange(RenderLayer*); + + // Notify us that a layer has been added or removed + void layerWasAdded(RenderLayer* parent, RenderLayer* child); + void layerWillBeRemoved(RenderLayer* parent, RenderLayer* child); + + // Get the nearest ancestor layer that has overflow or clip, but is not a stacking context + RenderLayer* enclosingNonStackingClippingLayer(const RenderLayer* layer) const; + + // Repaint parts of all composited layers that intersect the given absolute rectangle. + void repaintCompositedLayersAbsoluteRect(const IntRect&); + + RenderLayer* rootRenderLayer() const; + GraphicsLayer* rootPlatformLayer() const; + + enum RootLayerAttachment { + RootLayerUnattached, + RootLayerAttachedViaChromeClient, + RootLayerAttachedViaEnclosingIframe + }; + + RootLayerAttachment rootLayerAttachment() const { return m_rootLayerAttachment; } + void updateRootLayerAttachment(); + void updateRootLayerPosition(); + + void didMoveOnscreen(); + void willMoveOffscreen(); + + void didStartAcceleratedAnimation(CSSPropertyID); + +#if ENABLE(VIDEO) + // Use by RenderVideo to ask if it should try to use accelerated compositing. + bool canAccelerateVideoRendering(RenderVideo*) const; +#endif + + // Walk the tree looking for layers with 3d transforms. Useful in case you need + // to know if there is non-affine content, e.g. for drawing into an image. + bool has3DContent() const; + + // Most platforms connect compositing layer trees between iframes and their parent document. + // Some (currently just Mac) allow iframes to do their own compositing. + static bool allowsIndependentlyCompositedIFrames(const FrameView*); + bool shouldPropagateCompositingToEnclosingIFrame() const; + + // FIXME: This should be a RenderIFrame* + HTMLFrameOwnerElement* enclosingIFrameElement() const; + + static RenderLayerCompositor* iframeContentsCompositor(RenderIFrame*); + // Return true if the layers changed. + static bool parentIFrameContentLayers(RenderIFrame*); + + // Update the geometry of the layers used for clipping and scrolling in frames. + void frameViewDidChangeSize(const IntPoint& contentsOffset = IntPoint()); + void frameViewDidScroll(const IntPoint& = IntPoint()); + + String layerTreeAsText(); + + // These are named to avoid conflicts with the functions in GraphicsLayerClient + // These return the actual internal variables. + bool compositorShowDebugBorders() const { return m_showDebugBorders; } + bool compositorShowRepaintCounter() const { return m_showRepaintCounter; } + +private: + // GraphicsLayerClient Implementation + virtual void notifyAnimationStarted(const GraphicsLayer*, double) { } + virtual void notifySyncRequired(const GraphicsLayer*) { scheduleSync(); } + virtual void paintContents(const GraphicsLayer*, GraphicsContext&, GraphicsLayerPaintingPhase, const IntRect&) { } + + // These calls return false always. They are saying that the layers associated with this client + // (the clipLayer and scrollLayer) should never show debugging info. + virtual bool showDebugBorders() const { return false; } + virtual bool showRepaintCounter() const { return false; } + + // Whether the given RL needs a compositing layer. + bool needsToBeComposited(const RenderLayer*) const; + // Whether the layer has an intrinsic need for compositing layer. + bool requiresCompositingLayer(const RenderLayer*) const; + // Whether the layer could ever be composited. + bool canBeComposited(const RenderLayer*) const; + + // Make or destroy the backing for this layer; returns true if backing changed. + bool updateBacking(RenderLayer*, CompositingChangeRepaint shouldRepaint); + + // Repaint the given rect (which is layer's coords), and regions of child layers that intersect that rect. + void recursiveRepaintLayerRect(RenderLayer* layer, const IntRect& rect); + + typedef HashMap<RenderLayer*, IntRect> OverlapMap; + static void addToOverlapMap(OverlapMap&, RenderLayer*, IntRect& layerBounds, bool& boundsComputed); + static bool overlapsCompositedLayers(OverlapMap&, const IntRect& layerBounds); + + void updateCompositingLayersTimerFired(Timer<RenderLayerCompositor>*); + + // Returns true if any layer's compositing changed + void computeCompositingRequirements(RenderLayer*, OverlapMap*, struct CompositingState&, bool& layersChanged); + + // Recurses down the tree, parenting descendant compositing layers and collecting an array of child layers for the current compositing layer. + void rebuildCompositingLayerTree(RenderLayer* layer, const struct CompositingState&, Vector<GraphicsLayer*>& childGraphicsLayersOfEnclosingLayer); + + // Recurses down the tree, updating layer geometry only. + void updateLayerTreeGeometry(RenderLayer*); + + // Hook compositing layers together + void setCompositingParent(RenderLayer* childLayer, RenderLayer* parentLayer); + void removeCompositedChildren(RenderLayer*); + + bool layerHas3DContent(const RenderLayer*) const; + + void ensureRootPlatformLayer(); + void destroyRootPlatformLayer(); + + void attachRootPlatformLayer(RootLayerAttachment); + void detachRootPlatformLayer(); + + void rootLayerAttachmentChanged(); + + void scheduleNeedsStyleRecalc(Element*); + void notifyIFramesOfCompositingChange(); + + // Whether a running transition or animation enforces the need for a compositing layer. + bool requiresCompositingForAnimation(RenderObject*) const; + bool requiresCompositingForTransform(RenderObject*) const; + bool requiresCompositingForVideo(RenderObject*) const; + bool requiresCompositingForCanvas(RenderObject*) const; + bool requiresCompositingForPlugin(RenderObject*) const; + bool requiresCompositingForIFrame(RenderObject*) const; + bool requiresCompositingWhenDescendantsAreCompositing(RenderObject*) const; + bool requiresCompositingForFullScreen(RenderObject*) const; + +#if PLATFORM(ANDROID) + // Whether we are using layers for new android features (overflow support, fixed elements) + bool requiresCompositingForAndroidLayers(const RenderLayer* layer) const; +#endif + + bool requiresScrollLayer(RootLayerAttachment) const; + +private: + RenderView* m_renderView; + OwnPtr<GraphicsLayer> m_rootPlatformLayer; + Timer<RenderLayerCompositor> m_updateCompositingLayersTimer; + + bool m_hasAcceleratedCompositing; + ChromeClient::CompositingTriggerFlags m_compositingTriggers; + + bool m_showDebugBorders; + bool m_showRepaintCounter; + bool m_compositingConsultsOverlap; + + // When true, we have to wait until layout has happened before we can decide whether to enter compositing mode, + // because only then do we know the final size of plugins and iframes. + // FIXME: once set, this is never cleared. + mutable bool m_compositingDependsOnGeometry; + + bool m_compositing; + bool m_compositingLayersNeedRebuild; + + RootLayerAttachment m_rootLayerAttachment; + + // Enclosing clipping layer for iframe content + OwnPtr<GraphicsLayer> m_clipLayer; + OwnPtr<GraphicsLayer> m_scrollLayer; + +#if PROFILE_LAYER_REBUILD + int m_rootLayerUpdateCount; +#endif +}; + + +} // namespace WebCore + +#endif // RenderLayerCompositor_h diff --git a/Source/WebCore/rendering/RenderLineBoxList.cpp b/Source/WebCore/rendering/RenderLineBoxList.cpp new file mode 100644 index 0000000..85d7f18 --- /dev/null +++ b/Source/WebCore/rendering/RenderLineBoxList.cpp @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderLineBoxList.h" + +#include "HitTestResult.h" +#include "InlineTextBox.h" +#include "RenderArena.h" +#include "RenderInline.h" +#include "RenderView.h" +#include "RootInlineBox.h" + +using namespace std; + +namespace WebCore { + +#ifndef NDEBUG +RenderLineBoxList::~RenderLineBoxList() +{ + ASSERT(!m_firstLineBox); + ASSERT(!m_lastLineBox); +} +#endif + +void RenderLineBoxList::appendLineBox(InlineFlowBox* box) +{ + checkConsistency(); + + if (!m_firstLineBox) + m_firstLineBox = m_lastLineBox = box; + else { + m_lastLineBox->setNextLineBox(box); + box->setPreviousLineBox(m_lastLineBox); + m_lastLineBox = box; + } + + checkConsistency(); +} + +void RenderLineBoxList::deleteLineBoxTree(RenderArena* arena) +{ + InlineFlowBox* line = m_firstLineBox; + InlineFlowBox* nextLine; + while (line) { + nextLine = line->nextLineBox(); + line->deleteLine(arena); + line = nextLine; + } + m_firstLineBox = m_lastLineBox = 0; +} + +void RenderLineBoxList::extractLineBox(InlineFlowBox* box) +{ + checkConsistency(); + + m_lastLineBox = box->prevLineBox(); + if (box == m_firstLineBox) + m_firstLineBox = 0; + if (box->prevLineBox()) + box->prevLineBox()->setNextLineBox(0); + box->setPreviousLineBox(0); + for (InlineFlowBox* curr = box; curr; curr = curr->nextLineBox()) + curr->setExtracted(); + + checkConsistency(); +} + +void RenderLineBoxList::attachLineBox(InlineFlowBox* box) +{ + checkConsistency(); + + if (m_lastLineBox) { + m_lastLineBox->setNextLineBox(box); + box->setPreviousLineBox(m_lastLineBox); + } else + m_firstLineBox = box; + InlineFlowBox* last = box; + for (InlineFlowBox* curr = box; curr; curr = curr->nextLineBox()) { + curr->setExtracted(false); + last = curr; + } + m_lastLineBox = last; + + checkConsistency(); +} + +void RenderLineBoxList::removeLineBox(InlineFlowBox* box) +{ + checkConsistency(); + + if (box == m_firstLineBox) + m_firstLineBox = box->nextLineBox(); + if (box == m_lastLineBox) + m_lastLineBox = box->prevLineBox(); + if (box->nextLineBox()) + box->nextLineBox()->setPreviousLineBox(box->prevLineBox()); + if (box->prevLineBox()) + box->prevLineBox()->setNextLineBox(box->nextLineBox()); + + checkConsistency(); +} + +void RenderLineBoxList::deleteLineBoxes(RenderArena* arena) +{ + if (m_firstLineBox) { + InlineFlowBox* next; + for (InlineFlowBox* curr = m_firstLineBox; curr; curr = next) { + next = curr->nextLineBox(); + curr->destroy(arena); + } + m_firstLineBox = 0; + m_lastLineBox = 0; + } +} + +void RenderLineBoxList::dirtyLineBoxes() +{ + for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) + curr->dirtyLineBoxes(); +} + +bool RenderLineBoxList::rangeIntersectsRect(RenderBoxModelObject* renderer, int logicalTop, int logicalBottom, const IntRect& rect, int tx, int ty) const +{ + RenderBox* block; + if (renderer->isBox()) + block = toRenderBox(renderer); + else + block = renderer->containingBlock(); + int physicalStart = block->flipForWritingMode(logicalTop); + int physicalEnd = block->flipForWritingMode(logicalBottom); + int physicalExtent = abs(physicalEnd - physicalStart); + physicalStart = min(physicalStart, physicalEnd); + + if (renderer->style()->isHorizontalWritingMode()) { + physicalStart += ty; + if (physicalStart >= rect.bottom() || physicalStart + physicalExtent <= rect.y()) + return false; + } else { + physicalStart += tx; + if (physicalStart >= rect.right() || physicalStart + physicalExtent <= rect.x()) + return false; + } + + return true; +} + +bool RenderLineBoxList::anyLineIntersectsRect(RenderBoxModelObject* renderer, const IntRect& rect, int tx, int ty, bool usePrintRect, int outlineSize) const +{ + // We can check the first box and last box and avoid painting/hit testing if we don't + // intersect. This is a quick short-circuit that we can take to avoid walking any lines. + // FIXME: This check is flawed in the following extremely obscure way: + // if some line in the middle has a huge overflow, it might actually extend below the last line. + int firstLineTop = firstLineBox()->logicalTopVisualOverflow(); + if (usePrintRect && !firstLineBox()->parent()) + firstLineTop = min(firstLineTop, firstLineBox()->root()->lineTop()); + int lastLineBottom = lastLineBox()->logicalBottomVisualOverflow(); + if (usePrintRect && !lastLineBox()->parent()) + lastLineBottom = max(lastLineBottom, lastLineBox()->root()->lineBottom()); + int logicalTop = firstLineTop - outlineSize; + int logicalBottom = outlineSize + lastLineBottom; + + return rangeIntersectsRect(renderer, logicalTop, logicalBottom, rect, tx, ty); +} + +bool RenderLineBoxList::lineIntersectsDirtyRect(RenderBoxModelObject* renderer, InlineFlowBox* box, const PaintInfo& paintInfo, int tx, int ty) const +{ + int logicalTop = min(box->logicalTopVisualOverflow(), box->root()->selectionTop()) - renderer->maximalOutlineSize(paintInfo.phase); + int logicalBottom = box->logicalBottomVisualOverflow() + renderer->maximalOutlineSize(paintInfo.phase); + + return rangeIntersectsRect(renderer, logicalTop, logicalBottom, paintInfo.rect, tx, ty); +} + +void RenderLineBoxList::paint(RenderBoxModelObject* renderer, PaintInfo& paintInfo, int tx, int ty) const +{ + // Only paint during the foreground/selection phases. + if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseOutline + && paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines && paintInfo.phase != PaintPhaseTextClip + && paintInfo.phase != PaintPhaseMask) + return; + + ASSERT(renderer->isRenderBlock() || (renderer->isRenderInline() && renderer->hasLayer())); // The only way an inline could paint like this is if it has a layer. + + // If we have no lines then we have no work to do. + if (!firstLineBox()) + return; + + // FIXME: Paint-time pagination is obsolete and is now only used by embedded WebViews inside AppKit + // NSViews. Do not add any more code for this. + RenderView* v = renderer->view(); + bool usePrintRect = !v->printRect().isEmpty(); + int outlineSize = renderer->maximalOutlineSize(paintInfo.phase); + if (!anyLineIntersectsRect(renderer, paintInfo.rect, tx, ty, usePrintRect, outlineSize)) + return; + + PaintInfo info(paintInfo); + ListHashSet<RenderInline*> outlineObjects; + info.outlineObjects = &outlineObjects; + + // See if our root lines intersect with the dirty rect. If so, then we paint + // them. Note that boxes can easily overlap, so we can't make any assumptions + // based off positions of our first line box or our last line box. + for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) { + if (usePrintRect) { + // FIXME: This is the deprecated pagination model that is still needed + // for embedded views inside AppKit. AppKit is incapable of paginating vertical + // text pages, so we don't have to deal with vertical lines at all here. + int topForPaginationCheck = curr->topVisualOverflow(); + int bottomForPaginationCheck = curr->bottomVisualOverflow(); + if (!curr->parent()) { + // We're a root box. Use lineTop and lineBottom as well here. + topForPaginationCheck = min(topForPaginationCheck, curr->root()->lineTop()); + bottomForPaginationCheck = max(bottomForPaginationCheck, curr->root()->lineBottom()); + } + if (bottomForPaginationCheck - topForPaginationCheck <= v->printRect().height()) { + if (ty + bottomForPaginationCheck > v->printRect().bottom()) { + if (RootInlineBox* nextRootBox = curr->root()->nextRootBox()) + bottomForPaginationCheck = min(bottomForPaginationCheck, min(nextRootBox->topVisualOverflow(), nextRootBox->lineTop())); + } + if (ty + bottomForPaginationCheck > v->printRect().bottom()) { + if (ty + topForPaginationCheck < v->truncatedAt()) + v->setBestTruncatedAt(ty + topForPaginationCheck, renderer); + // If we were able to truncate, don't paint. + if (ty + topForPaginationCheck >= v->truncatedAt()) + break; + } + } + } + + if (lineIntersectsDirtyRect(renderer, curr, info, tx, ty)) + curr->paint(info, tx, ty); + } + + if (info.phase == PaintPhaseOutline || info.phase == PaintPhaseSelfOutline || info.phase == PaintPhaseChildOutlines) { + ListHashSet<RenderInline*>::iterator end = info.outlineObjects->end(); + for (ListHashSet<RenderInline*>::iterator it = info.outlineObjects->begin(); it != end; ++it) { + RenderInline* flow = *it; + flow->paintOutline(info.context, tx, ty); + } + info.outlineObjects->clear(); + } +} + + +bool RenderLineBoxList::hitTest(RenderBoxModelObject* renderer, const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction hitTestAction) const +{ + if (hitTestAction != HitTestForeground) + return false; + + ASSERT(renderer->isRenderBlock() || (renderer->isRenderInline() && renderer->hasLayer())); // The only way an inline could hit test like this is if it has a layer. + + // If we have no lines then we have no work to do. + if (!firstLineBox()) + return false; + + bool isHorizontal = firstLineBox()->isHorizontal(); + + int logicalPointStart = isHorizontal ? y - result.topPadding() : x - result.leftPadding(); + int logicalPointEnd = (isHorizontal ? y + result.bottomPadding() : x + result.rightPadding()) + 1; + IntRect rect(isHorizontal ? x : logicalPointStart, isHorizontal ? logicalPointStart : y, + isHorizontal ? 1 : logicalPointEnd - logicalPointStart, + isHorizontal ? logicalPointEnd - logicalPointStart : 1); + if (!anyLineIntersectsRect(renderer, rect, tx, ty)) + return false; + + // See if our root lines contain the point. If so, then we hit test + // them further. Note that boxes can easily overlap, so we can't make any assumptions + // based off positions of our first line box or our last line box. + for (InlineFlowBox* curr = lastLineBox(); curr; curr = curr->prevLineBox()) { + if (rangeIntersectsRect(renderer, curr->logicalTopVisualOverflow(), curr->logicalBottomVisualOverflow(), rect, tx, ty)) { + bool inside = curr->nodeAtPoint(request, result, x, y, tx, ty); + if (inside) { + renderer->updateHitTestResult(result, IntPoint(x - tx, y - ty)); + return true; + } + } + } + + return false; +} + +void RenderLineBoxList::dirtyLinesFromChangedChild(RenderObject* container, RenderObject* child) +{ + if (!container->parent() || (container->isRenderBlock() && (container->selfNeedsLayout() || !container->isBlockFlow()))) + return; + + // If we have no first line box, then just bail early. + if (!firstLineBox()) { + // For an empty inline, go ahead and propagate the check up to our parent, unless the parent + // is already dirty. + if (container->isInline() && !container->parent()->selfNeedsLayout()) + container->parent()->dirtyLinesFromChangedChild(container); + return; + } + + // Try to figure out which line box we belong in. First try to find a previous + // line box by examining our siblings. If we didn't find a line box, then use our + // parent's first line box. + RootInlineBox* box = 0; + RenderObject* curr = 0; + for (curr = child->previousSibling(); curr; curr = curr->previousSibling()) { + if (curr->isFloatingOrPositioned()) + continue; + + if (curr->isReplaced()) { + InlineBox* wrapper = toRenderBox(curr)->inlineBoxWrapper(); + if (wrapper) + box = wrapper->root(); + } else if (curr->isText()) { + InlineTextBox* textBox = toRenderText(curr)->lastTextBox(); + if (textBox) + box = textBox->root(); + } else if (curr->isRenderInline()) { + InlineFlowBox* flowBox = toRenderInline(curr)->lastLineBox(); + if (flowBox) + box = flowBox->root(); + } + + if (box) + break; + } + if (!box) + box = firstLineBox()->root(); + + // If we found a line box, then dirty it. + if (box) { + RootInlineBox* adjacentBox; + box->markDirty(); + + // dirty the adjacent lines that might be affected + // NOTE: we dirty the previous line because RootInlineBox objects cache + // the address of the first object on the next line after a BR, which we may be + // invalidating here. For more info, see how RenderBlock::layoutInlineChildren + // calls setLineBreakInfo with the result of findNextLineBreak. findNextLineBreak, + // despite the name, actually returns the first RenderObject after the BR. + // <rdar://problem/3849947> "Typing after pasting line does not appear until after window resize." + adjacentBox = box->prevRootBox(); + if (adjacentBox) + adjacentBox->markDirty(); + if (child->isBR() || (curr && curr->isBR())) { + adjacentBox = box->nextRootBox(); + if (adjacentBox) + adjacentBox->markDirty(); + } + } +} + +#ifndef NDEBUG + +void RenderLineBoxList::checkConsistency() const +{ +#ifdef CHECK_CONSISTENCY + const InlineFlowBox* prev = 0; + for (const InlineFlowBox* child = m_firstLineBox; child != 0; child = child->nextLineBox()) { + ASSERT(child->prevLineBox() == prev); + prev = child; + } + ASSERT(prev == m_lastLineBox); +#endif +} + +#endif + +} diff --git a/Source/WebCore/rendering/RenderLineBoxList.h b/Source/WebCore/rendering/RenderLineBoxList.h new file mode 100644 index 0000000..9708d67 --- /dev/null +++ b/Source/WebCore/rendering/RenderLineBoxList.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#ifndef RenderLineBoxList_h +#define RenderLineBoxList_h + +#include "RenderBox.h" + +namespace WebCore { + +class RenderLineBoxList { +public: + RenderLineBoxList() + : m_firstLineBox(0) + , m_lastLineBox(0) + { + } + +#ifndef NDEBUG + ~RenderLineBoxList(); +#endif + + InlineFlowBox* firstLineBox() const { return m_firstLineBox; } + InlineFlowBox* lastLineBox() const { return m_lastLineBox; } + + void checkConsistency() const; + + void appendLineBox(InlineFlowBox*); + + void deleteLineBoxTree(RenderArena*); + void deleteLineBoxes(RenderArena*); + + void extractLineBox(InlineFlowBox*); + void attachLineBox(InlineFlowBox*); + void removeLineBox(InlineFlowBox*); + + void dirtyLineBoxes(); + void dirtyLinesFromChangedChild(RenderObject* parent, RenderObject* child); + + void paint(RenderBoxModelObject*, PaintInfo&, int x, int y) const; + bool hitTest(RenderBoxModelObject*, const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction) const; + +private: + bool anyLineIntersectsRect(RenderBoxModelObject*, const IntRect&, int tx, int ty, bool usePrintRect = false, int outlineSize = 0) const; + bool lineIntersectsDirtyRect(RenderBoxModelObject*, InlineFlowBox*, const PaintInfo&, int tx, int ty) const; + bool rangeIntersectsRect(RenderBoxModelObject*, int logicalTop, int logicalBottom, const IntRect&, int tx, int ty) const; + + // For block flows, each box represents the root inline box for a line in the + // paragraph. + // For inline flows, each box represents a portion of that inline. + InlineFlowBox* m_firstLineBox; + InlineFlowBox* m_lastLineBox; +}; + + +#ifdef NDEBUG +inline void RenderLineBoxList::checkConsistency() const +{ +} +#endif + +} // namespace WebCore + +#endif // RenderFlow_h diff --git a/Source/WebCore/rendering/RenderListBox.cpp b/Source/WebCore/rendering/RenderListBox.cpp new file mode 100644 index 0000000..ed7f8ee --- /dev/null +++ b/Source/WebCore/rendering/RenderListBox.cpp @@ -0,0 +1,743 @@ +/* + * This file is part of the select element renderer in WebCore. + * + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderListBox.h" + +#include "AXObjectCache.h" +#include "CSSStyleSelector.h" +#include "Document.h" +#include "EventHandler.h" +#include "EventNames.h" +#include "FocusController.h" +#include "Frame.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "HTMLNames.h" +#include "HitTestResult.h" +#include "OptionGroupElement.h" +#include "OptionElement.h" +#include "Page.h" +#include "RenderScrollbar.h" +#include "RenderTheme.h" +#include "RenderView.h" +#include "Scrollbar.h" +#include "SelectElement.h" +#include "SelectionController.h" +#include "NodeRenderStyle.h" +#include <math.h> + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +const int rowSpacing = 1; + +const int optionsSpacingHorizontal = 2; + +const int minSize = 4; +const int maxDefaultSize = 10; + +// FIXME: This hardcoded baselineAdjustment is what we used to do for the old +// widget, but I'm not sure this is right for the new control. +const int baselineAdjustment = 7; + +RenderListBox::RenderListBox(Element* element) + : RenderBlock(element) + , m_optionsChanged(true) + , m_scrollToRevealSelectionAfterLayout(false) + , m_inAutoscroll(false) + , m_optionsWidth(0) + , m_indexOffset(0) +{ +} + +RenderListBox::~RenderListBox() +{ + setHasVerticalScrollbar(false); +} + +void RenderListBox::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBlock::styleDidChange(diff, oldStyle); + setReplaced(isInline()); +} + +void RenderListBox::updateFromElement() +{ + if (m_optionsChanged) { + const Vector<Element*>& listItems = toSelectElement(static_cast<Element*>(node()))->listItems(); + int size = numItems(); + + float width = 0; + for (int i = 0; i < size; ++i) { + Element* element = listItems[i]; + String text; + Font itemFont = style()->font(); + if (OptionElement* optionElement = toOptionElement(element)) + text = optionElement->textIndentedToRespectGroupLabel(); + else if (OptionGroupElement* optionGroupElement = toOptionGroupElement(element)) { + text = optionGroupElement->groupLabelText(); + FontDescription d = itemFont.fontDescription(); + d.setWeight(d.bolderWeight()); + itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing()); + itemFont.update(document()->styleSelector()->fontSelector()); + } + + if (!text.isEmpty()) { + float textWidth = itemFont.floatWidth(TextRun(text.impl(), 0, 0, 0, false, false, false, false)); + width = max(width, textWidth); + } + } + m_optionsWidth = static_cast<int>(ceilf(width)); + m_optionsChanged = false; + + setHasVerticalScrollbar(true); + + setNeedsLayoutAndPrefWidthsRecalc(); + } +} + +void RenderListBox::selectionChanged() +{ + repaint(); + if (!m_inAutoscroll) { + if (m_optionsChanged || needsLayout()) + m_scrollToRevealSelectionAfterLayout = true; + else + scrollToRevealSelection(); + } + + if (AXObjectCache::accessibilityEnabled()) + document()->axObjectCache()->selectedChildrenChanged(this); +} + +void RenderListBox::layout() +{ + RenderBlock::layout(); + if (m_scrollToRevealSelectionAfterLayout) + scrollToRevealSelection(); +} + +void RenderListBox::scrollToRevealSelection() +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + + m_scrollToRevealSelectionAfterLayout = false; + + int firstIndex = select->activeSelectionStartListIndex(); + if (firstIndex >= 0 && !listIndexIsVisible(select->activeSelectionEndListIndex())) + scrollToRevealElementAtListIndex(firstIndex); +} + +void RenderListBox::computePreferredLogicalWidths() +{ + ASSERT(!m_optionsChanged); + + m_minPreferredLogicalWidth = 0; + m_maxPreferredLogicalWidth = 0; + + if (style()->width().isFixed() && style()->width().value() > 0) + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value()); + else { + m_maxPreferredLogicalWidth = m_optionsWidth + 2 * optionsSpacingHorizontal; + if (m_vBar) + m_maxPreferredLogicalWidth += m_vBar->width(); + } + + if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) { + m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); + m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); + } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent())) + m_minPreferredLogicalWidth = 0; + else + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth; + + if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) { + m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); + m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); + } + + int toAdd = borderAndPaddingWidth(); + m_minPreferredLogicalWidth += toAdd; + m_maxPreferredLogicalWidth += toAdd; + + setPreferredLogicalWidthsDirty(false); +} + +int RenderListBox::size() const +{ + int specifiedSize = toSelectElement(static_cast<Element*>(node()))->size(); + if (specifiedSize > 1) + return max(minSize, specifiedSize); + return min(max(minSize, numItems()), maxDefaultSize); +} + +int RenderListBox::numVisibleItems() const +{ + // Only count fully visible rows. But don't return 0 even if only part of a row shows. + return max(1, (contentHeight() + rowSpacing) / itemHeight()); +} + +int RenderListBox::numItems() const +{ + return toSelectElement(static_cast<Element*>(node()))->listItems().size(); +} + +int RenderListBox::listHeight() const +{ + return itemHeight() * numItems() - rowSpacing; +} + +void RenderListBox::computeLogicalHeight() +{ + int toAdd = borderAndPaddingHeight(); + + int itemHeight = RenderListBox::itemHeight(); + setHeight(itemHeight * size() - rowSpacing + toAdd); + + RenderBlock::computeLogicalHeight(); + + if (m_vBar) { + bool enabled = numVisibleItems() < numItems(); + m_vBar->setEnabled(enabled); + m_vBar->setSteps(1, min(1, numVisibleItems() - 1), itemHeight); + m_vBar->setProportion(numVisibleItems(), numItems()); + if (!enabled) + m_indexOffset = 0; + } +} + +int RenderListBox::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode lineDirection, LinePositionMode linePositionMode) const +{ + return RenderBox::baselinePosition(baselineType, firstLine, lineDirection, linePositionMode) - baselineAdjustment; +} + +IntRect RenderListBox::itemBoundingBoxRect(int tx, int ty, int index) +{ + return IntRect(tx + borderLeft() + paddingLeft(), + ty + borderTop() + paddingTop() + itemHeight() * (index - m_indexOffset), + contentWidth(), itemHeight()); +} + +void RenderListBox::paintObject(PaintInfo& paintInfo, int tx, int ty) +{ + if (style()->visibility() != VISIBLE) + return; + + int listItemsSize = numItems(); + + if (paintInfo.phase == PaintPhaseForeground) { + int index = m_indexOffset; + while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) { + paintItemForeground(paintInfo, tx, ty, index); + index++; + } + } + + // Paint the children. + RenderBlock::paintObject(paintInfo, tx, ty); + + if (paintInfo.phase == PaintPhaseBlockBackground) + paintScrollbar(paintInfo, tx, ty); + else if (paintInfo.phase == PaintPhaseChildBlockBackground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) { + int index = m_indexOffset; + while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) { + paintItemBackground(paintInfo, tx, ty, index); + index++; + } + } +} + +void RenderListBox::paintScrollbar(PaintInfo& paintInfo, int tx, int ty) +{ + if (m_vBar) { + IntRect scrollRect(tx + width() - borderRight() - m_vBar->width(), + ty + borderTop(), + m_vBar->width(), + height() - (borderTop() + borderBottom())); + m_vBar->setFrameRect(scrollRect); + m_vBar->paint(paintInfo.context, paintInfo.rect); + } +} + +void RenderListBox::paintItemForeground(PaintInfo& paintInfo, int tx, int ty, int listIndex) +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + const Vector<Element*>& listItems = select->listItems(); + Element* element = listItems[listIndex]; + OptionElement* optionElement = toOptionElement(element); + + String itemText; + if (optionElement) + itemText = optionElement->textIndentedToRespectGroupLabel(); + else if (OptionGroupElement* optionGroupElement = toOptionGroupElement(element)) + itemText = optionGroupElement->groupLabelText(); + + // Determine where the item text should be placed + IntRect r = itemBoundingBoxRect(tx, ty, listIndex); + r.move(optionsSpacingHorizontal, style()->font().ascent()); + + RenderStyle* itemStyle = element->renderStyle(); + if (!itemStyle) + itemStyle = style(); + + Color textColor = element->renderStyle() ? element->renderStyle()->visitedDependentColor(CSSPropertyColor) : style()->visitedDependentColor(CSSPropertyColor); + if (optionElement && optionElement->selected()) { + if (frame()->selection()->isFocusedAndActive() && document()->focusedNode() == node()) + textColor = theme()->activeListBoxSelectionForegroundColor(); + // Honor the foreground color for disabled items + else if (!element->disabled()) + textColor = theme()->inactiveListBoxSelectionForegroundColor(); + } + + ColorSpace colorSpace = itemStyle->colorSpace(); + paintInfo.context->setFillColor(textColor, colorSpace); + + Font itemFont = style()->font(); + if (isOptionGroupElement(element)) { + FontDescription d = itemFont.fontDescription(); + d.setWeight(d.bolderWeight()); + itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing()); + itemFont.update(document()->styleSelector()->fontSelector()); + } + + unsigned length = itemText.length(); + const UChar* string = itemText.characters(); + TextRun textRun(string, length, 0, 0, 0, !itemStyle->isLeftToRightDirection(), itemStyle->unicodeBidi() == Override, false, false); + + // Draw the item text + if (itemStyle->visibility() != HIDDEN) + paintInfo.context->drawBidiText(itemFont, textRun, r.location()); +} + +void RenderListBox::paintItemBackground(PaintInfo& paintInfo, int tx, int ty, int listIndex) +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + const Vector<Element*>& listItems = select->listItems(); + Element* element = listItems[listIndex]; + OptionElement* optionElement = toOptionElement(element); + + Color backColor; + if (optionElement && optionElement->selected()) { + if (frame()->selection()->isFocusedAndActive() && document()->focusedNode() == node()) + backColor = theme()->activeListBoxSelectionBackgroundColor(); + else + backColor = theme()->inactiveListBoxSelectionBackgroundColor(); + } else + backColor = element->renderStyle() ? element->renderStyle()->visitedDependentColor(CSSPropertyBackgroundColor) : style()->visitedDependentColor(CSSPropertyBackgroundColor); + + // Draw the background for this list box item + if (!element->renderStyle() || element->renderStyle()->visibility() != HIDDEN) { + ColorSpace colorSpace = element->renderStyle() ? element->renderStyle()->colorSpace() : style()->colorSpace(); + IntRect itemRect = itemBoundingBoxRect(tx, ty, listIndex); + itemRect.intersect(controlClipRect(tx, ty)); + paintInfo.context->fillRect(itemRect, backColor, colorSpace); + } +} + +bool RenderListBox::isPointInOverflowControl(HitTestResult& result, int _x, int _y, int _tx, int _ty) +{ + if (!m_vBar) + return false; + + IntRect vertRect(_tx + width() - borderRight() - m_vBar->width(), + _ty + borderTop(), + m_vBar->width(), + height() - borderTop() - borderBottom()); + + if (vertRect.contains(_x, _y)) { + result.setScrollbar(m_vBar.get()); + return true; + } + return false; +} + +int RenderListBox::listIndexAtOffset(int offsetX, int offsetY) +{ + if (!numItems()) + return -1; + + if (offsetY < borderTop() + paddingTop() || offsetY > height() - paddingBottom() - borderBottom()) + return -1; + + int scrollbarWidth = m_vBar ? m_vBar->width() : 0; + if (offsetX < borderLeft() + paddingLeft() || offsetX > width() - borderRight() - paddingRight() - scrollbarWidth) + return -1; + + int newOffset = (offsetY - borderTop() - paddingTop()) / itemHeight() + m_indexOffset; + return newOffset < numItems() ? newOffset : -1; +} + +void RenderListBox::panScroll(const IntPoint& panStartMousePosition) +{ + const int maxSpeed = 20; + const int iconRadius = 7; + const int speedReducer = 4; + + // FIXME: This doesn't work correctly with transforms. + FloatPoint absOffset = localToAbsolute(); + + IntPoint currentMousePosition = frame()->eventHandler()->currentMousePosition(); + // We need to check if the current mouse position is out of the window. When the mouse is out of the window, the position is incoherent + static IntPoint previousMousePosition; + if (currentMousePosition.y() < 0) + currentMousePosition = previousMousePosition; + else + previousMousePosition = currentMousePosition; + + int yDelta = currentMousePosition.y() - panStartMousePosition.y(); + + // If the point is too far from the center we limit the speed + yDelta = max(min(yDelta, maxSpeed), -maxSpeed); + + if (abs(yDelta) < iconRadius) // at the center we let the space for the icon + return; + + if (yDelta > 0) + //offsetY = view()->viewHeight(); + absOffset.move(0, listHeight()); + else if (yDelta < 0) + yDelta--; + + // Let's attenuate the speed + yDelta /= speedReducer; + + IntPoint scrollPoint(0, 0); + scrollPoint.setY(absOffset.y() + yDelta); + int newOffset = scrollToward(scrollPoint); + if (newOffset < 0) + return; + + m_inAutoscroll = true; + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + select->updateListBoxSelection(!select->multiple()); + m_inAutoscroll = false; +} + +int RenderListBox::scrollToward(const IntPoint& destination) +{ + // FIXME: This doesn't work correctly with transforms. + FloatPoint absPos = localToAbsolute(); + int offsetX = destination.x() - absPos.x(); + int offsetY = destination.y() - absPos.y(); + + int rows = numVisibleItems(); + int offset = m_indexOffset; + + if (offsetY < borderTop() + paddingTop() && scrollToRevealElementAtListIndex(offset - 1)) + return offset - 1; + + if (offsetY > height() - paddingBottom() - borderBottom() && scrollToRevealElementAtListIndex(offset + rows)) + return offset + rows - 1; + + return listIndexAtOffset(offsetX, offsetY); +} + +void RenderListBox::autoscroll() +{ + IntPoint pos = frame()->view()->windowToContents(frame()->eventHandler()->currentMousePosition()); + + int endIndex = scrollToward(pos); + if (endIndex >= 0) { + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + m_inAutoscroll = true; + + if (!select->multiple()) + select->setActiveSelectionAnchorIndex(endIndex); + + select->setActiveSelectionEndIndex(endIndex); + select->updateListBoxSelection(!select->multiple()); + m_inAutoscroll = false; + } +} + +void RenderListBox::stopAutoscroll() +{ + toSelectElement(static_cast<Element*>(node()))->listBoxOnChange(); +} + +bool RenderListBox::scrollToRevealElementAtListIndex(int index) +{ + if (index < 0 || index >= numItems() || listIndexIsVisible(index)) + return false; + + int newOffset; + if (index < m_indexOffset) + newOffset = index; + else + newOffset = index - numVisibleItems() + 1; + + m_indexOffset = newOffset; + if (m_vBar) + m_vBar->setValue(m_indexOffset, Scrollbar::NotFromScrollAnimator); + + return true; +} + +bool RenderListBox::listIndexIsVisible(int index) +{ + return index >= m_indexOffset && index < m_indexOffset + numVisibleItems(); +} + +bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier, Node**) +{ + return m_vBar && m_vBar->scroll(direction, granularity, multiplier); +} + +bool RenderListBox::logicalScroll(ScrollLogicalDirection direction, ScrollGranularity granularity, float multiplier, Node**) +{ + return m_vBar && m_vBar->scroll(logicalToPhysical(direction, style()->isHorizontalWritingMode(), style()->isFlippedBlocksWritingMode()), granularity, multiplier); +} + +void RenderListBox::valueChanged(unsigned listIndex) +{ + Element* element = static_cast<Element*>(node()); + SelectElement* select = toSelectElement(element); + select->setSelectedIndex(select->listToOptionIndex(listIndex)); + element->dispatchFormControlChangeEvent(); +} + +int RenderListBox::scrollSize(ScrollbarOrientation orientation) const +{ + return ((orientation == VerticalScrollbar) && m_vBar) ? (m_vBar->totalSize() - m_vBar->visibleSize()) : 0; +} + +void RenderListBox::setScrollOffsetFromAnimation(const IntPoint& offset) +{ + if (m_vBar) + m_vBar->setValue(offset.y(), Scrollbar::FromScrollAnimator); +} + +void RenderListBox::valueChanged(Scrollbar*) +{ + int newOffset = m_vBar->value(); + if (newOffset != m_indexOffset) { + m_indexOffset = newOffset; + repaint(); + node()->dispatchEvent(Event::create(eventNames().scrollEvent, false, false)); + } +} + +int RenderListBox::itemHeight() const +{ + return style()->font().height() + rowSpacing; +} + +int RenderListBox::verticalScrollbarWidth() const +{ + return m_vBar ? m_vBar->width() : 0; +} + +// FIXME: We ignore padding in the vertical direction as far as these values are concerned, since that's +// how the control currently paints. +int RenderListBox::scrollWidth() const +{ + // There is no horizontal scrolling allowed. + return clientWidth(); +} + +int RenderListBox::scrollHeight() const +{ + return max(clientHeight(), listHeight()); +} + +int RenderListBox::scrollLeft() const +{ + return 0; +} + +void RenderListBox::setScrollLeft(int) +{ +} + +int RenderListBox::scrollTop() const +{ + return m_indexOffset * itemHeight(); +} + +void RenderListBox::setScrollTop(int newTop) +{ + // Determine an index and scroll to it. + int index = newTop / itemHeight(); + if (index < 0 || index >= numItems() || index == m_indexOffset) + return; + m_indexOffset = index; + if (m_vBar) + m_vBar->setValue(index, Scrollbar::NotFromScrollAnimator); +} + +bool RenderListBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction hitTestAction) +{ + if (!RenderBlock::nodeAtPoint(request, result, x, y, tx, ty, hitTestAction)) + return false; + const Vector<Element*>& listItems = toSelectElement(static_cast<Element*>(node()))->listItems(); + int size = numItems(); + tx += this->x(); + ty += this->y(); + for (int i = 0; i < size; ++i) { + if (itemBoundingBoxRect(tx, ty, i).contains(x, y)) { + if (Element* node = listItems[i]) { + result.setInnerNode(node); + if (!result.innerNonSharedNode()) + result.setInnerNonSharedNode(node); + result.setLocalPoint(IntPoint(x - tx, y - ty)); + break; + } + } + } + + return true; +} + +IntRect RenderListBox::controlClipRect(int tx, int ty) const +{ + IntRect clipRect = contentBoxRect(); + clipRect.move(tx, ty); + return clipRect; +} + +bool RenderListBox::isActive() const +{ + Page* page = frame()->page(); + return page && page->focusController()->isActive(); +} + +void RenderListBox::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect) +{ + IntRect scrollRect = rect; + scrollRect.move(width() - borderRight() - scrollbar->width(), borderTop()); + repaintRectangle(scrollRect); +} + +IntRect RenderListBox::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& scrollbarRect) const +{ + RenderView* view = this->view(); + if (!view) + return scrollbarRect; + + IntRect rect = scrollbarRect; + + int scrollbarLeft = width() - borderRight() - scrollbar->width(); + int scrollbarTop = borderTop(); + rect.move(scrollbarLeft, scrollbarTop); + + return view->frameView()->convertFromRenderer(this, rect); +} + +IntRect RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const +{ + RenderView* view = this->view(); + if (!view) + return parentRect; + + IntRect rect = view->frameView()->convertToRenderer(this, parentRect); + + int scrollbarLeft = width() - borderRight() - scrollbar->width(); + int scrollbarTop = borderTop(); + rect.move(-scrollbarLeft, -scrollbarTop); + return rect; +} + +IntPoint RenderListBox::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& scrollbarPoint) const +{ + RenderView* view = this->view(); + if (!view) + return scrollbarPoint; + + IntPoint point = scrollbarPoint; + + int scrollbarLeft = width() - borderRight() - scrollbar->width(); + int scrollbarTop = borderTop(); + point.move(scrollbarLeft, scrollbarTop); + + return view->frameView()->convertFromRenderer(this, point); +} + +IntPoint RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const +{ + RenderView* view = this->view(); + if (!view) + return parentPoint; + + IntPoint point = view->frameView()->convertToRenderer(this, parentPoint); + + int scrollbarLeft = width() - borderRight() - scrollbar->width(); + int scrollbarTop = borderTop(); + point.move(-scrollbarLeft, -scrollbarTop); + return point; +} + +PassRefPtr<Scrollbar> RenderListBox::createScrollbar() +{ + RefPtr<Scrollbar> widget; + bool hasCustomScrollbarStyle = style()->hasPseudoStyle(SCROLLBAR); + if (hasCustomScrollbarStyle) + widget = RenderScrollbar::createCustomScrollbar(this, VerticalScrollbar, this); + else + widget = Scrollbar::createNativeScrollbar(this, VerticalScrollbar, theme()->scrollbarControlSizeForPart(ListboxPart)); + document()->view()->addChild(widget.get()); + return widget.release(); +} + +void RenderListBox::destroyScrollbar() +{ + if (!m_vBar) + return; + + m_vBar->removeFromParent(); + m_vBar->setClient(0); + m_vBar = 0; +} + +void RenderListBox::setHasVerticalScrollbar(bool hasScrollbar) +{ + if (hasScrollbar == (m_vBar != 0)) + return; + + if (hasScrollbar) + m_vBar = createScrollbar(); + else + destroyScrollbar(); + + if (m_vBar) + m_vBar->styleChanged(); + +#if ENABLE(DASHBOARD_SUPPORT) + // Force an update since we know the scrollbars have changed things. + if (document()->hasDashboardRegions()) + document()->setDashboardRegionsDirty(true); +#endif +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderListBox.h b/Source/WebCore/rendering/RenderListBox.h new file mode 100644 index 0000000..1fbff0d --- /dev/null +++ b/Source/WebCore/rendering/RenderListBox.h @@ -0,0 +1,146 @@ +/* + * This file is part of the select element renderer in WebCore. + * + * Copyright (C) 2006, 2007, 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderListBox_h +#define RenderListBox_h + +#include "RenderBlock.h" +#include "ScrollbarClient.h" + +namespace WebCore { + +class RenderListBox : public RenderBlock, private ScrollbarClient { +public: + RenderListBox(Element*); + virtual ~RenderListBox(); + + void selectionChanged(); + + void setOptionsChanged(bool changed) { m_optionsChanged = changed; } + + int listIndexAtOffset(int x, int y); + IntRect itemBoundingBoxRect(int tx, int ty, int index); + + bool scrollToRevealElementAtListIndex(int index); + bool listIndexIsVisible(int index); + + int scrollToward(const IntPoint&); // Returns the new index or -1 if no scroll occurred + +private: + virtual const char* renderName() const { return "RenderListBox"; } + + virtual bool isListBox() const { return true; } + + virtual void updateFromElement(); + + virtual bool canHaveChildren() const { return false; } + + virtual bool hasControlClip() const { return true; } + virtual void paintObject(PaintInfo&, int tx, int ty); + virtual IntRect controlClipRect(int tx, int ty) const; + + virtual bool isPointInOverflowControl(HitTestResult&, int x, int y, int tx, int ty); + + virtual bool scroll(ScrollDirection, ScrollGranularity, float multiplier = 1, Node** stopNode = 0); + virtual bool logicalScroll(ScrollLogicalDirection, ScrollGranularity, float multiplier = 1, Node** stopNode = 0); + + virtual void computePreferredLogicalWidths(); + virtual int baselinePosition(FontBaseline, bool firstLine, LineDirectionMode, LinePositionMode = PositionOnContainingLine) const; + virtual void computeLogicalHeight(); + + virtual void layout(); + + virtual bool canBeProgramaticallyScrolled(bool) const { return true; } + virtual void autoscroll(); + virtual void stopAutoscroll(); + + virtual bool shouldPanScroll() const { return true; } + virtual void panScroll(const IntPoint&); + + virtual int verticalScrollbarWidth() const; + virtual int scrollLeft() const; + virtual int scrollTop() const; + virtual int scrollWidth() const; + virtual int scrollHeight() const; + virtual void setScrollLeft(int); + virtual void setScrollTop(int); + + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + // ScrollbarClient interface. + virtual int scrollSize(ScrollbarOrientation orientation) const; + virtual void setScrollOffsetFromAnimation(const IntPoint&); + virtual void valueChanged(Scrollbar*); + virtual void invalidateScrollbarRect(Scrollbar*, const IntRect&); + virtual bool isActive() const; + virtual bool scrollbarCornerPresent() const { return false; } // We don't support resize on list boxes yet. If we did this would have to change. + virtual IntRect convertFromScrollbarToContainingView(const Scrollbar*, const IntRect&) const; + virtual IntRect convertFromContainingViewToScrollbar(const Scrollbar*, const IntRect&) const; + virtual IntPoint convertFromScrollbarToContainingView(const Scrollbar*, const IntPoint&) const; + virtual IntPoint convertFromContainingViewToScrollbar(const Scrollbar*, const IntPoint&) const; + + void setHasVerticalScrollbar(bool hasScrollbar); + PassRefPtr<Scrollbar> createScrollbar(); + void destroyScrollbar(); + + int itemHeight() const; + void valueChanged(unsigned listIndex); + int size() const; + int numVisibleItems() const; + int numItems() const; + int listHeight() const; + void paintScrollbar(PaintInfo&, int tx, int ty); + void paintItemForeground(PaintInfo&, int tx, int ty, int listIndex); + void paintItemBackground(PaintInfo&, int tx, int ty, int listIndex); + void scrollToRevealSelection(); + + bool m_optionsChanged; + bool m_scrollToRevealSelectionAfterLayout; + bool m_inAutoscroll; + int m_optionsWidth; + int m_indexOffset; + + RefPtr<Scrollbar> m_vBar; +}; + +inline RenderListBox* toRenderListBox(RenderObject* object) +{ + ASSERT(!object || object->isListBox()); + return static_cast<RenderListBox*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderListBox(const RenderListBox*); + +} // namepace WebCore + +#endif // RenderListBox_h diff --git a/Source/WebCore/rendering/RenderListItem.cpp b/Source/WebCore/rendering/RenderListItem.cpp new file mode 100644 index 0000000..65606f3 --- /dev/null +++ b/Source/WebCore/rendering/RenderListItem.cpp @@ -0,0 +1,459 @@ +/** + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2003, 2004, 2005, 2006, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net) + * + * 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 "RenderListItem.h" + +#include "CachedImage.h" +#include "HTMLNames.h" +#include "HTMLOListElement.h" +#include "RenderListMarker.h" +#include "RenderView.h" +#include <wtf/StdLibExtras.h> + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +RenderListItem::RenderListItem(Node* node) + : RenderBlock(node) + , m_marker(0) + , m_hasExplicitValue(false) + , m_isValueUpToDate(false) + , m_notInList(false) +{ + setInline(false); +} + +void RenderListItem::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBlock::styleDidChange(diff, oldStyle); + + if (style()->listStyleType() != NoneListStyle + || (style()->listStyleImage() && !style()->listStyleImage()->errorOccurred())) { + RefPtr<RenderStyle> newStyle = RenderStyle::create(); + // The marker always inherits from the list item, regardless of where it might end + // up (e.g., in some deeply nested line box). See CSS3 spec. + newStyle->inheritFrom(style()); + if (!m_marker) + m_marker = new (renderArena()) RenderListMarker(this); + m_marker->setStyle(newStyle.release()); + } else if (m_marker) { + m_marker->destroy(); + m_marker = 0; + } +} + +void RenderListItem::destroy() +{ + if (m_marker) { + m_marker->destroy(); + m_marker = 0; + } + RenderBlock::destroy(); +} + +static bool isList(Node* node) +{ + return (node->hasTagName(ulTag) || node->hasTagName(olTag)); +} + +static Node* enclosingList(const RenderListItem* listItem) +{ + Node* firstNode = 0; + + for (const RenderObject* renderer = listItem->parent(); renderer; renderer = renderer->parent()) { + Node* node = renderer->node(); + if (node) { + if (isList(node)) + return node; + if (!firstNode) + firstNode = node; + } + } + + // If there's no actual <ul> or <ol> list element, then the first found + // node acts as our list for purposes of determining what other list items + // should be numbered as part of the same list. + return firstNode; +} + +static RenderListItem* previousListItem(Node* list, const RenderListItem* item) +{ + for (RenderObject* renderer = item->previousInPreOrder(); renderer && renderer != list->renderer(); renderer = renderer->previousInPreOrder()) { + if (!renderer->isListItem()) + continue; + Node* otherList = enclosingList(toRenderListItem(renderer)); + // This item is part of our current list, so it's what we're looking for. + if (list == otherList) + return toRenderListItem(renderer); + // We found ourself inside another list; lets skip the rest of it. + // Use nextInPreOrder() here because the other list itself may actually + // be a list item itself. We need to examine it, so we do this to counteract + // the previousInPreOrder() that will be done by the loop. + if (otherList) + renderer = otherList->renderer()->nextInPreOrder(); + } + return 0; +} + +inline int RenderListItem::calcValue() const +{ + if (m_hasExplicitValue) + return m_explicitValue; + Node* list = enclosingList(this); + // FIXME: This recurses to a possible depth of the length of the list. + // That's not good -- we need to change this to an iterative algorithm. + if (RenderListItem* previousItem = previousListItem(list, this)) + return previousItem->value() + 1; + if (list && list->hasTagName(olTag)) + return static_cast<HTMLOListElement*>(list)->start(); + return 1; +} + +void RenderListItem::updateValueNow() const +{ + m_value = calcValue(); + m_isValueUpToDate = true; +} + +bool RenderListItem::isEmpty() const +{ + return lastChild() == m_marker; +} + +static RenderObject* getParentOfFirstLineBox(RenderBlock* curr, RenderObject* marker) +{ + RenderObject* firstChild = curr->firstChild(); + if (!firstChild) + return 0; + + bool inQuirksMode = curr->document()->inQuirksMode(); + for (RenderObject* currChild = firstChild; currChild; currChild = currChild->nextSibling()) { + if (currChild == marker) + continue; + + if (currChild->isInline() && (!currChild->isRenderInline() || curr->generatesLineBoxesForInlineChild(currChild))) + return curr; + + if (currChild->isFloating() || currChild->isPositioned()) + continue; + + if (currChild->isTable() || !currChild->isRenderBlock() || (currChild->isBox() && toRenderBox(currChild)->isWritingModeRoot())) + break; + + if (curr->isListItem() && inQuirksMode && currChild->node() && + (currChild->node()->hasTagName(ulTag)|| currChild->node()->hasTagName(olTag))) + break; + + RenderObject* lineBox = getParentOfFirstLineBox(toRenderBlock(currChild), marker); + if (lineBox) + return lineBox; + } + + return 0; +} + +void RenderListItem::updateValue() +{ + if (!m_hasExplicitValue) { + m_isValueUpToDate = false; + if (m_marker) + m_marker->setNeedsLayoutAndPrefWidthsRecalc(); + } +} + +static RenderObject* firstNonMarkerChild(RenderObject* parent) +{ + RenderObject* result = parent->firstChild(); + while (result && result->isListMarker()) + result = result->nextSibling(); + return result; +} + +void RenderListItem::updateMarkerLocation() +{ + // Sanity check the location of our marker. + if (m_marker) { + RenderObject* markerPar = m_marker->parent(); + RenderObject* lineBoxParent = getParentOfFirstLineBox(this, m_marker); + if (!lineBoxParent) { + // If the marker is currently contained inside an anonymous box, + // then we are the only item in that anonymous box (since no line box + // parent was found). It's ok to just leave the marker where it is + // in this case. + if (markerPar && markerPar->isAnonymousBlock()) + lineBoxParent = markerPar; + else + lineBoxParent = this; + } + + if (markerPar != lineBoxParent || m_marker->preferredLogicalWidthsDirty()) { + // Removing and adding the marker can trigger repainting in + // containers other than ourselves, so we need to disable LayoutState. + view()->disableLayoutState(); + updateFirstLetter(); + m_marker->remove(); + if (!lineBoxParent) + lineBoxParent = this; + lineBoxParent->addChild(m_marker, firstNonMarkerChild(lineBoxParent)); + if (m_marker->preferredLogicalWidthsDirty()) + m_marker->computePreferredLogicalWidths(); + view()->enableLayoutState(); + } + } +} + +void RenderListItem::computePreferredLogicalWidths() +{ + ASSERT(preferredLogicalWidthsDirty()); + + updateMarkerLocation(); + + RenderBlock::computePreferredLogicalWidths(); +} + +void RenderListItem::layout() +{ + ASSERT(needsLayout()); + + updateMarkerLocation(); + RenderBlock::layout(); +} + +void RenderListItem::addOverflowFromChildren() +{ + RenderBlock::addOverflowFromChildren(); + positionListMarker(); +} + +void RenderListItem::positionListMarker() +{ + if (m_marker && m_marker->parent()->isBox() && !m_marker->isInside() && m_marker->inlineBoxWrapper()) { + int markerOldLogicalLeft = m_marker->logicalLeft(); + int blockOffset = 0; + int lineOffset = 0; + for (RenderBox* o = m_marker->parentBox(); o != this; o = o->parentBox()) { + blockOffset += o->logicalTop(); + lineOffset += o->logicalLeft(); + } + + bool adjustOverflow = false; + int markerLogicalLeft; + RootInlineBox* root = m_marker->inlineBoxWrapper()->root(); + bool hitSelfPaintingLayer = false; + + // FIXME: Need to account for relative positioning in the layout overflow. + if (style()->isLeftToRightDirection()) { + int leftLineOffset = logicalLeftOffsetForLine(blockOffset, logicalLeftOffsetForLine(blockOffset, false), false); + markerLogicalLeft = leftLineOffset - lineOffset - paddingStart() - borderStart() + m_marker->marginStart(); + m_marker->inlineBoxWrapper()->adjustLineDirectionPosition(markerLogicalLeft - markerOldLogicalLeft); + for (InlineFlowBox* box = m_marker->inlineBoxWrapper()->parent(); box; box = box->parent()) { + IntRect newLogicalVisualOverflowRect = box->logicalVisualOverflowRect(); + IntRect newLogicalLayoutOverflowRect = box->logicalLayoutOverflowRect(); + if (markerLogicalLeft < newLogicalVisualOverflowRect.x() && !hitSelfPaintingLayer) { + newLogicalVisualOverflowRect.setX(markerLogicalLeft); + newLogicalVisualOverflowRect.setWidth(box->logicalRightVisualOverflow() - newLogicalVisualOverflowRect.x()); + if (box == root) + adjustOverflow = true; + } + if (markerLogicalLeft < newLogicalLayoutOverflowRect.x()) { + newLogicalLayoutOverflowRect.setX(markerLogicalLeft); + newLogicalLayoutOverflowRect.setWidth(box->logicalRightLayoutOverflow() - newLogicalLayoutOverflowRect.x()); + if (box == root) + adjustOverflow = true; + } + box->setOverflowFromLogicalRects(newLogicalLayoutOverflowRect, newLogicalVisualOverflowRect); + if (box->boxModelObject()->hasSelfPaintingLayer()) + hitSelfPaintingLayer = true; + } + } else { + markerLogicalLeft = m_marker->logicalLeft() + paddingStart() + borderStart() + m_marker->marginEnd(); + int rightLineOffset = logicalRightOffsetForLine(blockOffset, logicalRightOffsetForLine(blockOffset, false), false); + markerLogicalLeft = rightLineOffset - lineOffset + paddingStart() + borderStart() + m_marker->marginEnd(); + m_marker->inlineBoxWrapper()->adjustLineDirectionPosition(markerLogicalLeft - markerOldLogicalLeft); + for (InlineFlowBox* box = m_marker->inlineBoxWrapper()->parent(); box; box = box->parent()) { + IntRect newLogicalVisualOverflowRect = box->logicalVisualOverflowRect(); + IntRect newLogicalLayoutOverflowRect = box->logicalLayoutOverflowRect(); + if (markerLogicalLeft + m_marker->logicalWidth() > newLogicalVisualOverflowRect.right() && !hitSelfPaintingLayer) { + newLogicalVisualOverflowRect.setWidth(markerLogicalLeft + m_marker->logicalWidth() - box->logicalLeftVisualOverflow()); + if (box == root) + adjustOverflow = true; + } + if (markerLogicalLeft + m_marker->logicalWidth() > newLogicalLayoutOverflowRect.right()) { + newLogicalLayoutOverflowRect.setWidth(markerLogicalLeft + m_marker->logicalWidth() - box->logicalLeftLayoutOverflow()); + if (box == root) + adjustOverflow = true; + } + box->setOverflowFromLogicalRects(newLogicalLayoutOverflowRect, newLogicalVisualOverflowRect); + + if (box->boxModelObject()->hasSelfPaintingLayer()) + hitSelfPaintingLayer = true; + } + } + + if (adjustOverflow) { + IntRect markerRect(markerLogicalLeft + lineOffset, blockOffset, m_marker->width(), m_marker->height()); + if (!style()->isHorizontalWritingMode()) + markerRect = markerRect.transposedRect(); + RenderBox* o = m_marker; + bool propagateVisualOverflow = true; + bool propagateLayoutOverflow = true; + do { + o = o->parentBox(); + if (o->hasOverflowClip()) + propagateVisualOverflow = false; + if (o->isRenderBlock()) { + if (propagateVisualOverflow) + toRenderBlock(o)->addVisualOverflow(markerRect); + if (propagateLayoutOverflow) + toRenderBlock(o)->addLayoutOverflow(markerRect); + } + if (o->hasOverflowClip()) + propagateLayoutOverflow = false; + if (o->hasSelfPaintingLayer()) + propagateVisualOverflow = false; + markerRect.move(-o->x(), -o->y()); + } while (o != this && propagateVisualOverflow && propagateLayoutOverflow); + } + } +} + +void RenderListItem::paint(PaintInfo& paintInfo, int tx, int ty) +{ + if (!logicalHeight()) + return; + + RenderBlock::paint(paintInfo, tx, ty); +} + +const String& RenderListItem::markerText() const +{ + if (m_marker) + return m_marker->text(); + DEFINE_STATIC_LOCAL(String, staticNullString, ()); + return staticNullString; +} + +String RenderListItem::markerTextWithSuffix() const +{ + if (!m_marker) + return String(); + + // Append the suffix for the marker in the right place depending + // on the direction of the text (right-to-left or left-to-right). + + const String& markerText = m_marker->text(); + const String markerSuffix = m_marker->suffix(); + Vector<UChar> resultVector; + + if (!m_marker->style()->isLeftToRightDirection()) + resultVector.append(markerSuffix.characters(), markerSuffix.length()); + + resultVector.append(markerText.characters(), markerText.length()); + + if (m_marker->style()->isLeftToRightDirection()) + resultVector.append(markerSuffix.characters(), markerSuffix.length()); + + return String::adopt(resultVector); +} + +void RenderListItem::explicitValueChanged() +{ + if (m_marker) + m_marker->setNeedsLayoutAndPrefWidthsRecalc(); + Node* listNode = enclosingList(this); + RenderObject* listRenderer = 0; + if (listNode) + listRenderer = listNode->renderer(); + for (RenderObject* renderer = this; renderer; renderer = renderer->nextInPreOrder(listRenderer)) + if (renderer->isListItem()) { + RenderListItem* item = toRenderListItem(renderer); + if (!item->m_hasExplicitValue) { + item->m_isValueUpToDate = false; + if (RenderListMarker* marker = item->m_marker) + marker->setNeedsLayoutAndPrefWidthsRecalc(); + } + } +} + +void RenderListItem::setExplicitValue(int value) +{ + ASSERT(node()); + + if (m_hasExplicitValue && m_explicitValue == value) + return; + m_explicitValue = value; + m_value = value; + m_hasExplicitValue = true; + explicitValueChanged(); +} + +void RenderListItem::clearExplicitValue() +{ + ASSERT(node()); + + if (!m_hasExplicitValue) + return; + m_hasExplicitValue = false; + m_isValueUpToDate = false; + explicitValueChanged(); +} + +void RenderListItem::updateListMarkerNumbers() +{ + Node* listNode = enclosingList(this); + ASSERT(listNode && listNode->renderer()); + if (!listNode || !listNode->renderer()) + return; + + RenderObject* list = listNode->renderer(); + RenderObject* child = nextInPreOrder(list); + while (child) { + if (child->node() && isList(child->node())) { + // We've found a nested, independent list: nothing to do here. + child = child->nextInPreOrderAfterChildren(list); + continue; + } + + if (child->isListItem()) { + RenderListItem* item = toRenderListItem(child); + + if (!item->m_isValueUpToDate) { + // If an item has been marked for update before, we can safely + // assume that all the following ones have too. + // This gives us the opportunity to stop here and avoid + // marking the same nodes again. + break; + } + + item->updateValue(); + } + + child = child->nextInPreOrder(list); + } +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderListItem.h b/Source/WebCore/rendering/RenderListItem.h new file mode 100644 index 0000000..fe2cb6a --- /dev/null +++ b/Source/WebCore/rendering/RenderListItem.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderListItem_h +#define RenderListItem_h + +#include "RenderBlock.h" + +namespace WebCore { + +class RenderListMarker; + +class RenderListItem : public RenderBlock { +public: + explicit RenderListItem(Node*); + + int value() const { if (!m_isValueUpToDate) updateValueNow(); return m_value; } + void updateValue(); + + bool hasExplicitValue() const { return m_hasExplicitValue; } + int explicitValue() const { return m_explicitValue; } + void setExplicitValue(int value); + void clearExplicitValue(); + + void setNotInList(bool notInList) { m_notInList = notInList; } + bool notInList() const { return m_notInList; } + + const String& markerText() const; + String markerTextWithSuffix() const; + + void updateListMarkerNumbers(); + +private: + virtual const char* renderName() const { return "RenderListItem"; } + + virtual bool isListItem() const { return true; } + + virtual void destroy(); + + virtual bool isEmpty() const; + virtual void paint(PaintInfo&, int tx, int ty); + + virtual void layout(); + virtual void computePreferredLogicalWidths(); + + void positionListMarker(); + + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + virtual bool requiresForcedStyleRecalcPropagation() const { return true; } + + virtual void addOverflowFromChildren(); + + void updateMarkerLocation(); + inline int calcValue() const; + void updateValueNow() const; + void explicitValueChanged(); + + RenderListMarker* m_marker; + int m_explicitValue; + mutable int m_value; + + bool m_hasExplicitValue : 1; + mutable bool m_isValueUpToDate : 1; + bool m_notInList : 1; +}; + +inline RenderListItem* toRenderListItem(RenderObject* object) +{ + ASSERT(!object || object->isListItem()); + return static_cast<RenderListItem*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderListItem(const RenderListItem*); + +} // namespace WebCore + +#endif // RenderListItem_h diff --git a/Source/WebCore/rendering/RenderListMarker.cpp b/Source/WebCore/rendering/RenderListMarker.cpp new file mode 100644 index 0000000..71b1eae --- /dev/null +++ b/Source/WebCore/rendering/RenderListMarker.cpp @@ -0,0 +1,1720 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net) + * Copyright (C) 2010 Daniel Bates (dbates@intudata.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 "RenderListMarker.h" + +#include "CachedImage.h" +#include "CharacterNames.h" +#include "Document.h" +#include "GraphicsContext.h" +#include "RenderLayer.h" +#include "RenderListItem.h" +#include "RenderView.h" + +using namespace std; +using namespace WTF; +using namespace Unicode; + +namespace WebCore { + +const int cMarkerPadding = 7; + +enum SequenceType { NumericSequence, AlphabeticSequence }; + +static String toRoman(int number, bool upper) +{ + // FIXME: CSS3 describes how to make this work for much larger numbers, + // using overbars and special characters. It also specifies the characters + // in the range U+2160 to U+217F instead of standard ASCII ones. + ASSERT(number >= 1 && number <= 3999); + + // Big enough to store largest roman number less than 3999 which + // is 3888 (MMMDCCCLXXXVIII) + const int lettersSize = 15; + UChar letters[lettersSize]; + + int length = 0; + const UChar ldigits[] = { 'i', 'v', 'x', 'l', 'c', 'd', 'm' }; + const UChar udigits[] = { 'I', 'V', 'X', 'L', 'C', 'D', 'M' }; + const UChar* digits = upper ? udigits : ldigits; + int d = 0; + do { + int num = number % 10; + if (num % 5 < 4) + for (int i = num % 5; i > 0; i--) + letters[lettersSize - ++length] = digits[d]; + if (num >= 4 && num <= 8) + letters[lettersSize - ++length] = digits[d + 1]; + if (num == 9) + letters[lettersSize - ++length] = digits[d + 2]; + if (num % 5 == 4) + letters[lettersSize - ++length] = digits[d]; + number /= 10; + d += 2; + } while (number); + + ASSERT(length <= lettersSize); + return String(&letters[lettersSize - length], length); +} + +static inline String toAlphabeticOrNumeric(int number, const UChar* sequence, unsigned sequenceSize, SequenceType type) +{ + ASSERT(sequenceSize >= 2); + + const int lettersSize = sizeof(number) * 8 + 1; // Binary is the worst case; requires one character per bit plus a minus sign. + + UChar letters[lettersSize]; + + bool isNegativeNumber = false; + unsigned numberShadow = number; + if (type == AlphabeticSequence) { + ASSERT(number > 0); + --numberShadow; + } else if (number < 0) { + numberShadow = -number; + isNegativeNumber = true; + } + letters[lettersSize - 1] = sequence[numberShadow % sequenceSize]; + int length = 1; + + if (type == AlphabeticSequence) { + while ((numberShadow /= sequenceSize) > 0) { + --numberShadow; + letters[lettersSize - ++length] = sequence[numberShadow % sequenceSize]; + } + } else { + while ((numberShadow /= sequenceSize) > 0) + letters[lettersSize - ++length] = sequence[numberShadow % sequenceSize]; + } + if (isNegativeNumber) + letters[lettersSize - ++length] = hyphenMinus; + + ASSERT(length <= lettersSize); + return String(&letters[lettersSize - length], length); +} + +static String toSymbolic(int number, const UChar* symbols, unsigned symbolsSize) +{ + ASSERT(number > 0); + ASSERT(symbolsSize >= 1); + unsigned numberShadow = number; + --numberShadow; + + // The asterisks list-style-type is the worst case; we show |numberShadow| asterisks. + Vector<UChar> letters; + letters.append(symbols[numberShadow % symbolsSize]); + unsigned numSymbols = numberShadow / symbolsSize; + while (numSymbols--) + letters.append(symbols[numberShadow % symbolsSize]); + return String::adopt(letters); +} + +static String toAlphabetic(int number, const UChar* alphabet, unsigned alphabetSize) +{ + return toAlphabeticOrNumeric(number, alphabet, alphabetSize, AlphabeticSequence); +} + +static String toNumeric(int number, const UChar* numerals, unsigned numeralsSize) +{ + return toAlphabeticOrNumeric(number, numerals, numeralsSize, NumericSequence); +} + +template <size_t size> static inline String toAlphabetic(int number, const UChar(&alphabet)[size]) +{ + return toAlphabetic(number, alphabet, size); +} + +template <size_t size> static inline String toNumeric(int number, const UChar(&alphabet)[size]) +{ + return toNumeric(number, alphabet, size); +} + +template <size_t size> static inline String toSymbolic(int number, const UChar(&alphabet)[size]) +{ + return toSymbolic(number, alphabet, size); +} + +static int toHebrewUnder1000(int number, UChar letters[5]) +{ + // FIXME: CSS3 mentions various refinements not implemented here. + // FIXME: Should take a look at Mozilla's HebrewToText function (in nsBulletFrame). + ASSERT(number >= 0 && number < 1000); + int length = 0; + int fourHundreds = number / 400; + for (int i = 0; i < fourHundreds; i++) + letters[length++] = 1511 + 3; + number %= 400; + if (number / 100) + letters[length++] = 1511 + (number / 100) - 1; + number %= 100; + if (number == 15 || number == 16) { + letters[length++] = 1487 + 9; + letters[length++] = 1487 + number - 9; + } else { + if (int tens = number / 10) { + static const UChar hebrewTens[9] = { 1497, 1499, 1500, 1502, 1504, 1505, 1506, 1508, 1510 }; + letters[length++] = hebrewTens[tens - 1]; + } + if (int ones = number % 10) + letters[length++] = 1487 + ones; + } + ASSERT(length <= 5); + return length; +} + +static String toHebrew(int number) +{ + // FIXME: CSS3 mentions ways to make this work for much larger numbers. + ASSERT(number >= 0 && number <= 999999); + + if (number == 0) { + static const UChar hebrewZero[3] = { 0x05D0, 0x05E4, 0x05E1 }; + return String(hebrewZero, 3); + } + + const int lettersSize = 11; // big enough for two 5-digit sequences plus a quote mark between + UChar letters[lettersSize]; + + int length; + if (number < 1000) + length = 0; + else { + length = toHebrewUnder1000(number / 1000, letters); + letters[length++] = '\''; + number = number % 1000; + } + length += toHebrewUnder1000(number, letters + length); + + ASSERT(length <= lettersSize); + return String(letters, length); +} + +static int toArmenianUnder10000(int number, bool upper, bool addCircumflex, UChar letters[9]) +{ + ASSERT(number >= 0 && number < 10000); + int length = 0; + + int lowerOffset = upper ? 0 : 0x0030; + + if (int thousands = number / 1000) { + if (thousands == 7) { + letters[length++] = 0x0548 + lowerOffset; + letters[length++] = 0x0552 + lowerOffset; + if (addCircumflex) + letters[length++] = 0x0302; + } else { + letters[length++] = (0x054C - 1 + lowerOffset) + thousands; + if (addCircumflex) + letters[length++] = 0x0302; + } + } + + if (int hundreds = (number / 100) % 10) { + letters[length++] = (0x0543 - 1 + lowerOffset) + hundreds; + if (addCircumflex) + letters[length++] = 0x0302; + } + + if (int tens = (number / 10) % 10) { + letters[length++] = (0x053A - 1 + lowerOffset) + tens; + if (addCircumflex) + letters[length++] = 0x0302; + } + + if (int ones = number % 10) { + letters[length++] = (0x531 - 1 + lowerOffset) + ones; + if (addCircumflex) + letters[length++] = 0x0302; + } + + return length; +} + +static String toArmenian(int number, bool upper) +{ + ASSERT(number >= 1 && number <= 99999999); + + const int lettersSize = 18; // twice what toArmenianUnder10000 needs + UChar letters[lettersSize]; + + int length = toArmenianUnder10000(number / 10000, upper, true, letters); + length += toArmenianUnder10000(number % 10000, upper, false, letters + length); + + ASSERT(length <= lettersSize); + return String(letters, length); +} + +static String toGeorgian(int number) +{ + ASSERT(number >= 1 && number <= 19999); + + const int lettersSize = 5; + UChar letters[lettersSize]; + + int length = 0; + + if (number > 9999) + letters[length++] = 0x10F5; + + if (int thousands = (number / 1000) % 10) { + static const UChar georgianThousands[9] = { + 0x10E9, 0x10EA, 0x10EB, 0x10EC, 0x10ED, 0x10EE, 0x10F4, 0x10EF, 0x10F0 + }; + letters[length++] = georgianThousands[thousands - 1]; + } + + if (int hundreds = (number / 100) % 10) { + static const UChar georgianHundreds[9] = { + 0x10E0, 0x10E1, 0x10E2, 0x10F3, 0x10E4, 0x10E5, 0x10E6, 0x10E7, 0x10E8 + }; + letters[length++] = georgianHundreds[hundreds - 1]; + } + + if (int tens = (number / 10) % 10) { + static const UChar georgianTens[9] = { + 0x10D8, 0x10D9, 0x10DA, 0x10DB, 0x10DC, 0x10F2, 0x10DD, 0x10DE, 0x10DF + }; + letters[length++] = georgianTens[tens - 1]; + } + + if (int ones = number % 10) { + static const UChar georgianOnes[9] = { + 0x10D0, 0x10D1, 0x10D2, 0x10D3, 0x10D4, 0x10D5, 0x10D6, 0x10F1, 0x10D7 + }; + letters[length++] = georgianOnes[ones - 1]; + } + + ASSERT(length <= lettersSize); + return String(letters, length); +} + +// The table uses the order from the CSS3 specification: +// first 3 group markers, then 3 digit markers, then ten digits. +static String toCJKIdeographic(int number, const UChar table[16]) +{ + ASSERT(number >= 0); + + enum AbstractCJKChar { + noChar, + secondGroupMarker, thirdGroupMarker, fourthGroupMarker, + secondDigitMarker, thirdDigitMarker, fourthDigitMarker, + digit0, digit1, digit2, digit3, digit4, + digit5, digit6, digit7, digit8, digit9 + }; + + if (number == 0) + return String(&table[digit0 - 1], 1); + + const int groupLength = 8; // 4 digits, 3 digit markers, and a group marker + const int bufferLength = 4 * groupLength; + AbstractCJKChar buffer[bufferLength] = { noChar }; + + for (int i = 0; i < 4; ++i) { + int groupValue = number % 10000; + number /= 10000; + + // Process least-significant group first, but put it in the buffer last. + AbstractCJKChar* group = &buffer[(3 - i) * groupLength]; + + if (groupValue && i) + group[7] = static_cast<AbstractCJKChar>(secondGroupMarker - 1 + i); + + // Put in the four digits and digit markers for any non-zero digits. + group[6] = static_cast<AbstractCJKChar>(digit0 + (groupValue % 10)); + if (number != 0 || groupValue > 9) { + int digitValue = ((groupValue / 10) % 10); + group[4] = static_cast<AbstractCJKChar>(digit0 + digitValue); + if (digitValue) + group[5] = secondDigitMarker; + } + if (number != 0 || groupValue > 99) { + int digitValue = ((groupValue / 100) % 10); + group[2] = static_cast<AbstractCJKChar>(digit0 + digitValue); + if (digitValue) + group[3] = thirdDigitMarker; + } + if (number != 0 || groupValue > 999) { + int digitValue = groupValue / 1000; + group[0] = static_cast<AbstractCJKChar>(digit0 + digitValue); + if (digitValue) + group[1] = fourthDigitMarker; + } + + // Remove the tens digit, but leave the marker, for any group that has + // a value of less than 20. + if (groupValue < 20) { + ASSERT(group[4] == noChar || group[4] == digit0 || group[4] == digit1); + group[4] = noChar; + } + + if (number == 0) + break; + } + + // Convert into characters, omitting consecutive runs of digit0 and + // any trailing digit0. + int length = 0; + UChar characters[bufferLength]; + AbstractCJKChar last = noChar; + for (int i = 0; i < bufferLength; ++i) { + AbstractCJKChar a = buffer[i]; + if (a != noChar) { + if (a != digit0 || last != digit0) + characters[length++] = table[a - 1]; + last = a; + } + } + if (last == digit0) + --length; + + return String(characters, length); +} + +static EListStyleType effectiveListMarkerType(EListStyleType type, int value) +{ + // Note, the following switch statement has been explicitly grouped + // by list-style-type ordinal range. + switch (type) { + case ArabicIndic: + case Bengali: + case BinaryListStyle: + case Cambodian: + case Circle: + case DecimalLeadingZero: + case DecimalListStyle: + case Devanagari: + case Disc: + case Gujarati: + case Gurmukhi: + case Kannada: + case Khmer: + case Lao: + case LowerHexadecimal: + case Malayalam: + case Mongolian: + case Myanmar: + case NoneListStyle: + case Octal: + case Oriya: + case Persian: + case Square: + case Telugu: + case Thai: + case Tibetan: + case UpperHexadecimal: + case Urdu: + return type; // Can represent all ordinals. + case Armenian: + return (value < 1 || value > 99999999) ? DecimalListStyle : type; + case CJKIdeographic: + return (value < 0) ? DecimalListStyle : type; + case Georgian: + return (value < 1 || value > 19999) ? DecimalListStyle : type; + case Hebrew: + return (value < 0 || value > 999999) ? DecimalListStyle : type; + case LowerRoman: + case UpperRoman: + return (value < 1 || value > 3999) ? DecimalListStyle : type; + case Afar: + case Amharic: + case AmharicAbegede: + case Asterisks: + case CjkEarthlyBranch: + case CjkHeavenlyStem: + case Ethiopic: + case EthiopicAbegede: + case EthiopicAbegedeAmEt: + case EthiopicAbegedeGez: + case EthiopicAbegedeTiEr: + case EthiopicAbegedeTiEt: + case EthiopicHalehameAaEr: + case EthiopicHalehameAaEt: + case EthiopicHalehameAmEt: + case EthiopicHalehameGez: + case EthiopicHalehameOmEt: + case EthiopicHalehameSidEt: + case EthiopicHalehameSoEt: + case EthiopicHalehameTiEr: + case EthiopicHalehameTiEt: + case EthiopicHalehameTig: + case Footnotes: + case Hangul: + case HangulConsonant: + case Hiragana: + case HiraganaIroha: + case Katakana: + case KatakanaIroha: + case LowerAlpha: + case LowerArmenian: + case LowerGreek: + case LowerLatin: + case LowerNorwegian: + case Oromo: + case Sidama: + case Somali: + case Tigre: + case TigrinyaEr: + case TigrinyaErAbegede: + case TigrinyaEt: + case TigrinyaEtAbegede: + case UpperAlpha: + case UpperArmenian: + case UpperGreek: + case UpperLatin: + case UpperNorwegian: + return (value < 1) ? DecimalListStyle : type; + } + + ASSERT_NOT_REACHED(); + return type; +} + +static UChar listMarkerSuffix(EListStyleType type, int value) +{ + // If the list-style-type cannot represent |value| because it's outside its + // ordinal range then we fall back to some list style that can represent |value|. + EListStyleType effectiveType = effectiveListMarkerType(type, value); + + // Note, the following switch statement has been explicitly + // grouped by list-style-type suffix. + switch (effectiveType) { + case Asterisks: + case Circle: + case Disc: + case Footnotes: + case NoneListStyle: + case Square: + return ' '; + case Afar: + case Amharic: + case AmharicAbegede: + case Ethiopic: + case EthiopicAbegede: + case EthiopicAbegedeAmEt: + case EthiopicAbegedeGez: + case EthiopicAbegedeTiEr: + case EthiopicAbegedeTiEt: + case EthiopicHalehameAaEr: + case EthiopicHalehameAaEt: + case EthiopicHalehameAmEt: + case EthiopicHalehameGez: + case EthiopicHalehameOmEt: + case EthiopicHalehameSidEt: + case EthiopicHalehameSoEt: + case EthiopicHalehameTiEr: + case EthiopicHalehameTiEt: + case EthiopicHalehameTig: + case Oromo: + case Sidama: + case Somali: + case Tigre: + case TigrinyaEr: + case TigrinyaErAbegede: + case TigrinyaEt: + case TigrinyaEtAbegede: + return ethiopicPrefaceColon; + case Armenian: + case ArabicIndic: + case Bengali: + case BinaryListStyle: + case Cambodian: + case CJKIdeographic: + case CjkEarthlyBranch: + case CjkHeavenlyStem: + case DecimalLeadingZero: + case DecimalListStyle: + case Devanagari: + case Georgian: + case Gujarati: + case Gurmukhi: + case Hangul: + case HangulConsonant: + case Hebrew: + case Hiragana: + case HiraganaIroha: + case Kannada: + case Katakana: + case KatakanaIroha: + case Khmer: + case Lao: + case LowerAlpha: + case LowerArmenian: + case LowerGreek: + case LowerHexadecimal: + case LowerLatin: + case LowerNorwegian: + case LowerRoman: + case Malayalam: + case Mongolian: + case Myanmar: + case Octal: + case Oriya: + case Persian: + case Telugu: + case Thai: + case Tibetan: + case UpperAlpha: + case UpperArmenian: + case UpperGreek: + case UpperHexadecimal: + case UpperLatin: + case UpperNorwegian: + case UpperRoman: + case Urdu: + return '.'; + } + + ASSERT_NOT_REACHED(); + return '.'; +} + +String listMarkerText(EListStyleType type, int value) +{ + // If the list-style-type, say hebrew, cannot represent |value| because it's outside + // its ordinal range then we fallback to some list style that can represent |value|. + switch (effectiveListMarkerType(type, value)) { + case NoneListStyle: + return ""; + + case Asterisks: { + static const UChar asterisksSymbols[1] = { + 0x002A + }; + return toSymbolic(value, asterisksSymbols); + } + // We use the same characters for text security. + // See RenderText::setInternalString. + case Circle: + return String(&whiteBullet, 1); + case Disc: + return String(&bullet, 1); + case Footnotes: { + static const UChar footnotesSymbols[4] = { + 0x002A, 0x2051, 0x2020, 0x2021 + }; + return toSymbolic(value, footnotesSymbols); + } + case Square: + // The CSS 2.1 test suite uses U+25EE BLACK MEDIUM SMALL SQUARE + // instead, but I think this looks better. + return String(&blackSquare, 1); + + case DecimalListStyle: + return String::number(value); + case DecimalLeadingZero: + if (value < -9 || value > 9) + return String::number(value); + if (value < 0) + return "-0" + String::number(-value); // -01 to -09 + return "0" + String::number(value); // 00 to 09 + + case ArabicIndic: { + static const UChar arabicIndicNumerals[10] = { + 0x0660, 0x0661, 0x0662, 0x0663, 0x0664, 0x0665, 0x0666, 0x0667, 0x0668, 0x0669 + }; + return toNumeric(value, arabicIndicNumerals); + } + case BinaryListStyle: { + static const UChar binaryNumerals[2] = { + '0', '1' + }; + return toNumeric(value, binaryNumerals); + } + case Bengali: { + static const UChar bengaliNumerals[10] = { + 0x09E6, 0x09E7, 0x09E8, 0x09E9, 0x09EA, 0x09EB, 0x09EC, 0x09ED, 0x09EE, 0x09EF + }; + return toNumeric(value, bengaliNumerals); + } + case Cambodian: + case Khmer: { + static const UChar khmerNumerals[10] = { + 0x17E0, 0x17E1, 0x17E2, 0x17E3, 0x17E4, 0x17E5, 0x17E6, 0x17E7, 0x17E8, 0x17E9 + }; + return toNumeric(value, khmerNumerals); + } + case Devanagari: { + static const UChar devanagariNumerals[10] = { + 0x0966, 0x0967, 0x0968, 0x0969, 0x096A, 0x096B, 0x096C, 0x096D, 0x096E, 0x096F + }; + return toNumeric(value, devanagariNumerals); + } + case Gujarati: { + static const UChar gujaratiNumerals[10] = { + 0x0AE6, 0x0AE7, 0x0AE8, 0x0AE9, 0x0AEA, 0x0AEB, 0x0AEC, 0x0AED, 0x0AEE, 0x0AEF + }; + return toNumeric(value, gujaratiNumerals); + } + case Gurmukhi: { + static const UChar gurmukhiNumerals[10] = { + 0x0A66, 0x0A67, 0x0A68, 0x0A69, 0x0A6A, 0x0A6B, 0x0A6C, 0x0A6D, 0x0A6E, 0x0A6F + }; + return toNumeric(value, gurmukhiNumerals); + } + case Kannada: { + static const UChar kannadaNumerals[10] = { + 0x0CE6, 0x0CE7, 0x0CE8, 0x0CE9, 0x0CEA, 0x0CEB, 0x0CEC, 0x0CED, 0x0CEE, 0x0CEF + }; + return toNumeric(value, kannadaNumerals); + } + case LowerHexadecimal: { + static const UChar lowerHexadecimalNumerals[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + return toNumeric(value, lowerHexadecimalNumerals); + } + case Lao: { + static const UChar laoNumerals[10] = { + 0x0ED0, 0x0ED1, 0x0ED2, 0x0ED3, 0x0ED4, 0x0ED5, 0x0ED6, 0x0ED7, 0x0ED8, 0x0ED9 + }; + return toNumeric(value, laoNumerals); + } + case Malayalam: { + static const UChar malayalamNumerals[10] = { + 0x0D66, 0x0D67, 0x0D68, 0x0D69, 0x0D6A, 0x0D6B, 0x0D6C, 0x0D6D, 0x0D6E, 0x0D6F + }; + return toNumeric(value, malayalamNumerals); + } + case Mongolian: { + static const UChar mongolianNumerals[10] = { + 0x1810, 0x1811, 0x1812, 0x1813, 0x1814, 0x1815, 0x1816, 0x1817, 0x1818, 0x1819 + }; + return toNumeric(value, mongolianNumerals); + } + case Myanmar: { + static const UChar myanmarNumerals[10] = { + 0x1040, 0x1041, 0x1042, 0x1043, 0x1044, 0x1045, 0x1046, 0x1047, 0x1048, 0x1049 + }; + return toNumeric(value, myanmarNumerals); + } + case Octal: { + static const UChar octalNumerals[8] = { + '0', '1', '2', '3', '4', '5', '6', '7' + }; + return toNumeric(value, octalNumerals); + } + case Oriya: { + static const UChar oriyaNumerals[10] = { + 0x0B66, 0x0B67, 0x0B68, 0x0B69, 0x0B6A, 0x0B6B, 0x0B6C, 0x0B6D, 0x0B6E, 0x0B6F + }; + return toNumeric(value, oriyaNumerals); + } + case Persian: + case Urdu: { + static const UChar urduNumerals[10] = { + 0x06F0, 0x06F1, 0x06F2, 0x06F3, 0x06F4, 0x06F5, 0x06F6, 0x06F7, 0x06F8, 0x06F9 + }; + return toNumeric(value, urduNumerals); + } + case Telugu: { + static const UChar teluguNumerals[10] = { + 0x0C66, 0x0C67, 0x0C68, 0x0C69, 0x0C6A, 0x0C6B, 0x0C6C, 0x0C6D, 0x0C6E, 0x0C6F + }; + return toNumeric(value, teluguNumerals); + } + case Tibetan: { + static const UChar tibetanNumerals[10] = { + 0x0F20, 0x0F21, 0x0F22, 0x0F23, 0x0F24, 0x0F25, 0x0F26, 0x0F27, 0x0F28, 0x0F29 + }; + return toNumeric(value, tibetanNumerals); + } + case Thai: { + static const UChar thaiNumerals[10] = { + 0x0E50, 0x0E51, 0x0E52, 0x0E53, 0x0E54, 0x0E55, 0x0E56, 0x0E57, 0x0E58, 0x0E59 + }; + return toNumeric(value, thaiNumerals); + } + case UpperHexadecimal: { + static const UChar upperHexadecimalNumerals[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + return toNumeric(value, upperHexadecimalNumerals); + } + + case LowerAlpha: + case LowerLatin: { + static const UChar lowerLatinAlphabet[26] = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' + }; + return toAlphabetic(value, lowerLatinAlphabet); + } + case UpperAlpha: + case UpperLatin: { + static const UChar upperLatinAlphabet[26] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' + }; + return toAlphabetic(value, upperLatinAlphabet); + } + case LowerGreek: { + static const UChar lowerGreekAlphabet[24] = { + 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, 0x03B8, + 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0, + 0x03C1, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7, 0x03C8, 0x03C9 + }; + return toAlphabetic(value, lowerGreekAlphabet); + } + + case Hiragana: { + // FIXME: This table comes from the CSS3 draft, and is probably + // incorrect, given the comments in that draft. + static const UChar hiraganaAlphabet[48] = { + 0x3042, 0x3044, 0x3046, 0x3048, 0x304A, 0x304B, 0x304D, 0x304F, + 0x3051, 0x3053, 0x3055, 0x3057, 0x3059, 0x305B, 0x305D, 0x305F, + 0x3061, 0x3064, 0x3066, 0x3068, 0x306A, 0x306B, 0x306C, 0x306D, + 0x306E, 0x306F, 0x3072, 0x3075, 0x3078, 0x307B, 0x307E, 0x307F, + 0x3080, 0x3081, 0x3082, 0x3084, 0x3086, 0x3088, 0x3089, 0x308A, + 0x308B, 0x308C, 0x308D, 0x308F, 0x3090, 0x3091, 0x3092, 0x3093 + }; + return toAlphabetic(value, hiraganaAlphabet); + } + case HiraganaIroha: { + // FIXME: This table comes from the CSS3 draft, and is probably + // incorrect, given the comments in that draft. + static const UChar hiraganaIrohaAlphabet[47] = { + 0x3044, 0x308D, 0x306F, 0x306B, 0x307B, 0x3078, 0x3068, 0x3061, + 0x308A, 0x306C, 0x308B, 0x3092, 0x308F, 0x304B, 0x3088, 0x305F, + 0x308C, 0x305D, 0x3064, 0x306D, 0x306A, 0x3089, 0x3080, 0x3046, + 0x3090, 0x306E, 0x304A, 0x304F, 0x3084, 0x307E, 0x3051, 0x3075, + 0x3053, 0x3048, 0x3066, 0x3042, 0x3055, 0x304D, 0x3086, 0x3081, + 0x307F, 0x3057, 0x3091, 0x3072, 0x3082, 0x305B, 0x3059 + }; + return toAlphabetic(value, hiraganaIrohaAlphabet); + } + case Katakana: { + // FIXME: This table comes from the CSS3 draft, and is probably + // incorrect, given the comments in that draft. + static const UChar katakanaAlphabet[48] = { + 0x30A2, 0x30A4, 0x30A6, 0x30A8, 0x30AA, 0x30AB, 0x30AD, 0x30AF, + 0x30B1, 0x30B3, 0x30B5, 0x30B7, 0x30B9, 0x30BB, 0x30BD, 0x30BF, + 0x30C1, 0x30C4, 0x30C6, 0x30C8, 0x30CA, 0x30CB, 0x30CC, 0x30CD, + 0x30CE, 0x30CF, 0x30D2, 0x30D5, 0x30D8, 0x30DB, 0x30DE, 0x30DF, + 0x30E0, 0x30E1, 0x30E2, 0x30E4, 0x30E6, 0x30E8, 0x30E9, 0x30EA, + 0x30EB, 0x30EC, 0x30ED, 0x30EF, 0x30F0, 0x30F1, 0x30F2, 0x30F3 + }; + return toAlphabetic(value, katakanaAlphabet); + } + case KatakanaIroha: { + // FIXME: This table comes from the CSS3 draft, and is probably + // incorrect, given the comments in that draft. + static const UChar katakanaIrohaAlphabet[47] = { + 0x30A4, 0x30ED, 0x30CF, 0x30CB, 0x30DB, 0x30D8, 0x30C8, 0x30C1, + 0x30EA, 0x30CC, 0x30EB, 0x30F2, 0x30EF, 0x30AB, 0x30E8, 0x30BF, + 0x30EC, 0x30BD, 0x30C4, 0x30CD, 0x30CA, 0x30E9, 0x30E0, 0x30A6, + 0x30F0, 0x30CE, 0x30AA, 0x30AF, 0x30E4, 0x30DE, 0x30B1, 0x30D5, + 0x30B3, 0x30A8, 0x30C6, 0x30A2, 0x30B5, 0x30AD, 0x30E6, 0x30E1, + 0x30DF, 0x30B7, 0x30F1, 0x30D2, 0x30E2, 0x30BB, 0x30B9 + }; + return toAlphabetic(value, katakanaIrohaAlphabet); + } + + case Afar: + case EthiopicHalehameAaEt: + case EthiopicHalehameAaEr: { + static const UChar ethiopicHalehameAaErAlphabet[18] = { + 0x1200, 0x1208, 0x1210, 0x1218, 0x1228, 0x1230, 0x1260, 0x1270, 0x1290, + 0x12A0, 0x12A8, 0x12C8, 0x12D0, 0x12E8, 0x12F0, 0x1308, 0x1338, 0x1348 + }; + return toAlphabetic(value, ethiopicHalehameAaErAlphabet); + } + case Amharic: + case EthiopicHalehameAmEt: { + static const UChar ethiopicHalehameAmEtAlphabet[33] = { + 0x1200, 0x1208, 0x1210, 0x1218, 0x1220, 0x1228, 0x1230, 0x1238, 0x1240, + 0x1260, 0x1270, 0x1278, 0x1280, 0x1290, 0x1298, 0x12A0, 0x12A8, 0x12B8, + 0x12C8, 0x12D0, 0x12D8, 0x12E0, 0x12E8, 0x12F0, 0x1300, 0x1308, 0x1320, + 0x1328, 0x1330, 0x1338, 0x1340, 0x1348, 0x1350 + }; + return toAlphabetic(value, ethiopicHalehameAmEtAlphabet); + } + case AmharicAbegede: + case EthiopicAbegedeAmEt: { + static const UChar ethiopicAbegedeAmEtAlphabet[33] = { + 0x12A0, 0x1260, 0x1308, 0x12F0, 0x1300, 0x1200, 0x12C8, 0x12D8, 0x12E0, + 0x1210, 0x1320, 0x1328, 0x12E8, 0x12A8, 0x12B8, 0x1208, 0x1218, 0x1290, + 0x1298, 0x1220, 0x12D0, 0x1348, 0x1338, 0x1240, 0x1228, 0x1230, 0x1238, + 0x1270, 0x1278, 0x1280, 0x1340, 0x1330, 0x1350 + }; + return toAlphabetic(value, ethiopicAbegedeAmEtAlphabet); + } + case CjkEarthlyBranch: { + static const UChar cjkEarthlyBranchAlphabet[12] = { + 0x5B50, 0x4E11, 0x5BC5, 0x536F, 0x8FB0, 0x5DF3, 0x5348, 0x672A, 0x7533, + 0x9149, 0x620C, 0x4EA5 + }; + return toAlphabetic(value, cjkEarthlyBranchAlphabet); + } + case CjkHeavenlyStem: { + static const UChar cjkHeavenlyStemAlphabet[10] = { + 0x7532, 0x4E59, 0x4E19, 0x4E01, 0x620A, 0x5DF1, 0x5E9A, 0x8F9B, 0x58EC, + 0x7678 + }; + return toAlphabetic(value, cjkHeavenlyStemAlphabet); + } + case Ethiopic: + case EthiopicHalehameGez: { + static const UChar ethiopicHalehameGezAlphabet[26] = { + 0x1200, 0x1208, 0x1210, 0x1218, 0x1220, 0x1228, 0x1230, 0x1240, 0x1260, + 0x1270, 0x1280, 0x1290, 0x12A0, 0x12A8, 0x12C8, 0x12D0, 0x12D8, 0x12E8, + 0x12F0, 0x1308, 0x1320, 0x1330, 0x1338, 0x1340, 0x1348, 0x1350 + }; + return toAlphabetic(value, ethiopicHalehameGezAlphabet); + } + case EthiopicAbegede: + case EthiopicAbegedeGez: { + static const UChar ethiopicAbegedeGezAlphabet[26] = { + 0x12A0, 0x1260, 0x1308, 0x12F0, 0x1200, 0x12C8, 0x12D8, 0x1210, 0x1320, + 0x12E8, 0x12A8, 0x1208, 0x1218, 0x1290, 0x1220, 0x12D0, 0x1348, 0x1338, + 0x1240, 0x1228, 0x1230, 0x1270, 0x1280, 0x1340, 0x1330, 0x1350 + }; + return toAlphabetic(value, ethiopicAbegedeGezAlphabet); + } + case HangulConsonant: { + static const UChar hangulConsonantAlphabet[14] = { + 0x3131, 0x3134, 0x3137, 0x3139, 0x3141, 0x3142, 0x3145, 0x3147, 0x3148, + 0x314A, 0x314B, 0x314C, 0x314D, 0x314E + }; + return toAlphabetic(value, hangulConsonantAlphabet); + } + case Hangul: { + static const UChar hangulAlphabet[14] = { + 0xAC00, 0xB098, 0xB2E4, 0xB77C, 0xB9C8, 0xBC14, 0xC0AC, 0xC544, 0xC790, + 0xCC28, 0xCE74, 0xD0C0, 0xD30C, 0xD558 + }; + return toAlphabetic(value, hangulAlphabet); + } + case Oromo: + case EthiopicHalehameOmEt: { + static const UChar ethiopicHalehameOmEtAlphabet[25] = { + 0x1200, 0x1208, 0x1218, 0x1228, 0x1230, 0x1238, 0x1240, 0x1260, 0x1270, + 0x1278, 0x1290, 0x1298, 0x12A0, 0x12A8, 0x12C8, 0x12E8, 0x12F0, 0x12F8, + 0x1300, 0x1308, 0x1320, 0x1328, 0x1338, 0x1330, 0x1348 + }; + return toAlphabetic(value, ethiopicHalehameOmEtAlphabet); + } + case Sidama: + case EthiopicHalehameSidEt: { + static const UChar ethiopicHalehameSidEtAlphabet[26] = { + 0x1200, 0x1208, 0x1210, 0x1218, 0x1228, 0x1230, 0x1238, 0x1240, 0x1260, + 0x1270, 0x1278, 0x1290, 0x1298, 0x12A0, 0x12A8, 0x12C8, 0x12E8, 0x12F0, + 0x12F8, 0x1300, 0x1308, 0x1320, 0x1328, 0x1338, 0x1330, 0x1348 + }; + return toAlphabetic(value, ethiopicHalehameSidEtAlphabet); + } + case Somali: + case EthiopicHalehameSoEt: { + static const UChar ethiopicHalehameSoEtAlphabet[22] = { + 0x1200, 0x1208, 0x1210, 0x1218, 0x1228, 0x1230, 0x1238, 0x1240, 0x1260, + 0x1270, 0x1290, 0x12A0, 0x12A8, 0x12B8, 0x12C8, 0x12D0, 0x12E8, 0x12F0, + 0x1300, 0x1308, 0x1338, 0x1348 + }; + return toAlphabetic(value, ethiopicHalehameSoEtAlphabet); + } + case Tigre: + case EthiopicHalehameTig: { + static const UChar ethiopicHalehameTigAlphabet[27] = { + 0x1200, 0x1208, 0x1210, 0x1218, 0x1228, 0x1230, 0x1238, 0x1240, 0x1260, + 0x1270, 0x1278, 0x1290, 0x12A0, 0x12A8, 0x12C8, 0x12D0, 0x12D8, 0x12E8, + 0x12F0, 0x1300, 0x1308, 0x1320, 0x1328, 0x1338, 0x1330, 0x1348, 0x1350 + }; + return toAlphabetic(value, ethiopicHalehameTigAlphabet); + } + case TigrinyaEr: + case EthiopicHalehameTiEr: { + static const UChar ethiopicHalehameTiErAlphabet[31] = { + 0x1200, 0x1208, 0x1210, 0x1218, 0x1228, 0x1230, 0x1238, 0x1240, 0x1250, + 0x1260, 0x1270, 0x1278, 0x1290, 0x1298, 0x12A0, 0x12A8, 0x12B8, 0x12C8, + 0x12D0, 0x12D8, 0x12E0, 0x12E8, 0x12F0, 0x1300, 0x1308, 0x1320, 0x1328, + 0x1330, 0x1338, 0x1348, 0x1350 + }; + return toAlphabetic(value, ethiopicHalehameTiErAlphabet); + } + case TigrinyaErAbegede: + case EthiopicAbegedeTiEr: { + static const UChar ethiopicAbegedeTiErAlphabet[31] = { + 0x12A0, 0x1260, 0x1308, 0x12F0, 0x1300, 0x1200, 0x12C8, 0x12D8, 0x12E0, + 0x1210, 0x1320, 0x1328, 0x12E8, 0x12A8, 0x12B8, 0x1208, 0x1218, 0x1290, + 0x1298, 0x12D0, 0x1348, 0x1338, 0x1240, 0x1250, 0x1228, 0x1230, 0x1238, + 0x1270, 0x1278, 0x1330, 0x1350 + }; + return toAlphabetic(value, ethiopicAbegedeTiErAlphabet); + } + case TigrinyaEt: + case EthiopicHalehameTiEt: { + static const UChar ethiopicHalehameTiEtAlphabet[34] = { + 0x1200, 0x1208, 0x1210, 0x1218, 0x1220, 0x1228, 0x1230, 0x1238, 0x1240, + 0x1250, 0x1260, 0x1270, 0x1278, 0x1280, 0x1290, 0x1298, 0x12A0, 0x12A8, + 0x12B8, 0x12C8, 0x12D0, 0x12D8, 0x12E0, 0x12E8, 0x12F0, 0x1300, 0x1308, + 0x1320, 0x1328, 0x1330, 0x1338, 0x1340, 0x1348, 0x1350 + }; + return toAlphabetic(value, ethiopicHalehameTiEtAlphabet); + } + case TigrinyaEtAbegede: + case EthiopicAbegedeTiEt: { + static const UChar ethiopicAbegedeTiEtAlphabet[34] = { + 0x12A0, 0x1260, 0x1308, 0x12F0, 0x1300, 0x1200, 0x12C8, 0x12D8, 0x12E0, + 0x1210, 0x1320, 0x1328, 0x12E8, 0x12A8, 0x12B8, 0x1208, 0x1218, 0x1290, + 0x1298, 0x1220, 0x12D0, 0x1348, 0x1338, 0x1240, 0x1250, 0x1228, 0x1230, + 0x1238, 0x1270, 0x1278, 0x1280, 0x1340, 0x1330, 0x1350 + }; + return toAlphabetic(value, ethiopicAbegedeTiEtAlphabet); + } + case UpperGreek: { + static const UChar upperGreekAlphabet[24] = { + 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, + 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, 0x03A0, 0x03A1, 0x03A3, + 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9 + }; + return toAlphabetic(value, upperGreekAlphabet); + } + case LowerNorwegian: { + static const UChar lowerNorwegianAlphabet[29] = { + 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, + 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070, 0x0071, 0x0072, + 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x00E6, + 0x00F8, 0x00E5 + }; + return toAlphabetic(value, lowerNorwegianAlphabet); + } + case UpperNorwegian: { + static const UChar upperNorwegianAlphabet[29] = { + 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, + 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050, 0x0051, 0x0052, + 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x00C6, + 0x00D8, 0x00C5 + }; + return toAlphabetic(value, upperNorwegianAlphabet); + } + case CJKIdeographic: { + static const UChar traditionalChineseInformalTable[16] = { + 0x842C, 0x5104, 0x5146, + 0x5341, 0x767E, 0x5343, + 0x96F6, 0x4E00, 0x4E8C, 0x4E09, 0x56DB, + 0x4E94, 0x516D, 0x4E03, 0x516B, 0x4E5D + }; + return toCJKIdeographic(value, traditionalChineseInformalTable); + } + + case LowerRoman: + return toRoman(value, false); + case UpperRoman: + return toRoman(value, true); + + case Armenian: + case UpperArmenian: + // CSS3 says "armenian" means "lower-armenian". + // But the CSS2.1 test suite contains uppercase test results for "armenian", + // so we'll match the test suite. + return toArmenian(value, true); + case LowerArmenian: + return toArmenian(value, false); + case Georgian: + return toGeorgian(value); + case Hebrew: + return toHebrew(value); + } + + ASSERT_NOT_REACHED(); + return ""; +} + +RenderListMarker::RenderListMarker(RenderListItem* item) + : RenderBox(item->document()) + , m_listItem(item) +{ + // init RenderObject attributes + setInline(true); // our object is Inline + setReplaced(true); // pretend to be replaced +} + +RenderListMarker::~RenderListMarker() +{ + if (m_image) + m_image->removeClient(this); +} + +void RenderListMarker::styleWillChange(StyleDifference diff, const RenderStyle* newStyle) +{ + if (style() && (newStyle->listStylePosition() != style()->listStylePosition() || newStyle->listStyleType() != style()->listStyleType())) + setNeedsLayoutAndPrefWidthsRecalc(); + + RenderBox::styleWillChange(diff, newStyle); +} + +void RenderListMarker::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBox::styleDidChange(diff, oldStyle); + + if (m_image != style()->listStyleImage()) { + if (m_image) + m_image->removeClient(this); + m_image = style()->listStyleImage(); + if (m_image) + m_image->addClient(this); + } +} + +InlineBox* RenderListMarker::createInlineBox() +{ + InlineBox* result = RenderBox::createInlineBox(); + result->setIsText(isText()); + return result; +} + +bool RenderListMarker::isImage() const +{ + return m_image && !m_image->errorOccurred(); +} + +IntRect RenderListMarker::localSelectionRect() +{ + InlineBox* box = inlineBoxWrapper(); + if (!box) + return IntRect(0, 0, width(), height()); + RootInlineBox* root = m_inlineBoxWrapper->root(); + int newLogicalTop = root->block()->style()->isFlippedBlocksWritingMode() ? m_inlineBoxWrapper->logicalBottom() - root->selectionBottom() : root->selectionTop() - m_inlineBoxWrapper->logicalTop(); + if (root->block()->style()->isHorizontalWritingMode()) + return IntRect(0, newLogicalTop, width(), root->selectionHeight()); + return IntRect(newLogicalTop, 0, root->selectionHeight(), height()); +} + +void RenderListMarker::paint(PaintInfo& paintInfo, int tx, int ty) +{ + if (paintInfo.phase != PaintPhaseForeground) + return; + + if (style()->visibility() != VISIBLE) + return; + + IntPoint boxOrigin(tx + x(), ty + y()); + IntRect overflowRect(visualOverflowRect()); + overflowRect.move(boxOrigin.x(), boxOrigin.y()); + overflowRect.inflate(maximalOutlineSize(paintInfo.phase)); + + if (!paintInfo.rect.intersects(overflowRect)) + return; + + IntRect box(boxOrigin, IntSize(width(), height())); + + IntRect marker = getRelativeMarkerRect(); + marker.move(boxOrigin.x(), boxOrigin.y()); + + GraphicsContext* context = paintInfo.context; + + if (isImage()) { +#if PLATFORM(MAC) + if (style()->highlight() != nullAtom && !paintInfo.context->paintingDisabled()) + paintCustomHighlight(tx, ty, style()->highlight(), true); +#endif + context->drawImage(m_image->image(this, marker.size()), style()->colorSpace(), marker); + if (selectionState() != SelectionNone) { + IntRect selRect = localSelectionRect(); + selRect.move(boxOrigin.x(), boxOrigin.y()); + context->fillRect(selRect, selectionBackgroundColor(), style()->colorSpace()); + } + return; + } + +#if PLATFORM(MAC) + // FIXME: paint gap between marker and list item proper + if (style()->highlight() != nullAtom && !paintInfo.context->paintingDisabled()) + paintCustomHighlight(tx, ty, style()->highlight(), true); +#endif + + if (selectionState() != SelectionNone) { + IntRect selRect = localSelectionRect(); + selRect.move(boxOrigin.x(), boxOrigin.y()); + context->fillRect(selRect, selectionBackgroundColor(), style()->colorSpace()); + } + + const Color color(style()->visitedDependentColor(CSSPropertyColor)); + context->setStrokeColor(color, style()->colorSpace()); + context->setStrokeStyle(SolidStroke); + context->setStrokeThickness(1.0f); + context->setFillColor(color, style()->colorSpace()); + + EListStyleType type = style()->listStyleType(); + switch (type) { + case Disc: + context->drawEllipse(marker); + return; + case Circle: + context->setFillColor(Color::transparent, ColorSpaceDeviceRGB); + context->drawEllipse(marker); + return; + case Square: + context->drawRect(marker); + return; + case NoneListStyle: + return; + case Afar: + case Amharic: + case AmharicAbegede: + case ArabicIndic: + case Armenian: + case BinaryListStyle: + case Bengali: + case Cambodian: + case CJKIdeographic: + case CjkEarthlyBranch: + case CjkHeavenlyStem: + case DecimalLeadingZero: + case DecimalListStyle: + case Devanagari: + case Ethiopic: + case EthiopicAbegede: + case EthiopicAbegedeAmEt: + case EthiopicAbegedeGez: + case EthiopicAbegedeTiEr: + case EthiopicAbegedeTiEt: + case EthiopicHalehameAaEr: + case EthiopicHalehameAaEt: + case EthiopicHalehameAmEt: + case EthiopicHalehameGez: + case EthiopicHalehameOmEt: + case EthiopicHalehameSidEt: + case EthiopicHalehameSoEt: + case EthiopicHalehameTiEr: + case EthiopicHalehameTiEt: + case EthiopicHalehameTig: + case Georgian: + case Gujarati: + case Gurmukhi: + case Hangul: + case HangulConsonant: + case Hebrew: + case Hiragana: + case HiraganaIroha: + case Kannada: + case Katakana: + case KatakanaIroha: + case Khmer: + case Lao: + case LowerAlpha: + case LowerArmenian: + case LowerGreek: + case LowerHexadecimal: + case LowerLatin: + case LowerNorwegian: + case LowerRoman: + case Malayalam: + case Mongolian: + case Myanmar: + case Octal: + case Oriya: + case Oromo: + case Persian: + case Sidama: + case Somali: + case Telugu: + case Thai: + case Tibetan: + case Tigre: + case TigrinyaEr: + case TigrinyaErAbegede: + case TigrinyaEt: + case TigrinyaEtAbegede: + case UpperAlpha: + case UpperArmenian: + case UpperGreek: + case UpperHexadecimal: + case UpperLatin: + case UpperNorwegian: + case UpperRoman: + case Urdu: + case Asterisks: + case Footnotes: + break; + } + if (m_text.isEmpty()) + return; + + TextRun textRun(m_text); + + if (!style()->isHorizontalWritingMode()) { + marker.move(-boxOrigin.x(), -boxOrigin.y()); + marker = marker.transposedRect(); + marker.move(box.x(), box.y() - logicalHeight()); + context->save(); + context->translate(marker.x(), marker.bottom()); + context->rotate(static_cast<float>(deg2rad(90.))); + context->translate(-marker.x(), -marker.bottom()); + } + + IntPoint textOrigin = IntPoint(marker.x(), marker.y() + style()->font().ascent()); + + if (type == Asterisks || type == Footnotes) + context->drawText(style()->font(), textRun, textOrigin); + else { + // Text is not arbitrary. We can judge whether it's RTL from the first character, + // and we only need to handle the direction RightToLeft for now. + bool textNeedsReversing = direction(m_text[0]) == RightToLeft; + Vector<UChar> reversedText; + if (textNeedsReversing) { + int length = m_text.length(); + reversedText.grow(length); + for (int i = 0; i < length; ++i) + reversedText[length - i - 1] = m_text[i]; + textRun = TextRun(reversedText.data(), length); + } + + const Font& font = style()->font(); + const UChar suffix = listMarkerSuffix(type, m_listItem->value()); + if (style()->isLeftToRightDirection()) { + int width = font.width(textRun); + context->drawText(style()->font(), textRun, textOrigin); + UChar suffixSpace[2] = { suffix, ' ' }; + context->drawText(style()->font(), TextRun(suffixSpace, 2), textOrigin + IntSize(width, 0)); + } else { + UChar spaceSuffix[2] = { ' ', suffix }; + TextRun spaceSuffixRun(spaceSuffix, 2); + int width = font.width(spaceSuffixRun); + context->drawText(style()->font(), spaceSuffixRun, textOrigin); + context->drawText(style()->font(), textRun, textOrigin + IntSize(width, 0)); + } + } + + if (!style()->isHorizontalWritingMode()) + context->restore(); +} + +void RenderListMarker::layout() +{ + ASSERT(needsLayout()); + + if (isImage()) { + setWidth(m_image->imageSize(this, style()->effectiveZoom()).width()); + setHeight(m_image->imageSize(this, style()->effectiveZoom()).height()); + } else { + setLogicalWidth(minPreferredLogicalWidth()); + setLogicalHeight(style()->font().height()); + } + + setMarginStart(0); + setMarginEnd(0); + + Length startMargin = style()->marginStart(); + Length endMargin = style()->marginEnd(); + if (startMargin.isFixed()) + setMarginStart(startMargin.value()); + if (endMargin.isFixed()) + setMarginEnd(endMargin.value()); + + setNeedsLayout(false); +} + +void RenderListMarker::imageChanged(WrappedImagePtr o, const IntRect*) +{ + // A list marker can't have a background or border image, so no need to call the base class method. + if (o != m_image->data()) + return; + + if (width() != m_image->imageSize(this, style()->effectiveZoom()).width() || height() != m_image->imageSize(this, style()->effectiveZoom()).height() || m_image->errorOccurred()) + setNeedsLayoutAndPrefWidthsRecalc(); + else + repaint(); +} + +void RenderListMarker::computePreferredLogicalWidths() +{ + ASSERT(preferredLogicalWidthsDirty()); + + m_text = ""; + + const Font& font = style()->font(); + + if (isImage()) { + // FIXME: This is a somewhat arbitrary width. Generated images for markers really won't become particularly useful + // until we support the CSS3 marker pseudoclass to allow control over the width and height of the marker box. + int bulletWidth = font.ascent() / 2; + m_image->setImageContainerSize(IntSize(bulletWidth, bulletWidth)); + IntSize imageSize = m_image->imageSize(this, style()->effectiveZoom()); + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = style()->isHorizontalWritingMode() ? imageSize.width() : imageSize.height(); + setPreferredLogicalWidthsDirty(false); + updateMargins(); + return; + } + + int logicalWidth = 0; + EListStyleType type = style()->listStyleType(); + switch (type) { + case NoneListStyle: + break; + case Asterisks: + case Footnotes: + m_text = listMarkerText(type, m_listItem->value()); + logicalWidth = font.width(m_text); // no suffix for these types + break; + case Circle: + case Disc: + case Square: + m_text = listMarkerText(type, 0); // value is ignored for these types + logicalWidth = (font.ascent() * 2 / 3 + 1) / 2 + 2; + break; + case Afar: + case Amharic: + case AmharicAbegede: + case ArabicIndic: + case Armenian: + case BinaryListStyle: + case Bengali: + case Cambodian: + case CJKIdeographic: + case CjkEarthlyBranch: + case CjkHeavenlyStem: + case DecimalLeadingZero: + case DecimalListStyle: + case Devanagari: + case Ethiopic: + case EthiopicAbegede: + case EthiopicAbegedeAmEt: + case EthiopicAbegedeGez: + case EthiopicAbegedeTiEr: + case EthiopicAbegedeTiEt: + case EthiopicHalehameAaEr: + case EthiopicHalehameAaEt: + case EthiopicHalehameAmEt: + case EthiopicHalehameGez: + case EthiopicHalehameOmEt: + case EthiopicHalehameSidEt: + case EthiopicHalehameSoEt: + case EthiopicHalehameTiEr: + case EthiopicHalehameTiEt: + case EthiopicHalehameTig: + case Georgian: + case Gujarati: + case Gurmukhi: + case Hangul: + case HangulConsonant: + case Hebrew: + case Hiragana: + case HiraganaIroha: + case Kannada: + case Katakana: + case KatakanaIroha: + case Khmer: + case Lao: + case LowerAlpha: + case LowerArmenian: + case LowerGreek: + case LowerHexadecimal: + case LowerLatin: + case LowerNorwegian: + case LowerRoman: + case Malayalam: + case Mongolian: + case Myanmar: + case Octal: + case Oriya: + case Oromo: + case Persian: + case Sidama: + case Somali: + case Telugu: + case Thai: + case Tibetan: + case Tigre: + case TigrinyaEr: + case TigrinyaErAbegede: + case TigrinyaEt: + case TigrinyaEtAbegede: + case UpperAlpha: + case UpperArmenian: + case UpperGreek: + case UpperHexadecimal: + case UpperLatin: + case UpperNorwegian: + case UpperRoman: + case Urdu: + m_text = listMarkerText(type, m_listItem->value()); + if (m_text.isEmpty()) + logicalWidth = 0; + else { + int itemWidth = font.width(m_text); + UChar suffixSpace[2] = { listMarkerSuffix(type, m_listItem->value()), ' ' }; + int suffixSpaceWidth = font.width(TextRun(suffixSpace, 2)); + logicalWidth = itemWidth + suffixSpaceWidth; + } + break; + } + + m_minPreferredLogicalWidth = logicalWidth; + m_maxPreferredLogicalWidth = logicalWidth; + + setPreferredLogicalWidthsDirty(false); + + updateMargins(); +} + +void RenderListMarker::updateMargins() +{ + const Font& font = style()->font(); + + int marginStart = 0; + int marginEnd = 0; + + if (isInside()) { + if (isImage()) + marginEnd = cMarkerPadding; + else switch (style()->listStyleType()) { + case Disc: + case Circle: + case Square: + marginStart = -1; + marginEnd = font.ascent() - minPreferredLogicalWidth() + 1; + break; + default: + break; + } + } else { + if (style()->isLeftToRightDirection()) { + if (isImage()) + marginStart = -minPreferredLogicalWidth() - cMarkerPadding; + else { + int offset = font.ascent() * 2 / 3; + switch (style()->listStyleType()) { + case Disc: + case Circle: + case Square: + marginStart = -offset - cMarkerPadding - 1; + break; + case NoneListStyle: + break; + default: + marginStart = m_text.isEmpty() ? 0 : -minPreferredLogicalWidth() - offset / 2; + } + } + marginEnd = -marginStart - minPreferredLogicalWidth(); + } else { + if (isImage()) + marginEnd = cMarkerPadding; + else { + int offset = font.ascent() * 2 / 3; + switch (style()->listStyleType()) { + case Disc: + case Circle: + case Square: + marginEnd = offset + cMarkerPadding + 1 - minPreferredLogicalWidth(); + break; + case NoneListStyle: + break; + default: + marginEnd = m_text.isEmpty() ? 0 : offset / 2; + } + } + marginStart = -marginEnd - minPreferredLogicalWidth(); + } + + } + + style()->setMarginStart(Length(marginStart, Fixed)); + style()->setMarginEnd(Length(marginEnd, Fixed)); +} + +int RenderListMarker::lineHeight(bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const +{ + if (!isImage()) + return m_listItem->lineHeight(firstLine, direction, PositionOfInteriorLineBoxes); + return RenderBox::lineHeight(firstLine, direction, linePositionMode); +} + +int RenderListMarker::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const +{ + if (!isImage()) + return m_listItem->baselinePosition(baselineType, firstLine, direction, PositionOfInteriorLineBoxes); + return RenderBox::baselinePosition(baselineType, firstLine, direction, linePositionMode); +} + +String RenderListMarker::suffix() const +{ + EListStyleType type = style()->listStyleType(); + const UChar suffix = listMarkerSuffix(type, m_listItem->value()); + + Vector<UChar> resultVector; + resultVector.append(suffix); + + // If the suffix is not ' ', an extra space is needed + if (suffix != ' ') { + if (style()->isLeftToRightDirection()) + resultVector.append(' '); + else + resultVector.prepend(' '); + } + + return String::adopt(resultVector); +} + +bool RenderListMarker::isInside() const +{ + return m_listItem->notInList() || style()->listStylePosition() == INSIDE; +} + +IntRect RenderListMarker::getRelativeMarkerRect() +{ + if (isImage()) + return IntRect(0, 0, m_image->imageSize(this, style()->effectiveZoom()).width(), m_image->imageSize(this, style()->effectiveZoom()).height()); + + IntRect relativeRect; + EListStyleType type = style()->listStyleType(); + switch (type) { + case Asterisks: + case Footnotes: { + const Font& font = style()->font(); + relativeRect = IntRect(0, 0, font.width(m_text), font.height()); + break; + } + case Disc: + case Circle: + case Square: { + // FIXME: Are these particular rounding rules necessary? + const Font& font = style()->font(); + int ascent = font.ascent(); + int bulletWidth = (ascent * 2 / 3 + 1) / 2; + relativeRect = IntRect(1, 3 * (ascent - ascent * 2 / 3) / 2, bulletWidth, bulletWidth); + break; + } + case NoneListStyle: + return IntRect(); + case Afar: + case Amharic: + case AmharicAbegede: + case ArabicIndic: + case Armenian: + case BinaryListStyle: + case Bengali: + case Cambodian: + case CJKIdeographic: + case CjkEarthlyBranch: + case CjkHeavenlyStem: + case DecimalLeadingZero: + case DecimalListStyle: + case Devanagari: + case Ethiopic: + case EthiopicAbegede: + case EthiopicAbegedeAmEt: + case EthiopicAbegedeGez: + case EthiopicAbegedeTiEr: + case EthiopicAbegedeTiEt: + case EthiopicHalehameAaEr: + case EthiopicHalehameAaEt: + case EthiopicHalehameAmEt: + case EthiopicHalehameGez: + case EthiopicHalehameOmEt: + case EthiopicHalehameSidEt: + case EthiopicHalehameSoEt: + case EthiopicHalehameTiEr: + case EthiopicHalehameTiEt: + case EthiopicHalehameTig: + case Georgian: + case Gujarati: + case Gurmukhi: + case Hangul: + case HangulConsonant: + case Hebrew: + case Hiragana: + case HiraganaIroha: + case Kannada: + case Katakana: + case KatakanaIroha: + case Khmer: + case Lao: + case LowerAlpha: + case LowerArmenian: + case LowerGreek: + case LowerHexadecimal: + case LowerLatin: + case LowerNorwegian: + case LowerRoman: + case Malayalam: + case Mongolian: + case Myanmar: + case Octal: + case Oriya: + case Oromo: + case Persian: + case Sidama: + case Somali: + case Telugu: + case Thai: + case Tibetan: + case Tigre: + case TigrinyaEr: + case TigrinyaErAbegede: + case TigrinyaEt: + case TigrinyaEtAbegede: + case UpperAlpha: + case UpperArmenian: + case UpperGreek: + case UpperHexadecimal: + case UpperLatin: + case UpperNorwegian: + case UpperRoman: + case Urdu: + if (m_text.isEmpty()) + return IntRect(); + const Font& font = style()->font(); + int itemWidth = font.width(m_text); + UChar suffixSpace[2] = { listMarkerSuffix(type, m_listItem->value()), ' ' }; + int suffixSpaceWidth = font.width(TextRun(suffixSpace, 2)); + relativeRect = IntRect(0, 0, itemWidth + suffixSpaceWidth, font.height()); + } + + if (!style()->isHorizontalWritingMode()) { + relativeRect = relativeRect.transposedRect(); + relativeRect.setX(width() - relativeRect.x() - relativeRect.width()); + } + + return relativeRect; +} + +void RenderListMarker::setSelectionState(SelectionState state) +{ + RenderBox::setSelectionState(state); + if (InlineBox* box = inlineBoxWrapper()) + if (RootInlineBox* root = box->root()) + root->setHasSelectedChildren(state != SelectionNone); + containingBlock()->setSelectionState(state); +} + +IntRect RenderListMarker::selectionRectForRepaint(RenderBoxModelObject* repaintContainer, bool clipToVisibleContent) +{ + ASSERT(!needsLayout()); + + if (selectionState() == SelectionNone || !inlineBoxWrapper()) + return IntRect(); + + RootInlineBox* root = inlineBoxWrapper()->root(); + IntRect rect(0, root->selectionTop() - y(), width(), root->selectionHeight()); + + if (clipToVisibleContent) + computeRectForRepaint(repaintContainer, rect); + else + rect = localToContainerQuad(FloatRect(rect), repaintContainer).enclosingBoundingBox(); + + return rect; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderListMarker.h b/Source/WebCore/rendering/RenderListMarker.h new file mode 100644 index 0000000..d23e674 --- /dev/null +++ b/Source/WebCore/rendering/RenderListMarker.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderListMarker_h +#define RenderListMarker_h + +#include "RenderBox.h" + +namespace WebCore { + +class RenderListItem; + +String listMarkerText(EListStyleType, int value); + +// Used to render the list item's marker. +// The RenderListMarker always has to be a child of a RenderListItem. +class RenderListMarker : public RenderBox { +public: + RenderListMarker(RenderListItem*); + virtual ~RenderListMarker(); + + virtual void computePreferredLogicalWidths(); + + const String& text() const { return m_text; } + String suffix() const; + + bool isInside() const; + +private: + virtual const char* renderName() const { return "RenderListMarker"; } + + virtual bool isListMarker() const { return true; } + + virtual void paint(PaintInfo&, int tx, int ty); + + virtual void layout(); + + virtual void imageChanged(WrappedImagePtr, const IntRect* = 0); + + virtual InlineBox* createInlineBox(); + + virtual int lineHeight(bool firstLine, LineDirectionMode, LinePositionMode = PositionOnContainingLine) const; + virtual int baselinePosition(FontBaseline, bool firstLine, LineDirectionMode, LinePositionMode = PositionOnContainingLine) const; + + bool isImage() const; + bool isText() const { return !isImage(); } + + virtual void setSelectionState(SelectionState); + virtual IntRect selectionRectForRepaint(RenderBoxModelObject* repaintContainer, bool clipToVisibleContent = true); + virtual bool canBeSelectionLeaf() const { return true; } + + void updateMargins(); + + virtual void styleWillChange(StyleDifference, const RenderStyle* newStyle); + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + IntRect getRelativeMarkerRect(); + IntRect localSelectionRect(); + + String m_text; + RefPtr<StyleImage> m_image; + RenderListItem* m_listItem; +}; + +inline RenderListMarker* toRenderListMarker(RenderObject* object) +{ + ASSERT(!object || object->isListMarker()); + return static_cast<RenderListMarker*>(object); +} + +inline const RenderListMarker* toRenderListMarker(const RenderObject* object) +{ + ASSERT(!object || object->isListMarker()); + return static_cast<const RenderListMarker*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderListMarker(const RenderListMarker*); + +} // namespace WebCore + +#endif // RenderListMarker_h diff --git a/Source/WebCore/rendering/RenderMarquee.cpp b/Source/WebCore/rendering/RenderMarquee.cpp new file mode 100644 index 0000000..1c08831 --- /dev/null +++ b/Source/WebCore/rendering/RenderMarquee.cpp @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) + * + * Portions are Copyright (C) 1998 Netscape Communications Corporation. + * + * Other contributors: + * Robert O'Callahan <roc+@cs.cmu.edu> + * David Baron <dbaron@fas.harvard.edu> + * Christian Biesinger <cbiesinger@web.de> + * Randall Jesup <rjesup@wgate.com> + * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> + * Josh Soref <timeless@mac.com> + * Boris Zbarsky <bzbarsky@mit.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Alternatively, the contents of this file may be used under the terms + * of either the Mozilla Public License Version 1.1, found at + * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public + * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html + * (the "GPL"), in which case the provisions of the MPL or the GPL are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of one of those two + * licenses (the MPL or the GPL) and not to allow others to use your + * version of this file under the LGPL, indicate your decision by + * deletingthe provisions above and replace them with the notice and + * other provisions required by the MPL or the GPL, as the case may be. + * If you do not delete the provisions above, a recipient may use your + * version of this file under any of the LGPL, the MPL or the GPL. + */ + +#include "config.h" + +#include "RenderMarquee.h" + +#include "FrameView.h" +#include "HTMLMarqueeElement.h" +#include "HTMLNames.h" +#include "RenderLayer.h" + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +RenderMarquee::RenderMarquee(RenderLayer* l) + : m_layer(l), m_currentLoop(0) + , m_totalLoops(0) + , m_timer(this, &RenderMarquee::timerFired) + , m_start(0), m_end(0), m_speed(0), m_reset(false) + , m_suspended(false), m_stopped(false), m_direction(MAUTO) +{ +} + +RenderMarquee::~RenderMarquee() +{ +} + +int RenderMarquee::marqueeSpeed() const +{ + int result = m_layer->renderer()->style()->marqueeSpeed(); + Node* n = m_layer->renderer()->node(); + if (n && n->hasTagName(marqueeTag)) { + HTMLMarqueeElement* marqueeElt = static_cast<HTMLMarqueeElement*>(n); + result = max(result, marqueeElt->minimumDelay()); + } + return result; +} + +EMarqueeDirection RenderMarquee::direction() const +{ + // FIXME: Support the CSS3 "auto" value for determining the direction of the marquee. + // For now just map MAUTO to MBACKWARD + EMarqueeDirection result = m_layer->renderer()->style()->marqueeDirection(); + TextDirection dir = m_layer->renderer()->style()->direction(); + if (result == MAUTO) + result = MBACKWARD; + if (result == MFORWARD) + result = (dir == LTR) ? MRIGHT : MLEFT; + if (result == MBACKWARD) + result = (dir == LTR) ? MLEFT : MRIGHT; + + // Now we have the real direction. Next we check to see if the increment is negative. + // If so, then we reverse the direction. + Length increment = m_layer->renderer()->style()->marqueeIncrement(); + if (increment.isNegative()) + result = static_cast<EMarqueeDirection>(-result); + + return result; +} + +bool RenderMarquee::isHorizontal() const +{ + return direction() == MLEFT || direction() == MRIGHT; +} + +int RenderMarquee::computePosition(EMarqueeDirection dir, bool stopAtContentEdge) +{ + RenderBox* box = m_layer->renderBox(); + ASSERT(box); + RenderStyle* s = box->style(); + if (isHorizontal()) { + bool ltr = s->isLeftToRightDirection(); + int clientWidth = box->clientWidth(); + int contentWidth = ltr ? box->rightLayoutOverflow() : box->leftLayoutOverflow(); + if (ltr) + contentWidth += (box->paddingRight() - box->borderLeft()); + else { + contentWidth = box->width() - contentWidth; + contentWidth += (box->paddingLeft() - box->borderRight()); + } + if (dir == MRIGHT) { + if (stopAtContentEdge) + return max(0, ltr ? (contentWidth - clientWidth) : (clientWidth - contentWidth)); + else + return ltr ? contentWidth : clientWidth; + } + else { + if (stopAtContentEdge) + return min(0, ltr ? (contentWidth - clientWidth) : (clientWidth - contentWidth)); + else + return ltr ? -clientWidth : -contentWidth; + } + } + else { + int contentHeight = box->bottomLayoutOverflow() - box->borderTop() + box->paddingBottom(); + int clientHeight = box->clientHeight(); + if (dir == MUP) { + if (stopAtContentEdge) + return min(contentHeight - clientHeight, 0); + else + return -clientHeight; + } + else { + if (stopAtContentEdge) + return max(contentHeight - clientHeight, 0); + else + return contentHeight; + } + } +} + +void RenderMarquee::start() +{ + if (m_timer.isActive() || m_layer->renderer()->style()->marqueeIncrement().isZero() +#if ENABLE(WCSS) && ENABLE(XHTMLMP) + || (m_layer->renderer()->document()->isXHTMLMPDocument() && !m_layer->renderer()->style()->marqueeLoopCount()) +#endif + ) + return; + + // We may end up propagating a scroll event. It is important that we suspend events until + // the end of the function since they could delete the layer, including the marquee. + FrameView* frameView = m_layer->renderer()->document()->view(); + if (frameView) + frameView->pauseScheduledEvents(); + + if (!m_suspended && !m_stopped) { + if (isHorizontal()) + m_layer->scrollToOffset(m_start, 0, false, false); + else + m_layer->scrollToOffset(0, m_start, false, false); + } + else { + m_suspended = false; + m_stopped = false; + } + + m_timer.startRepeating(speed() * 0.001); + + if (frameView) + frameView->resumeScheduledEvents(); +} + +void RenderMarquee::suspend() +{ + m_timer.stop(); + m_suspended = true; +} + +void RenderMarquee::stop() +{ + m_timer.stop(); + m_stopped = true; +} + +void RenderMarquee::updateMarqueePosition() +{ + bool activate = (m_totalLoops <= 0 || m_currentLoop < m_totalLoops); + if (activate) { + EMarqueeBehavior behavior = m_layer->renderer()->style()->marqueeBehavior(); + m_start = computePosition(direction(), behavior == MALTERNATE); + m_end = computePosition(reverseDirection(), behavior == MALTERNATE || behavior == MSLIDE); + if (!m_stopped) + start(); + } +} + +void RenderMarquee::updateMarqueeStyle() +{ + RenderStyle* s = m_layer->renderer()->style(); + + if (m_direction != s->marqueeDirection() || (m_totalLoops != s->marqueeLoopCount() && m_currentLoop >= m_totalLoops)) + m_currentLoop = 0; // When direction changes or our loopCount is a smaller number than our current loop, reset our loop. + + m_totalLoops = s->marqueeLoopCount(); + m_direction = s->marqueeDirection(); + + if (m_layer->renderer()->isHTMLMarquee()) { + // Hack for WinIE. In WinIE, a value of 0 or lower for the loop count for SLIDE means to only do + // one loop. + if (m_totalLoops <= 0 && s->marqueeBehavior() == MSLIDE) + m_totalLoops = 1; + + // Hack alert: Set the white-space value to nowrap for horizontal marquees with inline children, thus ensuring + // all the text ends up on one line by default. Limit this hack to the <marquee> element to emulate + // WinIE's behavior. Someone using CSS3 can use white-space: nowrap on their own to get this effect. + // Second hack alert: Set the text-align back to auto. WinIE completely ignores text-align on the + // marquee element. + // FIXME: Bring these up with the CSS WG. + if (isHorizontal() && m_layer->renderer()->childrenInline()) { + s->setWhiteSpace(NOWRAP); + s->setTextAlign(TAAUTO); + } + } + + // Marquee height hack!! Make sure that, if it is a horizontal marquee, the height attribute is overridden + // if it is smaller than the font size. If it is a vertical marquee and height is not specified, we default + // to a marquee of 200px. + if (isHorizontal()) { + if (s->height().isFixed() && s->height().value() < s->fontSize()) + s->setHeight(Length(s->fontSize(), Fixed)); + } else if (s->height().isAuto()) //vertical marquee with no specified height + s->setHeight(Length(200, Fixed)); + + if (speed() != marqueeSpeed()) { + m_speed = marqueeSpeed(); + if (m_timer.isActive()) + m_timer.startRepeating(speed() * 0.001); + } + + // Check the loop count to see if we should now stop. + bool activate = (m_totalLoops <= 0 || m_currentLoop < m_totalLoops); + if (activate && !m_timer.isActive()) + m_layer->renderer()->setNeedsLayout(true); + else if (!activate && m_timer.isActive()) + m_timer.stop(); +} + +void RenderMarquee::timerFired(Timer<RenderMarquee>*) +{ + if (m_layer->renderer()->needsLayout()) + return; + + if (m_reset) { + m_reset = false; + if (isHorizontal()) + m_layer->scrollToXOffset(m_start); + else + m_layer->scrollToYOffset(m_start); + return; + } + + RenderStyle* s = m_layer->renderer()->style(); + + int endPoint = m_end; + int range = m_end - m_start; + int newPos; + if (range == 0) + newPos = m_end; + else { + bool addIncrement = direction() == MUP || direction() == MLEFT; + bool isReversed = s->marqueeBehavior() == MALTERNATE && m_currentLoop % 2; + if (isReversed) { + // We're going in the reverse direction. + endPoint = m_start; + range = -range; + addIncrement = !addIncrement; + } + bool positive = range > 0; + int clientSize = (isHorizontal() ? m_layer->renderBox()->clientWidth() : m_layer->renderBox()->clientHeight()); + int increment = abs(m_layer->renderer()->style()->marqueeIncrement().calcValue(clientSize)); + int currentPos = (isHorizontal() ? m_layer->scrollXOffset() : m_layer->scrollYOffset()); + newPos = currentPos + (addIncrement ? increment : -increment); + if (positive) + newPos = min(newPos, endPoint); + else + newPos = max(newPos, endPoint); + } + + if (newPos == endPoint) { + m_currentLoop++; + if (m_totalLoops > 0 && m_currentLoop >= m_totalLoops) + m_timer.stop(); + else if (s->marqueeBehavior() != MALTERNATE) + m_reset = true; + } + + if (isHorizontal()) + m_layer->scrollToXOffset(newPos); + else + m_layer->scrollToYOffset(newPos); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderMarquee.h b/Source/WebCore/rendering/RenderMarquee.h new file mode 100644 index 0000000..79998ed --- /dev/null +++ b/Source/WebCore/rendering/RenderMarquee.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2003 Apple Computer, Inc. + * + * Portions are Copyright (C) 1998 Netscape Communications Corporation. + * + * Other contributors: + * Robert O'Callahan <roc+@cs.cmu.edu> + * David Baron <dbaron@fas.harvard.edu> + * Christian Biesinger <cbiesinger@web.de> + * Randall Jesup <rjesup@wgate.com> + * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> + * Josh Soref <timeless@mac.com> + * Boris Zbarsky <bzbarsky@mit.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Alternatively, the contents of this file may be used under the terms + * of either the Mozilla Public License Version 1.1, found at + * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public + * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html + * (the "GPL"), in which case the provisions of the MPL or the GPL are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of one of those two + * licenses (the MPL or the GPL) and not to allow others to use your + * version of this file under the LGPL, indicate your decision by + * deletingthe provisions above and replace them with the notice and + * other provisions required by the MPL or the GPL, as the case may be. + * If you do not delete the provisions above, a recipient may use your + * version of this file under any of the LGPL, the MPL or the GPL. + */ + +#ifndef RenderMarquee_h +#define RenderMarquee_h + +#include "Length.h" +#include "RenderStyleConstants.h" +#include "Timer.h" + +namespace WebCore { + +class RenderLayer; + +// This class handles the auto-scrolling of layers with overflow: marquee. +class RenderMarquee : public Noncopyable { +public: + explicit RenderMarquee(RenderLayer*); + virtual ~RenderMarquee(); + + int speed() const { return m_speed; } + int marqueeSpeed() const; + + EMarqueeDirection reverseDirection() const { return static_cast<EMarqueeDirection>(-direction()); } + EMarqueeDirection direction() const; + + bool isHorizontal() const; + + int computePosition(EMarqueeDirection, bool stopAtClientEdge); + + void setEnd(int end) { m_end = end; } + + void start(); + void suspend(); + void stop(); + + void updateMarqueeStyle(); + void updateMarqueePosition(); + +private: + void timerFired(Timer<RenderMarquee>*); + + RenderLayer* m_layer; + int m_currentLoop; + int m_totalLoops; + Timer<RenderMarquee> m_timer; + int m_start; + int m_end; + int m_speed; + Length m_height; + bool m_reset: 1; + bool m_suspended : 1; + bool m_stopped : 1; + EMarqueeDirection m_direction : 4; +}; + +} // namespace WebCore + +#endif // RenderMarquee_h diff --git a/Source/WebCore/rendering/RenderMedia.cpp b/Source/WebCore/rendering/RenderMedia.cpp new file mode 100644 index 0000000..f59a995 --- /dev/null +++ b/Source/WebCore/rendering/RenderMedia.cpp @@ -0,0 +1,650 @@ +/* + * Copyright (C) 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if ENABLE(VIDEO) +#include "RenderMedia.h" + +#include "EventNames.h" +#include "FloatConversion.h" +#include "HTMLNames.h" +#include "MediaControlElements.h" +#include "MouseEvent.h" +#include "Page.h" +#include "RenderLayer.h" +#include "RenderTheme.h" +#include <wtf/CurrentTime.h> +#include <wtf/MathExtras.h> + +#if PLATFORM(ANDROID) +#define TOUCH_DELAY 4 +#endif + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +static const double cTimeUpdateRepeatDelay = 0.2; +static const double cOpacityAnimationRepeatDelay = 0.05; + +RenderMedia::RenderMedia(HTMLMediaElement* video) + : RenderImage(video) + , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired) + , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired) + , m_mouseOver(false) + , m_opacityAnimationStartTime(0) + , m_opacityAnimationDuration(0) + , m_opacityAnimationFrom(0) + , m_opacityAnimationTo(1.0f) +#if PLATFORM(ANDROID) + , m_lastTouch(0) +#endif +{ + setImageResource(RenderImageResource::create()); +} + +RenderMedia::RenderMedia(HTMLMediaElement* video, const IntSize& intrinsicSize) + : RenderImage(video) + , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired) + , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired) + , m_mouseOver(false) + , m_opacityAnimationStartTime(0) + , m_opacityAnimationDuration(0) + , m_opacityAnimationFrom(0) + , m_opacityAnimationTo(1.0f) +#if PLATFORM(ANDROID) + , m_lastTouch(0) +#endif +{ + setImageResource(RenderImageResource::create()); + setIntrinsicSize(intrinsicSize); +} + +RenderMedia::~RenderMedia() +{ +} + +void RenderMedia::destroy() +{ + if (m_controlsShadowRoot && m_controlsShadowRoot->renderer()) { + + // detach the panel before removing the shadow renderer to prevent a crash in m_controlsShadowRoot->detach() + // when display: style changes + m_panel->detach(); + + removeChild(m_controlsShadowRoot->renderer()); + m_controlsShadowRoot->detach(); + m_controlsShadowRoot = 0; + } + RenderImage::destroy(); +} + +HTMLMediaElement* RenderMedia::mediaElement() const +{ + return static_cast<HTMLMediaElement*>(node()); +} + +MediaPlayer* RenderMedia::player() const +{ + return mediaElement()->player(); +} + +void RenderMedia::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderImage::styleDidChange(diff, oldStyle); + + if (m_controlsShadowRoot) { + if (m_panel) + m_panel->updateStyle(); + if (m_muteButton) + m_muteButton->updateStyle(); + if (m_playButton) + m_playButton->updateStyle(); + if (m_seekBackButton) + m_seekBackButton->updateStyle(); + if (m_seekForwardButton) + m_seekForwardButton->updateStyle(); + if (m_rewindButton) + m_rewindButton->updateStyle(); + if (m_returnToRealtimeButton) + m_returnToRealtimeButton->updateStyle(); + if (m_toggleClosedCaptionsButton) + m_toggleClosedCaptionsButton->updateStyle(); + if (m_statusDisplay) + m_statusDisplay->updateStyle(); + if (m_timelineContainer) + m_timelineContainer->updateStyle(); + if (m_timeline) + m_timeline->updateStyle(); + if (m_fullscreenButton) + m_fullscreenButton->updateStyle(); + if (m_currentTimeDisplay) + m_currentTimeDisplay->updateStyle(); + if (m_timeRemainingDisplay) + m_timeRemainingDisplay->updateStyle(); + if (m_volumeSliderContainer) + m_volumeSliderContainer->updateStyle(); + if (m_volumeSliderMuteButton) + m_volumeSliderMuteButton->updateStyle(); + if (m_volumeSlider) + m_volumeSlider->updateStyle(); + } +} + +void RenderMedia::layout() +{ + IntSize oldSize = contentBoxRect().size(); + + RenderImage::layout(); + + RenderBox* controlsRenderer = m_controlsShadowRoot ? m_controlsShadowRoot->renderBox() : 0; + if (!controlsRenderer) + return; + IntSize newSize = contentBoxRect().size(); + if (newSize != oldSize || controlsRenderer->needsLayout()) { + + if (m_currentTimeDisplay && m_timeRemainingDisplay) { + bool shouldShowTimeDisplays = shouldShowTimeDisplayControls(); + m_currentTimeDisplay->setVisible(shouldShowTimeDisplays); + m_timeRemainingDisplay->setVisible(shouldShowTimeDisplays); + } + + controlsRenderer->setLocation(borderLeft() + paddingLeft(), borderTop() + paddingTop()); + controlsRenderer->style()->setHeight(Length(newSize.height(), Fixed)); + controlsRenderer->style()->setWidth(Length(newSize.width(), Fixed)); + controlsRenderer->setNeedsLayout(true, false); + controlsRenderer->layout(); + setChildNeedsLayout(false); + } +} + +void RenderMedia::createControlsShadowRoot() +{ + ASSERT(!m_controlsShadowRoot); + m_controlsShadowRoot = MediaControlShadowRootElement::create(mediaElement()); + addChild(m_controlsShadowRoot->renderer()); +} + +void RenderMedia::createPanel() +{ + ASSERT(!m_panel); + m_panel = MediaControlElement::create(mediaElement(), MEDIA_CONTROLS_PANEL); + m_panel->attachToParent(m_controlsShadowRoot.get()); +} + +void RenderMedia::createMuteButton() +{ + ASSERT(!m_muteButton); + m_muteButton = MediaControlMuteButtonElement::create(mediaElement(), MediaControlMuteButtonElement::Controller); + m_muteButton->attachToParent(m_panel.get()); +} + +void RenderMedia::createPlayButton() +{ + ASSERT(!m_playButton); + m_playButton = MediaControlPlayButtonElement::create(mediaElement()); + m_playButton->attachToParent(m_panel.get()); +} + +void RenderMedia::createSeekBackButton() +{ + ASSERT(!m_seekBackButton); + m_seekBackButton = MediaControlSeekButtonElement::create(mediaElement(), MEDIA_CONTROLS_SEEK_BACK_BUTTON); + m_seekBackButton->attachToParent(m_panel.get()); +} + +void RenderMedia::createSeekForwardButton() +{ + ASSERT(!m_seekForwardButton); + m_seekForwardButton = MediaControlSeekButtonElement::create(mediaElement(), MEDIA_CONTROLS_SEEK_FORWARD_BUTTON); + m_seekForwardButton->attachToParent(m_panel.get()); +} + +void RenderMedia::createRewindButton() +{ + ASSERT(!m_rewindButton); + m_rewindButton = MediaControlRewindButtonElement::create(mediaElement()); + m_rewindButton->attachToParent(m_panel.get()); +} + +void RenderMedia::createReturnToRealtimeButton() +{ + ASSERT(!m_returnToRealtimeButton); + m_returnToRealtimeButton = MediaControlReturnToRealtimeButtonElement::create(mediaElement()); + m_returnToRealtimeButton->attachToParent(m_panel.get()); +} + +void RenderMedia::createToggleClosedCaptionsButton() +{ + ASSERT(!m_toggleClosedCaptionsButton); + m_toggleClosedCaptionsButton = MediaControlToggleClosedCaptionsButtonElement::create(mediaElement()); + m_toggleClosedCaptionsButton->attachToParent(m_panel.get()); +} + +void RenderMedia::createStatusDisplay() +{ + ASSERT(!m_statusDisplay); + m_statusDisplay = MediaControlStatusDisplayElement::create(mediaElement()); + m_statusDisplay->attachToParent(m_panel.get()); +} + +void RenderMedia::createTimelineContainer() +{ + ASSERT(!m_timelineContainer); + m_timelineContainer = MediaControlTimelineContainerElement::create(mediaElement()); + m_timelineContainer->attachToParent(m_panel.get()); +} + +void RenderMedia::createTimeline() +{ + ASSERT(!m_timeline); + m_timeline = MediaControlTimelineElement::create(mediaElement()); + m_timeline->setAttribute(precisionAttr, "float"); + m_timeline->attachToParent(m_timelineContainer.get()); +} + +void RenderMedia::createVolumeSliderContainer() +{ + ASSERT(!m_volumeSliderContainer); + m_volumeSliderContainer = MediaControlVolumeSliderContainerElement::create(mediaElement()); + m_volumeSliderContainer->attachToParent(m_panel.get()); +} + +void RenderMedia::createVolumeSlider() +{ + ASSERT(!m_volumeSlider); + m_volumeSlider = MediaControlVolumeSliderElement::create(mediaElement()); + m_volumeSlider->setAttribute(precisionAttr, "float"); + m_volumeSlider->setAttribute(maxAttr, "1"); + m_volumeSlider->setAttribute(valueAttr, String::number(mediaElement()->volume())); + m_volumeSlider->attachToParent(m_volumeSliderContainer.get()); +} + +void RenderMedia::createVolumeSliderMuteButton() +{ + ASSERT(!m_volumeSliderMuteButton); + m_volumeSliderMuteButton = MediaControlMuteButtonElement::create(mediaElement(), MediaControlMuteButtonElement::VolumeSlider); + m_volumeSliderMuteButton->attachToParent(m_volumeSliderContainer.get()); + +} + +void RenderMedia::createCurrentTimeDisplay() +{ + ASSERT(!m_currentTimeDisplay); + m_currentTimeDisplay = MediaControlTimeDisplayElement::create(mediaElement(), MEDIA_CONTROLS_CURRENT_TIME_DISPLAY); + m_currentTimeDisplay->attachToParent(m_timelineContainer.get()); +} + +void RenderMedia::createTimeRemainingDisplay() +{ + ASSERT(!m_timeRemainingDisplay); + m_timeRemainingDisplay = MediaControlTimeDisplayElement::create(mediaElement(), MEDIA_CONTROLS_TIME_REMAINING_DISPLAY); + m_timeRemainingDisplay->attachToParent(m_timelineContainer.get()); +} + +void RenderMedia::createFullscreenButton() +{ + ASSERT(!m_fullscreenButton); + m_fullscreenButton = MediaControlFullscreenButtonElement::create(mediaElement()); + m_fullscreenButton->attachToParent(m_panel.get()); +} + +void RenderMedia::updateFromElement() +{ + updateControls(); +} + +void RenderMedia::updateControls() +{ + HTMLMediaElement* media = mediaElement(); + if (!media->controls() || !media->inActiveDocument()) { + if (m_controlsShadowRoot) { + m_controlsShadowRoot->detach(); + m_panel = 0; + m_muteButton = 0; + m_playButton = 0; + m_statusDisplay = 0; + m_timelineContainer = 0; + m_timeline = 0; + m_seekBackButton = 0; + m_seekForwardButton = 0; + m_rewindButton = 0; + m_returnToRealtimeButton = 0; + m_currentTimeDisplay = 0; + m_timeRemainingDisplay = 0; + m_fullscreenButton = 0; + m_volumeSliderContainer = 0; + m_volumeSlider = 0; + m_volumeSliderMuteButton = 0; + m_controlsShadowRoot = 0; + m_toggleClosedCaptionsButton = 0; + } + m_opacityAnimationTo = 1.0f; + m_opacityAnimationTimer.stop(); + m_timeUpdateTimer.stop(); + return; + } + + if (!m_controlsShadowRoot) { + createControlsShadowRoot(); + createPanel(); + if (m_panel) { + createRewindButton(); + createPlayButton(); + createReturnToRealtimeButton(); + createStatusDisplay(); + createTimelineContainer(); + if (m_timelineContainer) { + createCurrentTimeDisplay(); + createTimeline(); + createTimeRemainingDisplay(); + } + createSeekBackButton(); + createSeekForwardButton(); + createToggleClosedCaptionsButton(); + createFullscreenButton(); + createMuteButton(); + createVolumeSliderContainer(); + if (m_volumeSliderContainer) { + createVolumeSlider(); + createVolumeSliderMuteButton(); + } + m_panel->attach(); + } + } + + if (media->canPlay()) { + if (m_timeUpdateTimer.isActive()) + m_timeUpdateTimer.stop(); + } else if (style()->visibility() == VISIBLE && m_timeline && m_timeline->renderer() && m_timeline->renderer()->style()->display() != NONE) { + m_timeUpdateTimer.startRepeating(cTimeUpdateRepeatDelay); + } + + + if (m_panel) { + // update() might alter the opacity of the element, especially if we are in the middle + // of an animation. This is the only element concerned as we animate only this element. + float opacityBeforeChangingStyle = m_panel->renderer() ? m_panel->renderer()->style()->opacity() : 0; + m_panel->update(); + changeOpacity(m_panel.get(), opacityBeforeChangingStyle); + } + if (m_muteButton) + m_muteButton->update(); + if (m_playButton) + m_playButton->update(); + if (m_timelineContainer) + m_timelineContainer->update(); + if (m_volumeSliderContainer) + m_volumeSliderContainer->update(); + if (m_timeline) + m_timeline->update(); + if (m_currentTimeDisplay) + m_currentTimeDisplay->update(); + if (m_timeRemainingDisplay) + m_timeRemainingDisplay->update(); + if (m_seekBackButton) + m_seekBackButton->update(); + if (m_seekForwardButton) + m_seekForwardButton->update(); + if (m_rewindButton) + m_rewindButton->update(); + if (m_returnToRealtimeButton) + m_returnToRealtimeButton->update(); + if (m_toggleClosedCaptionsButton) + m_toggleClosedCaptionsButton->update(); + if (m_statusDisplay) + m_statusDisplay->update(); + if (m_fullscreenButton) + m_fullscreenButton->update(); + if (m_volumeSlider) + m_volumeSlider->update(); + if (m_volumeSliderMuteButton) + m_volumeSliderMuteButton->update(); + + updateTimeDisplay(); + updateControlVisibility(); +} + +void RenderMedia::timeUpdateTimerFired(Timer<RenderMedia>*) +{ + if (m_timeline) + m_timeline->update(false); + updateTimeDisplay(); +} + +void RenderMedia::updateTimeDisplay() +{ + if (!m_currentTimeDisplay || !m_currentTimeDisplay->renderer() || m_currentTimeDisplay->renderer()->style()->display() == NONE || style()->visibility() != VISIBLE) + return; + + float now = mediaElement()->currentTime(); + float duration = mediaElement()->duration(); + + // Allow the theme to format the time + ExceptionCode ec; + m_currentTimeDisplay->setInnerText(theme()->formatMediaControlsCurrentTime(now, duration), ec); + m_currentTimeDisplay->setCurrentValue(now); + m_timeRemainingDisplay->setInnerText(theme()->formatMediaControlsRemainingTime(now, duration), ec); + m_timeRemainingDisplay->setCurrentValue(now - duration); +} + +void RenderMedia::updateControlVisibility() +{ + if (!m_panel || !m_panel->renderer()) + return; + + // Don't fade for audio controls. + HTMLMediaElement* media = mediaElement(); + if (!media->hasVideo()) + return; + + // Don't fade if the media element is not visible + if (style()->visibility() != VISIBLE) + return; + +#if PLATFORM(ANDROID) + if (WTF::currentTime() - m_lastTouch > TOUCH_DELAY) + m_mouseOver = false; + else + m_mouseOver = true; +#endif + + bool shouldHideController = !m_mouseOver && !media->canPlay(); + + // Do fading manually, css animations don't work with shadow trees + + float animateFrom = m_panel->renderer()->style()->opacity(); + float animateTo = shouldHideController ? 0.0f : 1.0f; + + if (animateFrom == animateTo) + return; + + if (m_opacityAnimationTimer.isActive()) { + if (m_opacityAnimationTo == animateTo) + return; + m_opacityAnimationTimer.stop(); + } + + if (animateFrom < animateTo) + m_opacityAnimationDuration = m_panel->renderer()->theme()->mediaControlsFadeInDuration(); + else + m_opacityAnimationDuration = m_panel->renderer()->theme()->mediaControlsFadeOutDuration(); + + m_opacityAnimationFrom = animateFrom; + m_opacityAnimationTo = animateTo; + + m_opacityAnimationStartTime = currentTime(); + m_opacityAnimationTimer.startRepeating(cOpacityAnimationRepeatDelay); +} + +void RenderMedia::changeOpacity(HTMLElement* e, float opacity) +{ + if (!e || !e->renderer() || !e->renderer()->style()) + return; + RefPtr<RenderStyle> s = RenderStyle::clone(e->renderer()->style()); + s->setOpacity(opacity); + // z-index can't be auto if opacity is used + s->setZIndex(0); + e->renderer()->setStyle(s.release()); +} + +void RenderMedia::opacityAnimationTimerFired(Timer<RenderMedia>*) +{ + double time = currentTime() - m_opacityAnimationStartTime; + if (time >= m_opacityAnimationDuration) { + time = m_opacityAnimationDuration; + m_opacityAnimationTimer.stop(); + } + float opacity = narrowPrecisionToFloat(m_opacityAnimationFrom + (m_opacityAnimationTo - m_opacityAnimationFrom) * time / m_opacityAnimationDuration); + changeOpacity(m_panel.get(), opacity); +} + +void RenderMedia::updateVolumeSliderContainer(bool visible) +{ + if (!mediaElement()->hasAudio() || !m_volumeSliderContainer || !m_volumeSlider) + return; + + if (visible && !m_volumeSliderContainer->isVisible()) { + if (!m_muteButton || !m_muteButton->renderer() || !m_muteButton->renderBox()) + return; + + RefPtr<RenderStyle> s = m_volumeSliderContainer->styleForElement(); + int height = s->height().isPercent() ? 0 : s->height().value(); + int width = s->width().isPercent() ? 0 : s->width().value(); + IntPoint offset = document()->page()->theme()->volumeSliderOffsetFromMuteButton(m_muteButton->renderer()->node(), IntSize(width, height)); + int x = offset.x() + m_muteButton->renderBox()->offsetLeft(); + int y = offset.y() + m_muteButton->renderBox()->offsetTop(); + + m_volumeSliderContainer->setPosition(x, y); + m_volumeSliderContainer->setVisible(true); + m_volumeSliderContainer->update(); + m_volumeSlider->update(); + } else if (!visible && m_volumeSliderContainer->isVisible()) { + m_volumeSliderContainer->setVisible(false); + m_volumeSliderContainer->updateStyle(); + } +} + +#if PLATFORM(ANDROID) +void RenderMedia::updateLastTouch() +{ + m_lastTouch = WTF::currentTime(); +} +#endif + +void RenderMedia::forwardEvent(Event* event) +{ +#if PLATFORM(ANDROID) + if (event->isMouseEvent()) + updateLastTouch(); +#if ENABLE(TOUCH_EVENTS) + if (event->isTouchEvent()) + updateLastTouch(); +#endif +#endif + + if (event->isMouseEvent() && m_controlsShadowRoot) { + MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); + IntPoint point(mouseEvent->absoluteLocation()); + + bool defaultHandled = false; + if (m_volumeSliderMuteButton && m_volumeSliderMuteButton->hitTest(point)) { + m_volumeSliderMuteButton->defaultEventHandler(event); + defaultHandled = event->defaultHandled(); + } + + bool showVolumeSlider = false; + if (!defaultHandled && m_muteButton && m_muteButton->hitTest(point)) { + m_muteButton->defaultEventHandler(event); + if (event->type() != eventNames().mouseoutEvent) + showVolumeSlider = true; + } + + if (m_volumeSliderContainer && m_volumeSliderContainer->hitTest(point)) + showVolumeSlider = true; + + if (m_volumeSlider && m_volumeSlider->hitTest(point)) { + m_volumeSlider->defaultEventHandler(event); + showVolumeSlider = true; + } + + updateVolumeSliderContainer(showVolumeSlider); + + if (m_playButton && m_playButton->hitTest(point)) + m_playButton->defaultEventHandler(event); + + if (m_seekBackButton && m_seekBackButton->hitTest(point)) + m_seekBackButton->defaultEventHandler(event); + + if (m_seekForwardButton && m_seekForwardButton->hitTest(point)) + m_seekForwardButton->defaultEventHandler(event); + + if (m_rewindButton && m_rewindButton->hitTest(point)) + m_rewindButton->defaultEventHandler(event); + + if (m_returnToRealtimeButton && m_returnToRealtimeButton->hitTest(point)) + m_returnToRealtimeButton->defaultEventHandler(event); + + if (m_toggleClosedCaptionsButton && m_toggleClosedCaptionsButton->hitTest(point)) + m_toggleClosedCaptionsButton->defaultEventHandler(event); + + if (m_timeline && m_timeline->hitTest(point)) + m_timeline->defaultEventHandler(event); + + if (m_fullscreenButton && m_fullscreenButton->hitTest(point)) + m_fullscreenButton->defaultEventHandler(event); + + if (event->type() == eventNames().mouseoverEvent) { + m_mouseOver = true; + updateControlVisibility(); + } + if (event->type() == eventNames().mouseoutEvent) { + // When the scrollbar thumb captures mouse events, we should treat the mouse as still being over our renderer if the new target is a descendant + Node* mouseOverNode = mouseEvent->relatedTarget() ? mouseEvent->relatedTarget()->toNode() : 0; + RenderObject* mouseOverRenderer = mouseOverNode ? mouseOverNode->renderer() : 0; + m_mouseOver = mouseOverRenderer && mouseOverRenderer->isDescendantOf(this); + updateControlVisibility(); + } + } +} + +// We want the timeline slider to be at least 100 pixels wide. +static const int minWidthToDisplayTimeDisplays = 16 + 16 + 45 + 100 + 45 + 16 + 1; + +bool RenderMedia::shouldShowTimeDisplayControls() const +{ + if (!m_currentTimeDisplay && !m_timeRemainingDisplay) + return false; + + int width = mediaElement()->renderBox()->width(); + return width >= minWidthToDisplayTimeDisplays * style()->effectiveZoom(); +} + +} // namespace WebCore + +#endif diff --git a/Source/WebCore/rendering/RenderMedia.h b/Source/WebCore/rendering/RenderMedia.h new file mode 100644 index 0000000..817252d --- /dev/null +++ b/Source/WebCore/rendering/RenderMedia.h @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderMedia_h +#define RenderMedia_h + +#if ENABLE(VIDEO) + +#include "RenderImage.h" +#include "Timer.h" + +namespace WebCore { + +class HTMLInputElement; +class HTMLMediaElement; +class MediaControlMuteButtonElement; +class MediaControlPlayButtonElement; +class MediaControlSeekButtonElement; +class MediaControlRewindButtonElement; +class MediaControlReturnToRealtimeButtonElement; +class MediaControlToggleClosedCaptionsButtonElement; +class MediaControlTimelineElement; +class MediaControlVolumeSliderElement; +class MediaControlFullscreenButtonElement; +class MediaControlTimeDisplayElement; +class MediaControlStatusDisplayElement; +class MediaControlTimelineContainerElement; +class MediaControlVolumeSliderContainerElement; +class MediaControlElement; +class MediaPlayer; + +class RenderMedia : public RenderImage { +public: + RenderMedia(HTMLMediaElement*); + RenderMedia(HTMLMediaElement*, const IntSize& intrinsicSize); + virtual ~RenderMedia(); + + const RenderObjectChildList* children() const { return &m_children; } + RenderObjectChildList* children() { return &m_children; } + + HTMLMediaElement* mediaElement() const; + MediaPlayer* player() const; + + bool shouldShowTimeDisplayControls() const; + + void updateFromElement(); + void updatePlayer(); + void updateControls(); + void updateTimeDisplay(); + + void forwardEvent(Event*); +#if PLATFORM(ANDROID) + void updateLastTouch(); +#endif + +protected: + virtual void layout(); + +private: + virtual RenderObjectChildList* virtualChildren() { return children(); } + virtual const RenderObjectChildList* virtualChildren() const { return children(); } + + virtual void destroy(); + + virtual const char* renderName() const { return "RenderMedia"; } + virtual bool isMedia() const { return true; } + virtual bool isImage() const { return false; } + + void createControlsShadowRoot(); + void destroyControlsShadowRoot(); + void createPanel(); + void createMuteButton(); + void createPlayButton(); + void createSeekBackButton(); + void createSeekForwardButton(); + void createRewindButton(); + void createReturnToRealtimeButton(); + void createToggleClosedCaptionsButton(); + void createStatusDisplay(); + void createTimelineContainer(); + void createTimeline(); + void createVolumeSliderContainer(); + void createVolumeSlider(); + void createVolumeSliderMuteButton(); + void createCurrentTimeDisplay(); + void createTimeRemainingDisplay(); + void createFullscreenButton(); + + void timeUpdateTimerFired(Timer<RenderMedia>*); + + void updateControlVisibility(); + void changeOpacity(HTMLElement*, float opacity); + void opacityAnimationTimerFired(Timer<RenderMedia>*); + + void updateVolumeSliderContainer(bool visible); + + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + virtual bool requiresForcedStyleRecalcPropagation() const { return true; } + + RefPtr<HTMLElement> m_controlsShadowRoot; + RefPtr<MediaControlElement> m_panel; + RefPtr<MediaControlMuteButtonElement> m_muteButton; + RefPtr<MediaControlPlayButtonElement> m_playButton; + RefPtr<MediaControlSeekButtonElement> m_seekBackButton; + RefPtr<MediaControlSeekButtonElement> m_seekForwardButton; + RefPtr<MediaControlRewindButtonElement> m_rewindButton; + RefPtr<MediaControlReturnToRealtimeButtonElement> m_returnToRealtimeButton; + RefPtr<MediaControlToggleClosedCaptionsButtonElement> m_toggleClosedCaptionsButton; + RefPtr<MediaControlTimelineElement> m_timeline; + RefPtr<MediaControlVolumeSliderElement> m_volumeSlider; + RefPtr<MediaControlMuteButtonElement> m_volumeSliderMuteButton; + RefPtr<MediaControlFullscreenButtonElement> m_fullscreenButton; + RefPtr<MediaControlTimelineContainerElement> m_timelineContainer; + RefPtr<MediaControlVolumeSliderContainerElement> m_volumeSliderContainer; + RefPtr<MediaControlTimeDisplayElement> m_currentTimeDisplay; + RefPtr<MediaControlTimeDisplayElement> m_timeRemainingDisplay; + RefPtr<MediaControlStatusDisplayElement> m_statusDisplay; + RenderObjectChildList m_children; + Node* m_lastUnderNode; + Node* m_nodeUnderMouse; + + Timer<RenderMedia> m_timeUpdateTimer; + Timer<RenderMedia> m_opacityAnimationTimer; + bool m_mouseOver; + double m_opacityAnimationStartTime; + double m_opacityAnimationDuration; + float m_opacityAnimationFrom; + float m_opacityAnimationTo; +#if PLATFORM(ANDROID) + double m_lastTouch; +#endif +}; + +inline RenderMedia* toRenderMedia(RenderObject* object) +{ + ASSERT(!object || object->isMedia()); + return static_cast<RenderMedia*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderMedia(const RenderMedia*); + +} // namespace WebCore + +#endif +#endif // RenderMedia_h diff --git a/Source/WebCore/rendering/RenderMediaControls.cpp b/Source/WebCore/rendering/RenderMediaControls.cpp new file mode 100644 index 0000000..9c4757c --- /dev/null +++ b/Source/WebCore/rendering/RenderMediaControls.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2009 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderMediaControls.h" + +#include "GraphicsContext.h" +#include "HTMLMediaElement.h" +#include "HTMLNames.h" +#include "RenderTheme.h" +#include <CoreGraphics/CoreGraphics.h> +#include <WebKitSystemInterface/WebKitSystemInterface.h> + +#if PLATFORM(WIN) +// The Windows version of WKSI defines these functions as capitalized, while the Mac version defines them as lower case. +#define wkMediaControllerThemeAvailable(themeStyle) WKMediaControllerThemeAvailable(themeStyle) +#define wkHitTestMediaUIPart(part, themeStyle, bounds, point) WKHitTestMediaUIPart(part, themeStyle, bounds, point) +#define wkMeasureMediaUIPart(part, themeStyle, bounds, naturalSize) WKMeasureMediaUIPart(part, themeStyle, bounds, naturalSize) +#define wkDrawMediaUIPart(part, themeStyle, context, rect, state) WKDrawMediaUIPart(part, themeStyle, context, rect, state) +#define wkDrawMediaSliderTrack(themeStyle, context, rect, timeLoaded, currentTime, duration, state) WKDrawMediaSliderTrack(themeStyle, context, rect, timeLoaded, currentTime, duration, state) +#endif + +using namespace std; + +namespace WebCore { + +#if ENABLE(VIDEO) + +static WKMediaControllerThemeState determineState(RenderObject* o) +{ + int result = 0; + RenderTheme* theme = o->theme(); + if (!theme->isEnabled(o) || theme->isReadOnlyControl(o)) + result |= WKMediaControllerFlagDisabled; + if (theme->isPressed(o)) + result |= WKMediaControllerFlagPressed; + if (theme->isFocused(o)) + result |= WKMediaControllerFlagFocused; + return static_cast<WKMediaControllerThemeState>(result); +} + +// Utility to scale when the UI part are not scaled by wkDrawMediaUIPart +static FloatRect getUnzoomedRectAndAdjustCurrentContext(RenderObject* o, const PaintInfo& paintInfo, const IntRect &originalRect) +{ + float zoomLevel = o->style()->effectiveZoom(); + FloatRect unzoomedRect(originalRect); + if (zoomLevel != 1.0f) { + unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel); + unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel); + paintInfo.context->translate(unzoomedRect.x(), unzoomedRect.y()); + paintInfo.context->scale(FloatSize(zoomLevel, zoomLevel)); + paintInfo.context->translate(-unzoomedRect.x(), -unzoomedRect.y()); + } + return unzoomedRect; +} + +static const int mediaSliderThumbWidth = 13; +static const int mediaSliderThumbHeight = 14; + +void RenderMediaControls::adjustMediaSliderThumbSize(RenderObject* o) +{ + ControlPart part = o->style()->appearance(); + + if (part != MediaSliderThumbPart && part != MediaVolumeSliderThumbPart) + return; + + CGSize size; + wkMeasureMediaUIPart(part == MediaSliderThumbPart ? MediaSliderThumb : MediaVolumeSliderThumb, WKMediaControllerThemeQuickTime, 0, &size); + + float zoomLevel = o->style()->effectiveZoom(); + o->style()->setWidth(Length(static_cast<int>(size.width * zoomLevel), Fixed)); + o->style()->setHeight(Length(static_cast<int>(size.height * zoomLevel), Fixed)); +} + +bool RenderMediaControls::paintMediaControlsPart(MediaControlElementType part, RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + static const int themeStyle = WKMediaControllerThemeQuickTime; + paintInfo.context->save(); + switch (part) { + case MediaFullscreenButton: + wkDrawMediaUIPart(WKMediaUIPartFullscreenButton, themeStyle, paintInfo.context->platformContext(), r, determineState(o)); + break; + case MediaShowClosedCaptionsButton: + case MediaHideClosedCaptionsButton: + if (MediaControlToggleClosedCaptionsButtonElement* btn = static_cast<MediaControlToggleClosedCaptionsButtonElement*>(o->node())) { + bool captionsVisible = btn->displayType() == MediaHideClosedCaptionsButton; + wkDrawMediaUIPart(captionsVisible ? WKMediaUIPartHideClosedCaptionsButton : WKMediaUIPartShowClosedCaptionsButton, themeStyle, paintInfo.context->platformContext(), r, determineState(o)); + } + break; + case MediaMuteButton: + case MediaUnMuteButton: + if (MediaControlMuteButtonElement* btn = static_cast<MediaControlMuteButtonElement*>(o->node())) { + bool audioEnabled = btn->displayType() == MediaMuteButton; + wkDrawMediaUIPart(audioEnabled ? WKMediaUIPartMuteButton : WKMediaUIPartUnMuteButton, themeStyle, paintInfo.context->platformContext(), r, determineState(o)); + } + break; + case MediaPauseButton: + case MediaPlayButton: + if (MediaControlPlayButtonElement* btn = static_cast<MediaControlPlayButtonElement*>(o->node())) { + bool canPlay = btn->displayType() == MediaPlayButton; + wkDrawMediaUIPart(canPlay ? WKMediaUIPartPlayButton : WKMediaUIPartPauseButton, themeStyle, paintInfo.context->platformContext(), r, determineState(o)); + } + break; + case MediaRewindButton: + wkDrawMediaUIPart(WKMediaUIPartRewindButton, themeStyle, paintInfo.context->platformContext(), r, determineState(o)); + break; + case MediaSeekBackButton: + wkDrawMediaUIPart(WKMediaUIPartSeekBackButton, themeStyle, paintInfo.context->platformContext(), r, determineState(o)); + break; + case MediaSeekForwardButton: + wkDrawMediaUIPart(WKMediaUIPartSeekForwardButton, themeStyle, paintInfo.context->platformContext(), r, determineState(o)); + break; + case MediaSlider: { + if (HTMLMediaElement* mediaElement = toParentMediaElement(o)) { + FloatRect unzoomedRect = getUnzoomedRectAndAdjustCurrentContext(o, paintInfo, r); + wkDrawMediaSliderTrack(themeStyle, paintInfo.context->platformContext(), unzoomedRect, mediaElement->percentLoaded() * mediaElement->duration(), mediaElement->currentTime(), mediaElement->duration(), determineState(o)); + } + break; + } + case MediaSliderThumb: + wkDrawMediaUIPart(WKMediaUIPartTimelineSliderThumb, themeStyle, paintInfo.context->platformContext(), r, determineState(o)); + break; + case MediaVolumeSliderContainer: + wkDrawMediaUIPart(WKMediaUIPartVolumeSliderContainer, themeStyle, paintInfo.context->platformContext(), r, determineState(o)); + break; + case MediaVolumeSlider: + wkDrawMediaUIPart(WKMediaUIPartVolumeSlider, themeStyle, paintInfo.context->platformContext(), r, determineState(o)); + break; + case MediaVolumeSliderThumb: + wkDrawMediaUIPart(WKMediaUIPartVolumeSliderThumb, themeStyle, paintInfo.context->platformContext(), r, determineState(o)); + break; + case MediaTimelineContainer: + wkDrawMediaUIPart(WKMediaUIPartBackground, themeStyle, paintInfo.context->platformContext(), r, determineState(o)); + break; + case MediaCurrentTimeDisplay: + ASSERT_NOT_REACHED(); + break; + case MediaTimeRemainingDisplay: + ASSERT_NOT_REACHED(); + break; + case MediaControlsPanel: + ASSERT_NOT_REACHED(); + break; + } + paintInfo.context->restore(); + + return false; +} + +IntPoint RenderMediaControls::volumeSliderOffsetFromMuteButton(Node* muteButton, const IntSize& size) +{ + static const int xOffset = -4; + static const int yOffset = 5; + + float zoomLevel = muteButton->renderer()->style()->effectiveZoom(); + int y = yOffset * zoomLevel + muteButton->renderBox()->offsetHeight() - size.height(); + FloatPoint absPoint = muteButton->renderer()->localToAbsolute(FloatPoint(muteButton->renderBox()->offsetLeft(), y), true, true); + if (absPoint.y() < 0) + y = muteButton->renderBox()->height(); + return IntPoint(xOffset * zoomLevel, y); + +} +#endif // #if ENABLE(VIDEO) + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderMediaControls.h b/Source/WebCore/rendering/RenderMediaControls.h new file mode 100644 index 0000000..9edeef1 --- /dev/null +++ b/Source/WebCore/rendering/RenderMediaControls.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2009 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderMediaControls_h +#define RenderMediaControls_h + +#if ENABLE(VIDEO) + +#include "RenderObject.h" +#include "MediaControlElements.h" + +namespace WebCore { + +class HTMLMediaElement; +class RenderMediaControls { +public: + static bool paintMediaControlsPart(MediaControlElementType, RenderObject*, const PaintInfo&, const IntRect&); + static void adjustMediaSliderThumbSize(RenderObject*); + static IntPoint volumeSliderOffsetFromMuteButton(Node*, const IntSize&); +}; + +} // namespace WebCore + +#endif // ENABLE(VIDEO) + +#endif // RenderMediaControls_h diff --git a/Source/WebCore/rendering/RenderMediaControlsChromium.cpp b/Source/WebCore/rendering/RenderMediaControlsChromium.cpp new file mode 100644 index 0000000..f938a52 --- /dev/null +++ b/Source/WebCore/rendering/RenderMediaControlsChromium.cpp @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2009 Apple Inc. + * Copyright (C) 2009 Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderMediaControlsChromium.h" + +#include "Gradient.h" +#include "GraphicsContext.h" +#include "HTMLMediaElement.h" +#include "HTMLNames.h" + +namespace WebCore { + +#if ENABLE(VIDEO) + +typedef WTF::HashMap<const char*, Image*> MediaControlImageMap; +static MediaControlImageMap* gMediaControlImageMap = 0; + +static Image* platformResource(const char* name) +{ + if (!gMediaControlImageMap) + gMediaControlImageMap = new MediaControlImageMap(); + if (Image* image = gMediaControlImageMap->get(name)) + return image; + if (Image* image = Image::loadPlatformResource(name).releaseRef()) { + gMediaControlImageMap->set(name, image); + return image; + } + ASSERT_NOT_REACHED(); + return 0; +} + +static bool hasSource(const HTMLMediaElement* mediaElement) +{ + return mediaElement->networkState() != HTMLMediaElement::NETWORK_EMPTY + && mediaElement->networkState() != HTMLMediaElement::NETWORK_NO_SOURCE; +} + +static bool paintMediaButton(GraphicsContext* context, const IntRect& rect, Image* image) +{ + IntRect imageRect = image->rect(); + context->drawImage(image, ColorSpaceDeviceRGB, rect); + return true; +} + +static bool paintMediaMuteButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + HTMLMediaElement* mediaElement = toParentMediaElement(object); + if (!mediaElement) + return false; + + static Image* soundFull = platformResource("mediaSoundFull"); + static Image* soundNone = platformResource("mediaSoundNone"); + static Image* soundDisabled = platformResource("mediaSoundDisabled"); + + if (!hasSource(mediaElement) || !mediaElement->hasAudio()) + return paintMediaButton(paintInfo.context, rect, soundDisabled); + + return paintMediaButton(paintInfo.context, rect, mediaElement->muted() ? soundNone: soundFull); +} + +static bool paintMediaPlayButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + HTMLMediaElement* mediaElement = toParentMediaElement(object); + if (!mediaElement) + return false; + + static Image* mediaPlay = platformResource("mediaPlay"); + static Image* mediaPause = platformResource("mediaPause"); + static Image* mediaPlayDisabled = platformResource("mediaPlayDisabled"); + + if (!hasSource(mediaElement)) + return paintMediaButton(paintInfo.context, rect, mediaPlayDisabled); + + return paintMediaButton(paintInfo.context, rect, mediaElement->canPlay() ? mediaPlay : mediaPause); +} + +static Image* getMediaSliderThumb() +{ + static Image* mediaSliderThumb = platformResource("mediaSliderThumb"); + return mediaSliderThumb; +} + +static bool paintMediaSlider(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + HTMLMediaElement* mediaElement = toParentMediaElement(object); + if (!mediaElement) + return false; + + RenderStyle* style = object->style(); + GraphicsContext* context = paintInfo.context; + + // Draw the border of the time bar. + // FIXME: this should be a rounded rect but need to fix GraphicsContextSkia first. + // https://bugs.webkit.org/show_bug.cgi?id=30143 + context->save(); + context->setShouldAntialias(true); + context->setStrokeStyle(SolidStroke); + context->setStrokeColor(style->visitedDependentColor(CSSPropertyBorderLeftColor), ColorSpaceDeviceRGB); + context->setStrokeThickness(style->borderLeftWidth()); + context->setFillColor(style->visitedDependentColor(CSSPropertyBackgroundColor), ColorSpaceDeviceRGB); + context->drawRect(rect); + context->restore(); + + // Draw the buffered ranges. + // FIXME: Draw multiple ranges if there are multiple buffered ranges. + IntRect bufferedRect = rect; + bufferedRect.inflate(-style->borderLeftWidth()); + + double bufferedWidth = 0.0; + if (mediaElement->percentLoaded() > 0.0) { + // Account for the width of the slider thumb. + Image* mediaSliderThumb = getMediaSliderThumb(); + double thumbWidth = mediaSliderThumb->width() / 2.0 + 1.0; + double rectWidth = bufferedRect.width() - thumbWidth; + if (rectWidth < 0.0) + rectWidth = 0.0; + bufferedWidth = rectWidth * mediaElement->percentLoaded() + thumbWidth; + } + bufferedRect.setWidth(bufferedWidth); + + // Don't bother drawing an empty area. + if (!bufferedRect.isEmpty()) { + IntPoint sliderTopLeft = bufferedRect.location(); + IntPoint sliderTopRight = sliderTopLeft; + sliderTopRight.move(0, bufferedRect.height()); + + RefPtr<Gradient> gradient = Gradient::create(sliderTopLeft, sliderTopRight); + Color startColor = object->style()->visitedDependentColor(CSSPropertyColor); + gradient->addColorStop(0.0, startColor); + gradient->addColorStop(1.0, Color(startColor.red() / 2, startColor.green() / 2, startColor.blue() / 2, startColor.alpha())); + + context->save(); + context->setStrokeStyle(NoStroke); + context->setFillGradient(gradient); + context->fillRect(bufferedRect); + context->restore(); + } + + return true; +} + +static bool paintMediaSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + if (!object->parent()->isSlider()) + return false; + + HTMLMediaElement* mediaElement = toParentMediaElement(object->parent()); + if (!mediaElement) + return false; + + if (!hasSource(mediaElement)) + return true; + + Image* mediaSliderThumb = getMediaSliderThumb(); + return paintMediaButton(paintInfo.context, rect, mediaSliderThumb); +} + +static bool paintMediaVolumeSlider(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + HTMLMediaElement* mediaElement = toParentMediaElement(object); + if (!mediaElement) + return false; + + GraphicsContext* context = paintInfo.context; + Color originalColor = context->strokeColor(); + if (originalColor != Color::white) + context->setStrokeColor(Color::white, ColorSpaceDeviceRGB); + + int x = rect.x() + rect.width() / 2; + context->drawLine(IntPoint(x, rect.y()), IntPoint(x, rect.y() + rect.height())); + + if (originalColor != Color::white) + context->setStrokeColor(originalColor, ColorSpaceDeviceRGB); + return true; +} + +static bool paintMediaVolumeSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + if (!object->parent()->isSlider()) + return false; + + static Image* mediaVolumeSliderThumb = platformResource("mediaVolumeSliderThumb"); + return paintMediaButton(paintInfo.context, rect, mediaVolumeSliderThumb); +} + +static bool paintMediaTimelineContainer(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + HTMLMediaElement* mediaElement = toParentMediaElement(object); + if (!mediaElement) + return false; + + if (!rect.isEmpty()) { + GraphicsContext* context = paintInfo.context; + Color originalColor = context->strokeColor(); + float originalThickness = context->strokeThickness(); + StrokeStyle originalStyle = context->strokeStyle(); + + context->setStrokeStyle(SolidStroke); + + // Draw the left border using CSS defined width and color. + context->setStrokeThickness(object->style()->borderLeftWidth()); + context->setStrokeColor(object->style()->visitedDependentColor(CSSPropertyBorderLeftColor).rgb(), ColorSpaceDeviceRGB); + context->drawLine(IntPoint(rect.x() + 1, rect.y()), + IntPoint(rect.x() + 1, rect.y() + rect.height())); + + // Draw the right border using CSS defined width and color. + context->setStrokeThickness(object->style()->borderRightWidth()); + context->setStrokeColor(object->style()->visitedDependentColor(CSSPropertyBorderRightColor).rgb(), ColorSpaceDeviceRGB); + context->drawLine(IntPoint(rect.x() + rect.width() - 1, rect.y()), + IntPoint(rect.x() + rect.width() - 1, rect.y() + rect.height())); + + context->setStrokeColor(originalColor, ColorSpaceDeviceRGB); + context->setStrokeThickness(originalThickness); + context->setStrokeStyle(originalStyle); + } + return true; +} + +bool RenderMediaControlsChromium::shouldRenderMediaControlPart(ControlPart part, Element* e) +{ + UNUSED_PARAM(e); + + switch (part) { + case MediaMuteButtonPart: + case MediaPlayButtonPart: + case MediaSliderPart: + case MediaSliderThumbPart: + case MediaVolumeSliderContainerPart: + case MediaVolumeSliderPart: + case MediaVolumeSliderThumbPart: + case MediaControlsBackgroundPart: + case MediaCurrentTimePart: + case MediaTimeRemainingPart: + return true; + default: + ; + } + return false; +} + +bool RenderMediaControlsChromium::paintMediaControlsPart(MediaControlElementType part, RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + switch (part) { + case MediaMuteButton: + case MediaUnMuteButton: + return paintMediaMuteButton(object, paintInfo, rect); + case MediaPauseButton: + case MediaPlayButton: + return paintMediaPlayButton(object, paintInfo, rect); + case MediaSlider: + return paintMediaSlider(object, paintInfo, rect); + case MediaSliderThumb: + return paintMediaSliderThumb(object, paintInfo, rect); + case MediaVolumeSlider: + return paintMediaVolumeSlider(object, paintInfo, rect); + case MediaVolumeSliderThumb: + return paintMediaVolumeSliderThumb(object, paintInfo, rect); + case MediaTimelineContainer: + return paintMediaTimelineContainer(object, paintInfo, rect); + case MediaVolumeSliderMuteButton: + case MediaFullscreenButton: + case MediaSeekBackButton: + case MediaSeekForwardButton: + case MediaVolumeSliderContainer: + case MediaCurrentTimeDisplay: + case MediaTimeRemainingDisplay: + case MediaControlsPanel: + case MediaRewindButton: + case MediaReturnToRealtimeButton: + case MediaStatusDisplay: + case MediaShowClosedCaptionsButton: + case MediaHideClosedCaptionsButton: + ASSERT_NOT_REACHED(); + break; + } + return false; +} + +void RenderMediaControlsChromium::adjustMediaSliderThumbSize(RenderObject* object) +{ + static Image* mediaSliderThumb = platformResource("mediaSliderThumb"); + static Image* mediaVolumeSliderThumb = platformResource("mediaVolumeSliderThumb"); + + Image* thumbImage = 0; + if (object->style()->appearance() == MediaSliderThumbPart) + thumbImage = mediaSliderThumb; + else if (object->style()->appearance() == MediaVolumeSliderThumbPart) + thumbImage = mediaVolumeSliderThumb; + + float zoomLevel = object->style()->effectiveZoom(); + if (thumbImage) { + object->style()->setWidth(Length(static_cast<int>(thumbImage->width() * zoomLevel), Fixed)); + object->style()->setHeight(Length(static_cast<int>(thumbImage->height() * zoomLevel), Fixed)); + } +} + +#endif // #if ENABLE(VIDEO) + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderMediaControlsChromium.h b/Source/WebCore/rendering/RenderMediaControlsChromium.h new file mode 100644 index 0000000..060a9d4 --- /dev/null +++ b/Source/WebCore/rendering/RenderMediaControlsChromium.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2009 Apple Inc. + * Copyright (C) 2009 Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderMediaControlsChromium_h +#define RenderMediaControlsChromium_h + +#include "RenderObject.h" +#include "MediaControlElements.h" + +namespace WebCore { + +class HTMLMediaElement; +class RenderMediaControlsChromium { +public: + static bool shouldRenderMediaControlPart(ControlPart, Element*); + static bool paintMediaControlsPart(MediaControlElementType, RenderObject*, const PaintInfo&, const IntRect&); + static void adjustMediaSliderThumbSize(RenderObject*); +}; + +} // namespace WebCore + +#endif // RenderMediaControlsChromium_h diff --git a/Source/WebCore/rendering/RenderMenuList.cpp b/Source/WebCore/rendering/RenderMenuList.cpp new file mode 100644 index 0000000..5ad661f --- /dev/null +++ b/Source/WebCore/rendering/RenderMenuList.cpp @@ -0,0 +1,562 @@ +/* + * This file is part of the select element renderer in WebCore. + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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 "RenderMenuList.h" + +#include "AXObjectCache.h" +#include "Chrome.h" +#include "CSSStyleSelector.h" +#include "Frame.h" +#include "FrameView.h" +#include "HTMLNames.h" +#include "NodeRenderStyle.h" +#include "OptionElement.h" +#include "OptionGroupElement.h" +#include "PopupMenu.h" +#include "RenderBR.h" +#include "RenderScrollbar.h" +#include "RenderTheme.h" +#include "SelectElement.h" +#include <math.h> + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +RenderMenuList::RenderMenuList(Element* element) + : RenderFlexibleBox(element) + , m_buttonText(0) + , m_innerBlock(0) + , m_optionsChanged(true) + , m_optionsWidth(0) + , m_lastSelectedIndex(-1) + , m_popup(0) + , m_popupIsVisible(false) +{ +} + +RenderMenuList::~RenderMenuList() +{ + if (m_popup) + m_popup->disconnectClient(); + m_popup = 0; +} + +void RenderMenuList::createInnerBlock() +{ + if (m_innerBlock) { + ASSERT(firstChild() == m_innerBlock); + ASSERT(!m_innerBlock->nextSibling()); + return; + } + + // Create an anonymous block. + ASSERT(!firstChild()); + m_innerBlock = createAnonymousBlock(); + adjustInnerStyle(); + RenderFlexibleBox::addChild(m_innerBlock); +} + +void RenderMenuList::adjustInnerStyle() +{ + m_innerBlock->style()->setBoxFlex(1.0f); + + m_innerBlock->style()->setPaddingLeft(Length(theme()->popupInternalPaddingLeft(style()), Fixed)); + m_innerBlock->style()->setPaddingRight(Length(theme()->popupInternalPaddingRight(style()), Fixed)); + m_innerBlock->style()->setPaddingTop(Length(theme()->popupInternalPaddingTop(style()), Fixed)); + m_innerBlock->style()->setPaddingBottom(Length(theme()->popupInternalPaddingBottom(style()), Fixed)); + + if (document()->page()->chrome()->selectItemWritingDirectionIsNatural()) { + // Items in the popup will not respect the CSS text-align and direction properties, + // so we must adjust our own style to match. + m_innerBlock->style()->setTextAlign(LEFT); + TextDirection direction = (m_buttonText && m_buttonText->text()->defaultWritingDirection() == WTF::Unicode::RightToLeft) ? RTL : LTR; + m_innerBlock->style()->setDirection(direction); + } +} + +void RenderMenuList::addChild(RenderObject* newChild, RenderObject* beforeChild) +{ + createInnerBlock(); + m_innerBlock->addChild(newChild, beforeChild); +} + +void RenderMenuList::removeChild(RenderObject* oldChild) +{ + if (oldChild == m_innerBlock || !m_innerBlock) { + RenderFlexibleBox::removeChild(oldChild); + m_innerBlock = 0; + } else + m_innerBlock->removeChild(oldChild); +} + +void RenderMenuList::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBlock::styleDidChange(diff, oldStyle); + + if (m_buttonText) + m_buttonText->setStyle(style()); + if (m_innerBlock) // RenderBlock handled updating the anonymous block's style. + adjustInnerStyle(); + + setReplaced(isInline()); + + bool fontChanged = !oldStyle || oldStyle->font() != style()->font(); + if (fontChanged) + updateOptionsWidth(); +} + +void RenderMenuList::updateOptionsWidth() +{ + float maxOptionWidth = 0; + const Vector<Element*>& listItems = toSelectElement(static_cast<Element*>(node()))->listItems(); + int size = listItems.size(); + for (int i = 0; i < size; ++i) { + Element* element = listItems[i]; + OptionElement* optionElement = toOptionElement(element); + if (!optionElement) + continue; + + String text = optionElement->textIndentedToRespectGroupLabel(); + if (theme()->popupOptionSupportsTextIndent()) { + // Add in the option's text indent. We can't calculate percentage values for now. + float optionWidth = 0; + if (RenderStyle* optionStyle = element->renderStyle()) + optionWidth += optionStyle->textIndent().calcMinValue(0); + if (!text.isEmpty()) + optionWidth += style()->font().floatWidth(text); + maxOptionWidth = max(maxOptionWidth, optionWidth); + } else if (!text.isEmpty()) + maxOptionWidth = max(maxOptionWidth, style()->font().floatWidth(text)); + } + + int width = static_cast<int>(ceilf(maxOptionWidth)); + if (m_optionsWidth == width) + return; + + m_optionsWidth = width; + if (parent()) + setNeedsLayoutAndPrefWidthsRecalc(); +} + +void RenderMenuList::updateFromElement() +{ + if (m_optionsChanged) { + updateOptionsWidth(); + m_optionsChanged = false; + } + + if (m_popupIsVisible) + m_popup->updateFromElement(); + else + setTextFromOption(toSelectElement(static_cast<Element*>(node()))->selectedIndex()); +} + +void RenderMenuList::setTextFromOption(int optionIndex) +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + const Vector<Element*>& listItems = select->listItems(); + int size = listItems.size(); + + int i = select->optionToListIndex(optionIndex); + String text = ""; + if (i >= 0 && i < size) { + if (OptionElement* optionElement = toOptionElement(listItems[i])) + text = optionElement->textIndentedToRespectGroupLabel(); + } + + setText(text.stripWhiteSpace()); +} + +void RenderMenuList::setText(const String& s) +{ + if (s.isEmpty()) { + if (!m_buttonText || !m_buttonText->isBR()) { + if (m_buttonText) + m_buttonText->destroy(); + m_buttonText = new (renderArena()) RenderBR(document()); + m_buttonText->setStyle(style()); + addChild(m_buttonText); + } + } else { + if (m_buttonText && !m_buttonText->isBR()) + m_buttonText->setText(s.impl()); + else { + if (m_buttonText) + m_buttonText->destroy(); + m_buttonText = new (renderArena()) RenderText(document(), s.impl()); + m_buttonText->setStyle(style()); + addChild(m_buttonText); + } + adjustInnerStyle(); + } +} + +String RenderMenuList::text() const +{ + return m_buttonText ? m_buttonText->text() : 0; +} + +IntRect RenderMenuList::controlClipRect(int tx, int ty) const +{ + // Clip to the intersection of the content box and the content box for the inner box + // This will leave room for the arrows which sit in the inner box padding, + // and if the inner box ever spills out of the outer box, that will get clipped too. + IntRect outerBox(tx + borderLeft() + paddingLeft(), + ty + borderTop() + paddingTop(), + contentWidth(), + contentHeight()); + + IntRect innerBox(tx + m_innerBlock->x() + m_innerBlock->paddingLeft(), + ty + m_innerBlock->y() + m_innerBlock->paddingTop(), + m_innerBlock->contentWidth(), + m_innerBlock->contentHeight()); + + return intersection(outerBox, innerBox); +} + +void RenderMenuList::computePreferredLogicalWidths() +{ + m_minPreferredLogicalWidth = 0; + m_maxPreferredLogicalWidth = 0; + + if (style()->width().isFixed() && style()->width().value() > 0) + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value()); + else + m_maxPreferredLogicalWidth = max(m_optionsWidth, theme()->minimumMenuListSize(style())) + m_innerBlock->paddingLeft() + m_innerBlock->paddingRight(); + + if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) { + m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); + m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); + } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent())) + m_minPreferredLogicalWidth = 0; + else + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth; + + if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) { + m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); + m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); + } + + int toAdd = borderAndPaddingWidth(); + m_minPreferredLogicalWidth += toAdd; + m_maxPreferredLogicalWidth += toAdd; + + setPreferredLogicalWidthsDirty(false); +} + +void RenderMenuList::showPopup() +{ + if (m_popupIsVisible) + return; + + // Create m_innerBlock here so it ends up as the first child. + // This is important because otherwise we might try to create m_innerBlock + // inside the showPopup call and it would fail. + createInnerBlock(); + if (!m_popup) + m_popup = document()->page()->chrome()->createPopupMenu(this); + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + m_popupIsVisible = true; + + // Compute the top left taking transforms into account, but use + // the actual width of the element to size the popup. + FloatPoint absTopLeft = localToAbsolute(FloatPoint(), false, true); + IntRect absBounds = absoluteBoundingBoxRect(); + absBounds.setLocation(roundedIntPoint(absTopLeft)); + m_popup->show(absBounds, document()->view(), + select->optionToListIndex(select->selectedIndex())); +} + +void RenderMenuList::hidePopup() +{ + if (m_popup) + m_popup->hide(); +} + +void RenderMenuList::valueChanged(unsigned listIndex, bool fireOnChange) +{ + // Check to ensure a page navigation has not occurred while + // the popup was up. + Document* doc = static_cast<Element*>(node())->document(); + if (!doc || doc != doc->frame()->document()) + return; + + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + select->setSelectedIndexByUser(select->listToOptionIndex(listIndex), true, fireOnChange); +} + +#if ENABLE(NO_LISTBOX_RENDERING) +void RenderMenuList::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow) +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + select->listBoxSelectItem(listIndex, allowMultiplySelections, shift, fireOnChangeNow); +} + +bool RenderMenuList::multiple() +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + return select->multiple(); +} +#endif + +void RenderMenuList::didSetSelectedIndex() +{ + int index = selectedIndex(); + if (m_lastSelectedIndex == index) + return; + + m_lastSelectedIndex = index; + + if (AXObjectCache::accessibilityEnabled()) + document()->axObjectCache()->postNotification(this, AXObjectCache::AXMenuListValueChanged, true, PostSynchronously); +} + +String RenderMenuList::itemText(unsigned listIndex) const +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + const Vector<Element*>& listItems = select->listItems(); + if (listIndex >= listItems.size()) + return String(); + Element* element = listItems[listIndex]; + if (OptionGroupElement* optionGroupElement = toOptionGroupElement(element)) + return optionGroupElement->groupLabelText(); + else if (OptionElement* optionElement = toOptionElement(element)) + return optionElement->textIndentedToRespectGroupLabel(); + return String(); +} + +String RenderMenuList::itemLabel(unsigned) const +{ + return String(); +} + +String RenderMenuList::itemIcon(unsigned) const +{ + return String(); +} + +String RenderMenuList::itemAccessibilityText(unsigned listIndex) const +{ + // Allow the accessible name be changed if necessary. + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + const Vector<Element*>& listItems = select->listItems(); + if (listIndex >= listItems.size()) + return String(); + + return listItems[listIndex]->getAttribute(aria_labelAttr); +} + +String RenderMenuList::itemToolTip(unsigned listIndex) const +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + const Vector<Element*>& listItems = select->listItems(); + if (listIndex >= listItems.size()) + return String(); + Element* element = listItems[listIndex]; + return element->title(); +} + +bool RenderMenuList::itemIsEnabled(unsigned listIndex) const +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + const Vector<Element*>& listItems = select->listItems(); + if (listIndex >= listItems.size()) + return false; + Element* element = listItems[listIndex]; + if (!isOptionElement(element)) + return false; + + bool groupEnabled = true; + if (Element* parentElement = element->parentElement()) { + if (isOptionGroupElement(parentElement)) + groupEnabled = parentElement->isEnabledFormControl(); + } + if (!groupEnabled) + return false; + + return element->isEnabledFormControl(); +} + +PopupMenuStyle RenderMenuList::itemStyle(unsigned listIndex) const +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + const Vector<Element*>& listItems = select->listItems(); + if (listIndex >= listItems.size()) { + // If we are making an out of bounds access, then we want to use the style + // of a different option element (index 0). However, if there isn't an option element + // before at index 0, we fall back to the menu's style. + if (!listIndex) + return menuStyle(); + + // Try to retrieve the style of an option element we know exists (index 0). + listIndex = 0; + } + Element* element = listItems[listIndex]; + + RenderStyle* style = element->renderStyle() ? element->renderStyle() : element->computedStyle(); + return style ? PopupMenuStyle(style->visitedDependentColor(CSSPropertyColor), itemBackgroundColor(listIndex), style->font(), style->visibility() == VISIBLE, style->display() == NONE, style->textIndent(), style->direction()) : menuStyle(); +} + +Color RenderMenuList::itemBackgroundColor(unsigned listIndex) const +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + const Vector<Element*>& listItems = select->listItems(); + if (listIndex >= listItems.size()) + return style()->visitedDependentColor(CSSPropertyBackgroundColor); + Element* element = listItems[listIndex]; + + Color backgroundColor; + if (element->renderStyle()) + backgroundColor = element->renderStyle()->visitedDependentColor(CSSPropertyBackgroundColor); + // If the item has an opaque background color, return that. + if (!backgroundColor.hasAlpha()) + return backgroundColor; + + // Otherwise, the item's background is overlayed on top of the menu background. + backgroundColor = style()->visitedDependentColor(CSSPropertyBackgroundColor).blend(backgroundColor); + if (!backgroundColor.hasAlpha()) + return backgroundColor; + + // If the menu background is not opaque, then add an opaque white background behind. + return Color(Color::white).blend(backgroundColor); +} + +PopupMenuStyle RenderMenuList::menuStyle() const +{ + RenderStyle* s = m_innerBlock ? m_innerBlock->style() : style(); + return PopupMenuStyle(s->visitedDependentColor(CSSPropertyColor), s->visitedDependentColor(CSSPropertyBackgroundColor), s->font(), s->visibility() == VISIBLE, s->display() == NONE, s->textIndent(), s->direction()); +} + +HostWindow* RenderMenuList::hostWindow() const +{ + return document()->view()->hostWindow(); +} + +PassRefPtr<Scrollbar> RenderMenuList::createScrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, ScrollbarControlSize controlSize) +{ + RefPtr<Scrollbar> widget; + bool hasCustomScrollbarStyle = style()->hasPseudoStyle(SCROLLBAR); + if (hasCustomScrollbarStyle) + widget = RenderScrollbar::createCustomScrollbar(client, orientation, this); + else + widget = Scrollbar::createNativeScrollbar(client, orientation, controlSize); + return widget.release(); +} + +int RenderMenuList::clientInsetLeft() const +{ + return 0; +} + +int RenderMenuList::clientInsetRight() const +{ + return 0; +} + +int RenderMenuList::clientPaddingLeft() const +{ + return paddingLeft(); +} + +const int endOfLinePadding = 2; +int RenderMenuList::clientPaddingRight() const +{ + if (style()->appearance() == MenulistPart || style()->appearance() == MenulistButtonPart) { + // For these appearance values, the theme applies padding to leave room for the + // drop-down button. But leaving room for the button inside the popup menu itself + // looks strange, so we return a small default padding to avoid having a large empty + // space appear on the side of the popup menu. + return endOfLinePadding; + } + + // If the appearance isn't MenulistPart, then the select is styled (non-native), so + // we want to return the user specified padding. + return paddingRight(); +} + +int RenderMenuList::listSize() const +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + return select->listItems().size(); +} + +int RenderMenuList::selectedIndex() const +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + return select->optionToListIndex(select->selectedIndex()); +} + +void RenderMenuList::popupDidHide() +{ + m_popupIsVisible = false; +} + +bool RenderMenuList::itemIsSeparator(unsigned listIndex) const +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + const Vector<Element*>& listItems = select->listItems(); + if (listIndex >= listItems.size()) + return false; + Element* element = listItems[listIndex]; + return element->hasTagName(hrTag); +} + +bool RenderMenuList::itemIsLabel(unsigned listIndex) const +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + const Vector<Element*>& listItems = select->listItems(); + if (listIndex >= listItems.size()) + return false; + Element* element = listItems[listIndex]; + return isOptionGroupElement(element); +} + +bool RenderMenuList::itemIsSelected(unsigned listIndex) const +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + const Vector<Element*>& listItems = select->listItems(); + if (listIndex >= listItems.size()) + return false; + Element* element = listItems[listIndex]; + if (OptionElement* optionElement = toOptionElement(element)) + return optionElement->selected(); + return false; +} + +void RenderMenuList::setTextFromItem(unsigned listIndex) +{ + SelectElement* select = toSelectElement(static_cast<Element*>(node())); + setTextFromOption(select->listToOptionIndex(listIndex)); +} + +FontSelector* RenderMenuList::fontSelector() const +{ + return document()->styleSelector()->fontSelector(); +} + +} diff --git a/Source/WebCore/rendering/RenderMenuList.h b/Source/WebCore/rendering/RenderMenuList.h new file mode 100644 index 0000000..2c99b1e --- /dev/null +++ b/Source/WebCore/rendering/RenderMenuList.h @@ -0,0 +1,150 @@ +/* + * This file is part of the select element renderer in WebCore. + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderMenuList_h +#define RenderMenuList_h + +#include "PopupMenu.h" +#include "PopupMenuClient.h" +#include "RenderFlexibleBox.h" + +#if PLATFORM(MAC) +#define POPUP_MENU_PULLS_DOWN 0 +#else +#define POPUP_MENU_PULLS_DOWN 1 +#endif + +namespace WebCore { + +class RenderText; + +#if ENABLE(NO_LISTBOX_RENDERING) +class RenderMenuList : public RenderFlexibleBox, private ListPopupMenuClient { +#else +class RenderMenuList : public RenderFlexibleBox, private PopupMenuClient { +#endif + +public: + RenderMenuList(Element*); + virtual ~RenderMenuList(); + +public: + bool popupIsVisible() const { return m_popupIsVisible; } + void showPopup(); + void hidePopup(); + + void setOptionsChanged(bool changed) { m_optionsChanged = changed; } + + void didSetSelectedIndex(); + + String text() const; + +private: + virtual bool isMenuList() const { return true; } + + virtual void addChild(RenderObject* newChild, RenderObject* beforeChild = 0); + virtual void removeChild(RenderObject*); + virtual bool createsAnonymousWrapper() const { return true; } + virtual bool canHaveChildren() const { return false; } + + virtual void updateFromElement(); + + virtual bool hasControlClip() const { return true; } + virtual IntRect controlClipRect(int tx, int ty) const; + + virtual const char* renderName() const { return "RenderMenuList"; } + + virtual void computePreferredLogicalWidths(); + + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + virtual bool requiresForcedStyleRecalcPropagation() const { return true; } + + // PopupMenuClient methods + virtual String itemText(unsigned listIndex) const; + virtual String itemLabel(unsigned listIndex) const; + virtual String itemIcon(unsigned listIndex) const; + virtual String itemToolTip(unsigned listIndex) const; + virtual String itemAccessibilityText(unsigned listIndex) const; + virtual bool itemIsEnabled(unsigned listIndex) const; + virtual PopupMenuStyle itemStyle(unsigned listIndex) const; + virtual PopupMenuStyle menuStyle() const; + virtual int clientInsetLeft() const; + virtual int clientInsetRight() const; + virtual int clientPaddingLeft() const; + virtual int clientPaddingRight() const; + virtual int listSize() const; + virtual int selectedIndex() const; + virtual void popupDidHide(); + virtual bool itemIsSeparator(unsigned listIndex) const; + virtual bool itemIsLabel(unsigned listIndex) const; + virtual bool itemIsSelected(unsigned listIndex) const; + virtual void setTextFromItem(unsigned listIndex); + virtual bool valueShouldChangeOnHotTrack() const { return true; } + virtual bool shouldPopOver() const { return !POPUP_MENU_PULLS_DOWN; } + virtual void valueChanged(unsigned listIndex, bool fireOnChange = true); + virtual void selectionChanged(unsigned, bool) {} + virtual void selectionCleared() {} + virtual FontSelector* fontSelector() const; + virtual HostWindow* hostWindow() const; + virtual PassRefPtr<Scrollbar> createScrollbar(ScrollbarClient*, ScrollbarOrientation, ScrollbarControlSize); + +#if ENABLE(NO_LISTBOX_RENDERING) + virtual void listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow = true); + virtual bool multiple(); +#endif + + virtual bool hasLineIfEmpty() const { return true; } + + Color itemBackgroundColor(unsigned listIndex) const; + + void createInnerBlock(); + void adjustInnerStyle(); + void setText(const String&); + void setTextFromOption(int optionIndex); + void updateOptionsWidth(); + + RenderText* m_buttonText; + RenderBlock* m_innerBlock; + + bool m_optionsChanged; + int m_optionsWidth; + + int m_lastSelectedIndex; + + RefPtr<PopupMenu> m_popup; + bool m_popupIsVisible; +}; + +inline RenderMenuList* toRenderMenuList(RenderObject* object) +{ + ASSERT(!object || object->isMenuList()); + return static_cast<RenderMenuList*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderMenuList(const RenderMenuList*); + +} + +#endif diff --git a/Source/WebCore/rendering/RenderMeter.cpp b/Source/WebCore/rendering/RenderMeter.cpp new file mode 100644 index 0000000..6439651 --- /dev/null +++ b/Source/WebCore/rendering/RenderMeter.cpp @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * + * 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" + +#if ENABLE(METER_TAG) + +#include "RenderMeter.h" + +#include "HTMLMeterElement.h" +#include "HTMLNames.h" +#include "RenderTheme.h" +#include "ShadowElement.h" + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +class MeterPartElement : public ShadowBlockElement { +public: + static PassRefPtr<MeterPartElement> createForPart(HTMLElement*, PseudoId); + + void hide(); + void restoreVisibility(); + + virtual void updateStyleForPart(PseudoId); + +private: + MeterPartElement(HTMLElement*); + void saveVisibility(); + + EVisibility m_originalVisibility; +}; + +MeterPartElement::MeterPartElement(HTMLElement* shadowParent) + : ShadowBlockElement(shadowParent) +{ +} + +PassRefPtr<MeterPartElement> MeterPartElement::createForPart(HTMLElement* shadowParent, PseudoId pseudoId) +{ + RefPtr<MeterPartElement> ret = adoptRef(new MeterPartElement(shadowParent)); + ret->initAsPart(pseudoId); + ret->saveVisibility(); + return ret; +} + +void MeterPartElement::hide() +{ + if (renderer()) + renderer()->style()->setVisibility(HIDDEN); +} + +void MeterPartElement::restoreVisibility() +{ + if (renderer()) + renderer()->style()->setVisibility(m_originalVisibility); +} + +void MeterPartElement::updateStyleForPart(PseudoId pseudoId) +{ + if (renderer()->style()->styleType() == pseudoId) + return; + + ShadowBlockElement::updateStyleForPart(pseudoId); + saveVisibility(); +} + +void MeterPartElement::saveVisibility() +{ + m_originalVisibility = renderer()->style()->visibility(); +} + +RenderMeter::RenderMeter(HTMLMeterElement* element) + : RenderIndicator(element) +{ +} + +RenderMeter::~RenderMeter() +{ + if (shadowAttached()) { + m_verticalValuePart->detach(); + m_verticalBarPart->detach(); + m_horizontalValuePart->detach(); + m_horizontalBarPart->detach(); + } +} + +PassRefPtr<MeterPartElement> RenderMeter::createPart(PseudoId pseudoId) +{ + RefPtr<MeterPartElement> element = MeterPartElement::createForPart(static_cast<HTMLElement*>(node()), pseudoId); + if (element->renderer()) + addChild(element->renderer()); + return element; +} + +void RenderMeter::updateFromElement() +{ + if (!shadowAttached()) { + m_horizontalBarPart = createPart(barPseudoId(HORIZONTAL)); + m_horizontalValuePart = createPart(valuePseudoId(HORIZONTAL)); + m_verticalBarPart = createPart(barPseudoId(VERTICAL)); + m_verticalValuePart = createPart(valuePseudoId(VERTICAL)); + } + + m_horizontalBarPart->updateStyleForPart(barPseudoId(HORIZONTAL)); + m_horizontalValuePart->updateStyleForPart(valuePseudoId(HORIZONTAL)); + m_verticalBarPart->updateStyleForPart(barPseudoId(VERTICAL)); + m_verticalValuePart->updateStyleForPart(valuePseudoId(VERTICAL)); + RenderIndicator::updateFromElement(); +} + +void RenderMeter::computeLogicalWidth() +{ + RenderBox::computeLogicalWidth(); + setWidth(theme()->meterSizeForBounds(this, frameRect()).width()); +} + +void RenderMeter::computeLogicalHeight() +{ + RenderBox::computeLogicalHeight(); + setHeight(theme()->meterSizeForBounds(this, frameRect()).height()); +} + +void RenderMeter::layoutParts() +{ + m_horizontalBarPart->layoutAsPart(barPartRect()); + m_horizontalValuePart->layoutAsPart(valuePartRect(HORIZONTAL)); + m_verticalBarPart->layoutAsPart(barPartRect()); + m_verticalValuePart->layoutAsPart(valuePartRect(VERTICAL)); + + if (shouldHaveParts()) { + if (HORIZONTAL == orientation()) { + m_verticalBarPart->hide(); + m_verticalValuePart->hide(); + m_horizontalBarPart->restoreVisibility(); + m_horizontalValuePart->restoreVisibility(); + } else { + m_verticalBarPart->restoreVisibility(); + m_verticalValuePart->restoreVisibility(); + m_horizontalBarPart->hide(); + m_horizontalValuePart->hide(); + } + } else { + m_verticalBarPart->hide(); + m_verticalValuePart->hide(); + m_horizontalBarPart->hide(); + m_horizontalValuePart->hide(); + } +} + +bool RenderMeter::shouldHaveParts() const +{ + EBoxOrient currentOrientation = orientation(); + bool hasTheme = theme()->supportsMeter(style()->appearance(), HORIZONTAL == currentOrientation); + if (!hasTheme) + return true; + bool shadowsHaveStyle = ShadowBlockElement::partShouldHaveStyle(this, barPseudoId(currentOrientation)) || ShadowBlockElement::partShouldHaveStyle(this, valuePseudoId(currentOrientation)); + if (shadowsHaveStyle) + return true; + return false; +} + +double RenderMeter::valueRatio() const +{ + HTMLMeterElement* element = static_cast<HTMLMeterElement*>(node()); + double min = element->min(); + double max = element->max(); + double value = element->value(); + + if (max <= min) + return 0; + return (value - min) / (max - min); +} + +IntRect RenderMeter::barPartRect() const +{ + return IntRect(borderLeft() + paddingLeft(), borderTop() + paddingTop(), lround(width() - borderLeft() - paddingLeft() - borderRight() - paddingRight()), height() - borderTop() - paddingTop() - borderBottom() - paddingBottom()); +} + +IntRect RenderMeter::valuePartRect(EBoxOrient asOrientation) const +{ + IntRect rect = barPartRect(); + + if (HORIZONTAL == asOrientation) { + int width = static_cast<int>(rect.width()*valueRatio()); + if (!style()->isLeftToRightDirection()) { + rect.setX(rect.x() + (rect.width() - width)); + rect.setWidth(width); + } else + rect.setWidth(width); + } else { + int height = static_cast<int>(rect.height()*valueRatio()); + rect.setY(rect.y() + (rect.height() - height)); + rect.setHeight(height); + } + + return rect; +} + +EBoxOrient RenderMeter::orientation() const +{ + IntRect rect = barPartRect(); + return rect.height() <= rect.width() ? HORIZONTAL : VERTICAL; +} + +PseudoId RenderMeter::valuePseudoId(EBoxOrient asOrientation) const +{ + HTMLMeterElement* element = static_cast<HTMLMeterElement*>(node()); + + if (HORIZONTAL == asOrientation) { + switch (element->gaugeRegion()) { + case HTMLMeterElement::GaugeRegionOptimum: + return METER_HORIZONTAL_OPTIMUM; + case HTMLMeterElement::GaugeRegionSuboptimal: + return METER_HORIZONTAL_SUBOPTIMAL; + case HTMLMeterElement::GaugeRegionEvenLessGood: + return METER_HORIZONTAL_EVEN_LESS_GOOD; + } + } else { + switch (element->gaugeRegion()) { + case HTMLMeterElement::GaugeRegionOptimum: + return METER_VERTICAL_OPTIMUM; + case HTMLMeterElement::GaugeRegionSuboptimal: + return METER_VERTICAL_SUBOPTIMAL; + case HTMLMeterElement::GaugeRegionEvenLessGood: + return METER_VERTICAL_EVEN_LESS_GOOD; + } + } + + ASSERT_NOT_REACHED(); + return NOPSEUDO; +} + +PseudoId RenderMeter::barPseudoId(EBoxOrient asOrientation) const +{ + return HORIZONTAL == asOrientation ? METER_HORIZONTAL_BAR : METER_VERTICAL_BAR; +} + +} // namespace WebCore + +#endif diff --git a/Source/WebCore/rendering/RenderMeter.h b/Source/WebCore/rendering/RenderMeter.h new file mode 100644 index 0000000..2f5f5f5 --- /dev/null +++ b/Source/WebCore/rendering/RenderMeter.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + * + * 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. + * + */ + +#ifndef RenderMeter_h +#define RenderMeter_h + +#if ENABLE(METER_TAG) +#include "RenderBlock.h" +#include "RenderIndicator.h" +#include "RenderWidget.h" + + +namespace WebCore { + +class HTMLMeterElement; +class MeterPartElement; + +class RenderMeter : public RenderIndicator { +public: + RenderMeter(HTMLMeterElement*); + virtual ~RenderMeter(); + +private: + virtual const char* renderName() const { return "RenderMeter"; } + virtual bool isMeter() const { return true; } + virtual void updateFromElement(); + virtual void computeLogicalWidth(); + virtual void computeLogicalHeight(); + + virtual void layoutParts(); + + bool shadowAttached() const { return m_horizontalBarPart; } + IntRect valuePartRect(EBoxOrient) const; + PseudoId valuePseudoId(EBoxOrient) const; + IntRect barPartRect() const; + PseudoId barPseudoId(EBoxOrient) const; + EBoxOrient orientation() const; + + double valueRatio() const; + bool shouldHaveParts() const; + PassRefPtr<MeterPartElement> createPart(PseudoId); + + RefPtr<MeterPartElement> m_horizontalBarPart; + RefPtr<MeterPartElement> m_horizontalValuePart; + RefPtr<MeterPartElement> m_verticalBarPart; + RefPtr<MeterPartElement> m_verticalValuePart; +}; + +inline RenderMeter* toRenderMeter(RenderObject* object) +{ + ASSERT(!object || object->isMeter()); + return static_cast<RenderMeter*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderMeter(const RenderMeter*); + +} // namespace WebCore + +#endif + +#endif // RenderMeter_h + diff --git a/Source/WebCore/rendering/RenderObject.cpp b/Source/WebCore/rendering/RenderObject.cpp new file mode 100644 index 0000000..27a53d0 --- /dev/null +++ b/Source/WebCore/rendering/RenderObject.cpp @@ -0,0 +1,2776 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * (C) 2004 Allan Sandfeld Jensen (kde@carewolf.com) + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2009 Google Inc. All rights reserved. + * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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 "RenderObject.h" + +#include "AXObjectCache.h" +#include "Chrome.h" +#include "CSSStyleSelector.h" +#include "DashArray.h" +#include "EditingBoundary.h" +#include "FloatQuad.h" +#include "Frame.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "HTMLNames.h" +#include "HitTestResult.h" +#include "Page.h" +#include "RenderArena.h" +#include "RenderCounter.h" +#include "RenderFlexibleBox.h" +#include "RenderImage.h" +#include "RenderImageResourceStyleImage.h" +#include "RenderInline.h" +#include "RenderLayer.h" +#include "RenderListItem.h" +#include "RenderRuby.h" +#include "RenderRubyText.h" +#include "RenderTableCell.h" +#include "RenderTableCol.h" +#include "RenderTableRow.h" +#include "RenderTheme.h" +#include "RenderView.h" +#include "TransformState.h" +#include "htmlediting.h" +#include <algorithm> +#ifdef ANDROID_LAYOUT +#include "Settings.h" +#endif +#include <stdio.h> +#include <wtf/RefCountedLeakCounter.h> +#include <wtf/UnusedParam.h> + +#if USE(ACCELERATED_COMPOSITING) +#include "RenderLayerCompositor.h" +#endif + +#if ENABLE(WML) +#include "WMLNames.h" +#endif + +#if ENABLE(SVG) +#include "RenderSVGResourceContainer.h" +#include "SVGRenderSupport.h" +#endif + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +#ifndef NDEBUG +static void* baseOfRenderObjectBeingDeleted; +#endif + +bool RenderObject::s_affectsParentBlock = false; + +void* RenderObject::operator new(size_t sz, RenderArena* renderArena) throw() +{ + return renderArena->allocate(sz); +} + +void RenderObject::operator delete(void* ptr, size_t sz) +{ + ASSERT(baseOfRenderObjectBeingDeleted == ptr); + + // Stash size where destroy can find it. + *(size_t *)ptr = sz; +} + +RenderObject* RenderObject::createObject(Node* node, RenderStyle* style) +{ + Document* doc = node->document(); + RenderArena* arena = doc->renderArena(); + + // Minimal support for content properties replacing an entire element. + // Works only if we have exactly one piece of content and it's a URL. + // Otherwise acts as if we didn't support this feature. + const ContentData* contentData = style->contentData(); + if (contentData && !contentData->next() && contentData->isImage() && doc != node) { + RenderImage* image = new (arena) RenderImage(node); + image->setStyle(style); + if (StyleImage* styleImage = contentData->image()) + image->setImageResource(RenderImageResourceStyleImage::create(styleImage)); + else + image->setImageResource(RenderImageResource::create()); + return image; + } + + if (node->hasTagName(rubyTag)) { + if (style->display() == INLINE) + return new (arena) RenderRubyAsInline(node); + else if (style->display() == BLOCK) + return new (arena) RenderRubyAsBlock(node); + } + // treat <rt> as ruby text ONLY if it still has its default treatment of block + if (node->hasTagName(rtTag) && style->display() == BLOCK) + return new (arena) RenderRubyText(node); + + switch (style->display()) { + case NONE: + return 0; + case INLINE: + return new (arena) RenderInline(node); + case BLOCK: + case INLINE_BLOCK: + case RUN_IN: + case COMPACT: + return new (arena) RenderBlock(node); + case LIST_ITEM: + return new (arena) RenderListItem(node); + case TABLE: + case INLINE_TABLE: + return new (arena) RenderTable(node); + case TABLE_ROW_GROUP: + case TABLE_HEADER_GROUP: + case TABLE_FOOTER_GROUP: + return new (arena) RenderTableSection(node); + case TABLE_ROW: + return new (arena) RenderTableRow(node); + case TABLE_COLUMN_GROUP: + case TABLE_COLUMN: + return new (arena) RenderTableCol(node); + case TABLE_CELL: + return new (arena) RenderTableCell(node); + case TABLE_CAPTION: +#if ENABLE(WCSS) + // As per the section 17.1 of the spec WAP-239-WCSS-20011026-a.pdf, + // the marquee box inherits and extends the characteristics of the + // principal block box ([CSS2] section 9.2.1). + case WAP_MARQUEE: +#endif + return new (arena) RenderBlock(node); + case BOX: + case INLINE_BOX: + return new (arena) RenderFlexibleBox(node); + } + + return 0; +} + +#ifndef NDEBUG +static WTF::RefCountedLeakCounter renderObjectCounter("RenderObject"); +#endif + +RenderObject::RenderObject(Node* node) + : CachedResourceClient() + , m_style(0) + , m_node(node) + , m_parent(0) + , m_previous(0) + , m_next(0) +#ifndef NDEBUG + , m_hasAXObject(false) + , m_setNeedsLayoutForbidden(false) +#endif + , m_needsLayout(false) + , m_needsPositionedMovementLayout(false) + , m_normalChildNeedsLayout(false) + , m_posChildNeedsLayout(false) + , m_preferredLogicalWidthsDirty(false) + , m_floating(false) + , m_positioned(false) + , m_relPositioned(false) + , m_paintBackground(false) + , m_isAnonymous(node == node->document()) + , m_isText(false) + , m_isBox(false) + , m_inline(true) + , m_replaced(false) + , m_isDragging(false) + , m_hasLayer(false) + , m_hasOverflowClip(false) + , m_hasTransform(false) + , m_hasReflection(false) + , m_hasOverrideSize(false) + , m_hasCounterNodeMap(false) + , m_everHadLayout(false) + , m_childrenInline(false) + , m_marginBeforeQuirk(false) + , m_marginAfterQuirk(false) + , m_hasMarkupTruncation(false) + , m_selectionState(SelectionNone) + , m_hasColumns(false) + , m_cellWidthChanged(false) +{ +#ifndef NDEBUG + renderObjectCounter.increment(); +#endif + ASSERT(node); +} + +RenderObject::~RenderObject() +{ + ASSERT(!node() || documentBeingDestroyed() || !frame()->view() || frame()->view()->layoutRoot() != this); +#ifndef NDEBUG + ASSERT(!m_hasAXObject); + renderObjectCounter.decrement(); +#endif +} + +RenderTheme* RenderObject::theme() const +{ + ASSERT(document()->page()); + + return document()->page()->theme(); +} + +bool RenderObject::isDescendantOf(const RenderObject* obj) const +{ + for (const RenderObject* r = this; r; r = r->m_parent) { + if (r == obj) + return true; + } + return false; +} + +bool RenderObject::isBody() const +{ + return node() && node()->hasTagName(bodyTag); +} + +bool RenderObject::isHR() const +{ + return node() && node()->hasTagName(hrTag); +} + +bool RenderObject::isLegend() const +{ + return node() && (node()->hasTagName(legendTag) +#if ENABLE(WML) + || node()->hasTagName(WMLNames::insertedLegendTag) +#endif + ); +} + +bool RenderObject::isHTMLMarquee() const +{ + return node() && node()->renderer() == this && node()->hasTagName(marqueeTag); +} + +void RenderObject::addChild(RenderObject* newChild, RenderObject* beforeChild) +{ + RenderObjectChildList* children = virtualChildren(); + ASSERT(children); + if (!children) + return; + + bool needsTable = false; + + if (newChild->isTableCol() && newChild->style()->display() == TABLE_COLUMN_GROUP) + needsTable = !isTable(); + else if (newChild->isRenderBlock() && newChild->style()->display() == TABLE_CAPTION) + needsTable = !isTable(); + else if (newChild->isTableSection()) + needsTable = !isTable(); + else if (newChild->isTableRow()) + needsTable = !isTableSection(); + else if (newChild->isTableCell()) { + needsTable = !isTableRow(); + // I'm not 100% sure this is the best way to fix this, but without this + // change we recurse infinitely when trying to render the CSS2 test page: + // http://www.bath.ac.uk/%7Epy8ieh/internet/eviltests/htmlbodyheadrendering2.html. + // See Radar 2925291. + if (needsTable && isTableCell() && !children->firstChild() && !newChild->isTableCell()) + needsTable = false; + } + + if (needsTable) { + RenderTable* table; + RenderObject* afterChild = beforeChild ? beforeChild->previousSibling() : children->lastChild(); + if (afterChild && afterChild->isAnonymous() && afterChild->isTable()) + table = toRenderTable(afterChild); + else { + table = new (renderArena()) RenderTable(document() /* is anonymous */); + RefPtr<RenderStyle> newStyle = RenderStyle::create(); + newStyle->inheritFrom(style()); + newStyle->setDisplay(TABLE); + table->setStyle(newStyle.release()); + addChild(table, beforeChild); + } + table->addChild(newChild); + } else { + // Just add it... + children->insertChildNode(this, newChild, beforeChild); + } + RenderCounter::rendererSubtreeAttached(newChild); + if (newChild->isText() && newChild->style()->textTransform() == CAPITALIZE) { + RefPtr<StringImpl> textToTransform = toRenderText(newChild)->originalText(); + if (textToTransform) + toRenderText(newChild)->setText(textToTransform.release(), true); + } +} + +void RenderObject::removeChild(RenderObject* oldChild) +{ + RenderObjectChildList* children = virtualChildren(); + ASSERT(children); + if (!children) + return; + + // We do this here instead of in removeChildNode, since the only extremely low-level uses of remove/appendChildNode + // cannot affect the positioned object list, and the floating object list is irrelevant (since the list gets cleared on + // layout anyway). + if (oldChild->isFloatingOrPositioned()) + toRenderBox(oldChild)->removeFloatingOrPositionedChildFromBlockLists(); + + children->removeChildNode(this, oldChild); +} + +RenderObject* RenderObject::nextInPreOrder() const +{ + if (RenderObject* o = firstChild()) + return o; + + return nextInPreOrderAfterChildren(); +} + +RenderObject* RenderObject::nextInPreOrderAfterChildren() const +{ + RenderObject* o; + if (!(o = nextSibling())) { + o = parent(); + while (o && !o->nextSibling()) + o = o->parent(); + if (o) + o = o->nextSibling(); + } + + return o; +} + +RenderObject* RenderObject::nextInPreOrder(RenderObject* stayWithin) const +{ + if (RenderObject* o = firstChild()) + return o; + + return nextInPreOrderAfterChildren(stayWithin); +} + +RenderObject* RenderObject::nextInPreOrderAfterChildren(RenderObject* stayWithin) const +{ + if (this == stayWithin) + return 0; + + const RenderObject* current = this; + RenderObject* next; + while (!(next = current->nextSibling())) { + current = current->parent(); + if (!current || current == stayWithin) + return 0; + } + return next; +} + +RenderObject* RenderObject::previousInPreOrder() const +{ + if (RenderObject* o = previousSibling()) { + while (o->lastChild()) + o = o->lastChild(); + return o; + } + + return parent(); +} + +RenderObject* RenderObject::childAt(unsigned index) const +{ + RenderObject* child = firstChild(); + for (unsigned i = 0; child && i < index; i++) + child = child->nextSibling(); + return child; +} + +RenderObject* RenderObject::firstLeafChild() const +{ + RenderObject* r = firstChild(); + while (r) { + RenderObject* n = 0; + n = r->firstChild(); + if (!n) + break; + r = n; + } + return r; +} + +RenderObject* RenderObject::lastLeafChild() const +{ + RenderObject* r = lastChild(); + while (r) { + RenderObject* n = 0; + n = r->lastChild(); + if (!n) + break; + r = n; + } + return r; +} + +static void addLayers(RenderObject* obj, RenderLayer* parentLayer, RenderObject*& newObject, + RenderLayer*& beforeChild) +{ + if (obj->hasLayer()) { + if (!beforeChild && newObject) { + // We need to figure out the layer that follows newObject. We only do + // this the first time we find a child layer, and then we update the + // pointer values for newObject and beforeChild used by everyone else. + beforeChild = newObject->parent()->findNextLayer(parentLayer, newObject); + newObject = 0; + } + parentLayer->addChild(toRenderBoxModelObject(obj)->layer(), beforeChild); + return; + } + + for (RenderObject* curr = obj->firstChild(); curr; curr = curr->nextSibling()) + addLayers(curr, parentLayer, newObject, beforeChild); +} + +void RenderObject::addLayers(RenderLayer* parentLayer, RenderObject* newObject) +{ + if (!parentLayer) + return; + + RenderObject* object = newObject; + RenderLayer* beforeChild = 0; + WebCore::addLayers(this, parentLayer, object, beforeChild); +} + +void RenderObject::removeLayers(RenderLayer* parentLayer) +{ + if (!parentLayer) + return; + + if (hasLayer()) { + parentLayer->removeChild(toRenderBoxModelObject(this)->layer()); + return; + } + + for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) + curr->removeLayers(parentLayer); +} + +void RenderObject::moveLayers(RenderLayer* oldParent, RenderLayer* newParent) +{ + if (!newParent) + return; + + if (hasLayer()) { + RenderLayer* layer = toRenderBoxModelObject(this)->layer(); + ASSERT(oldParent == layer->parent()); + if (oldParent) + oldParent->removeChild(layer); + newParent->addChild(layer); + return; + } + + for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) + curr->moveLayers(oldParent, newParent); +} + +RenderLayer* RenderObject::findNextLayer(RenderLayer* parentLayer, RenderObject* startPoint, + bool checkParent) +{ + // Error check the parent layer passed in. If it's null, we can't find anything. + if (!parentLayer) + return 0; + + // Step 1: If our layer is a child of the desired parent, then return our layer. + RenderLayer* ourLayer = hasLayer() ? toRenderBoxModelObject(this)->layer() : 0; + if (ourLayer && ourLayer->parent() == parentLayer) + return ourLayer; + + // Step 2: If we don't have a layer, or our layer is the desired parent, then descend + // into our siblings trying to find the next layer whose parent is the desired parent. + if (!ourLayer || ourLayer == parentLayer) { + for (RenderObject* curr = startPoint ? startPoint->nextSibling() : firstChild(); + curr; curr = curr->nextSibling()) { + RenderLayer* nextLayer = curr->findNextLayer(parentLayer, 0, false); + if (nextLayer) + return nextLayer; + } + } + + // Step 3: If our layer is the desired parent layer, then we're finished. We didn't + // find anything. + if (parentLayer == ourLayer) + return 0; + + // Step 4: If |checkParent| is set, climb up to our parent and check its siblings that + // follow us to see if we can locate a layer. + if (checkParent && parent()) + return parent()->findNextLayer(parentLayer, this, true); + + return 0; +} + +RenderLayer* RenderObject::enclosingLayer() const +{ + const RenderObject* curr = this; + while (curr) { + RenderLayer* layer = curr->hasLayer() ? toRenderBoxModelObject(curr)->layer() : 0; + if (layer) + return layer; + curr = curr->parent(); + } + return 0; +} + +RenderBox* RenderObject::enclosingBox() const +{ + RenderObject* curr = const_cast<RenderObject*>(this); + while (curr) { + if (curr->isBox()) + return toRenderBox(curr); + curr = curr->parent(); + } + + ASSERT_NOT_REACHED(); + return 0; +} + +RenderBoxModelObject* RenderObject::enclosingBoxModelObject() const +{ + RenderObject* curr = const_cast<RenderObject*>(this); + while (curr) { + if (curr->isBoxModelObject()) + return toRenderBoxModelObject(curr); + curr = curr->parent(); + } + + ASSERT_NOT_REACHED(); + return 0; +} + +RenderBlock* RenderObject::firstLineBlock() const +{ + return 0; +} + +void RenderObject::setPreferredLogicalWidthsDirty(bool b, bool markParents) +{ + bool alreadyDirty = m_preferredLogicalWidthsDirty; + m_preferredLogicalWidthsDirty = b; + if (b && !alreadyDirty && markParents && (isText() || (style()->position() != FixedPosition && style()->position() != AbsolutePosition))) + invalidateContainerPreferredLogicalWidths(); +} + +void RenderObject::invalidateContainerPreferredLogicalWidths() +{ + // In order to avoid pathological behavior when inlines are deeply nested, we do include them + // in the chain that we mark dirty (even though they're kind of irrelevant). + RenderObject* o = isTableCell() ? containingBlock() : container(); + while (o && !o->m_preferredLogicalWidthsDirty) { + // Don't invalidate the outermost object of an unrooted subtree. That object will be + // invalidated when the subtree is added to the document. + RenderObject* container = o->isTableCell() ? o->containingBlock() : o->container(); + if (!container && !o->isRenderView()) + break; + + o->m_preferredLogicalWidthsDirty = true; + if (o->style()->position() == FixedPosition || o->style()->position() == AbsolutePosition) + // A positioned object has no effect on the min/max width of its containing block ever. + // We can optimize this case and not go up any further. + break; + o = container; + } +} + +void RenderObject::setLayerNeedsFullRepaint() +{ + ASSERT(hasLayer()); + toRenderBoxModelObject(this)->layer()->setNeedsFullRepaint(true); +} + +RenderBlock* RenderObject::containingBlock() const +{ + if (isTableCell()) { + const RenderTableCell* cell = toRenderTableCell(this); + if (parent() && cell->section()) + return cell->table(); + return 0; + } + + if (isRenderView()) + return const_cast<RenderView*>(toRenderView(this)); + + RenderObject* o = parent(); + if (!isText() && m_style->position() == FixedPosition) { + while (o && !o->isRenderView() && !(o->hasTransform() && o->isRenderBlock())) + o = o->parent(); + } else if (!isText() && m_style->position() == AbsolutePosition) { + while (o && (o->style()->position() == StaticPosition || (o->isInline() && !o->isReplaced())) && !o->isRenderView() && !(o->hasTransform() && o->isRenderBlock())) { + // For relpositioned inlines, we return the nearest enclosing block. We don't try + // to return the inline itself. This allows us to avoid having a positioned objects + // list in all RenderInlines and lets us return a strongly-typed RenderBlock* result + // from this method. The container() method can actually be used to obtain the + // inline directly. + if (o->style()->position() == RelativePosition && o->isInline() && !o->isReplaced()) + return o->containingBlock(); +#if ENABLE(SVG) + if (o->isSVGForeignObject()) //foreignObject is the containing block for contents inside it + break; +#endif + + o = o->parent(); + } + } else { + while (o && ((o->isInline() && !o->isReplaced()) || o->isTableRow() || o->isTableSection() + || o->isTableCol() || o->isFrameSet() || o->isMedia() +#if ENABLE(SVG) + || o->isSVGContainer() || o->isSVGRoot() +#endif + )) + o = o->parent(); + } + + if (!o || !o->isRenderBlock()) + return 0; // This can still happen in case of an orphaned tree + + return toRenderBlock(o); +} + +static bool mustRepaintFillLayers(const RenderObject* renderer, const FillLayer* layer) +{ + // Nobody will use multiple layers without wanting fancy positioning. + if (layer->next()) + return true; + + // Make sure we have a valid image. + StyleImage* img = layer->image(); + if (!img || !img->canRender(renderer->style()->effectiveZoom())) + return false; + + if (!layer->xPosition().isZero() || !layer->yPosition().isZero()) + return true; + + if (layer->size().type == SizeLength) { + if (layer->size().size.width().isPercent() || layer->size().size.height().isPercent()) + return true; + } else if (layer->size().type == Contain || layer->size().type == Cover || img->usesImageContainerSize()) + return true; + + return false; +} + +bool RenderObject::mustRepaintBackgroundOrBorder() const +{ + if (hasMask() && mustRepaintFillLayers(this, style()->maskLayers())) + return true; + + // If we don't have a background/border/mask, then nothing to do. + if (!hasBoxDecorations()) + return false; + + if (mustRepaintFillLayers(this, style()->backgroundLayers())) + return true; + + // Our fill layers are ok. Let's check border. + if (style()->hasBorder()) { + // Border images are not ok. + StyleImage* borderImage = style()->borderImage().image(); + bool shouldPaintBorderImage = borderImage && borderImage->canRender(style()->effectiveZoom()); + + // If the image hasn't loaded, we're still using the normal border style. + if (shouldPaintBorderImage && borderImage->isLoaded()) + return true; + } + + return false; +} + +void RenderObject::drawLineForBoxSide(GraphicsContext* graphicsContext, int x1, int y1, int x2, int y2, + BoxSide s, Color c, EBorderStyle style, + int adjbw1, int adjbw2) +{ + int width = (s == BSTop || s == BSBottom ? y2 - y1 : x2 - x1); + + if (style == DOUBLE && width < 3) + style = SOLID; + + switch (style) { + case BNONE: + case BHIDDEN: + return; + case DOTTED: + case DASHED: + graphicsContext->setStrokeColor(c, m_style->colorSpace()); + graphicsContext->setStrokeThickness(width); + graphicsContext->setStrokeStyle(style == DASHED ? DashedStroke : DottedStroke); + + if (width > 0) + switch (s) { + case BSBottom: + case BSTop: + graphicsContext->drawLine(IntPoint(x1, (y1 + y2) / 2), IntPoint(x2, (y1 + y2) / 2)); + break; + case BSRight: + case BSLeft: + graphicsContext->drawLine(IntPoint((x1 + x2) / 2, y1), IntPoint((x1 + x2) / 2, y2)); + break; + } + break; + case DOUBLE: { + int third = (width + 1) / 3; + + if (adjbw1 == 0 && adjbw2 == 0) { + graphicsContext->setStrokeStyle(NoStroke); + graphicsContext->setFillColor(c, m_style->colorSpace()); + switch (s) { + case BSTop: + case BSBottom: + graphicsContext->drawRect(IntRect(x1, y1, x2 - x1, third)); + graphicsContext->drawRect(IntRect(x1, y2 - third, x2 - x1, third)); + break; + case BSLeft: + graphicsContext->drawRect(IntRect(x1, y1 + 1, third, y2 - y1 - 1)); + graphicsContext->drawRect(IntRect(x2 - third, y1 + 1, third, y2 - y1 - 1)); + break; + case BSRight: + graphicsContext->drawRect(IntRect(x1, y1 + 1, third, y2 - y1 - 1)); + graphicsContext->drawRect(IntRect(x2 - third, y1 + 1, third, y2 - y1 - 1)); + break; + } + } else { + int adjbw1bigthird = ((adjbw1 > 0) ? adjbw1 + 1 : adjbw1 - 1) / 3; + int adjbw2bigthird = ((adjbw2 > 0) ? adjbw2 + 1 : adjbw2 - 1) / 3; + + switch (s) { + case BSTop: + drawLineForBoxSide(graphicsContext, x1 + max((-adjbw1 * 2 + 1) / 3, 0), + y1, x2 - max((-adjbw2 * 2 + 1) / 3, 0), y1 + third, + s, c, SOLID, adjbw1bigthird, adjbw2bigthird); + drawLineForBoxSide(graphicsContext, x1 + max((adjbw1 * 2 + 1) / 3, 0), + y2 - third, x2 - max((adjbw2 * 2 + 1) / 3, 0), y2, + s, c, SOLID, adjbw1bigthird, adjbw2bigthird); + break; + case BSLeft: + drawLineForBoxSide(graphicsContext, x1, y1 + max((-adjbw1 * 2 + 1) / 3, 0), + x1 + third, y2 - max((-adjbw2 * 2 + 1) / 3, 0), + s, c, SOLID, adjbw1bigthird, adjbw2bigthird); + drawLineForBoxSide(graphicsContext, x2 - third, y1 + max((adjbw1 * 2 + 1) / 3, 0), + x2, y2 - max((adjbw2 * 2 + 1) / 3, 0), + s, c, SOLID, adjbw1bigthird, adjbw2bigthird); + break; + case BSBottom: + drawLineForBoxSide(graphicsContext, x1 + max((adjbw1 * 2 + 1) / 3, 0), + y1, x2 - max((adjbw2 * 2 + 1) / 3, 0), y1 + third, + s, c, SOLID, adjbw1bigthird, adjbw2bigthird); + drawLineForBoxSide(graphicsContext, x1 + max((-adjbw1 * 2 + 1) / 3, 0), + y2 - third, x2 - max((-adjbw2 * 2 + 1) / 3, 0), y2, + s, c, SOLID, adjbw1bigthird, adjbw2bigthird); + break; + case BSRight: + drawLineForBoxSide(graphicsContext, x1, y1 + max((adjbw1 * 2 + 1) / 3, 0), + x1 + third, y2 - max((adjbw2 * 2 + 1) / 3, 0), + s, c, SOLID, adjbw1bigthird, adjbw2bigthird); + drawLineForBoxSide(graphicsContext, x2 - third, y1 + max((-adjbw1 * 2 + 1) / 3, 0), + x2, y2 - max((-adjbw2 * 2 + 1) / 3, 0), + s, c, SOLID, adjbw1bigthird, adjbw2bigthird); + break; + default: + break; + } + } + break; + } + case RIDGE: + case GROOVE: + { + EBorderStyle s1; + EBorderStyle s2; + if (style == GROOVE) { + s1 = INSET; + s2 = OUTSET; + } else { + s1 = OUTSET; + s2 = INSET; + } + + int adjbw1bighalf = ((adjbw1 > 0) ? adjbw1 + 1 : adjbw1 - 1) / 2; + int adjbw2bighalf = ((adjbw2 > 0) ? adjbw2 + 1 : adjbw2 - 1) / 2; + + switch (s) { + case BSTop: + drawLineForBoxSide(graphicsContext, x1 + max(-adjbw1, 0) / 2, y1, x2 - max(-adjbw2, 0) / 2, (y1 + y2 + 1) / 2, + s, c, s1, adjbw1bighalf, adjbw2bighalf); + drawLineForBoxSide(graphicsContext, x1 + max(adjbw1 + 1, 0) / 2, (y1 + y2 + 1) / 2, x2 - max(adjbw2 + 1, 0) / 2, y2, + s, c, s2, adjbw1 / 2, adjbw2 / 2); + break; + case BSLeft: + drawLineForBoxSide(graphicsContext, x1, y1 + max(-adjbw1, 0) / 2, (x1 + x2 + 1) / 2, y2 - max(-adjbw2, 0) / 2, + s, c, s1, adjbw1bighalf, adjbw2bighalf); + drawLineForBoxSide(graphicsContext, (x1 + x2 + 1) / 2, y1 + max(adjbw1 + 1, 0) / 2, x2, y2 - max(adjbw2 + 1, 0) / 2, + s, c, s2, adjbw1 / 2, adjbw2 / 2); + break; + case BSBottom: + drawLineForBoxSide(graphicsContext, x1 + max(adjbw1, 0) / 2, y1, x2 - max(adjbw2, 0) / 2, (y1 + y2 + 1) / 2, + s, c, s2, adjbw1bighalf, adjbw2bighalf); + drawLineForBoxSide(graphicsContext, x1 + max(-adjbw1 + 1, 0) / 2, (y1 + y2 + 1) / 2, x2 - max(-adjbw2 + 1, 0) / 2, y2, + s, c, s1, adjbw1 / 2, adjbw2 / 2); + break; + case BSRight: + drawLineForBoxSide(graphicsContext, x1, y1 + max(adjbw1, 0) / 2, (x1 + x2 + 1) / 2, y2 - max(adjbw2, 0) / 2, + s, c, s2, adjbw1bighalf, adjbw2bighalf); + drawLineForBoxSide(graphicsContext, (x1 + x2 + 1) / 2, y1 + max(-adjbw1 + 1, 0) / 2, x2, y2 - max(-adjbw2 + 1, 0) / 2, + s, c, s1, adjbw1 / 2, adjbw2 / 2); + break; + } + break; + } + case INSET: + if (s == BSTop || s == BSLeft) + c = c.dark(); + // fall through + case OUTSET: + if (style == OUTSET && (s == BSBottom || s == BSRight)) + c = c.dark(); + // fall through + case SOLID: { + graphicsContext->setStrokeStyle(NoStroke); + graphicsContext->setFillColor(c, m_style->colorSpace()); + ASSERT(x2 >= x1); + ASSERT(y2 >= y1); + if (!adjbw1 && !adjbw2) { + graphicsContext->drawRect(IntRect(x1, y1, x2 - x1, y2 - y1)); + return; + } + FloatPoint quad[4]; + switch (s) { + case BSTop: + quad[0] = FloatPoint(x1 + max(-adjbw1, 0), y1); + quad[1] = FloatPoint(x1 + max(adjbw1, 0), y2); + quad[2] = FloatPoint(x2 - max(adjbw2, 0), y2); + quad[3] = FloatPoint(x2 - max(-adjbw2, 0), y1); + break; + case BSBottom: + quad[0] = FloatPoint(x1 + max(adjbw1, 0), y1); + quad[1] = FloatPoint(x1 + max(-adjbw1, 0), y2); + quad[2] = FloatPoint(x2 - max(-adjbw2, 0), y2); + quad[3] = FloatPoint(x2 - max(adjbw2, 0), y1); + break; + case BSLeft: + quad[0] = FloatPoint(x1, y1 + max(-adjbw1, 0)); + quad[1] = FloatPoint(x1, y2 - max(-adjbw2, 0)); + quad[2] = FloatPoint(x2, y2 - max(adjbw2, 0)); + quad[3] = FloatPoint(x2, y1 + max(adjbw1, 0)); + break; + case BSRight: + quad[0] = FloatPoint(x1, y1 + max(adjbw1, 0)); + quad[1] = FloatPoint(x1, y2 - max(adjbw2, 0)); + quad[2] = FloatPoint(x2, y2 - max(-adjbw2, 0)); + quad[3] = FloatPoint(x2, y1 + max(-adjbw1, 0)); + break; + } + graphicsContext->drawConvexPolygon(4, quad); + break; + } + } +} + +IntRect RenderObject::borderInnerRect(const IntRect& borderRect, unsigned short topWidth, unsigned short bottomWidth, unsigned short leftWidth, unsigned short rightWidth) const +{ + return IntRect( + borderRect.x() + leftWidth, + borderRect.y() + topWidth, + borderRect.width() - leftWidth - rightWidth, + borderRect.height() - topWidth - bottomWidth); +} + +#if HAVE(PATH_BASED_BORDER_RADIUS_DRAWING) +void RenderObject::drawBoxSideFromPath(GraphicsContext* graphicsContext, IntRect borderRect, Path borderPath, + float thickness, float drawThickness, BoxSide s, const RenderStyle* style, + Color c, EBorderStyle borderStyle) +{ + if (thickness <= 0) + return; + + if (borderStyle == DOUBLE && thickness < 3) + borderStyle = SOLID; + + switch (borderStyle) { + case BNONE: + case BHIDDEN: + return; + case DOTTED: + case DASHED: { + graphicsContext->setStrokeColor(c, style->colorSpace()); + + // The stroke is doubled here because the provided path is the + // outside edge of the border so half the stroke is clipped off. + // The extra multiplier is so that the clipping mask can antialias + // the edges to prevent jaggies. + graphicsContext->setStrokeThickness(drawThickness * 2 * 1.1f); + graphicsContext->setStrokeStyle(borderStyle == DASHED ? DashedStroke : DottedStroke); + + // If the number of dashes that fit in the path is odd and non-integral then we + // will have an awkwardly-sized dash at the end of the path. To try to avoid that + // here, we simply make the whitespace dashes ever so slightly bigger. + // FIXME: This could be even better if we tried to manipulate the dash offset + // and possibly the whiteSpaceWidth to get the corners dash-symmetrical. + float patWidth = thickness * ((borderStyle == DASHED) ? 3.0f : 1.0f); + float whiteSpaceWidth = patWidth; + float numberOfDashes = borderPath.length() / patWidth; + bool evenNumberOfFullDashes = !((int)numberOfDashes % 2); + bool integralNumberOfDashes = !(numberOfDashes - (int)numberOfDashes); + if (!evenNumberOfFullDashes && !integralNumberOfDashes) { + float numberOfWhitespaceDashes = numberOfDashes / 2; + whiteSpaceWidth += (patWidth / numberOfWhitespaceDashes); + } + + DashArray lineDash; + lineDash.append(patWidth); + lineDash.append(whiteSpaceWidth); + graphicsContext->setLineDash(lineDash, patWidth); + graphicsContext->strokePath(borderPath); + return; + } + case DOUBLE: { + int outerBorderTopWidth = style->borderTopWidth() / 3; + int outerBorderRightWidth = style->borderRightWidth() / 3; + int outerBorderBottomWidth = style->borderBottomWidth() / 3; + int outerBorderLeftWidth = style->borderLeftWidth() / 3; + + int innerBorderTopWidth = style->borderTopWidth() * 2 / 3; + int innerBorderRightWidth = style->borderRightWidth() * 2 / 3; + int innerBorderBottomWidth = style->borderBottomWidth() * 2 / 3; + int innerBorderLeftWidth = style->borderLeftWidth() * 2 / 3; + + // We need certain integer rounding results + if (style->borderTopWidth() % 3 == 2) + outerBorderTopWidth += 1; + if (style->borderRightWidth() % 3 == 2) + outerBorderRightWidth += 1; + if (style->borderBottomWidth() % 3 == 2) + outerBorderBottomWidth += 1; + if (style->borderLeftWidth() % 3 == 2) + outerBorderLeftWidth += 1; + + if (style->borderTopWidth() % 3 == 1) + innerBorderTopWidth += 1; + if (style->borderRightWidth() % 3 == 1) + innerBorderRightWidth += 1; + if (style->borderBottomWidth() % 3 == 1) + innerBorderBottomWidth += 1; + if (style->borderLeftWidth() % 3 == 1) + innerBorderLeftWidth += 1; + + // Get the inner border rects for both the outer border line and the inner border line + IntRect outerBorderInnerRect = borderInnerRect(borderRect, outerBorderTopWidth, outerBorderBottomWidth, + outerBorderLeftWidth, outerBorderRightWidth); + IntRect innerBorderInnerRect = borderInnerRect(borderRect, innerBorderTopWidth, innerBorderBottomWidth, + innerBorderLeftWidth, innerBorderRightWidth); + + // Get the inner radii for the outer border line + IntSize outerBorderTopLeftInnerRadius, outerBorderTopRightInnerRadius, outerBorderBottomLeftInnerRadius, + outerBorderBottomRightInnerRadius; + style->getInnerBorderRadiiForRectWithBorderWidths(outerBorderInnerRect, outerBorderTopWidth, outerBorderBottomWidth, + outerBorderLeftWidth, outerBorderRightWidth, outerBorderTopLeftInnerRadius, outerBorderTopRightInnerRadius, + outerBorderBottomLeftInnerRadius, outerBorderBottomRightInnerRadius); + + // Get the inner radii for the inner border line + IntSize innerBorderTopLeftInnerRadius, innerBorderTopRightInnerRadius, innerBorderBottomLeftInnerRadius, + innerBorderBottomRightInnerRadius; + style->getInnerBorderRadiiForRectWithBorderWidths(innerBorderInnerRect, innerBorderTopWidth, innerBorderBottomWidth, + innerBorderLeftWidth, innerBorderRightWidth, innerBorderTopLeftInnerRadius, innerBorderTopRightInnerRadius, + innerBorderBottomLeftInnerRadius, innerBorderBottomRightInnerRadius); + + // Draw inner border line + graphicsContext->save(); + graphicsContext->addRoundedRectClip(innerBorderInnerRect, innerBorderTopLeftInnerRadius, + innerBorderTopRightInnerRadius, innerBorderBottomLeftInnerRadius, innerBorderBottomRightInnerRadius); + drawBoxSideFromPath(graphicsContext, borderRect, borderPath, thickness, drawThickness, s, style, c, SOLID); + graphicsContext->restore(); + + // Draw outer border line + graphicsContext->save(); + graphicsContext->clipOutRoundedRect(outerBorderInnerRect, outerBorderTopLeftInnerRadius, + outerBorderTopRightInnerRadius, outerBorderBottomLeftInnerRadius, outerBorderBottomRightInnerRadius); + drawBoxSideFromPath(graphicsContext, borderRect, borderPath, thickness, drawThickness, s, style, c, SOLID); + graphicsContext->restore(); + + return; + } + case RIDGE: + case GROOVE: + { + EBorderStyle s1; + EBorderStyle s2; + if (borderStyle == GROOVE) { + s1 = INSET; + s2 = OUTSET; + } else { + s1 = OUTSET; + s2 = INSET; + } + + IntRect halfBorderRect = borderInnerRect(borderRect, style->borderLeftWidth() / 2, style->borderBottomWidth() / 2, + style->borderLeftWidth() / 2, style->borderRightWidth() / 2); + + IntSize topLeftHalfRadius, topRightHalfRadius, bottomLeftHalfRadius, bottomRightHalfRadius; + style->getInnerBorderRadiiForRectWithBorderWidths(halfBorderRect, style->borderLeftWidth() / 2, + style->borderBottomWidth() / 2, style->borderLeftWidth() / 2, style->borderRightWidth() / 2, + topLeftHalfRadius, topRightHalfRadius, bottomLeftHalfRadius, bottomRightHalfRadius); + + // Paint full border + drawBoxSideFromPath(graphicsContext, borderRect, borderPath, thickness, drawThickness, s, style, c, s1); + + // Paint inner only + graphicsContext->save(); + graphicsContext->addRoundedRectClip(halfBorderRect, topLeftHalfRadius, topRightHalfRadius, + bottomLeftHalfRadius, bottomRightHalfRadius); + drawBoxSideFromPath(graphicsContext, borderRect, borderPath, thickness, drawThickness, s, style, c, s2); + graphicsContext->restore(); + + return; + } + case INSET: + if (s == BSTop || s == BSLeft) + c = c.dark(); + break; + case OUTSET: + if (s == BSBottom || s == BSRight) + c = c.dark(); + break; + default: + break; + } + + graphicsContext->setStrokeStyle(NoStroke); + graphicsContext->setFillColor(c, style->colorSpace()); + graphicsContext->drawRect(borderRect); +} +#else +void RenderObject::drawArcForBoxSide(GraphicsContext* graphicsContext, int x, int y, float thickness, IntSize radius, + int angleStart, int angleSpan, BoxSide s, Color c, + EBorderStyle style, bool firstCorner) +{ + // FIXME: This function should be removed when all ports implement GraphicsContext::clipConvexPolygon()!! + // At that time, everyone can use RenderObject::drawBoxSideFromPath() instead. This should happen soon. + if ((style == DOUBLE && thickness / 2 < 3) || ((style == RIDGE || style == GROOVE) && thickness / 2 < 2)) + style = SOLID; + + switch (style) { + case BNONE: + case BHIDDEN: + return; + case DOTTED: + case DASHED: + graphicsContext->setStrokeColor(c, m_style->colorSpace()); + graphicsContext->setStrokeStyle(style == DOTTED ? DottedStroke : DashedStroke); + graphicsContext->setStrokeThickness(thickness); + graphicsContext->strokeArc(IntRect(x, y, radius.width() * 2, radius.height() * 2), angleStart, angleSpan); + break; + case DOUBLE: { + float third = thickness / 3.0f; + float innerThird = (thickness + 1.0f) / 6.0f; + int shiftForInner = static_cast<int>(innerThird * 2.5f); + + int outerY = y; + int outerHeight = radius.height() * 2; + int innerX = x + shiftForInner; + int innerY = y + shiftForInner; + int innerWidth = (radius.width() - shiftForInner) * 2; + int innerHeight = (radius.height() - shiftForInner) * 2; + if (innerThird > 1 && (s == BSTop || (firstCorner && (s == BSLeft || s == BSRight)))) { + outerHeight += 2; + innerHeight += 2; + } + + graphicsContext->setStrokeStyle(SolidStroke); + graphicsContext->setStrokeColor(c, m_style->colorSpace()); + graphicsContext->setStrokeThickness(third); + graphicsContext->strokeArc(IntRect(x, outerY, radius.width() * 2, outerHeight), angleStart, angleSpan); + graphicsContext->setStrokeThickness(innerThird > 2 ? innerThird - 1 : innerThird); + graphicsContext->strokeArc(IntRect(innerX, innerY, innerWidth, innerHeight), angleStart, angleSpan); + break; + } + case GROOVE: + case RIDGE: { + Color c2; + if ((style == RIDGE && (s == BSTop || s == BSLeft)) || + (style == GROOVE && (s == BSBottom || s == BSRight))) + c2 = c.dark(); + else { + c2 = c; + c = c.dark(); + } + + graphicsContext->setStrokeStyle(SolidStroke); + graphicsContext->setStrokeColor(c, m_style->colorSpace()); + graphicsContext->setStrokeThickness(thickness); + graphicsContext->strokeArc(IntRect(x, y, radius.width() * 2, radius.height() * 2), angleStart, angleSpan); + + float halfThickness = (thickness + 1.0f) / 4.0f; + int shiftForInner = static_cast<int>(halfThickness * 1.5f); + graphicsContext->setStrokeColor(c2, m_style->colorSpace()); + graphicsContext->setStrokeThickness(halfThickness > 2 ? halfThickness - 1 : halfThickness); + graphicsContext->strokeArc(IntRect(x + shiftForInner, y + shiftForInner, (radius.width() - shiftForInner) * 2, + (radius.height() - shiftForInner) * 2), angleStart, angleSpan); + break; + } + case INSET: + if (s == BSTop || s == BSLeft) + c = c.dark(); + case OUTSET: + if (style == OUTSET && (s == BSBottom || s == BSRight)) + c = c.dark(); + case SOLID: + graphicsContext->setStrokeStyle(SolidStroke); + graphicsContext->setStrokeColor(c, m_style->colorSpace()); + graphicsContext->setStrokeThickness(thickness); + graphicsContext->strokeArc(IntRect(x, y, radius.width() * 2, radius.height() * 2), angleStart, angleSpan); + break; + } +} +#endif + +void RenderObject::paintFocusRing(GraphicsContext* context, int tx, int ty, RenderStyle* style) +{ + Vector<IntRect> focusRingRects; + addFocusRingRects(focusRingRects, tx, ty); + if (style->outlineStyleIsAuto()) + context->drawFocusRing(focusRingRects, style->outlineWidth(), style->outlineOffset(), style->visitedDependentColor(CSSPropertyOutlineColor)); + else + addPDFURLRect(context, unionRect(focusRingRects)); +} + +void RenderObject::addPDFURLRect(GraphicsContext* context, const IntRect& rect) +{ + if (rect.isEmpty()) + return; + Node* n = node(); + if (!n || !n->isLink() || !n->isElementNode()) + return; + const AtomicString& href = static_cast<Element*>(n)->getAttribute(hrefAttr); + if (href.isNull()) + return; + context->setURLForRect(n->document()->completeURL(href), rect); +} + +void RenderObject::paintOutline(GraphicsContext* graphicsContext, int tx, int ty, int w, int h) +{ + if (!hasOutline()) + return; + + RenderStyle* styleToUse = style(); + int ow = styleToUse->outlineWidth(); + EBorderStyle os = styleToUse->outlineStyle(); + + Color oc = styleToUse->visitedDependentColor(CSSPropertyOutlineColor); + + int offset = styleToUse->outlineOffset(); + + if (styleToUse->outlineStyleIsAuto() || hasOutlineAnnotation()) { + if (!theme()->supportsFocusRing(styleToUse)) { + // Only paint the focus ring by hand if the theme isn't able to draw the focus ring. + paintFocusRing(graphicsContext, tx, ty, styleToUse); + } + } + + if (styleToUse->outlineStyleIsAuto() || styleToUse->outlineStyle() == BNONE) + return; + + tx -= offset; + ty -= offset; + w += 2 * offset; + h += 2 * offset; + + if (h < 0 || w < 0) + return; + + drawLineForBoxSide(graphicsContext, tx - ow, ty - ow, tx, ty + h + ow, BSLeft, oc, os, ow, ow); + drawLineForBoxSide(graphicsContext, tx - ow, ty - ow, tx + w + ow, ty, BSTop, oc, os, ow, ow); + drawLineForBoxSide(graphicsContext, tx + w, ty - ow, tx + w + ow, ty + h + ow, BSRight, oc, os, ow, ow); + drawLineForBoxSide(graphicsContext, tx - ow, ty + h, tx + w + ow, ty + h + ow, BSBottom, oc, os, ow, ow); +} + +IntRect RenderObject::absoluteBoundingBoxRect(bool useTransforms) +{ + if (useTransforms) { + Vector<FloatQuad> quads; + absoluteQuads(quads); + + size_t n = quads.size(); + if (!n) + return IntRect(); + + IntRect result = quads[0].enclosingBoundingBox(); + for (size_t i = 1; i < n; ++i) + result.unite(quads[i].enclosingBoundingBox()); + return result; + } + + FloatPoint absPos = localToAbsolute(); + Vector<IntRect> rects; + absoluteRects(rects, absPos.x(), absPos.y()); + + size_t n = rects.size(); + if (!n) + return IntRect(); + + IntRect result = rects[0]; + for (size_t i = 1; i < n; ++i) + result.unite(rects[i]); + return result; +} + +void RenderObject::absoluteFocusRingQuads(Vector<FloatQuad>& quads) +{ + Vector<IntRect> rects; + // FIXME: addFocusRingRects() needs to be passed this transform-unaware + // localToAbsolute() offset here because RenderInline::addFocusRingRects() + // implicitly assumes that. This doesn't work correctly with transformed + // descendants. + FloatPoint absolutePoint = localToAbsolute(); + addFocusRingRects(rects, absolutePoint.x(), absolutePoint.y()); + size_t count = rects.size(); + for (size_t i = 0; i < count; ++i) { + IntRect rect = rects[i]; + rect.move(-absolutePoint.x(), -absolutePoint.y()); + quads.append(localToAbsoluteQuad(FloatQuad(rect))); + } +} + +void RenderObject::addAbsoluteRectForLayer(IntRect& result) +{ + if (hasLayer()) + result.unite(absoluteBoundingBoxRect()); + for (RenderObject* current = firstChild(); current; current = current->nextSibling()) + current->addAbsoluteRectForLayer(result); +} + +IntRect RenderObject::paintingRootRect(IntRect& topLevelRect) +{ + IntRect result = absoluteBoundingBoxRect(); + topLevelRect = result; + for (RenderObject* current = firstChild(); current; current = current->nextSibling()) + current->addAbsoluteRectForLayer(result); + return result; +} + +void RenderObject::paint(PaintInfo& /*paintInfo*/, int /*tx*/, int /*ty*/) +{ +} + +RenderBoxModelObject* RenderObject::containerForRepaint() const +{ +#if USE(ACCELERATED_COMPOSITING) + if (RenderView* v = view()) { + if (v->usesCompositing()) { + RenderLayer* compLayer = enclosingLayer()->enclosingCompositingLayer(); + return compLayer ? compLayer->renderer() : 0; + } + } +#endif + // Do root-relative repaint. + return 0; +} + +void RenderObject::repaintUsingContainer(RenderBoxModelObject* repaintContainer, const IntRect& r, bool immediate) +{ + if (!repaintContainer) { + view()->repaintViewRectangle(r, immediate); + return; + } + +#if USE(ACCELERATED_COMPOSITING) + RenderView* v = view(); + if (repaintContainer->isRenderView()) { + ASSERT(repaintContainer == v); + if (!v->hasLayer() || !v->layer()->isComposited() || v->layer()->backing()->paintingGoesToWindow()) { + v->repaintViewRectangle(r, immediate); + return; + } + } + + if (v->usesCompositing()) { + ASSERT(repaintContainer->hasLayer() && repaintContainer->layer()->isComposited()); + repaintContainer->layer()->setBackingNeedsRepaintInRect(r); + } +#else + if (repaintContainer->isRenderView()) + toRenderView(repaintContainer)->repaintViewRectangle(r, immediate); +#endif +} + +void RenderObject::repaint(bool immediate) +{ + // Don't repaint if we're unrooted (note that view() still returns the view when unrooted) + RenderView* view; + if (!isRooted(&view)) + return; + + if (view->printing()) + return; // Don't repaint if we're printing. + + RenderBoxModelObject* repaintContainer = containerForRepaint(); + repaintUsingContainer(repaintContainer ? repaintContainer : view, clippedOverflowRectForRepaint(repaintContainer), immediate); +} + +void RenderObject::repaintRectangle(const IntRect& r, bool immediate) +{ + // Don't repaint if we're unrooted (note that view() still returns the view when unrooted) + RenderView* view; + if (!isRooted(&view)) + return; + + if (view->printing()) + return; // Don't repaint if we're printing. + + IntRect dirtyRect(r); + + // FIXME: layoutDelta needs to be applied in parts before/after transforms and + // repaint containers. https://bugs.webkit.org/show_bug.cgi?id=23308 + dirtyRect.move(view->layoutDelta()); + + RenderBoxModelObject* repaintContainer = containerForRepaint(); + computeRectForRepaint(repaintContainer, dirtyRect); + repaintUsingContainer(repaintContainer ? repaintContainer : view, dirtyRect, immediate); +} + +bool RenderObject::repaintAfterLayoutIfNeeded(RenderBoxModelObject* repaintContainer, const IntRect& oldBounds, const IntRect& oldOutlineBox, const IntRect* newBoundsPtr, const IntRect* newOutlineBoxRectPtr) +{ + RenderView* v = view(); + if (v->printing()) + return false; // Don't repaint if we're printing. + + // This ASSERT fails due to animations. See https://bugs.webkit.org/show_bug.cgi?id=37048 + // ASSERT(!newBoundsPtr || *newBoundsPtr == clippedOverflowRectForRepaint(repaintContainer)); + IntRect newBounds = newBoundsPtr ? *newBoundsPtr : clippedOverflowRectForRepaint(repaintContainer); + IntRect newOutlineBox; + + bool fullRepaint = selfNeedsLayout(); + // Presumably a background or a border exists if border-fit:lines was specified. + if (!fullRepaint && style()->borderFit() == BorderFitLines) + fullRepaint = true; + if (!fullRepaint) { + // This ASSERT fails due to animations. See https://bugs.webkit.org/show_bug.cgi?id=37048 + // ASSERT(!newOutlineBoxRectPtr || *newOutlineBoxRectPtr == outlineBoundsForRepaint(repaintContainer)); + newOutlineBox = newOutlineBoxRectPtr ? *newOutlineBoxRectPtr : outlineBoundsForRepaint(repaintContainer); + if (newOutlineBox.location() != oldOutlineBox.location() || (mustRepaintBackgroundOrBorder() && (newBounds != oldBounds || newOutlineBox != oldOutlineBox))) + fullRepaint = true; + } + + if (!repaintContainer) + repaintContainer = v; + + if (fullRepaint) { + repaintUsingContainer(repaintContainer, oldBounds); + if (newBounds != oldBounds) + repaintUsingContainer(repaintContainer, newBounds); + return true; + } + + if (newBounds == oldBounds && newOutlineBox == oldOutlineBox) + return false; + + int deltaLeft = newBounds.x() - oldBounds.x(); + if (deltaLeft > 0) + repaintUsingContainer(repaintContainer, IntRect(oldBounds.x(), oldBounds.y(), deltaLeft, oldBounds.height())); + else if (deltaLeft < 0) + repaintUsingContainer(repaintContainer, IntRect(newBounds.x(), newBounds.y(), -deltaLeft, newBounds.height())); + + int deltaRight = newBounds.right() - oldBounds.right(); + if (deltaRight > 0) + repaintUsingContainer(repaintContainer, IntRect(oldBounds.right(), newBounds.y(), deltaRight, newBounds.height())); + else if (deltaRight < 0) + repaintUsingContainer(repaintContainer, IntRect(newBounds.right(), oldBounds.y(), -deltaRight, oldBounds.height())); + + int deltaTop = newBounds.y() - oldBounds.y(); + if (deltaTop > 0) + repaintUsingContainer(repaintContainer, IntRect(oldBounds.x(), oldBounds.y(), oldBounds.width(), deltaTop)); + else if (deltaTop < 0) + repaintUsingContainer(repaintContainer, IntRect(newBounds.x(), newBounds.y(), newBounds.width(), -deltaTop)); + + int deltaBottom = newBounds.bottom() - oldBounds.bottom(); + if (deltaBottom > 0) + repaintUsingContainer(repaintContainer, IntRect(newBounds.x(), oldBounds.bottom(), newBounds.width(), deltaBottom)); + else if (deltaBottom < 0) + repaintUsingContainer(repaintContainer, IntRect(oldBounds.x(), newBounds.bottom(), oldBounds.width(), -deltaBottom)); + + if (newOutlineBox == oldOutlineBox) + return false; + + // We didn't move, but we did change size. Invalidate the delta, which will consist of possibly + // two rectangles (but typically only one). + RenderStyle* outlineStyle = outlineStyleForRepaint(); + int ow = outlineStyle->outlineSize(); + int width = abs(newOutlineBox.width() - oldOutlineBox.width()); + if (width) { + int shadowLeft; + int shadowRight; + style()->getBoxShadowHorizontalExtent(shadowLeft, shadowRight); + + int borderRight = isBox() ? toRenderBox(this)->borderRight() : 0; + int boxWidth = isBox() ? toRenderBox(this)->width() : 0; + int borderWidth = max(-outlineStyle->outlineOffset(), max(borderRight, max(style()->borderTopRightRadius().width().calcValue(boxWidth), style()->borderBottomRightRadius().width().calcValue(boxWidth)))) + max(ow, shadowRight); + IntRect rightRect(newOutlineBox.x() + min(newOutlineBox.width(), oldOutlineBox.width()) - borderWidth, + newOutlineBox.y(), + width + borderWidth, + max(newOutlineBox.height(), oldOutlineBox.height())); + int right = min(newBounds.right(), oldBounds.right()); + if (rightRect.x() < right) { + rightRect.setWidth(min(rightRect.width(), right - rightRect.x())); + repaintUsingContainer(repaintContainer, rightRect); + } + } + int height = abs(newOutlineBox.height() - oldOutlineBox.height()); + if (height) { + int shadowTop; + int shadowBottom; + style()->getBoxShadowVerticalExtent(shadowTop, shadowBottom); + + int borderBottom = isBox() ? toRenderBox(this)->borderBottom() : 0; + int boxHeight = isBox() ? toRenderBox(this)->height() : 0; + int borderHeight = max(-outlineStyle->outlineOffset(), max(borderBottom, max(style()->borderBottomLeftRadius().height().calcValue(boxHeight), style()->borderBottomRightRadius().height().calcValue(boxHeight)))) + max(ow, shadowBottom); + IntRect bottomRect(newOutlineBox.x(), + min(newOutlineBox.bottom(), oldOutlineBox.bottom()) - borderHeight, + max(newOutlineBox.width(), oldOutlineBox.width()), + height + borderHeight); + int bottom = min(newBounds.bottom(), oldBounds.bottom()); + if (bottomRect.y() < bottom) { + bottomRect.setHeight(min(bottomRect.height(), bottom - bottomRect.y())); + repaintUsingContainer(repaintContainer, bottomRect); + } + } + return false; +} + +void RenderObject::repaintDuringLayoutIfMoved(const IntRect&) +{ +} + +void RenderObject::repaintOverhangingFloats(bool) +{ +} + +bool RenderObject::checkForRepaintDuringLayout() const +{ + // FIXME: <https://bugs.webkit.org/show_bug.cgi?id=20885> It is probably safe to also require + // m_everHadLayout. Currently, only RenderBlock::layoutBlock() adds this condition. See also + // <https://bugs.webkit.org/show_bug.cgi?id=15129>. + return !document()->view()->needsFullRepaint() && !hasLayer(); +} + +IntRect RenderObject::rectWithOutlineForRepaint(RenderBoxModelObject* repaintContainer, int outlineWidth) +{ + IntRect r(clippedOverflowRectForRepaint(repaintContainer)); + r.inflate(outlineWidth); + return r; +} + +IntRect RenderObject::clippedOverflowRectForRepaint(RenderBoxModelObject*) +{ + ASSERT_NOT_REACHED(); + return IntRect(); +} + +void RenderObject::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& rect, bool fixed) +{ + if (repaintContainer == this) + return; + + if (RenderObject* o = parent()) { + if (o->isBlockFlow()) { + RenderBlock* cb = toRenderBlock(o); + if (cb->hasColumns()) + cb->adjustRectForColumns(rect); + } + + if (o->hasOverflowClip()) { + // o->height() is inaccurate if we're in the middle of a layout of |o|, so use the + // layer's size instead. Even if the layer's size is wrong, the layer itself will repaint + // anyway if its size does change. + RenderBox* boxParent = toRenderBox(o); + + IntRect repaintRect(rect); + repaintRect.move(-boxParent->layer()->scrolledContentOffset()); // For overflow:auto/scroll/hidden. + + IntRect boxRect(0, 0, boxParent->layer()->width(), boxParent->layer()->height()); + rect = intersection(repaintRect, boxRect); + if (rect.isEmpty()) + return; + } + + o->computeRectForRepaint(repaintContainer, rect, fixed); + } +} + +void RenderObject::dirtyLinesFromChangedChild(RenderObject*) +{ +} + +#ifndef NDEBUG + +void RenderObject::showTreeForThis() const +{ + if (node()) + node()->showTreeForThis(); +} + +void RenderObject::showRenderObject() const +{ + showRenderObject(0); +} + +void RenderObject::showRenderObject(int printedCharacters) const +{ + // As this function is intended to be used when debugging, the + // this pointer may be 0. + if (!this) { + fputs("(null)\n", stderr); + return; + } + + printedCharacters += fprintf(stderr, "%s %p", renderName(), this); + + if (node()) { + if (printedCharacters) + for (; printedCharacters < 39; printedCharacters++) + fputc(' ', stderr); + fputc('\t', stderr); + node()->showNode(); + } else + fputc('\n', stderr); +} + +void RenderObject::showRenderTreeAndMark(const RenderObject* markedObject1, const char* markedLabel1, const RenderObject* markedObject2, const char* markedLabel2, int depth) const +{ + int printedCharacters = 0; + if (markedObject1 == this && markedLabel1) + printedCharacters += fprintf(stderr, "%s", markedLabel1); + if (markedObject2 == this && markedLabel2) + printedCharacters += fprintf(stderr, "%s", markedLabel2); + for (; printedCharacters < depth * 2; printedCharacters++) + fputc(' ', stderr); + + showRenderObject(printedCharacters); + if (!this) + return; + + for (const RenderObject* child = firstChild(); child; child = child->nextSibling()) + child->showRenderTreeAndMark(markedObject1, markedLabel1, markedObject2, markedLabel2, depth + 1); +} + +#endif // NDEBUG + +Color RenderObject::selectionBackgroundColor() const +{ + Color color; + if (style()->userSelect() != SELECT_NONE) { + RefPtr<RenderStyle> pseudoStyle = getUncachedPseudoStyle(SELECTION); + if (pseudoStyle && pseudoStyle->visitedDependentColor(CSSPropertyBackgroundColor).isValid()) + color = pseudoStyle->visitedDependentColor(CSSPropertyBackgroundColor).blendWithWhite(); + else + color = frame()->selection()->isFocusedAndActive() ? + theme()->activeSelectionBackgroundColor() : + theme()->inactiveSelectionBackgroundColor(); + } + + return color; +} + +Color RenderObject::selectionColor(int colorProperty) const +{ + Color color; + // If the element is unselectable, or we are only painting the selection, + // don't override the foreground color with the selection foreground color. + if (style()->userSelect() == SELECT_NONE + || (frame()->view()->paintBehavior() & PaintBehaviorSelectionOnly)) + return color; + + if (RefPtr<RenderStyle> pseudoStyle = getUncachedPseudoStyle(SELECTION)) { + color = pseudoStyle->visitedDependentColor(colorProperty); + if (!color.isValid()) + color = pseudoStyle->visitedDependentColor(CSSPropertyColor); + } else + color = frame()->selection()->isFocusedAndActive() ? + theme()->activeSelectionForegroundColor() : + theme()->inactiveSelectionForegroundColor(); + + return color; +} + +Color RenderObject::selectionForegroundColor() const +{ + return selectionColor(CSSPropertyWebkitTextFillColor); +} + +Color RenderObject::selectionEmphasisMarkColor() const +{ + return selectionColor(CSSPropertyWebkitTextEmphasisColor); +} + +#if ENABLE(DRAG_SUPPORT) +Node* RenderObject::draggableNode(bool dhtmlOK, bool uaOK, int x, int y, bool& dhtmlWillDrag) const +{ + if (!dhtmlOK && !uaOK) + return 0; + + for (const RenderObject* curr = this; curr; curr = curr->parent()) { + Node* elt = curr->node(); + if (elt && elt->nodeType() == Node::TEXT_NODE) { + // Since there's no way for the author to address the -webkit-user-drag style for a text node, + // we use our own judgement. + if (uaOK && view()->frameView()->frame()->eventHandler()->shouldDragAutoNode(curr->node(), IntPoint(x, y))) { + dhtmlWillDrag = false; + return curr->node(); + } + if (elt->canStartSelection()) + // In this case we have a click in the unselected portion of text. If this text is + // selectable, we want to start the selection process instead of looking for a parent + // to try to drag. + return 0; + } else { + EUserDrag dragMode = curr->style()->userDrag(); + if (dhtmlOK && dragMode == DRAG_ELEMENT) { + dhtmlWillDrag = true; + return curr->node(); + } + if (uaOK && dragMode == DRAG_AUTO + && view()->frameView()->frame()->eventHandler()->shouldDragAutoNode(curr->node(), IntPoint(x, y))) { + dhtmlWillDrag = false; + return curr->node(); + } + } + } + return 0; +} +#endif // ENABLE(DRAG_SUPPORT) + +void RenderObject::selectionStartEnd(int& spos, int& epos) const +{ + view()->selectionStartEnd(spos, epos); +} + +void RenderObject::handleDynamicFloatPositionChange() +{ + // We have gone from not affecting the inline status of the parent flow to suddenly + // having an impact. See if there is a mismatch between the parent flow's + // childrenInline() state and our state. + setInline(style()->isDisplayInlineType()); + if (isInline() != parent()->childrenInline()) { + if (!isInline()) + toRenderBoxModelObject(parent())->childBecameNonInline(this); + else { + // An anonymous block must be made to wrap this inline. + RenderBlock* block = toRenderBlock(parent())->createAnonymousBlock(); + RenderObjectChildList* childlist = parent()->virtualChildren(); + childlist->insertChildNode(parent(), block, this); + block->children()->appendChildNode(block, childlist->removeChildNode(parent(), this)); + } + } +} + +void RenderObject::setAnimatableStyle(PassRefPtr<RenderStyle> style) +{ + if (!isText() && style) + setStyle(animation()->updateAnimations(this, style.get())); + else + setStyle(style); +} + +StyleDifference RenderObject::adjustStyleDifference(StyleDifference diff, unsigned contextSensitiveProperties) const +{ +#if USE(ACCELERATED_COMPOSITING) + // If transform changed, and we are not composited, need to do a layout. + if (contextSensitiveProperties & ContextSensitivePropertyTransform) { + // Text nodes share style with their parents but transforms don't apply to them, + // hence the !isText() check. + // FIXME: when transforms are taken into account for overflow, we will need to do a layout. + if (!isText() && (!hasLayer() || !toRenderBoxModelObject(this)->layer()->isComposited())) + diff = StyleDifferenceLayout; + else if (diff < StyleDifferenceRecompositeLayer) + diff = StyleDifferenceRecompositeLayer; + } + + // If opacity changed, and we are not composited, need to repaint (also + // ignoring text nodes) + if (contextSensitiveProperties & ContextSensitivePropertyOpacity) { + if (!isText() && (!hasLayer() || !toRenderBoxModelObject(this)->layer()->isComposited())) + diff = StyleDifferenceRepaintLayer; + else if (diff < StyleDifferenceRecompositeLayer) + diff = StyleDifferenceRecompositeLayer; + } + + // The answer to requiresLayer() for plugins and iframes can change outside of the style system, + // since it depends on whether we decide to composite these elements. When the layer status of + // one of these elements changes, we need to force a layout. + if (diff == StyleDifferenceEqual && style() && isBoxModelObject()) { + if (hasLayer() != toRenderBoxModelObject(this)->requiresLayer()) + diff = StyleDifferenceLayout; + } +#else + UNUSED_PARAM(contextSensitiveProperties); +#endif + + // If we have no layer(), just treat a RepaintLayer hint as a normal Repaint. + if (diff == StyleDifferenceRepaintLayer && !hasLayer()) + diff = StyleDifferenceRepaint; + + return diff; +} + +void RenderObject::setStyle(PassRefPtr<RenderStyle> style) +{ + if (m_style == style) { +#if USE(ACCELERATED_COMPOSITING) + // We need to run through adjustStyleDifference() for iframes and plugins, so + // style sharing is disabled for them. That should ensure that we never hit this code path. + ASSERT(!isRenderIFrame() && !isEmbeddedObject() &&!isApplet()); +#endif + return; + } + + StyleDifference diff = StyleDifferenceEqual; + unsigned contextSensitiveProperties = ContextSensitivePropertyNone; + if (m_style) + diff = m_style->diff(style.get(), contextSensitiveProperties); + + diff = adjustStyleDifference(diff, contextSensitiveProperties); + + styleWillChange(diff, style.get()); + + RefPtr<RenderStyle> oldStyle = m_style.release(); + m_style = style; + + updateFillImages(oldStyle ? oldStyle->backgroundLayers() : 0, m_style ? m_style->backgroundLayers() : 0); + updateFillImages(oldStyle ? oldStyle->maskLayers() : 0, m_style ? m_style->maskLayers() : 0); + + updateImage(oldStyle ? oldStyle->borderImage().image() : 0, m_style ? m_style->borderImage().image() : 0); + updateImage(oldStyle ? oldStyle->maskBoxImage().image() : 0, m_style ? m_style->maskBoxImage().image() : 0); + + // We need to ensure that view->maximalOutlineSize() is valid for any repaints that happen + // during styleDidChange (it's used by clippedOverflowRectForRepaint()). + if (m_style->outlineWidth() > 0 && m_style->outlineSize() > maximalOutlineSize(PaintPhaseOutline)) + toRenderView(document()->renderer())->setMaximalOutlineSize(m_style->outlineSize()); + + styleDidChange(diff, oldStyle.get()); + + if (!m_parent || isText()) + return; + + // Now that the layer (if any) has been updated, we need to adjust the diff again, + // check whether we should layout now, and decide if we need to repaint. + StyleDifference updatedDiff = adjustStyleDifference(diff, contextSensitiveProperties); + + if (diff <= StyleDifferenceLayoutPositionedMovementOnly) { + if (updatedDiff == StyleDifferenceLayout) + setNeedsLayoutAndPrefWidthsRecalc(); + else if (updatedDiff == StyleDifferenceLayoutPositionedMovementOnly) + setNeedsPositionedMovementLayout(); + } + + if (updatedDiff == StyleDifferenceRepaintLayer || updatedDiff == StyleDifferenceRepaint) { + // Do a repaint with the new style now, e.g., for example if we go from + // not having an outline to having an outline. + repaint(); + } +} + +void RenderObject::setStyleInternal(PassRefPtr<RenderStyle> style) +{ + m_style = style; +} + +void RenderObject::styleWillChange(StyleDifference diff, const RenderStyle* newStyle) +{ + if (m_style) { + // If our z-index changes value or our visibility changes, + // we need to dirty our stacking context's z-order list. + if (newStyle) { + bool visibilityChanged = m_style->visibility() != newStyle->visibility() + || m_style->zIndex() != newStyle->zIndex() + || m_style->hasAutoZIndex() != newStyle->hasAutoZIndex(); +#if ENABLE(DASHBOARD_SUPPORT) + if (visibilityChanged) + document()->setDashboardRegionsDirty(true); +#endif + if (visibilityChanged && AXObjectCache::accessibilityEnabled()) + document()->axObjectCache()->childrenChanged(this); + + // Keep layer hierarchy visibility bits up to date if visibility changes. + if (m_style->visibility() != newStyle->visibility()) { + if (RenderLayer* l = enclosingLayer()) { + if (newStyle->visibility() == VISIBLE) + l->setHasVisibleContent(true); + else if (l->hasVisibleContent() && (this == l->renderer() || l->renderer()->style()->visibility() != VISIBLE)) { + l->dirtyVisibleContentStatus(); + if (diff > StyleDifferenceRepaintLayer) + repaint(); + } + } + } + } + + if (m_parent && (diff == StyleDifferenceRepaint || newStyle->outlineSize() < m_style->outlineSize())) + repaint(); + if (isFloating() && (m_style->floating() != newStyle->floating())) + // For changes in float styles, we need to conceivably remove ourselves + // from the floating objects list. + toRenderBox(this)->removeFloatingOrPositionedChildFromBlockLists(); + else if (isPositioned() && (m_style->position() != newStyle->position())) + // For changes in positioning styles, we need to conceivably remove ourselves + // from the positioned objects list. + toRenderBox(this)->removeFloatingOrPositionedChildFromBlockLists(); + + s_affectsParentBlock = isFloatingOrPositioned() && + (!newStyle->isFloating() && newStyle->position() != AbsolutePosition && newStyle->position() != FixedPosition) + && parent() && (parent()->isBlockFlow() || parent()->isRenderInline()); + + // reset style flags + if (diff == StyleDifferenceLayout || diff == StyleDifferenceLayoutPositionedMovementOnly) { + m_floating = false; + m_positioned = false; + m_relPositioned = false; + } + m_paintBackground = false; + m_hasOverflowClip = false; + m_hasTransform = false; + m_hasReflection = false; + } else + s_affectsParentBlock = false; + + if (view()->frameView()) { + bool shouldBlitOnFixedBackgroundImage = false; +#if ENABLE(FAST_MOBILE_SCROLLING) + // On low-powered/mobile devices, preventing blitting on a scroll can cause noticeable delays + // when scrolling a page with a fixed background image. As an optimization, assuming there are + // no fixed positoned elements on the page, we can acclerate scrolling (via blitting) if we + // ignore the CSS property "background-attachment: fixed". + shouldBlitOnFixedBackgroundImage = true; +#endif + + bool newStyleSlowScroll = newStyle && !shouldBlitOnFixedBackgroundImage && newStyle->hasFixedBackgroundImage(); + bool oldStyleSlowScroll = m_style && !shouldBlitOnFixedBackgroundImage && m_style->hasFixedBackgroundImage(); + if (oldStyleSlowScroll != newStyleSlowScroll) { + if (oldStyleSlowScroll) + view()->frameView()->removeSlowRepaintObject(); + if (newStyleSlowScroll) + view()->frameView()->addSlowRepaintObject(); + } + } +} + +void RenderObject::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + if (s_affectsParentBlock) + handleDynamicFloatPositionChange(); + + if (!m_parent) + return; + + if (diff == StyleDifferenceLayout) { + RenderCounter::rendererStyleChanged(this, oldStyle, m_style.get()); + + // If the object already needs layout, then setNeedsLayout won't do + // any work. But if the containing block has changed, then we may need + // to mark the new containing blocks for layout. The change that can + // directly affect the containing block of this object is a change to + // the position style. + if (m_needsLayout && oldStyle->position() != m_style->position()) + markContainingBlocksForLayout(); + + setNeedsLayoutAndPrefWidthsRecalc(); + } else if (diff == StyleDifferenceLayoutPositionedMovementOnly) + setNeedsPositionedMovementLayout(); + + // Don't check for repaint here; we need to wait until the layer has been + // updated by subclasses before we know if we have to repaint (in setStyle()). +} + +void RenderObject::updateFillImages(const FillLayer* oldLayers, const FillLayer* newLayers) +{ + // Optimize the common case + if (oldLayers && !oldLayers->next() && newLayers && !newLayers->next() && (oldLayers->image() == newLayers->image())) + return; + + // Go through the new layers and addClients first, to avoid removing all clients of an image. + for (const FillLayer* currNew = newLayers; currNew; currNew = currNew->next()) { + if (currNew->image()) + currNew->image()->addClient(this); + } + + for (const FillLayer* currOld = oldLayers; currOld; currOld = currOld->next()) { + if (currOld->image()) + currOld->image()->removeClient(this); + } +} + +void RenderObject::updateImage(StyleImage* oldImage, StyleImage* newImage) +{ + if (oldImage != newImage) { + if (oldImage) + oldImage->removeClient(this); + if (newImage) + newImage->addClient(this); + } +} + +IntRect RenderObject::viewRect() const +{ + return view()->viewRect(); +} + +FloatPoint RenderObject::localToAbsolute(FloatPoint localPoint, bool fixed, bool useTransforms) const +{ + TransformState transformState(TransformState::ApplyTransformDirection, localPoint); + mapLocalToContainer(0, fixed, useTransforms, transformState); + transformState.flatten(); + + return transformState.lastPlanarPoint(); +} + +FloatPoint RenderObject::absoluteToLocal(FloatPoint containerPoint, bool fixed, bool useTransforms) const +{ + TransformState transformState(TransformState::UnapplyInverseTransformDirection, containerPoint); + mapAbsoluteToLocalPoint(fixed, useTransforms, transformState); + transformState.flatten(); + + return transformState.lastPlanarPoint(); +} + +void RenderObject::mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool fixed, bool useTransforms, TransformState& transformState) const +{ + if (repaintContainer == this) + return; + + RenderObject* o = parent(); + if (!o) + return; + + IntSize columnOffset; + o->adjustForColumns(columnOffset, roundedIntPoint(transformState.mappedPoint())); + if (!columnOffset.isZero()) + transformState.move(columnOffset); + + if (o->hasOverflowClip()) + transformState.move(-toRenderBox(o)->layer()->scrolledContentOffset()); + + o->mapLocalToContainer(repaintContainer, fixed, useTransforms, transformState); +} + +void RenderObject::mapAbsoluteToLocalPoint(bool fixed, bool useTransforms, TransformState& transformState) const +{ + RenderObject* o = parent(); + if (o) { + o->mapAbsoluteToLocalPoint(fixed, useTransforms, transformState); + if (o->hasOverflowClip()) + transformState.move(toRenderBox(o)->layer()->scrolledContentOffset()); + } +} + +bool RenderObject::shouldUseTransformFromContainer(const RenderObject* containerObject) const +{ +#if ENABLE(3D_RENDERING) + // hasTransform() indicates whether the object has transform, transform-style or perspective. We just care about transform, + // so check the layer's transform directly. + return (hasLayer() && toRenderBoxModelObject(this)->layer()->transform()) || (containerObject && containerObject->style()->hasPerspective()); +#else + UNUSED_PARAM(containerObject); + return hasTransform(); +#endif +} + +void RenderObject::getTransformFromContainer(const RenderObject* containerObject, const IntSize& offsetInContainer, TransformationMatrix& transform) const +{ + transform.makeIdentity(); + transform.translate(offsetInContainer.width(), offsetInContainer.height()); + RenderLayer* layer; + if (hasLayer() && (layer = toRenderBoxModelObject(this)->layer()) && layer->transform()) + transform.multLeft(layer->currentTransform()); + +#if ENABLE(3D_RENDERING) + if (containerObject && containerObject->hasLayer() && containerObject->style()->hasPerspective()) { + // Perpsective on the container affects us, so we have to factor it in here. + ASSERT(containerObject->hasLayer()); + FloatPoint perspectiveOrigin = toRenderBoxModelObject(containerObject)->layer()->perspectiveOrigin(); + + TransformationMatrix perspectiveMatrix; + perspectiveMatrix.applyPerspective(containerObject->style()->perspective()); + + transform.translateRight3d(-perspectiveOrigin.x(), -perspectiveOrigin.y(), 0); + transform.multiply(perspectiveMatrix); + transform.translateRight3d(perspectiveOrigin.x(), perspectiveOrigin.y(), 0); + } +#else + UNUSED_PARAM(containerObject); +#endif +} + +FloatQuad RenderObject::localToContainerQuad(const FloatQuad& localQuad, RenderBoxModelObject* repaintContainer, bool fixed) const +{ + // Track the point at the center of the quad's bounding box. As mapLocalToContainer() calls offsetFromContainer(), + // it will use that point as the reference point to decide which column's transform to apply in multiple-column blocks. + TransformState transformState(TransformState::ApplyTransformDirection, localQuad.boundingBox().center(), &localQuad); + mapLocalToContainer(repaintContainer, fixed, true, transformState); + transformState.flatten(); + + return transformState.lastPlanarQuad(); +} + +IntSize RenderObject::offsetFromContainer(RenderObject* o, const IntPoint& point) const +{ + ASSERT(o == container()); + + IntSize offset; + + o->adjustForColumns(offset, point); + + if (o->hasOverflowClip()) + offset -= toRenderBox(o)->layer()->scrolledContentOffset(); + + return offset; +} + +IntSize RenderObject::offsetFromAncestorContainer(RenderObject* container) const +{ + IntSize offset; + IntPoint referencePoint; + const RenderObject* currContainer = this; + do { + RenderObject* nextContainer = currContainer->container(); + ASSERT(nextContainer); // This means we reached the top without finding container. + if (!nextContainer) + break; + ASSERT(!currContainer->hasTransform()); + IntSize currentOffset = currContainer->offsetFromContainer(nextContainer, referencePoint); + offset += currentOffset; + referencePoint.move(currentOffset); + currContainer = nextContainer; + } while (currContainer != container); + + return offset; +} + +IntRect RenderObject::localCaretRect(InlineBox*, int, int* extraWidthToEndOfLine) +{ + if (extraWidthToEndOfLine) + *extraWidthToEndOfLine = 0; + + return IntRect(); +} + +RenderView* RenderObject::view() const +{ + return toRenderView(document()->renderer()); +} + +bool RenderObject::isRooted(RenderView** view) +{ + RenderObject* o = this; + while (o->parent()) + o = o->parent(); + + if (!o->isRenderView()) + return false; + + if (view) + *view = toRenderView(o); + + return true; +} + +bool RenderObject::hasOutlineAnnotation() const +{ + return node() && node()->isLink() && document()->printing(); +} + +RenderObject* RenderObject::container(RenderBoxModelObject* repaintContainer, bool* repaintContainerSkipped) const +{ + if (repaintContainerSkipped) + *repaintContainerSkipped = false; + + // This method is extremely similar to containingBlock(), but with a few notable + // exceptions. + // (1) It can be used on orphaned subtrees, i.e., it can be called safely even when + // the object is not part of the primary document subtree yet. + // (2) For normal flow elements, it just returns the parent. + // (3) For absolute positioned elements, it will return a relative positioned inline. + // containingBlock() simply skips relpositioned inlines and lets an enclosing block handle + // the layout of the positioned object. This does mean that computePositionedLogicalWidth and + // computePositionedLogicalHeight have to use container(). + RenderObject* o = parent(); + + if (isText()) + return o; + + EPosition pos = m_style->position(); + if (pos == FixedPosition) { + // container() can be called on an object that is not in the + // tree yet. We don't call view() since it will assert if it + // can't get back to the canvas. Instead we just walk as high up + // as we can. If we're in the tree, we'll get the root. If we + // aren't we'll get the root of our little subtree (most likely + // we'll just return 0). + // FIXME: The definition of view() has changed to not crawl up the render tree. It might + // be safe now to use it. + while (o && o->parent() && !(o->hasTransform() && o->isRenderBlock())) { + if (repaintContainerSkipped && o == repaintContainer) + *repaintContainerSkipped = true; + o = o->parent(); + } + } else if (pos == AbsolutePosition) { + // Same goes here. We technically just want our containing block, but + // we may not have one if we're part of an uninstalled subtree. We'll + // climb as high as we can though. + while (o && o->style()->position() == StaticPosition && !o->isRenderView() && !(o->hasTransform() && o->isRenderBlock())) { + if (repaintContainerSkipped && o == repaintContainer) + *repaintContainerSkipped = true; + o = o->parent(); + } + } + + return o; +} + +bool RenderObject::isSelectionBorder() const +{ + SelectionState st = selectionState(); + return st == SelectionStart || st == SelectionEnd || st == SelectionBoth; +} + +void RenderObject::destroy() +{ + // Destroy any leftover anonymous children. + RenderObjectChildList* children = virtualChildren(); + if (children) + children->destroyLeftoverChildren(); + + // If this renderer is being autoscrolled, stop the autoscroll timer + + // FIXME: RenderObject::destroy should not get called with a renderer whose document + // has a null frame, so we assert this. However, we don't want release builds to crash which is why we + // check that the frame is not null. + ASSERT(frame()); + if (frame() && frame()->eventHandler()->autoscrollRenderer() == this) + frame()->eventHandler()->stopAutoscrollTimer(true); + + if (m_hasCounterNodeMap) + RenderCounter::destroyCounterNodes(this); + + if (AXObjectCache::accessibilityEnabled()) { + document()->axObjectCache()->childrenChanged(this->parent()); + document()->axObjectCache()->remove(this); + } + animation()->cancelAnimations(this); + + // By default no ref-counting. RenderWidget::destroy() doesn't call + // this function because it needs to do ref-counting. If anything + // in this function changes, be sure to fix RenderWidget::destroy() as well. + + remove(); + + // FIXME: Would like to do this in RenderBoxModelObject, but the timing is so complicated that this can't easily + // be moved into RenderBoxModelObject::destroy. + if (hasLayer()) { + setHasLayer(false); + toRenderBoxModelObject(this)->destroyLayer(); + } + arenaDelete(renderArena(), this); +} + +void RenderObject::arenaDelete(RenderArena* arena, void* base) +{ + if (m_style) { + for (const FillLayer* bgLayer = m_style->backgroundLayers(); bgLayer; bgLayer = bgLayer->next()) { + if (StyleImage* backgroundImage = bgLayer->image()) + backgroundImage->removeClient(this); + } + + for (const FillLayer* maskLayer = m_style->maskLayers(); maskLayer; maskLayer = maskLayer->next()) { + if (StyleImage* maskImage = maskLayer->image()) + maskImage->removeClient(this); + } + + if (StyleImage* borderImage = m_style->borderImage().image()) + borderImage->removeClient(this); + + if (StyleImage* maskBoxImage = m_style->maskBoxImage().image()) + maskBoxImage->removeClient(this); + } + +#ifndef NDEBUG + void* savedBase = baseOfRenderObjectBeingDeleted; + baseOfRenderObjectBeingDeleted = base; +#endif + delete this; +#ifndef NDEBUG + baseOfRenderObjectBeingDeleted = savedBase; +#endif + + // Recover the size left there for us by operator delete and free the memory. + arena->free(*(size_t*)base, base); +} + +VisiblePosition RenderObject::positionForCoordinates(int x, int y) +{ + return positionForPoint(IntPoint(x, y)); +} + +VisiblePosition RenderObject::positionForPoint(const IntPoint&) +{ + return createVisiblePosition(caretMinOffset(), DOWNSTREAM); +} + +void RenderObject::updateDragState(bool dragOn) +{ + bool valueChanged = (dragOn != m_isDragging); + m_isDragging = dragOn; + if (valueChanged && style()->affectedByDragRules() && node()) + node()->setNeedsStyleRecalc(); + for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) + curr->updateDragState(dragOn); +} + +bool RenderObject::hitTest(const HitTestRequest& request, HitTestResult& result, const IntPoint& point, int tx, int ty, HitTestFilter hitTestFilter) +{ + bool inside = false; + if (hitTestFilter != HitTestSelf) { + // First test the foreground layer (lines and inlines). + inside = nodeAtPoint(request, result, point.x(), point.y(), tx, ty, HitTestForeground); + + // Test floats next. + if (!inside) + inside = nodeAtPoint(request, result, point.x(), point.y(), tx, ty, HitTestFloat); + + // Finally test to see if the mouse is in the background (within a child block's background). + if (!inside) + inside = nodeAtPoint(request, result, point.x(), point.y(), tx, ty, HitTestChildBlockBackgrounds); + } + + // See if the mouse is inside us but not any of our descendants + if (hitTestFilter != HitTestDescendants && !inside) + inside = nodeAtPoint(request, result, point.x(), point.y(), tx, ty, HitTestBlockBackground); + + return inside; +} + +void RenderObject::updateHitTestResult(HitTestResult& result, const IntPoint& point) +{ + if (result.innerNode()) + return; + + Node* n = node(); + if (n) { + result.setInnerNode(n); + if (!result.innerNonSharedNode()) + result.setInnerNonSharedNode(n); + result.setLocalPoint(point); + } +} + +bool RenderObject::nodeAtPoint(const HitTestRequest&, HitTestResult&, int /*x*/, int /*y*/, int /*tx*/, int /*ty*/, HitTestAction) +{ + return false; +} + +void RenderObject::scheduleRelayout() +{ + if (isRenderView()) { + FrameView* view = toRenderView(this)->frameView(); + if (view) + view->scheduleRelayout(); + } else if (parent()) { + FrameView* v = view() ? view()->frameView() : 0; + if (v) + v->scheduleRelayoutOfSubtree(this); + } +} + +void RenderObject::layout() +{ + ASSERT(needsLayout()); + RenderObject* child = firstChild(); + while (child) { + child->layoutIfNeeded(); + ASSERT(!child->needsLayout()); + child = child->nextSibling(); + } + setNeedsLayout(false); +} + +PassRefPtr<RenderStyle> RenderObject::uncachedFirstLineStyle(RenderStyle* style) const +{ + if (!document()->usesFirstLineRules()) + return 0; + + ASSERT(!isText()); + + RefPtr<RenderStyle> result; + + if (isBlockFlow()) { + if (RenderBlock* firstLineBlock = this->firstLineBlock()) + result = firstLineBlock->getUncachedPseudoStyle(FIRST_LINE, style, firstLineBlock == this ? style : 0); + } else if (!isAnonymous() && isRenderInline()) { + RenderStyle* parentStyle = parent()->firstLineStyle(); + if (parentStyle != parent()->style()) + result = getUncachedPseudoStyle(FIRST_LINE_INHERITED, parentStyle, style); + } + + return result.release(); +} + +RenderStyle* RenderObject::firstLineStyleSlowCase() const +{ + ASSERT(document()->usesFirstLineRules()); + + RenderStyle* style = m_style.get(); + const RenderObject* renderer = isText() ? parent() : this; + if (renderer->isBlockFlow()) { + if (RenderBlock* firstLineBlock = renderer->firstLineBlock()) + style = firstLineBlock->getCachedPseudoStyle(FIRST_LINE, style); + } else if (!renderer->isAnonymous() && renderer->isRenderInline()) { + RenderStyle* parentStyle = renderer->parent()->firstLineStyle(); + if (parentStyle != renderer->parent()->style()) { + // A first-line style is in effect. Cache a first-line style for ourselves. + renderer->style()->setHasPseudoStyle(FIRST_LINE_INHERITED); + style = renderer->getCachedPseudoStyle(FIRST_LINE_INHERITED, parentStyle); + } + } + + return style; +} + +RenderStyle* RenderObject::getCachedPseudoStyle(PseudoId pseudo, RenderStyle* parentStyle) const +{ + if (pseudo < FIRST_INTERNAL_PSEUDOID && !style()->hasPseudoStyle(pseudo)) + return 0; + + RenderStyle* cachedStyle = style()->getCachedPseudoStyle(pseudo); + if (cachedStyle) + return cachedStyle; + + RefPtr<RenderStyle> result = getUncachedPseudoStyle(pseudo, parentStyle); + if (result) + return style()->addCachedPseudoStyle(result.release()); + return 0; +} + +PassRefPtr<RenderStyle> RenderObject::getUncachedPseudoStyle(PseudoId pseudo, RenderStyle* parentStyle, RenderStyle* ownStyle) const +{ + if (pseudo < FIRST_INTERNAL_PSEUDOID && !ownStyle && !style()->hasPseudoStyle(pseudo)) + return 0; + + if (!parentStyle) { + ASSERT(!ownStyle); + parentStyle = style(); + } + + Node* n = node(); + while (n && !n->isElementNode()) + n = n->parentNode(); + if (!n) + return 0; + + RefPtr<RenderStyle> result; + if (pseudo == FIRST_LINE_INHERITED) { + result = document()->styleSelector()->styleForElement(static_cast<Element*>(n), parentStyle, false); + result->setStyleType(FIRST_LINE_INHERITED); + } else + result = document()->styleSelector()->pseudoStyleForElement(pseudo, static_cast<Element*>(n), parentStyle); + return result.release(); +} + +static Color decorationColor(RenderObject* renderer) +{ + Color result; + if (renderer->style()->textStrokeWidth() > 0) { + // Prefer stroke color if possible but not if it's fully transparent. + result = renderer->style()->visitedDependentColor(CSSPropertyWebkitTextStrokeColor); + if (result.alpha()) + return result; + } + + result = renderer->style()->visitedDependentColor(CSSPropertyWebkitTextFillColor); + return result; +} + +void RenderObject::getTextDecorationColors(int decorations, Color& underline, Color& overline, + Color& linethrough, bool quirksMode) +{ + RenderObject* curr = this; + do { + int currDecs = curr->style()->textDecoration(); + if (currDecs) { + if (currDecs & UNDERLINE) { + decorations &= ~UNDERLINE; + underline = decorationColor(curr); + } + if (currDecs & OVERLINE) { + decorations &= ~OVERLINE; + overline = decorationColor(curr); + } + if (currDecs & LINE_THROUGH) { + decorations &= ~LINE_THROUGH; + linethrough = decorationColor(curr); + } + } + curr = curr->parent(); + if (curr && curr->isAnonymousBlock() && toRenderBlock(curr)->continuation()) + curr = toRenderBlock(curr)->continuation(); + } while (curr && decorations && (!quirksMode || !curr->node() || + (!curr->node()->hasTagName(aTag) && !curr->node()->hasTagName(fontTag)))); + + // If we bailed out, use the element we bailed out at (typically a <font> or <a> element). + if (decorations && curr) { + if (decorations & UNDERLINE) + underline = decorationColor(curr); + if (decorations & OVERLINE) + overline = decorationColor(curr); + if (decorations & LINE_THROUGH) + linethrough = decorationColor(curr); + } +} + +#if ENABLE(DASHBOARD_SUPPORT) +void RenderObject::addDashboardRegions(Vector<DashboardRegionValue>& regions) +{ + // Convert the style regions to absolute coordinates. + if (style()->visibility() != VISIBLE || !isBox()) + return; + + RenderBox* box = toRenderBox(this); + + const Vector<StyleDashboardRegion>& styleRegions = style()->dashboardRegions(); + unsigned i, count = styleRegions.size(); + for (i = 0; i < count; i++) { + StyleDashboardRegion styleRegion = styleRegions[i]; + + int w = box->width(); + int h = box->height(); + + DashboardRegionValue region; + region.label = styleRegion.label; + region.bounds = IntRect(styleRegion.offset.left().value(), + styleRegion.offset.top().value(), + w - styleRegion.offset.left().value() - styleRegion.offset.right().value(), + h - styleRegion.offset.top().value() - styleRegion.offset.bottom().value()); + region.type = styleRegion.type; + + region.clip = region.bounds; + computeAbsoluteRepaintRect(region.clip); + if (region.clip.height() < 0) { + region.clip.setHeight(0); + region.clip.setWidth(0); + } + + FloatPoint absPos = localToAbsolute(); + region.bounds.setX(absPos.x() + styleRegion.offset.left().value()); + region.bounds.setY(absPos.y() + styleRegion.offset.top().value()); + + if (frame()) { + float pageScaleFactor = frame()->page()->chrome()->scaleFactor(); + if (pageScaleFactor != 1.0f) { + region.bounds.scale(pageScaleFactor); + region.clip.scale(pageScaleFactor); + } + } + + regions.append(region); + } +} + +void RenderObject::collectDashboardRegions(Vector<DashboardRegionValue>& regions) +{ + // RenderTexts don't have their own style, they just use their parent's style, + // so we don't want to include them. + if (isText()) + return; + + addDashboardRegions(regions); + for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) + curr->collectDashboardRegions(regions); +} +#endif + +bool RenderObject::willRenderImage(CachedImage*) +{ + // Without visibility we won't render (and therefore don't care about animation). + if (style()->visibility() != VISIBLE) + return false; + + // If we're not in a window (i.e., we're dormant from being put in the b/f cache or in a background tab) + // then we don't want to render either. + return !document()->inPageCache() && !document()->view()->isOffscreen(); +} + +int RenderObject::maximalOutlineSize(PaintPhase p) const +{ + if (p != PaintPhaseOutline && p != PaintPhaseSelfOutline && p != PaintPhaseChildOutlines) + return 0; + return toRenderView(document()->renderer())->maximalOutlineSize(); +} + +int RenderObject::caretMinOffset() const +{ + return 0; +} + +int RenderObject::caretMaxOffset() const +{ + if (isReplaced()) + return node() ? max(1U, node()->childNodeCount()) : 1; + if (isHR()) + return 1; + return 0; +} + +unsigned RenderObject::caretMaxRenderedOffset() const +{ + return 0; +} + +int RenderObject::previousOffset(int current) const +{ + return current - 1; +} + +int RenderObject::previousOffsetForBackwardDeletion(int current) const +{ + return current - 1; +} + +int RenderObject::nextOffset(int current) const +{ + return current + 1; +} + +void RenderObject::adjustRectForOutlineAndShadow(IntRect& rect) const +{ + int outlineSize = outlineStyleForRepaint()->outlineSize(); + if (const ShadowData* boxShadow = style()->boxShadow()) { + boxShadow->adjustRectForShadow(rect, outlineSize); + return; + } + + rect.inflate(outlineSize); +} + +AnimationController* RenderObject::animation() const +{ + return frame()->animation(); +} + +void RenderObject::imageChanged(CachedImage* image, const IntRect* rect) +{ + imageChanged(static_cast<WrappedImagePtr>(image), rect); +} + +RenderBoxModelObject* RenderObject::offsetParent() const +{ + // If any of the following holds true return null and stop this algorithm: + // A is the root element. + // A is the HTML body element. + // The computed value of the position property for element A is fixed. + if (isRoot() || isBody() || (isPositioned() && style()->position() == FixedPosition)) + return 0; + + // If A is an area HTML element which has a map HTML element somewhere in the ancestor + // chain return the nearest ancestor map HTML element and stop this algorithm. + // FIXME: Implement! + + // Return the nearest ancestor element of A for which at least one of the following is + // true and stop this algorithm if such an ancestor is found: + // * The computed value of the position property is not static. + // * It is the HTML body element. + // * The computed value of the position property of A is static and the ancestor + // is one of the following HTML elements: td, th, or table. + // * Our own extension: if there is a difference in the effective zoom + bool skipTables = isPositioned() || isRelPositioned(); + float currZoom = style()->effectiveZoom(); + RenderObject* curr = parent(); + while (curr && (!curr->node() || + (!curr->isPositioned() && !curr->isRelPositioned() && !curr->isBody()))) { + Node* element = curr->node(); + if (!skipTables && element) { + bool isTableElement = element->hasTagName(tableTag) || + element->hasTagName(tdTag) || + element->hasTagName(thTag); + +#if ENABLE(WML) + if (!isTableElement && element->isWMLElement()) + isTableElement = element->hasTagName(WMLNames::tableTag) || + element->hasTagName(WMLNames::tdTag); +#endif + + if (isTableElement) + break; + } + + float newZoom = curr->style()->effectiveZoom(); + if (currZoom != newZoom) + break; + currZoom = newZoom; + curr = curr->parent(); + } + return curr && curr->isBoxModelObject() ? toRenderBoxModelObject(curr) : 0; +} + +VisiblePosition RenderObject::createVisiblePosition(int offset, EAffinity affinity) +{ + // If this is a non-anonymous renderer in an editable area, then it's simple. + if (Node* node = this->node()) { + if (!node->isContentEditable()) { + // If it can be found, we prefer a visually equivalent position that is editable. + Position position(node, offset); + Position candidate = position.downstream(CanCrossEditingBoundary); + if (candidate.node()->isContentEditable()) + return VisiblePosition(candidate, affinity); + candidate = position.upstream(CanCrossEditingBoundary); + if (candidate.node()->isContentEditable()) + return VisiblePosition(candidate, affinity); + } + return VisiblePosition(node, offset, affinity); + } + + // We don't want to cross the boundary between editable and non-editable + // regions of the document, but that is either impossible or at least + // extremely unlikely in any normal case because we stop as soon as we + // find a single non-anonymous renderer. + + // Find a nearby non-anonymous renderer. + RenderObject* child = this; + while (RenderObject* parent = child->parent()) { + // Find non-anonymous content after. + RenderObject* renderer = child; + while ((renderer = renderer->nextInPreOrder(parent))) { + if (Node* node = renderer->node()) + return VisiblePosition(node, 0, DOWNSTREAM); + } + + // Find non-anonymous content before. + renderer = child; + while ((renderer = renderer->previousInPreOrder())) { + if (renderer == parent) + break; + if (Node* node = renderer->node()) + return VisiblePosition(lastDeepEditingPositionForNode(node), DOWNSTREAM); + } + + // Use the parent itself unless it too is anonymous. + if (Node* node = parent->node()) + return VisiblePosition(node, 0, DOWNSTREAM); + + // Repeat at the next level up. + child = parent; + } + + // Everything was anonymous. Give up. + return VisiblePosition(); +} + +VisiblePosition RenderObject::createVisiblePosition(const Position& position) +{ + if (position.isNotNull()) + return VisiblePosition(position); + + ASSERT(!node()); + return createVisiblePosition(0, DOWNSTREAM); +} + +#if ENABLE(SVG) +RenderSVGResourceContainer* RenderObject::toRenderSVGResourceContainer() +{ + ASSERT_NOT_REACHED(); + return 0; +} + +void RenderObject::setNeedsBoundariesUpdate() +{ + if (RenderObject* renderer = parent()) + renderer->setNeedsBoundariesUpdate(); +} + +FloatRect RenderObject::objectBoundingBox() const +{ + ASSERT_NOT_REACHED(); + return FloatRect(); +} + +FloatRect RenderObject::strokeBoundingBox() const +{ + ASSERT_NOT_REACHED(); + return FloatRect(); +} + +// Returns the smallest rectangle enclosing all of the painted content +// respecting clipping, masking, filters, opacity, stroke-width and markers +FloatRect RenderObject::repaintRectInLocalCoordinates() const +{ + ASSERT_NOT_REACHED(); + return FloatRect(); +} + +AffineTransform RenderObject::localTransform() const +{ + static const AffineTransform identity; + return identity; +} + +const AffineTransform& RenderObject::localToParentTransform() const +{ + static const AffineTransform identity; + return identity; +} + +bool RenderObject::nodeAtFloatPoint(const HitTestRequest&, HitTestResult&, const FloatPoint&, HitTestAction) +{ + ASSERT_NOT_REACHED(); + return false; +} + +#endif // ENABLE(SVG) + +} // namespace WebCore + +#ifndef NDEBUG + +void showTree(const WebCore::RenderObject* ro) +{ + if (ro) + ro->showTreeForThis(); +} + +void showRenderTree(const WebCore::RenderObject* object1) +{ + showRenderTree(object1, 0); +} + +void showRenderTree(const WebCore::RenderObject* object1, const WebCore::RenderObject* object2) +{ + if (object1) { + const WebCore::RenderObject* root = object1; + while (root->parent()) + root = root->parent(); + root->showRenderTreeAndMark(object1, "*", object2, "-", 0); + } +} + +#endif diff --git a/Source/WebCore/rendering/RenderObject.h b/Source/WebCore/rendering/RenderObject.h new file mode 100644 index 0000000..2f443ef --- /dev/null +++ b/Source/WebCore/rendering/RenderObject.h @@ -0,0 +1,1050 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * (C) 2004 Allan Sandfeld Jensen (kde@carewolf.com) + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderObject_h +#define RenderObject_h + +#include "AffineTransform.h" +#include "CachedResourceClient.h" +#include "CSSPrimitiveValue.h" +#include "Document.h" +#include "Element.h" +#include "FloatQuad.h" +#include "PaintInfo.h" +#include "RenderObjectChildList.h" +#include "RenderStyle.h" +#include "TextAffinity.h" +#include "TransformationMatrix.h" +#include <wtf/UnusedParam.h> + +#if PLATFORM(CG) || PLATFORM(CAIRO) || PLATFORM(QT) +#define HAVE_PATH_BASED_BORDER_RADIUS_DRAWING 1 +#endif + +namespace WebCore { + +class AnimationController; +class HitTestResult; +class InlineBox; +class InlineFlowBox; +class OverlapTestRequestClient; +class Path; +class Position; +class RenderBoxModelObject; +class RenderInline; +class RenderBlock; +class RenderFlow; +class RenderLayer; +class RenderTheme; +class TransformState; +class VisiblePosition; +#if ENABLE(SVG) +class RenderSVGResourceContainer; +#endif + +enum HitTestFilter { + HitTestAll, + HitTestSelf, + HitTestDescendants +}; + +enum HitTestAction { + HitTestBlockBackground, + HitTestChildBlockBackground, + HitTestChildBlockBackgrounds, + HitTestFloat, + HitTestForeground +}; + +// Sides used when drawing borders and outlines. This is in RenderObject rather than RenderBoxModelObject since outlines can +// be drawn by SVG around bounding boxes. +enum BoxSide { + BSTop, + BSBottom, + BSLeft, + BSRight +}; + +const int caretWidth = 1; + +#if ENABLE(DASHBOARD_SUPPORT) +struct DashboardRegionValue { + bool operator==(const DashboardRegionValue& o) const + { + return type == o.type && bounds == o.bounds && clip == o.clip && label == o.label; + } + bool operator!=(const DashboardRegionValue& o) const + { + return !(*this == o); + } + + String label; + IntRect bounds; + IntRect clip; + int type; +}; +#endif + +// Base class for all rendering tree objects. +class RenderObject : public CachedResourceClient { + friend class RenderBlock; + friend class RenderBox; + friend class RenderLayer; + friend class RenderObjectChildList; + friend class RenderSVGContainer; +public: + // Anonymous objects should pass the document as their node, and they will then automatically be + // marked as anonymous in the constructor. + RenderObject(Node*); + virtual ~RenderObject(); + + RenderTheme* theme() const; + + virtual const char* renderName() const = 0; + + RenderObject* parent() const { return m_parent; } + bool isDescendantOf(const RenderObject*) const; + + RenderObject* previousSibling() const { return m_previous; } + RenderObject* nextSibling() const { return m_next; } + + RenderObject* firstChild() const + { + if (const RenderObjectChildList* children = virtualChildren()) + return children->firstChild(); + return 0; + } + RenderObject* lastChild() const + { + if (const RenderObjectChildList* children = virtualChildren()) + return children->lastChild(); + return 0; + } + virtual RenderObjectChildList* virtualChildren() { return 0; } + virtual const RenderObjectChildList* virtualChildren() const { return 0; } + + RenderObject* nextInPreOrder() const; + RenderObject* nextInPreOrder(RenderObject* stayWithin) const; + RenderObject* nextInPreOrderAfterChildren() const; + RenderObject* nextInPreOrderAfterChildren(RenderObject* stayWithin) const; + RenderObject* previousInPreOrder() const; + RenderObject* childAt(unsigned) const; + + RenderObject* firstLeafChild() const; + RenderObject* lastLeafChild() const; + + // The following six functions are used when the render tree hierarchy changes to make sure layers get + // properly added and removed. Since containership can be implemented by any subclass, and since a hierarchy + // can contain a mixture of boxes and other object types, these functions need to be in the base class. + RenderLayer* enclosingLayer() const; + void addLayers(RenderLayer* parentLayer, RenderObject* newObject); + void removeLayers(RenderLayer* parentLayer); + void moveLayers(RenderLayer* oldParent, RenderLayer* newParent); + RenderLayer* findNextLayer(RenderLayer* parentLayer, RenderObject* startPoint, bool checkParent = true); + + // Convenience function for getting to the nearest enclosing box of a RenderObject. + RenderBox* enclosingBox() const; + RenderBoxModelObject* enclosingBoxModelObject() const; + + virtual bool isEmpty() const { return firstChild() == 0; } + +#ifndef NDEBUG + void setHasAXObject(bool flag) { m_hasAXObject = flag; } + bool hasAXObject() const { return m_hasAXObject; } + bool isSetNeedsLayoutForbidden() const { return m_setNeedsLayoutForbidden; } + void setNeedsLayoutIsForbidden(bool flag) { m_setNeedsLayoutForbidden = flag; } +#endif + + // Obtains the nearest enclosing block (including this block) that contributes a first-line style to our inline + // children. + virtual RenderBlock* firstLineBlock() const; + + // Called when an object that was floating or positioned becomes a normal flow object + // again. We have to make sure the render tree updates as needed to accommodate the new + // normal flow object. + void handleDynamicFloatPositionChange(); + + // RenderObject tree manipulation + ////////////////////////////////////////// + virtual bool canHaveChildren() const { return virtualChildren(); } + virtual bool isChildAllowed(RenderObject*, RenderStyle*) const { return true; } + virtual void addChild(RenderObject* newChild, RenderObject* beforeChild = 0); + virtual void addChildIgnoringContinuation(RenderObject* newChild, RenderObject* beforeChild = 0) { return addChild(newChild, beforeChild); } + virtual void removeChild(RenderObject*); + virtual bool createsAnonymousWrapper() const { return false; } + ////////////////////////////////////////// + +protected: + ////////////////////////////////////////// + // Helper functions. Dangerous to use! + void setPreviousSibling(RenderObject* previous) { m_previous = previous; } + void setNextSibling(RenderObject* next) { m_next = next; } + void setParent(RenderObject* parent) { m_parent = parent; } + ////////////////////////////////////////// +private: + void addAbsoluteRectForLayer(IntRect& result); + void setLayerNeedsFullRepaint(); + +public: +#ifndef NDEBUG + void showTreeForThis() const; + + void showRenderObject() const; + // We don't make printedCharacters an optional parameter so that + // showRenderObject can be called from gdb easily. + void showRenderObject(int printedCharacters) const; + void showRenderTreeAndMark(const RenderObject* markedObject1 = 0, const char* markedLabel1 = 0, const RenderObject* markedObject2 = 0, const char* markedLabel2 = 0, int depth = 0) const; +#endif + + static RenderObject* createObject(Node*, RenderStyle*); + + // Overloaded new operator. Derived classes must override operator new + // in order to allocate out of the RenderArena. + void* operator new(size_t, RenderArena*) throw(); + + // Overridden to prevent the normal delete from being called. + void operator delete(void*, size_t); + +private: + // The normal operator new is disallowed on all render objects. + void* operator new(size_t) throw(); + +public: + RenderArena* renderArena() const { return document()->renderArena(); } + + virtual bool isApplet() const { return false; } + virtual bool isBR() const { return false; } + virtual bool isBlockFlow() const { return false; } + virtual bool isBoxModelObject() const { return false; } + virtual bool isCounter() const { return false; } + virtual bool isDetails() const { return false; } + virtual bool isDetailsMarker() const { return false; } + virtual bool isEmbeddedObject() const { return false; } + virtual bool isFieldset() const { return false; } + virtual bool isFileUploadControl() const { return false; } + virtual bool isFrame() const { return false; } + virtual bool isFrameSet() const { return false; } + virtual bool isImage() const { return false; } + virtual bool isInlineBlockOrInlineTable() const { return false; } + virtual bool isListBox() const { return false; } + virtual bool isListItem() const { return false; } + virtual bool isListMarker() const { return false; } + virtual bool isMedia() const { return false; } + virtual bool isMenuList() const { return false; } +#if ENABLE(METER_TAG) + virtual bool isMeter() const { return false; } +#endif +#if ENABLE(PROGRESS_TAG) + virtual bool isProgress() const { return false; } +#endif + virtual bool isRenderBlock() const { return false; } + virtual bool isRenderButton() const { return false; } + virtual bool isRenderIFrame() const { return false; } + virtual bool isRenderImage() const { return false; } + virtual bool isRenderInline() const { return false; } + virtual bool isRenderPart() const { return false; } + virtual bool isRenderView() const { return false; } + virtual bool isReplica() const { return false; } + + virtual bool isRuby() const { return false; } + virtual bool isRubyBase() const { return false; } + virtual bool isRubyRun() const { return false; } + virtual bool isRubyText() const { return false; } + + virtual bool isSlider() const { return false; } + virtual bool isSummary() const { return false; } + virtual bool isTable() const { return false; } + virtual bool isTableCell() const { return false; } + virtual bool isTableCol() const { return false; } + virtual bool isTableRow() const { return false; } + virtual bool isTableSection() const { return false; } + virtual bool isTextControl() const { return false; } + virtual bool isTextArea() const { return false; } + virtual bool isTextField() const { return false; } + virtual bool isVideo() const { return false; } + virtual bool isWidget() const { return false; } + virtual bool isCanvas() const { return false; } +#if ENABLE(FULLSCREEN_API) + virtual bool isRenderFullScreen() const { return false; } +#endif + + bool isRoot() const { return document()->documentElement() == m_node; } + bool isBody() const; + bool isHR() const; + bool isLegend() const; + + bool isHTMLMarquee() const; + + inline bool isBeforeContent() const; + inline bool isAfterContent() const; + static inline bool isBeforeContent(const RenderObject* obj) { return obj && obj->isBeforeContent(); } + static inline bool isAfterContent(const RenderObject* obj) { return obj && obj->isAfterContent(); } + + bool childrenInline() const { return m_childrenInline; } + void setChildrenInline(bool b = true) { m_childrenInline = b; } + bool hasColumns() const { return m_hasColumns; } + void setHasColumns(bool b = true) { m_hasColumns = b; } + bool cellWidthChanged() const { return m_cellWidthChanged; } + void setCellWidthChanged(bool b = true) { m_cellWidthChanged = b; } + + virtual bool requiresForcedStyleRecalcPropagation() const { return false; } + +#if ENABLE(MATHML) + virtual bool isRenderMathMLBlock() const { return false; } +#endif // ENABLE(MATHML) + +#if ENABLE(SVG) + // FIXME: Until all SVG renders can be subclasses of RenderSVGModelObject we have + // to add SVG renderer methods to RenderObject with an ASSERT_NOT_REACHED() default implementation. + virtual bool isSVGRoot() const { return false; } + virtual bool isSVGContainer() const { return false; } + virtual bool isSVGViewportContainer() const { return false; } + virtual bool isSVGGradientStop() const { return false; } + virtual bool isSVGHiddenContainer() const { return false; } + virtual bool isSVGPath() const { return false; } + virtual bool isSVGText() const { return false; } + virtual bool isSVGTextPath() const { return false; } + virtual bool isSVGInline() const { return false; } + virtual bool isSVGInlineText() const { return false; } + virtual bool isSVGImage() const { return false; } + virtual bool isSVGForeignObject() const { return false; } + virtual bool isSVGResourceContainer() const { return false; } + virtual bool isSVGResourceFilterPrimitive() const { return false; } + virtual bool isSVGShadowTreeRootContainer() const { return false; } + + virtual RenderSVGResourceContainer* toRenderSVGResourceContainer(); + + // FIXME: Those belong into a SVG specific base-class for all renderers (see above) + // Unfortunately we don't have such a class yet, because it's not possible for all renderers + // to inherit from RenderSVGObject -> RenderObject (some need RenderBlock inheritance for instance) + virtual void setNeedsTransformUpdate() { } + virtual void setNeedsBoundariesUpdate(); + + // Per SVG 1.1 objectBoundingBox ignores clipping, masking, filter effects, opacity and stroke-width. + // This is used for all computation of objectBoundingBox relative units and by SVGLocateable::getBBox(). + // NOTE: Markers are not specifically ignored here by SVG 1.1 spec, but we ignore them + // since stroke-width is ignored (and marker size can depend on stroke-width). + // objectBoundingBox is returned local coordinates. + // The name objectBoundingBox is taken from the SVG 1.1 spec. + virtual FloatRect objectBoundingBox() const; + virtual FloatRect strokeBoundingBox() const; + + // Returns the smallest rectangle enclosing all of the painted content + // respecting clipping, masking, filters, opacity, stroke-width and markers + virtual FloatRect repaintRectInLocalCoordinates() const; + + // This only returns the transform="" value from the element + // most callsites want localToParentTransform() instead. + virtual AffineTransform localTransform() const; + + // Returns the full transform mapping from local coordinates to local coords for the parent SVG renderer + // This includes any viewport transforms and x/y offsets as well as the transform="" value off the element. + virtual const AffineTransform& localToParentTransform() const; + + // SVG uses FloatPoint precise hit testing, and passes the point in parent + // coordinates instead of in repaint container coordinates. Eventually the + // rest of the rendering tree will move to a similar model. + virtual bool nodeAtFloatPoint(const HitTestRequest&, HitTestResult&, const FloatPoint& pointInParent, HitTestAction); +#endif + + bool isAnonymous() const { return m_isAnonymous; } + void setIsAnonymous(bool b) { m_isAnonymous = b; } + bool isAnonymousBlock() const + { + return m_isAnonymous && style()->display() == BLOCK && style()->styleType() == NOPSEUDO && !isListMarker(); + } + bool isAnonymousColumnsBlock() const { return style()->specifiesColumns() && isAnonymousBlock(); } + bool isAnonymousColumnSpanBlock() const { return style()->columnSpan() && isAnonymousBlock(); } + bool isElementContinuation() const { return node() && node()->renderer() != this; } + bool isInlineElementContinuation() const { return isElementContinuation() && isInline(); } + bool isBlockElementContinuation() const { return isElementContinuation() && !isInline(); } + virtual RenderBoxModelObject* virtualContinuation() const { return 0; } + + bool isFloating() const { return m_floating; } + bool isPositioned() const { return m_positioned; } // absolute or fixed positioning + bool isRelPositioned() const { return m_relPositioned; } // relative positioning + bool isText() const { return m_isText; } + bool isBox() const { return m_isBox; } + bool isInline() const { return m_inline; } // inline object + bool isRunIn() const { return style()->display() == RUN_IN; } // run-in object + bool isDragging() const { return m_isDragging; } + bool isReplaced() const { return m_replaced; } // a "replaced" element (see CSS) + + bool hasLayer() const { return m_hasLayer; } + + bool hasBoxDecorations() const { return m_paintBackground; } + bool mustRepaintBackgroundOrBorder() const; + bool hasBackground() const { return style()->hasBackground(); } + bool needsLayout() const { return m_needsLayout || m_normalChildNeedsLayout || m_posChildNeedsLayout || m_needsPositionedMovementLayout; } + bool selfNeedsLayout() const { return m_needsLayout; } + bool needsPositionedMovementLayout() const { return m_needsPositionedMovementLayout; } + bool needsPositionedMovementLayoutOnly() const { return m_needsPositionedMovementLayout && !m_needsLayout && !m_normalChildNeedsLayout && !m_posChildNeedsLayout; } + bool posChildNeedsLayout() const { return m_posChildNeedsLayout; } + bool normalChildNeedsLayout() const { return m_normalChildNeedsLayout; } + + bool preferredLogicalWidthsDirty() const { return m_preferredLogicalWidthsDirty; } + + bool isSelectionBorder() const; + + bool hasClip() const { return isPositioned() && style()->hasClip(); } + bool hasOverflowClip() const { return m_hasOverflowClip; } + + bool hasTransform() const { return m_hasTransform; } + bool hasMask() const { return style() && style()->hasMask(); } + + void drawLineForBoxSide(GraphicsContext*, int x1, int y1, int x2, int y2, BoxSide, + Color, EBorderStyle, int adjbw1, int adjbw2); +#if HAVE(PATH_BASED_BORDER_RADIUS_DRAWING) + void drawBoxSideFromPath(GraphicsContext*, IntRect, Path, + float thickness, float drawThickness, BoxSide, const RenderStyle*, + Color, EBorderStyle); +#else + // FIXME: This function should be removed when all ports implement GraphicsContext::clipConvexPolygon()!! + // At that time, everyone can use RenderObject::drawBoxSideFromPath() instead. This should happen soon. + void drawArcForBoxSide(GraphicsContext*, int x, int y, float thickness, IntSize radius, int angleStart, + int angleSpan, BoxSide, Color, EBorderStyle, bool firstCorner); +#endif + + IntRect borderInnerRect(const IntRect&, unsigned short topWidth, unsigned short bottomWidth, + unsigned short leftWidth, unsigned short rightWidth) const; + + // The pseudo element style can be cached or uncached. Use the cached method if the pseudo element doesn't respect + // any pseudo classes (and therefore has no concept of changing state). + RenderStyle* getCachedPseudoStyle(PseudoId, RenderStyle* parentStyle = 0) const; + PassRefPtr<RenderStyle> getUncachedPseudoStyle(PseudoId, RenderStyle* parentStyle = 0, RenderStyle* ownStyle = 0) const; + + virtual void updateDragState(bool dragOn); + + RenderView* view() const; + + // Returns true if this renderer is rooted, and optionally returns the hosting view (the root of the hierarchy). + bool isRooted(RenderView** = 0); + + Node* node() const { return m_isAnonymous ? 0 : m_node; } + void setNode(Node* node) { m_node = node; } + + Document* document() const { return m_node->document(); } + Frame* frame() const { return document()->frame(); } + + bool hasOutlineAnnotation() const; + bool hasOutline() const { return style()->hasOutline() || hasOutlineAnnotation(); } + + // Returns the object containing this one. Can be different from parent for positioned elements. + // If repaintContainer and repaintContainerSkipped are not null, on return *repaintContainerSkipped + // is true if the renderer returned is an ancestor of repaintContainer. + RenderObject* container(RenderBoxModelObject* repaintContainer = 0, bool* repaintContainerSkipped = 0) const; + + virtual RenderObject* hoverAncestor() const { return parent(); } + + // IE Extension that can be called on any RenderObject. See the implementation for the details. + RenderBoxModelObject* offsetParent() const; + + void markContainingBlocksForLayout(bool scheduleRelayout = true, RenderObject* newRoot = 0); + void setNeedsLayout(bool b, bool markParents = true); + void setChildNeedsLayout(bool b, bool markParents = true); + void setNeedsPositionedMovementLayout(); + void setPreferredLogicalWidthsDirty(bool, bool markParents = true); + void invalidateContainerPreferredLogicalWidths(); + + void setNeedsLayoutAndPrefWidthsRecalc() + { + setNeedsLayout(true); + setPreferredLogicalWidthsDirty(true); + } + + void setPositioned(bool b = true) { m_positioned = b; } + void setRelPositioned(bool b = true) { m_relPositioned = b; } + void setFloating(bool b = true) { m_floating = b; } + void setInline(bool b = true) { m_inline = b; } + void setHasBoxDecorations(bool b = true) { m_paintBackground = b; } + void setIsText() { m_isText = true; } + void setIsBox() { m_isBox = true; } + void setReplaced(bool b = true) { m_replaced = b; } + void setHasOverflowClip(bool b = true) { m_hasOverflowClip = b; } + void setHasLayer(bool b = true) { m_hasLayer = b; } + void setHasTransform(bool b = true) { m_hasTransform = b; } + void setHasReflection(bool b = true) { m_hasReflection = b; } + + void scheduleRelayout(); + + void updateFillImages(const FillLayer*, const FillLayer*); + void updateImage(StyleImage*, StyleImage*); + + virtual void paint(PaintInfo&, int tx, int ty); + + // Recursive function that computes the size and position of this object and all its descendants. + virtual void layout(); + + /* This function performs a layout only if one is needed. */ + void layoutIfNeeded() { if (needsLayout()) layout(); } + + // used for element state updates that cannot be fixed with a + // repaint and do not need a relayout + virtual void updateFromElement() { } + +#if ENABLE(DASHBOARD_SUPPORT) + virtual void addDashboardRegions(Vector<DashboardRegionValue>&); + void collectDashboardRegions(Vector<DashboardRegionValue>&); +#endif + + bool hitTest(const HitTestRequest&, HitTestResult&, const IntPoint&, int tx, int ty, HitTestFilter = HitTestAll); + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + virtual void updateHitTestResult(HitTestResult&, const IntPoint&); + + VisiblePosition positionForCoordinates(int x, int y); + virtual VisiblePosition positionForPoint(const IntPoint&); + VisiblePosition createVisiblePosition(int offset, EAffinity); + VisiblePosition createVisiblePosition(const Position&); + + virtual void dirtyLinesFromChangedChild(RenderObject*); + + // Called to update a style that is allowed to trigger animations. + // FIXME: Right now this will typically be called only when updating happens from the DOM on explicit elements. + // We don't yet handle generated content animation such as first-letter or before/after (we'll worry about this later). + void setAnimatableStyle(PassRefPtr<RenderStyle>); + + // Set the style of the object and update the state of the object accordingly. + virtual void setStyle(PassRefPtr<RenderStyle>); + + // Updates only the local style ptr of the object. Does not update the state of the object, + // and so only should be called when the style is known not to have changed (or from setStyle). + void setStyleInternal(PassRefPtr<RenderStyle>); + + // returns the containing block level element for this element. + RenderBlock* containingBlock() const; + + // Convert the given local point to absolute coordinates + // FIXME: Temporary. If useTransforms is true, take transforms into account. Eventually localToAbsolute() will always be transform-aware. + FloatPoint localToAbsolute(FloatPoint localPoint = FloatPoint(), bool fixed = false, bool useTransforms = false) const; + FloatPoint absoluteToLocal(FloatPoint, bool fixed = false, bool useTransforms = false) const; + + // Convert a local quad to absolute coordinates, taking transforms into account. + FloatQuad localToAbsoluteQuad(const FloatQuad& quad, bool fixed = false) const + { + return localToContainerQuad(quad, 0, fixed); + } + // Convert a local quad into the coordinate system of container, taking transforms into account. + FloatQuad localToContainerQuad(const FloatQuad&, RenderBoxModelObject* repaintContainer, bool fixed = false) const; + + // Return the offset from the container() renderer (excluding transforms). In multi-column layout, + // different offsets apply at different points, so return the offset that applies to the given point. + virtual IntSize offsetFromContainer(RenderObject*, const IntPoint&) const; + // Return the offset from an object up the container() chain. Asserts that none of the intermediate objects have transforms. + IntSize offsetFromAncestorContainer(RenderObject*) const; + + virtual void absoluteRects(Vector<IntRect>&, int, int) { } + // FIXME: useTransforms should go away eventually + IntRect absoluteBoundingBoxRect(bool useTransforms = false); + + // Build an array of quads in absolute coords for line boxes + virtual void absoluteQuads(Vector<FloatQuad>&) { } + + void absoluteFocusRingQuads(Vector<FloatQuad>&); + + // the rect that will be painted if this object is passed as the paintingRoot + IntRect paintingRootRect(IntRect& topLevelRect); + + virtual int minPreferredLogicalWidth() const { return 0; } + virtual int maxPreferredLogicalWidth() const { return 0; } + + RenderStyle* style() const { return m_style.get(); } + RenderStyle* firstLineStyle() const { return document()->usesFirstLineRules() ? firstLineStyleSlowCase() : style(); } + RenderStyle* style(bool firstLine) const { return firstLine ? firstLineStyle() : style(); } + + // Used only by Element::pseudoStyleCacheIsInvalid to get a first line style based off of a + // given new style, without accessing the cache. + PassRefPtr<RenderStyle> uncachedFirstLineStyle(RenderStyle*) const; + + // Anonymous blocks that are part of of a continuation chain will return their inline continuation's outline style instead. + // This is typically only relevant when repainting. + virtual RenderStyle* outlineStyleForRepaint() const { return style(); } + + void getTextDecorationColors(int decorations, Color& underline, Color& overline, + Color& linethrough, bool quirksMode = false); + + // Return the RenderBox in the container chain which is responsible for painting this object, or 0 + // if painting is root-relative. This is the container that should be passed to the 'forRepaint' + // methods. + RenderBoxModelObject* containerForRepaint() const; + // Actually do the repaint of rect r for this object which has been computed in the coordinate space + // of repaintContainer. If repaintContainer is 0, repaint via the view. + void repaintUsingContainer(RenderBoxModelObject* repaintContainer, const IntRect& r, bool immediate = false); + + // Repaint the entire object. Called when, e.g., the color of a border changes, or when a border + // style changes. + void repaint(bool immediate = false); + + // Repaint a specific subrectangle within a given object. The rect |r| is in the object's coordinate space. + void repaintRectangle(const IntRect&, bool immediate = false); + + // Repaint only if our old bounds and new bounds are different. The caller may pass in newBounds and newOutlineBox if they are known. + bool repaintAfterLayoutIfNeeded(RenderBoxModelObject* repaintContainer, const IntRect& oldBounds, const IntRect& oldOutlineBox, const IntRect* newBoundsPtr = 0, const IntRect* newOutlineBoxPtr = 0); + + // Repaint only if the object moved. + virtual void repaintDuringLayoutIfMoved(const IntRect& rect); + + // Called to repaint a block's floats. + virtual void repaintOverhangingFloats(bool paintAllDescendants = false); + + bool checkForRepaintDuringLayout() const; + + // Returns the rect that should be repainted whenever this object changes. The rect is in the view's + // coordinate space. This method deals with outlines and overflow. + IntRect absoluteClippedOverflowRect() + { + return clippedOverflowRectForRepaint(0); + } + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer); + virtual IntRect rectWithOutlineForRepaint(RenderBoxModelObject* repaintContainer, int outlineWidth); + + // Given a rect in the object's coordinate space, compute a rect suitable for repainting + // that rect in view coordinates. + void computeAbsoluteRepaintRect(IntRect& r, bool fixed = false) + { + return computeRectForRepaint(0, r, fixed); + } + // Given a rect in the object's coordinate space, compute a rect suitable for repainting + // that rect in the coordinate space of repaintContainer. + virtual void computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect&, bool fixed = false); + + // If multiple-column layout results in applying an offset to the given point, add the same + // offset to the given size. + virtual void adjustForColumns(IntSize&, const IntPoint&) const { } + + virtual unsigned int length() const { return 1; } + + bool isFloatingOrPositioned() const { return (isFloating() || isPositioned()); } + + bool isTransparent() const { return style()->opacity() < 1.0f; } + float opacity() const { return style()->opacity(); } + + bool hasReflection() const { return m_hasReflection; } + + // Applied as a "slop" to dirty rect checks during the outline painting phase's dirty-rect checks. + int maximalOutlineSize(PaintPhase) const; + + void setHasMarkupTruncation(bool b = true) { m_hasMarkupTruncation = b; } + bool hasMarkupTruncation() const { return m_hasMarkupTruncation; } + + enum SelectionState { + SelectionNone, // The object is not selected. + SelectionStart, // The object either contains the start of a selection run or is the start of a run + SelectionInside, // The object is fully encompassed by a selection run + SelectionEnd, // The object either contains the end of a selection run or is the end of a run + SelectionBoth // The object contains an entire run or is the sole selected object in that run + }; + + // The current selection state for an object. For blocks, the state refers to the state of the leaf + // descendants (as described above in the SelectionState enum declaration). + SelectionState selectionState() const { return static_cast<SelectionState>(m_selectionState);; } + + // Sets the selection state for an object. + virtual void setSelectionState(SelectionState state) { m_selectionState = state; } + + // A single rectangle that encompasses all of the selected objects within this object. Used to determine the tightest + // possible bounding box for the selection. + IntRect selectionRect(bool clipToVisibleContent = true) { return selectionRectForRepaint(0, clipToVisibleContent); } + virtual IntRect selectionRectForRepaint(RenderBoxModelObject* /*repaintContainer*/, bool /*clipToVisibleContent*/ = true) { return IntRect(); } + + // Whether or not an object can be part of the leaf elements of the selection. + virtual bool canBeSelectionLeaf() const { return false; } + + // Whether or not a block has selected children. + bool hasSelectedChildren() const { return m_selectionState != SelectionNone; } + + // Obtains the selection colors that should be used when painting a selection. + Color selectionBackgroundColor() const; + Color selectionForegroundColor() const; + Color selectionEmphasisMarkColor() const; + + // Whether or not a given block needs to paint selection gaps. + virtual bool shouldPaintSelectionGaps() const { return false; } + +#if ENABLE(DRAG_SUPPORT) + Node* draggableNode(bool dhtmlOK, bool uaOK, int x, int y, bool& dhtmlWillDrag) const; +#endif + + /** + * Returns the local coordinates of the caret within this render object. + * @param caretOffset zero-based offset determining position within the render object. + * @param extraWidthToEndOfLine optional out arg to give extra width to end of line - + * useful for character range rect computations + */ + virtual IntRect localCaretRect(InlineBox*, int caretOffset, int* extraWidthToEndOfLine = 0); + + bool isMarginBeforeQuirk() const { return m_marginBeforeQuirk; } + bool isMarginAfterQuirk() const { return m_marginAfterQuirk; } + void setMarginBeforeQuirk(bool b = true) { m_marginBeforeQuirk = b; } + void setMarginAfterQuirk(bool b = true) { m_marginAfterQuirk = b; } + + // When performing a global document tear-down, the renderer of the document is cleared. We use this + // as a hook to detect the case of document destruction and don't waste time doing unnecessary work. + bool documentBeingDestroyed() const; + + virtual void destroy(); + + // Virtual function helpers for CSS3 Flexible Box Layout + virtual bool isFlexibleBox() const { return false; } + virtual bool isFlexingChildren() const { return false; } + virtual bool isStretchingChildren() const { return false; } + + virtual int caretMinOffset() const; + virtual int caretMaxOffset() const; + virtual unsigned caretMaxRenderedOffset() const; + + virtual int previousOffset(int current) const; + virtual int previousOffsetForBackwardDeletion(int current) const; + virtual int nextOffset(int current) const; + + virtual void imageChanged(CachedImage*, const IntRect* = 0); + virtual void imageChanged(WrappedImagePtr, const IntRect* = 0) { } + virtual bool willRenderImage(CachedImage*); + + void selectionStartEnd(int& spos, int& epos) const; + + bool hasOverrideSize() const { return m_hasOverrideSize; } + void setHasOverrideSize(bool b) { m_hasOverrideSize = b; } + + void remove() { if (parent()) parent()->removeChild(this); } + + AnimationController* animation() const; + + bool visibleToHitTesting() const { return style()->visibility() == VISIBLE && style()->pointerEvents() != PE_NONE; } + + // Map points and quads through elements, potentially via 3d transforms. You should never need to call these directly; use + // localToAbsolute/absoluteToLocal methods instead. + virtual void mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool useTransforms, bool fixed, TransformState&) const; + virtual void mapAbsoluteToLocalPoint(bool fixed, bool useTransforms, TransformState&) const; + + bool shouldUseTransformFromContainer(const RenderObject* container) const; + void getTransformFromContainer(const RenderObject* container, const IntSize& offsetInContainer, TransformationMatrix&) const; + + virtual void addFocusRingRects(Vector<IntRect>&, int /*tx*/, int /*ty*/) { }; + + IntRect absoluteOutlineBounds() const + { + return outlineBoundsForRepaint(0); + } + +protected: + // Overrides should call the superclass at the end + virtual void styleWillChange(StyleDifference, const RenderStyle* newStyle); + // Overrides should call the superclass at the start + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + void paintFocusRing(GraphicsContext*, int tx, int ty, RenderStyle*); + void paintOutline(GraphicsContext*, int tx, int ty, int w, int h); + void addPDFURLRect(GraphicsContext*, const IntRect&); + + virtual IntRect viewRect() const; + + void adjustRectForOutlineAndShadow(IntRect&) const; + + void arenaDelete(RenderArena*, void* objectBase); + + virtual IntRect outlineBoundsForRepaint(RenderBoxModelObject* /*repaintContainer*/, IntPoint* /*cachedOffsetToRepaintContainer*/ = 0) const { return IntRect(); } + + class LayoutRepainter { + public: + LayoutRepainter(RenderObject& object, bool checkForRepaint, const IntRect* oldBounds = 0) + : m_object(object) + , m_repaintContainer(0) + , m_checkForRepaint(checkForRepaint) + { + if (m_checkForRepaint) { + m_repaintContainer = m_object.containerForRepaint(); + m_oldBounds = oldBounds ? *oldBounds : m_object.clippedOverflowRectForRepaint(m_repaintContainer); + m_oldOutlineBox = m_object.outlineBoundsForRepaint(m_repaintContainer); + } + } + + // Return true if it repainted. + bool repaintAfterLayout() + { + return m_checkForRepaint ? m_object.repaintAfterLayoutIfNeeded(m_repaintContainer, m_oldBounds, m_oldOutlineBox) : false; + } + + bool checkForRepaint() const { return m_checkForRepaint; } + + private: + RenderObject& m_object; + RenderBoxModelObject* m_repaintContainer; + IntRect m_oldBounds; + IntRect m_oldOutlineBox; + bool m_checkForRepaint; + }; + +private: + RenderStyle* firstLineStyleSlowCase() const; + StyleDifference adjustStyleDifference(StyleDifference, unsigned contextSensitiveProperties) const; + + Color selectionColor(int colorProperty) const; + + RefPtr<RenderStyle> m_style; + + Node* m_node; + + RenderObject* m_parent; + RenderObject* m_previous; + RenderObject* m_next; + +#ifndef NDEBUG + bool m_hasAXObject; + bool m_setNeedsLayoutForbidden : 1; +#endif + + // 32 bits have been used here. THERE ARE NO FREE BITS AVAILABLE. + bool m_needsLayout : 1; + bool m_needsPositionedMovementLayout :1; + bool m_normalChildNeedsLayout : 1; + bool m_posChildNeedsLayout : 1; + bool m_preferredLogicalWidthsDirty : 1; + bool m_floating : 1; + + bool m_positioned : 1; + bool m_relPositioned : 1; + bool m_paintBackground : 1; // if the box has something to paint in the + // background painting phase (background, border, etc) + + bool m_isAnonymous : 1; + bool m_isText : 1; + bool m_isBox : 1; + bool m_inline : 1; + bool m_replaced : 1; + bool m_isDragging : 1; + + bool m_hasLayer : 1; + bool m_hasOverflowClip : 1; // Set in the case of overflow:auto/scroll/hidden + bool m_hasTransform : 1; + bool m_hasReflection : 1; + + bool m_hasOverrideSize : 1; + +public: + bool m_hasCounterNodeMap : 1; + bool m_everHadLayout : 1; + +private: + // These bitfields are moved here from subclasses to pack them together + // from RenderBlock + bool m_childrenInline : 1; + bool m_marginBeforeQuirk : 1; + bool m_marginAfterQuirk : 1; + bool m_hasMarkupTruncation : 1; + unsigned m_selectionState : 3; // SelectionState + bool m_hasColumns : 1; + + // from RenderTableCell + bool m_cellWidthChanged : 1; + +private: + // Store state between styleWillChange and styleDidChange + static bool s_affectsParentBlock; +}; + +inline bool RenderObject::documentBeingDestroyed() const +{ + return !document()->renderer(); +} + +inline bool RenderObject::isBeforeContent() const +{ + if (style()->styleType() != BEFORE) + return false; + // Text nodes don't have their own styles, so ignore the style on a text node. + if (isText() && !isBR()) + return false; + return true; +} + +inline bool RenderObject::isAfterContent() const +{ + if (style()->styleType() != AFTER) + return false; + // Text nodes don't have their own styles, so ignore the style on a text node. + if (isText() && !isBR()) + return false; + return true; +} + +inline void RenderObject::setNeedsLayout(bool b, bool markParents) +{ + bool alreadyNeededLayout = m_needsLayout; + m_needsLayout = b; + if (b) { + ASSERT(!isSetNeedsLayoutForbidden()); + if (!alreadyNeededLayout) { + if (markParents) + markContainingBlocksForLayout(); + if (hasLayer()) + setLayerNeedsFullRepaint(); + } + } else { + m_everHadLayout = true; + m_posChildNeedsLayout = false; + m_normalChildNeedsLayout = false; + m_needsPositionedMovementLayout = false; + } +} + +inline void RenderObject::setChildNeedsLayout(bool b, bool markParents) +{ + bool alreadyNeededLayout = m_normalChildNeedsLayout; + m_normalChildNeedsLayout = b; + if (b) { + ASSERT(!isSetNeedsLayoutForbidden()); + if (!alreadyNeededLayout && markParents) + markContainingBlocksForLayout(); + } else { + m_posChildNeedsLayout = false; + m_normalChildNeedsLayout = false; + m_needsPositionedMovementLayout = false; + } +} + +inline void RenderObject::setNeedsPositionedMovementLayout() +{ + bool alreadyNeededLayout = needsLayout(); + m_needsPositionedMovementLayout = true; + if (!alreadyNeededLayout) { + markContainingBlocksForLayout(); + if (hasLayer()) + setLayerNeedsFullRepaint(); + } +} + +inline bool objectIsRelayoutBoundary(const RenderObject *obj) +{ + // FIXME: In future it may be possible to broaden this condition in order to improve performance. + // Table cells are excluded because even when their CSS height is fixed, their height() + // may depend on their contents. + return obj->isTextControl() + || (obj->hasOverflowClip() && !obj->style()->width().isIntrinsicOrAuto() && !obj->style()->height().isIntrinsicOrAuto() && !obj->style()->height().isPercent() && !obj->isTableCell()) +#if ENABLE(SVG) + || obj->isSVGRoot() +#endif + ; +} + +inline void RenderObject::markContainingBlocksForLayout(bool scheduleRelayout, RenderObject* newRoot) +{ + ASSERT(!scheduleRelayout || !newRoot); + + RenderObject* o = container(); + RenderObject* last = this; + + while (o) { + // Don't mark the outermost object of an unrooted subtree. That object will be + // marked when the subtree is added to the document. + RenderObject* container = o->container(); + if (!container && !o->isRenderView()) + return; + if (!last->isText() && (last->style()->position() == FixedPosition || last->style()->position() == AbsolutePosition)) { + if ((last->style()->top().isAuto() && last->style()->bottom().isAuto()) || last->style()->top().isStatic()) { + RenderObject* parent = last->parent(); + if (!parent->normalChildNeedsLayout()) { + parent->setChildNeedsLayout(true, false); + if (parent != newRoot) + parent->markContainingBlocksForLayout(scheduleRelayout, newRoot); + } + } + if (o->m_posChildNeedsLayout) + return; + o->m_posChildNeedsLayout = true; + ASSERT(!o->isSetNeedsLayoutForbidden()); + } else { + if (o->m_normalChildNeedsLayout) + return; + o->m_normalChildNeedsLayout = true; + ASSERT(!o->isSetNeedsLayoutForbidden()); + } + + if (o == newRoot) + return; + + last = o; + if (scheduleRelayout && objectIsRelayoutBoundary(last)) + break; + o = container; + } + + if (scheduleRelayout) + last->scheduleRelayout(); +} + +inline void makeMatrixRenderable(TransformationMatrix& matrix, bool has3DRendering) +{ +#if !ENABLE(3D_RENDERING) + UNUSED_PARAM(has3DRendering); + matrix.makeAffine(); +#else + if (!has3DRendering) + matrix.makeAffine(); +#endif +} + +inline int adjustForAbsoluteZoom(int value, RenderObject* renderer) +{ + return adjustForAbsoluteZoom(value, renderer->style()); +} + +inline FloatPoint adjustFloatPointForAbsoluteZoom(const FloatPoint& point, RenderObject* renderer) +{ + // The result here is in floats, so we don't need the truncation hack from the integer version above. + float zoomFactor = renderer->style()->effectiveZoom(); + if (zoomFactor == 1) + return point; + return FloatPoint(point.x() / zoomFactor, point.y() / zoomFactor); +} + +inline void adjustFloatQuadForAbsoluteZoom(FloatQuad& quad, RenderObject* renderer) +{ + quad.setP1(adjustFloatPointForAbsoluteZoom(quad.p1(), renderer)); + quad.setP2(adjustFloatPointForAbsoluteZoom(quad.p2(), renderer)); + quad.setP3(adjustFloatPointForAbsoluteZoom(quad.p3(), renderer)); + quad.setP4(adjustFloatPointForAbsoluteZoom(quad.p4(), renderer)); +} + +inline void adjustFloatRectForAbsoluteZoom(FloatRect& rect, RenderObject* renderer) +{ + RenderStyle* style = renderer->style(); + rect.setX(adjustFloatForAbsoluteZoom(rect.x(), style)); + rect.setY(adjustFloatForAbsoluteZoom(rect.y(), style)); + rect.setWidth(adjustFloatForAbsoluteZoom(rect.width(), style)); + rect.setHeight(adjustFloatForAbsoluteZoom(rect.height(), style)); +} + +} // namespace WebCore + +#ifndef NDEBUG +// Outside the WebCore namespace for ease of invocation from gdb. +void showTree(const WebCore::RenderObject*); +void showRenderTree(const WebCore::RenderObject* object1); +// We don't make object2 an optional parameter so that showRenderTree +// can be called from gdb easily. +void showRenderTree(const WebCore::RenderObject* object1, const WebCore::RenderObject* object2); +#endif + +#endif // RenderObject_h diff --git a/Source/WebCore/rendering/RenderObjectChildList.cpp b/Source/WebCore/rendering/RenderObjectChildList.cpp new file mode 100644 index 0000000..c7c8e44 --- /dev/null +++ b/Source/WebCore/rendering/RenderObjectChildList.cpp @@ -0,0 +1,472 @@ +/* + * Copyright (C) 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderObjectChildList.h" + +#include "AXObjectCache.h" +#include "RenderBlock.h" +#include "RenderCounter.h" +#include "RenderImage.h" +#include "RenderImageResourceStyleImage.h" +#include "RenderInline.h" +#include "RenderLayer.h" +#include "RenderListItem.h" +#include "RenderStyle.h" +#include "RenderTextFragment.h" +#include "RenderView.h" + +namespace WebCore { + +void RenderObjectChildList::destroyLeftoverChildren() +{ + while (firstChild()) { + if (firstChild()->isListMarker() || (firstChild()->style()->styleType() == FIRST_LETTER && !firstChild()->isText())) + firstChild()->remove(); // List markers are owned by their enclosing list and so don't get destroyed by this container. Similarly, first letters are destroyed by their remaining text fragment. + else if (firstChild()->isRunIn() && firstChild()->node()) { + firstChild()->node()->setRenderer(0); + firstChild()->node()->setNeedsStyleRecalc(); + firstChild()->destroy(); + } else { + // Destroy any anonymous children remaining in the render tree, as well as implicit (shadow) DOM elements like those used in the engine-based text fields. + if (firstChild()->node()) + firstChild()->node()->setRenderer(0); + firstChild()->destroy(); + } + } +} + +RenderObject* RenderObjectChildList::removeChildNode(RenderObject* owner, RenderObject* oldChild, bool fullRemove) +{ + ASSERT(oldChild->parent() == owner); + + // So that we'll get the appropriate dirty bit set (either that a normal flow child got yanked or + // that a positioned child got yanked). We also repaint, so that the area exposed when the child + // disappears gets repainted properly. + if (!owner->documentBeingDestroyed() && fullRemove && oldChild->m_everHadLayout) { + oldChild->setNeedsLayoutAndPrefWidthsRecalc(); + if (oldChild->isBody()) + owner->view()->repaint(); + else + oldChild->repaint(); + } + + // If we have a line box wrapper, delete it. + if (oldChild->isBox()) + toRenderBox(oldChild)->deleteLineBoxWrapper(); + + if (!owner->documentBeingDestroyed() && fullRemove) { + // if we remove visible child from an invisible parent, we don't know the layer visibility any more + RenderLayer* layer = 0; + if (owner->style()->visibility() != VISIBLE && oldChild->style()->visibility() == VISIBLE && !oldChild->hasLayer()) { + layer = owner->enclosingLayer(); + layer->dirtyVisibleContentStatus(); + } + + // Keep our layer hierarchy updated. + if (oldChild->firstChild() || oldChild->hasLayer()) { + if (!layer) + layer = owner->enclosingLayer(); + oldChild->removeLayers(layer); + } + + if (oldChild->isListItem()) + toRenderListItem(oldChild)->updateListMarkerNumbers(); + + if (oldChild->isPositioned() && owner->childrenInline()) + owner->dirtyLinesFromChangedChild(oldChild); + +#if ENABLE(SVG) + // Update cached boundaries in SVG renderers, if a child is removed. + owner->setNeedsBoundariesUpdate(); +#endif + } + + // If oldChild is the start or end of the selection, then clear the selection to + // avoid problems of invalid pointers. + // FIXME: The SelectionController should be responsible for this when it + // is notified of DOM mutations. + if (!owner->documentBeingDestroyed() && oldChild->isSelectionBorder()) + owner->view()->clearSelection(); + + // remove the child + if (oldChild->previousSibling()) + oldChild->previousSibling()->setNextSibling(oldChild->nextSibling()); + if (oldChild->nextSibling()) + oldChild->nextSibling()->setPreviousSibling(oldChild->previousSibling()); + + if (firstChild() == oldChild) + setFirstChild(oldChild->nextSibling()); + if (lastChild() == oldChild) + setLastChild(oldChild->previousSibling()); + + oldChild->setPreviousSibling(0); + oldChild->setNextSibling(0); + oldChild->setParent(0); + + if (AXObjectCache::accessibilityEnabled()) + owner->document()->axObjectCache()->childrenChanged(owner); + + return oldChild; +} + +void RenderObjectChildList::appendChildNode(RenderObject* owner, RenderObject* newChild, bool fullAppend) +{ + ASSERT(newChild->parent() == 0); + ASSERT(!owner->isBlockFlow() || (!newChild->isTableSection() && !newChild->isTableRow() && !newChild->isTableCell())); + + newChild->setParent(owner); + RenderObject* lChild = lastChild(); + + if (lChild) { + newChild->setPreviousSibling(lChild); + lChild->setNextSibling(newChild); + } else + setFirstChild(newChild); + + setLastChild(newChild); + + if (fullAppend) { + // Keep our layer hierarchy updated. Optimize for the common case where we don't have any children + // and don't have a layer attached to ourselves. + RenderLayer* layer = 0; + if (newChild->firstChild() || newChild->hasLayer()) { + layer = owner->enclosingLayer(); + newChild->addLayers(layer, newChild); + } + + // if the new child is visible but this object was not, tell the layer it has some visible content + // that needs to be drawn and layer visibility optimization can't be used + if (owner->style()->visibility() != VISIBLE && newChild->style()->visibility() == VISIBLE && !newChild->hasLayer()) { + if (!layer) + layer = owner->enclosingLayer(); + if (layer) + layer->setHasVisibleContent(true); + } + + if (newChild->isListItem()) + toRenderListItem(newChild)->updateListMarkerNumbers(); + + if (!newChild->isFloatingOrPositioned() && owner->childrenInline()) + owner->dirtyLinesFromChangedChild(newChild); + } + + newChild->setNeedsLayoutAndPrefWidthsRecalc(); // Goes up the containing block hierarchy. + if (!owner->normalChildNeedsLayout()) + owner->setChildNeedsLayout(true); // We may supply the static position for an absolute positioned child. + + if (AXObjectCache::accessibilityEnabled()) + owner->document()->axObjectCache()->childrenChanged(owner); +} + +void RenderObjectChildList::insertChildNode(RenderObject* owner, RenderObject* child, RenderObject* beforeChild, bool fullInsert) +{ + if (!beforeChild) { + appendChildNode(owner, child, fullInsert); + return; + } + + ASSERT(!child->parent()); + while (beforeChild->parent() != owner && beforeChild->parent()->isAnonymousBlock()) + beforeChild = beforeChild->parent(); + ASSERT(beforeChild->parent() == owner); + + ASSERT(!owner->isBlockFlow() || (!child->isTableSection() && !child->isTableRow() && !child->isTableCell())); + + if (beforeChild == firstChild()) + setFirstChild(child); + + RenderObject* prev = beforeChild->previousSibling(); + child->setNextSibling(beforeChild); + beforeChild->setPreviousSibling(child); + if (prev) + prev->setNextSibling(child); + child->setPreviousSibling(prev); + + child->setParent(owner); + + if (fullInsert) { + // Keep our layer hierarchy updated. Optimize for the common case where we don't have any children + // and don't have a layer attached to ourselves. + RenderLayer* layer = 0; + if (child->firstChild() || child->hasLayer()) { + layer = owner->enclosingLayer(); + child->addLayers(layer, child); + } + + // if the new child is visible but this object was not, tell the layer it has some visible content + // that needs to be drawn and layer visibility optimization can't be used + if (owner->style()->visibility() != VISIBLE && child->style()->visibility() == VISIBLE && !child->hasLayer()) { + if (!layer) + layer = owner->enclosingLayer(); + if (layer) + layer->setHasVisibleContent(true); + } + + if (child->isListItem()) + toRenderListItem(child)->updateListMarkerNumbers(); + + if (!child->isFloating() && owner->childrenInline()) + owner->dirtyLinesFromChangedChild(child); + } + + child->setNeedsLayoutAndPrefWidthsRecalc(); + if (!owner->normalChildNeedsLayout()) + owner->setChildNeedsLayout(true); // We may supply the static position for an absolute positioned child. + + if (AXObjectCache::accessibilityEnabled()) + owner->document()->axObjectCache()->childrenChanged(owner); +} + +static RenderObject* beforeAfterContainer(RenderObject* container, PseudoId type) +{ + if (type == BEFORE) { + // An anonymous (generated) inline run-in that has PseudoId BEFORE must come from a grandparent. + // Therefore we should skip these generated run-ins when checking our immediate children. + // If we don't find our :before child immediately, then we should check if we own a + // generated inline run-in in the next level of children. + RenderObject* first = container; + do { + // Skip list markers and generated run-ins + first = first->firstChild(); + while (first && (first->isListMarker() || (first->isRenderInline() && first->isRunIn() && first->isAnonymous()))) + first = first->nextSibling(); + } while (first && first->isAnonymous() && first->style()->styleType() == NOPSEUDO); + + if (!first) + return 0; + + if (first->style()->styleType() == type) + return first; + + // Check for a possible generated run-in, using run-in positioning rules. + // Skip inlines and floating / positioned blocks, and place as the first child. + first = container->firstChild(); + if (!first->isRenderBlock()) + return 0; + while (first && first->isFloatingOrPositioned()) + first = first->nextSibling(); + if (first) { + first = first->firstChild(); + // We still need to skip any list markers that could exist before the run-in. + while (first && first->isListMarker()) + first = first->nextSibling(); + if (first && first->style()->styleType() == type && first->isRenderInline() && first->isRunIn() && first->isAnonymous()) + return first; + } + return 0; + } + + if (type == AFTER) { + RenderObject* last = container; + do { + last = last->lastChild(); + } while (last && last->isAnonymous() && last->style()->styleType() == NOPSEUDO && !last->isListMarker()); + if (last && last->style()->styleType() != type) + return 0; + return last; + } + + ASSERT_NOT_REACHED(); + return 0; +} + +static RenderObject* findBeforeAfterParent(RenderObject* object) +{ + // Only table parts need to search for the :before or :after parent + if (!(object->isTable() || object->isTableSection() || object->isTableRow())) + return object; + + RenderObject* beforeAfterParent = object; + while (beforeAfterParent && !(beforeAfterParent->isText() || beforeAfterParent->isImage())) + beforeAfterParent = beforeAfterParent->firstChild(); + return beforeAfterParent; +} + +static void invalidateCountersInContainer(RenderObject* container, const AtomicString& identifier) +{ + if (!container) + return; + container = findBeforeAfterParent(container); + if (!container) + return; + // Sometimes the counter is attached directly on the container. + if (container->isCounter()) { + toRenderCounter(container)->invalidate(identifier); + return; + } + for (RenderObject* content = container->firstChild(); content; content = content->nextSibling()) { + if (content->isCounter()) + toRenderCounter(content)->invalidate(identifier); + } +} + +void RenderObjectChildList::invalidateCounters(RenderObject* owner, const AtomicString& identifier) +{ + ASSERT(!owner->documentBeingDestroyed()); + invalidateCountersInContainer(beforeAfterContainer(owner, BEFORE), identifier); + invalidateCountersInContainer(beforeAfterContainer(owner, AFTER), identifier); +} + +void RenderObjectChildList::updateBeforeAfterContent(RenderObject* owner, PseudoId type, RenderObject* styledObject) +{ + // Double check that the document did in fact use generated content rules. Otherwise we should not have been called. + ASSERT(owner->document()->usesBeforeAfterRules()); + + // In CSS2, before/after pseudo-content cannot nest. Check this first. + if (owner->style()->styleType() == BEFORE || owner->style()->styleType() == AFTER) + return; + + if (!styledObject) + styledObject = owner; + + RenderStyle* pseudoElementStyle = styledObject->getCachedPseudoStyle(type); + RenderObject* child = beforeAfterContainer(owner, type); + + // Whether or not we currently have generated content attached. + bool oldContentPresent = child; + + // Whether or not we now want generated content. + bool newContentWanted = pseudoElementStyle && pseudoElementStyle->display() != NONE; + + // For <q><p/></q>, if this object is the inline continuation of the <q>, we only want to generate + // :after content and not :before content. + if (newContentWanted && type == BEFORE && owner->isElementContinuation()) + newContentWanted = false; + + // Similarly, if we're the beginning of a <q>, and there's an inline continuation for our object, + // then we don't generate the :after content. + if (newContentWanted && type == AFTER && owner->virtualContinuation()) + newContentWanted = false; + + // If we don't want generated content any longer, or if we have generated content, but it's no longer + // identical to the new content data we want to build render objects for, then we nuke all + // of the old generated content. + if (oldContentPresent && (!newContentWanted || Node::diff(child->style(), pseudoElementStyle) == Node::Detach)) { + // Nuke the child. + if (child->style()->styleType() == type) { + oldContentPresent = false; + child->destroy(); + child = (type == BEFORE) ? owner->virtualChildren()->firstChild() : owner->virtualChildren()->lastChild(); + } + } + + // If we have no pseudo-element style or if the pseudo-element style's display type is NONE, then we + // have no generated content and can now return. + if (!newContentWanted) + return; + + if (owner->isRenderInline() && !pseudoElementStyle->isDisplayInlineType() && pseudoElementStyle->floating() == FNONE && + !(pseudoElementStyle->position() == AbsolutePosition || pseudoElementStyle->position() == FixedPosition)) + // According to the CSS2 spec (the end of section 12.1), the only allowed + // display values for the pseudo style are NONE and INLINE for inline flows. + // FIXME: CSS2.1 lifted this restriction, but block display types will crash. + // For now we at least relax the restriction to allow all inline types like inline-block + // and inline-table. + pseudoElementStyle->setDisplay(INLINE); + + if (oldContentPresent) { + if (child && child->style()->styleType() == type) { + // We have generated content present still. We want to walk this content and update our + // style information with the new pseudo-element style. + child->setStyle(pseudoElementStyle); + + RenderObject* beforeAfterParent = findBeforeAfterParent(child); + if (!beforeAfterParent) + return; + + // Note that if we ever support additional types of generated content (which should be way off + // in the future), this code will need to be patched. + for (RenderObject* genChild = beforeAfterParent->firstChild(); genChild; genChild = genChild->nextSibling()) { + if (genChild->isText()) + // Generated text content is a child whose style also needs to be set to the pseudo-element style. + genChild->setStyle(pseudoElementStyle); + else if (genChild->isImage()) { + // Images get an empty style that inherits from the pseudo. + RefPtr<RenderStyle> style = RenderStyle::create(); + style->inheritFrom(pseudoElementStyle); + genChild->setStyle(style.release()); + } else { + // RenderListItem may insert a list marker here. We do not need to care about this case. + // Otherwise, genChild must be a first-letter container. updateFirstLetter() will take care of it. + ASSERT(genChild->isListMarker() || genChild->style()->styleType() == FIRST_LETTER); + } + } + } + return; // We've updated the generated content. That's all we needed to do. + } + + RenderObject* insertBefore = (type == BEFORE) ? owner->virtualChildren()->firstChild() : 0; + + // Generated content consists of a single container that houses multiple children (specified + // by the content property). This generated content container gets the pseudo-element style set on it. + RenderObject* generatedContentContainer = 0; + + // Walk our list of generated content and create render objects for each. + for (const ContentData* content = pseudoElementStyle->contentData(); content; content = content->next()) { + RenderObject* renderer = 0; + switch (content->type()) { + case CONTENT_NONE: + break; + case CONTENT_TEXT: + renderer = new (owner->renderArena()) RenderTextFragment(owner->document() /* anonymous object */, content->text()); + renderer->setStyle(pseudoElementStyle); + break; + case CONTENT_OBJECT: { + RenderImage* image = new (owner->renderArena()) RenderImage(owner->document()); // anonymous object + RefPtr<RenderStyle> style = RenderStyle::create(); + style->inheritFrom(pseudoElementStyle); + image->setStyle(style.release()); + if (StyleImage* styleImage = content->image()) + image->setImageResource(RenderImageResourceStyleImage::create(styleImage)); + else + image->setImageResource(RenderImageResource::create()); + renderer = image; + break; + } + case CONTENT_COUNTER: + renderer = new (owner->renderArena()) RenderCounter(owner->document(), *content->counter()); + renderer->setStyle(pseudoElementStyle); + break; + } + + if (renderer) { + if (!generatedContentContainer) { + // Make a generated box that might be any display type now that we are able to drill down into children + // to find the original content properly. + generatedContentContainer = RenderObject::createObject(owner->document(), pseudoElementStyle); + generatedContentContainer->setStyle(pseudoElementStyle); + owner->addChild(generatedContentContainer, insertBefore); + } + if (generatedContentContainer->isChildAllowed(renderer, pseudoElementStyle)) + generatedContentContainer->addChild(renderer); + else + renderer->destroy(); + } + } +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderObjectChildList.h b/Source/WebCore/rendering/RenderObjectChildList.h new file mode 100644 index 0000000..8b80f37 --- /dev/null +++ b/Source/WebCore/rendering/RenderObjectChildList.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderObjectChildList_h +#define RenderObjectChildList_h + +#include "RenderStyleConstants.h" +#include <wtf/Forward.h> + +namespace WebCore { + +class RenderObject; + +class RenderObjectChildList { +public: + RenderObjectChildList() + : m_firstChild(0) + , m_lastChild(0) + { + } + + RenderObject* firstChild() const { return m_firstChild; } + RenderObject* lastChild() const { return m_lastChild; } + + // FIXME: Temporary while RenderBox still exists. Eventually this will just happen during insert/append/remove methods on the child list, and nobody + // will need to manipulate firstChild or lastChild directly. + void setFirstChild(RenderObject* child) { m_firstChild = child; } + void setLastChild(RenderObject* child) { m_lastChild = child; } + + void destroyLeftoverChildren(); + + RenderObject* removeChildNode(RenderObject* owner, RenderObject*, bool fullRemove = true); + void appendChildNode(RenderObject* owner, RenderObject*, bool fullAppend = true); + void insertChildNode(RenderObject* owner, RenderObject* child, RenderObject* before, bool fullInsert = true); + + void updateBeforeAfterContent(RenderObject* owner, PseudoId type, RenderObject* styledObject = 0); + void invalidateCounters(RenderObject* owner, const AtomicString& identifier); + +private: + RenderObject* m_firstChild; + RenderObject* m_lastChild; +}; + +} // namespace WebCore + +#endif // RenderObjectChildList_h diff --git a/Source/WebCore/rendering/RenderOverflow.h b/Source/WebCore/rendering/RenderOverflow.h new file mode 100644 index 0000000..7dc2bcb --- /dev/null +++ b/Source/WebCore/rendering/RenderOverflow.h @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderOverflow_h +#define RenderOverflow_h + +#include "IntRect.h" + +namespace WebCore +{ +// RenderOverflow is a class for tracking content that spills out of a box. This class is used by RenderBox and +// InlineFlowBox. +// +// There are two types of overflow: layout overflow (which is expected to be reachable via scrolling mechanisms) and +// visual overflow (which is not expected to be reachable via scrolling mechanisms). +// +// Layout overflow examples include other boxes that spill out of our box, For example, in the inline case a tall image +// could spill out of a line box. + +// Examples of visual overflow are shadows, text stroke (and eventually outline and border-image). + +// This object is allocated only when some of these fields have non-default values in the owning box. +class RenderOverflow : public Noncopyable { +public: + RenderOverflow(const IntRect& layoutRect, const IntRect& visualRect) + : m_topLayoutOverflow(layoutRect.y()) + , m_bottomLayoutOverflow(layoutRect.bottom()) + , m_leftLayoutOverflow(layoutRect.x()) + , m_rightLayoutOverflow(layoutRect.right()) + , m_topVisualOverflow(visualRect.y()) + , m_bottomVisualOverflow(visualRect.bottom()) + , m_leftVisualOverflow(visualRect.x()) + , m_rightVisualOverflow(visualRect.right()) + { + } + + int topLayoutOverflow() const { return m_topLayoutOverflow; } + int bottomLayoutOverflow() const { return m_bottomLayoutOverflow; } + int leftLayoutOverflow() const { return m_leftLayoutOverflow; } + int rightLayoutOverflow() const { return m_rightLayoutOverflow; } + IntRect layoutOverflowRect() const; + + int topVisualOverflow() const { return m_topVisualOverflow; } + int bottomVisualOverflow() const { return m_bottomVisualOverflow; } + int leftVisualOverflow() const { return m_leftVisualOverflow; } + int rightVisualOverflow() const { return m_rightVisualOverflow; } + IntRect visualOverflowRect() const; + + void setTopLayoutOverflow(int overflow) { m_topLayoutOverflow = overflow; } + void setBottomLayoutOverflow(int overflow) { m_bottomLayoutOverflow = overflow; } + void setLeftLayoutOverflow(int overflow) { m_leftLayoutOverflow = overflow; } + void setRightLayoutOverflow(int overflow) { m_rightLayoutOverflow = overflow; } + + void setTopVisualOverflow(int overflow) { m_topVisualOverflow = overflow; } + void setBottomVisualOverflow(int overflow) { m_bottomVisualOverflow = overflow; } + void setLeftVisualOverflow(int overflow) { m_leftVisualOverflow = overflow; } + void setRightVisualOverflow(int overflow) { m_rightVisualOverflow = overflow; } + + void move(int dx, int dy); + + void addLayoutOverflow(const IntRect&); + void addVisualOverflow(const IntRect&); + + void setLayoutOverflow(const IntRect&); + void setVisualOverflow(const IntRect&); + + void resetLayoutOverflow(const IntRect& defaultRect); + +private: + int m_topLayoutOverflow; + int m_bottomLayoutOverflow; + int m_leftLayoutOverflow; + int m_rightLayoutOverflow; + + int m_topVisualOverflow; + int m_bottomVisualOverflow; + int m_leftVisualOverflow; + int m_rightVisualOverflow; +}; + +inline IntRect RenderOverflow::layoutOverflowRect() const +{ + return IntRect(m_leftLayoutOverflow, m_topLayoutOverflow, m_rightLayoutOverflow - m_leftLayoutOverflow, m_bottomLayoutOverflow - m_topLayoutOverflow); +} + +inline IntRect RenderOverflow::visualOverflowRect() const +{ + return IntRect(m_leftVisualOverflow, m_topVisualOverflow, m_rightVisualOverflow - m_leftVisualOverflow, m_bottomVisualOverflow - m_topVisualOverflow); +} + +inline void RenderOverflow::move(int dx, int dy) +{ + m_topLayoutOverflow += dy; + m_bottomLayoutOverflow += dy; + m_leftLayoutOverflow += dx; + m_rightLayoutOverflow += dx; + + m_topVisualOverflow += dy; + m_bottomVisualOverflow += dy; + m_leftVisualOverflow += dx; + m_rightVisualOverflow += dx; +} + +inline void RenderOverflow::addLayoutOverflow(const IntRect& rect) +{ + m_topLayoutOverflow = std::min(rect.y(), m_topLayoutOverflow); + m_bottomLayoutOverflow = std::max(rect.bottom(), m_bottomLayoutOverflow); + m_leftLayoutOverflow = std::min(rect.x(), m_leftLayoutOverflow); + m_rightLayoutOverflow = std::max(rect.right(), m_rightLayoutOverflow); +} + +inline void RenderOverflow::addVisualOverflow(const IntRect& rect) +{ + m_topVisualOverflow = std::min(rect.y(), m_topVisualOverflow); + m_bottomVisualOverflow = std::max(rect.bottom(), m_bottomVisualOverflow); + m_leftVisualOverflow = std::min(rect.x(), m_leftVisualOverflow); + m_rightVisualOverflow = std::max(rect.right(), m_rightVisualOverflow); +} + +inline void RenderOverflow::setLayoutOverflow(const IntRect& rect) +{ + m_topLayoutOverflow = rect.y(); + m_bottomLayoutOverflow = rect.bottom(); + m_leftLayoutOverflow = rect.x(); + m_rightLayoutOverflow = rect.right(); +} + +inline void RenderOverflow::setVisualOverflow(const IntRect& rect) +{ + m_topVisualOverflow = rect.y(); + m_bottomVisualOverflow = rect.bottom(); + m_leftVisualOverflow = rect.x(); + m_rightVisualOverflow = rect.right(); +} + +inline void RenderOverflow::resetLayoutOverflow(const IntRect& rect) +{ + m_topLayoutOverflow = rect.y(); + m_bottomLayoutOverflow = rect.bottom(); + m_leftLayoutOverflow = rect.x(); + m_rightLayoutOverflow = rect.right(); +} + +} // namespace WebCore + +#endif // RenderOverflow_h diff --git a/Source/WebCore/rendering/RenderPart.cpp b/Source/WebCore/rendering/RenderPart.cpp new file mode 100644 index 0000000..3262961 --- /dev/null +++ b/Source/WebCore/rendering/RenderPart.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Simon Hausmann <hausmann@kde.org> + * (C) 2000 Stefan Schimanski (1Stein@gmx.de) + * Copyright (C) 2004, 2005, 2006, 2009 Apple Inc. All rights reserved. + * + * 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 "RenderPart.h" + +#include "RenderView.h" +#include "Frame.h" +#include "FrameView.h" +#include "HTMLFrameElementBase.h" + +namespace WebCore { + +RenderPart::RenderPart(Element* node) + : RenderWidget(node) +{ + setInline(false); +} + +RenderPart::~RenderPart() +{ + clearWidget(); +} + +void RenderPart::setWidget(PassRefPtr<Widget> widget) +{ + if (widget == this->widget()) + return; + + RenderWidget::setWidget(widget); + + // make sure the scrollbars are set correctly for restore + // ### find better fix + viewCleared(); +} + +void RenderPart::viewCleared() +{ +} + +} diff --git a/Source/WebCore/rendering/RenderPart.h b/Source/WebCore/rendering/RenderPart.h new file mode 100644 index 0000000..18f2346 --- /dev/null +++ b/Source/WebCore/rendering/RenderPart.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Simon Hausmann <hausmann@kde.org> + * Copyright (C) 2006, 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderPart_h +#define RenderPart_h + +#include "RenderWidget.h" + +namespace WebCore { + +// Renderer for frames via RenderFrameBase, and plug-ins via RenderEmbeddedObject. +class RenderPart : public RenderWidget { +public: + RenderPart(Element*); + virtual ~RenderPart(); + + virtual void setWidget(PassRefPtr<Widget>); + virtual void viewCleared(); + +private: + virtual bool isRenderPart() const { return true; } + virtual const char* renderName() const { return "RenderPart"; } +}; + +inline RenderPart* toRenderPart(RenderObject* object) +{ + ASSERT(!object || object->isRenderPart()); + return static_cast<RenderPart*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderPart(const RenderPart*); + +} + +#endif diff --git a/Source/WebCore/rendering/RenderProgress.cpp b/Source/WebCore/rendering/RenderProgress.cpp new file mode 100644 index 0000000..84de6fb --- /dev/null +++ b/Source/WebCore/rendering/RenderProgress.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * + * 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" + +#if ENABLE(PROGRESS_TAG) + +#include "RenderProgress.h" + +#include "HTMLNames.h" +#include "HTMLProgressElement.h" +#include "RenderTheme.h" +#include "ShadowElement.h" +#include <wtf/CurrentTime.h> +#include <wtf/RefPtr.h> + +using namespace std; + +namespace WebCore { + +RenderProgress::RenderProgress(HTMLProgressElement* element) + : RenderIndicator(element) + , m_position(-1) + , m_animationStartTime(0) + , m_animationRepeatInterval(0) + , m_animationDuration(0) + , m_animating(false) + , m_animationTimer(this, &RenderProgress::animationTimerFired) +{ +} + +RenderProgress::~RenderProgress() +{ + if (m_valuePart) + m_valuePart->detach(); +} + +void RenderProgress::updateFromElement() +{ + if (!m_valuePart) { + m_valuePart = ShadowBlockElement::createForPart(static_cast<HTMLElement*>(node()), PROGRESS_BAR_VALUE); + if (m_valuePart->renderer()) + addChild(m_valuePart->renderer()); + } + + if (shouldHaveParts()) + style()->setAppearance(NoControlPart); + else if (m_valuePart->renderer()) + m_valuePart->renderer()->style()->setVisibility(HIDDEN); + + HTMLProgressElement* element = progressElement(); + if (m_position == element->position()) + return; + m_position = element->position(); + + updateAnimationState(); + RenderIndicator::updateFromElement(); +} + +double RenderProgress::animationProgress() const +{ + return m_animating ? (fmod((currentTime() - m_animationStartTime), m_animationDuration) / m_animationDuration) : 0; +} + +bool RenderProgress::isDeterminate() const +{ + return 0 <= position(); +} + +void RenderProgress::animationTimerFired(Timer<RenderProgress>*) +{ + repaint(); +} + +void RenderProgress::paint(PaintInfo& paintInfo, int tx, int ty) +{ + if (paintInfo.phase == PaintPhaseBlockBackground) { + if (!m_animationTimer.isActive() && m_animating) + m_animationTimer.startOneShot(m_animationRepeatInterval); + } + + RenderIndicator::paint(paintInfo, tx, ty); +} + +void RenderProgress::layoutParts() +{ + m_valuePart->layoutAsPart(valuePartRect()); + updateAnimationState(); +} + +bool RenderProgress::shouldHaveParts() const +{ + if (!style()->hasAppearance()) + return true; + if (ShadowBlockElement::partShouldHaveStyle(this, PROGRESS_BAR_VALUE)) + return true; + return false; +} + +void RenderProgress::updateAnimationState() +{ + m_animationDuration = theme()->animationDurationForProgressBar(this); + m_animationRepeatInterval = theme()->animationRepeatIntervalForProgressBar(this); + + bool animating = style()->hasAppearance() && m_animationDuration > 0; + if (animating == m_animating) + return; + + m_animating = animating; + if (m_animating) { + m_animationStartTime = currentTime(); + m_animationTimer.startOneShot(m_animationRepeatInterval); + } else + m_animationTimer.stop(); +} + +IntRect RenderProgress::valuePartRect() const +{ + IntRect rect(borderLeft() + paddingLeft(), borderTop() + paddingTop(), lround((width() - borderLeft() - paddingLeft() - borderRight() - paddingRight()) * position()), height() - borderTop() - paddingTop() - borderBottom() - paddingBottom()); + if (!style()->isLeftToRightDirection()) + rect.setX(width() - borderRight() - paddingRight() - rect.width()); + return rect; +} + +HTMLProgressElement* RenderProgress::progressElement() const +{ + return static_cast<HTMLProgressElement*>(node()); +} + +} // namespace WebCore + +#endif diff --git a/Source/WebCore/rendering/RenderProgress.h b/Source/WebCore/rendering/RenderProgress.h new file mode 100644 index 0000000..9ed5741 --- /dev/null +++ b/Source/WebCore/rendering/RenderProgress.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + * + * 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. + * + */ + +#ifndef RenderProgress_h +#define RenderProgress_h + +#if ENABLE(PROGRESS_TAG) +#include "RenderBlock.h" +#include "RenderIndicator.h" + +namespace WebCore { + +class HTMLProgressElement; +class ShadowBlockElement; + +class RenderProgress : public RenderIndicator { +public: + RenderProgress(HTMLProgressElement*); + virtual ~RenderProgress(); + + double position() const { return m_position; } + double animationProgress() const; + double animationStartTime() const { return m_animationStartTime; } + + bool isDeterminate() const; + + HTMLProgressElement* progressElement() const; + +private: + virtual const char* renderName() const { return "RenderProgress"; } + virtual bool isProgress() const { return true; } + virtual void updateFromElement(); + virtual void paint(PaintInfo&, int tx, int ty); + + virtual void layoutParts(); + + IntRect valuePartRect() const; + bool shouldHaveParts() const; + + void animationTimerFired(Timer<RenderProgress>*); + void updateAnimationState(); + + double m_position; + double m_animationStartTime; + double m_animationRepeatInterval; + double m_animationDuration; + bool m_animating; + Timer<RenderProgress> m_animationTimer; + RefPtr<ShadowBlockElement> m_valuePart; +}; + +inline RenderProgress* toRenderProgress(RenderObject* object) +{ + ASSERT(!object || object->isProgress()); + return static_cast<RenderProgress*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderProgress(const RenderProgress*); + +} // namespace WebCore + +#endif + +#endif // RenderProgress_h + diff --git a/Source/WebCore/rendering/RenderReplaced.cpp b/Source/WebCore/rendering/RenderReplaced.cpp new file mode 100644 index 0000000..974a8d0 --- /dev/null +++ b/Source/WebCore/rendering/RenderReplaced.cpp @@ -0,0 +1,412 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * Copyright (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved. + * + * 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 "RenderReplaced.h" + +#include "GraphicsContext.h" +#include "RenderBlock.h" +#include "RenderLayer.h" +#include "RenderTheme.h" +#include "RenderView.h" +#include "VisiblePosition.h" + +using namespace std; + +namespace WebCore { + +const int cDefaultWidth = 300; +const int cDefaultHeight = 150; + +RenderReplaced::RenderReplaced(Node* node) + : RenderBox(node) + , m_intrinsicSize(cDefaultWidth, cDefaultHeight) + , m_hasIntrinsicSize(false) +{ + setReplaced(true); +} + +RenderReplaced::RenderReplaced(Node* node, const IntSize& intrinsicSize) + : RenderBox(node) + , m_intrinsicSize(intrinsicSize) + , m_hasIntrinsicSize(true) +{ + setReplaced(true); +} + +RenderReplaced::~RenderReplaced() +{ +} + +void RenderReplaced::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBox::styleDidChange(diff, oldStyle); + + bool hadStyle = (oldStyle != 0); + float oldZoom = hadStyle ? oldStyle->effectiveZoom() : RenderStyle::initialZoom(); + if (style() && style()->effectiveZoom() != oldZoom) + intrinsicSizeChanged(); +} + +void RenderReplaced::layout() +{ + ASSERT(needsLayout()); + + LayoutRepainter repainter(*this, checkForRepaintDuringLayout()); + + setHeight(minimumReplacedHeight()); + + computeLogicalWidth(); + computeLogicalHeight(); + + m_overflow.clear(); + addShadowOverflow(); + updateLayerTransform(); + + repainter.repaintAfterLayout(); + setNeedsLayout(false); +} + +void RenderReplaced::intrinsicSizeChanged() +{ + int scaledWidth = static_cast<int>(cDefaultWidth * style()->effectiveZoom()); + int scaledHeight = static_cast<int>(cDefaultHeight * style()->effectiveZoom()); + m_intrinsicSize = IntSize(scaledWidth, scaledHeight); + setNeedsLayoutAndPrefWidthsRecalc(); +} + +void RenderReplaced::paint(PaintInfo& paintInfo, int tx, int ty) +{ + if (!shouldPaint(paintInfo, tx, ty)) + return; + + tx += x(); + ty += y(); + + if (hasBoxDecorations() && (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection)) + paintBoxDecorations(paintInfo, tx, ty); + + if (paintInfo.phase == PaintPhaseMask) { + paintMask(paintInfo, tx, ty); + return; + } + + if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth()) + paintOutline(paintInfo.context, tx, ty, width(), height()); + + if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection) + return; + + if (!paintInfo.shouldPaintWithinRoot(this)) + return; + + bool drawSelectionTint = selectionState() != SelectionNone && !document()->printing(); + if (paintInfo.phase == PaintPhaseSelection) { + if (selectionState() == SelectionNone) + return; + drawSelectionTint = false; + } + + bool completelyClippedOut = false; + if (style()->hasBorderRadius()) { + IntRect borderRect = IntRect(tx, ty, width(), height()); + + if (borderRect.isEmpty()) + completelyClippedOut = true; + else { + // Push a clip if we have a border radius, since we want to round the foreground content that gets painted. + paintInfo.context->save(); + + IntSize topLeft, topRight, bottomLeft, bottomRight; + style()->getBorderRadiiForRect(borderRect, topLeft, topRight, bottomLeft, bottomRight); + + paintInfo.context->addRoundedRectClip(borderRect, topLeft, topRight, bottomLeft, bottomRight); + } + } + + if (!completelyClippedOut) { + paintReplaced(paintInfo, tx, ty); + + if (style()->hasBorderRadius()) + paintInfo.context->restore(); + } + + // The selection tint never gets clipped by border-radius rounding, since we want it to run right up to the edges of + // surrounding content. + if (drawSelectionTint) { + IntRect selectionPaintingRect = localSelectionRect(); + selectionPaintingRect.move(tx, ty); + paintInfo.context->fillRect(selectionPaintingRect, selectionBackgroundColor(), style()->colorSpace()); + } +} + +bool RenderReplaced::shouldPaint(PaintInfo& paintInfo, int& tx, int& ty) +{ + if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseOutline && paintInfo.phase != PaintPhaseSelfOutline + && paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseMask) + return false; + + if (!paintInfo.shouldPaintWithinRoot(this)) + return false; + + // if we're invisible or haven't received a layout yet, then just bail. + if (style()->visibility() != VISIBLE) + return false; + + int currentTX = tx + x(); + int currentTY = ty + y(); + + // Early exit if the element touches the edges. + int top = currentTY + topVisualOverflow(); + int bottom = currentTY + bottomVisualOverflow(); + if (isSelected() && m_inlineBoxWrapper) { + int selTop = ty + m_inlineBoxWrapper->root()->selectionTop(); + int selBottom = ty + selTop + m_inlineBoxWrapper->root()->selectionHeight(); + top = min(selTop, top); + bottom = max(selBottom, bottom); + } + + int os = 2 * maximalOutlineSize(paintInfo.phase); + if (currentTX + leftVisualOverflow() >= paintInfo.rect.right() + os || currentTX + rightVisualOverflow() <= paintInfo.rect.x() - os) + return false; + if (top >= paintInfo.rect.bottom() + os || bottom <= paintInfo.rect.y() - os) + return false; + + return true; +} + +static inline bool lengthIsSpecified(Length length) +{ + LengthType lengthType = length.type(); + return lengthType == Fixed || lengthType == Percent; +} + +int RenderReplaced::computeReplacedLogicalWidth(bool includeMaxWidth) const +{ + int logicalWidth; + if (lengthIsSpecified(style()->width())) + logicalWidth = computeReplacedLogicalWidthUsing(style()->logicalWidth()); + else if (m_hasIntrinsicSize) + logicalWidth = calcAspectRatioLogicalWidth(); + else + logicalWidth = intrinsicLogicalWidth(); + + int minLogicalWidth = computeReplacedLogicalWidthUsing(style()->logicalMinWidth()); + int maxLogicalWidth = !includeMaxWidth || style()->logicalMaxWidth().isUndefined() ? logicalWidth : computeReplacedLogicalWidthUsing(style()->logicalMaxWidth()); + + return max(minLogicalWidth, min(logicalWidth, maxLogicalWidth)); +} + +int RenderReplaced::computeReplacedLogicalHeight() const +{ + int logicalHeight; + if (lengthIsSpecified(style()->logicalHeight())) + logicalHeight = computeReplacedLogicalHeightUsing(style()->logicalHeight()); + else if (m_hasIntrinsicSize) + logicalHeight = calcAspectRatioLogicalHeight(); + else + logicalHeight = intrinsicLogicalHeight(); + + int minLogicalHeight = computeReplacedLogicalHeightUsing(style()->logicalMinHeight()); + int maxLogicalHeight = style()->logicalMaxHeight().isUndefined() ? logicalHeight : computeReplacedLogicalHeightUsing(style()->logicalMaxHeight()); + + return max(minLogicalHeight, min(logicalHeight, maxLogicalHeight)); +} + +int RenderReplaced::calcAspectRatioLogicalWidth() const +{ + int intrinsicWidth = intrinsicLogicalWidth(); + int intrinsicHeight = intrinsicLogicalHeight(); + if (!intrinsicHeight) + return 0; + return RenderBox::computeReplacedLogicalHeight() * intrinsicWidth / intrinsicHeight; +} + +int RenderReplaced::calcAspectRatioLogicalHeight() const +{ + int intrinsicWidth = intrinsicLogicalWidth(); + int intrinsicHeight = intrinsicLogicalHeight(); + if (!intrinsicWidth) + return 0; + return RenderBox::computeReplacedLogicalWidth() * intrinsicHeight / intrinsicWidth; +} + +void RenderReplaced::computePreferredLogicalWidths() +{ + ASSERT(preferredLogicalWidthsDirty()); + + int borderAndPadding = borderAndPaddingWidth(); + m_maxPreferredLogicalWidth = computeReplacedLogicalWidth(false) + borderAndPadding; + + if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) + m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, style()->maxWidth().value() + (style()->boxSizing() == CONTENT_BOX ? borderAndPadding : 0)); + + if (style()->width().isPercent() || style()->height().isPercent() + || style()->maxWidth().isPercent() || style()->maxHeight().isPercent() + || style()->minWidth().isPercent() || style()->minHeight().isPercent()) + m_minPreferredLogicalWidth = 0; + else + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth; + + setPreferredLogicalWidthsDirty(false); +} + +unsigned RenderReplaced::caretMaxRenderedOffset() const +{ + return 1; +} + +VisiblePosition RenderReplaced::positionForPoint(const IntPoint& point) +{ + InlineBox* box = inlineBoxWrapper(); + if (!box) + return createVisiblePosition(0, DOWNSTREAM); + + // FIXME: This code is buggy if the replaced element is relative positioned. + + RootInlineBox* root = box->root(); + + int top = root->selectionTop(); + int bottom = root->selectionBottom(); + + int blockDirectionPosition = box->isHorizontal() ? point.y() + y() : point.x() + x(); + int lineDirectionPosition = box->isHorizontal() ? point.x() + x() : point.y() + y(); + + if (blockDirectionPosition < top) + return createVisiblePosition(caretMinOffset(), DOWNSTREAM); // coordinates are above + + if (blockDirectionPosition >= bottom) + return createVisiblePosition(caretMaxOffset(), DOWNSTREAM); // coordinates are below + + if (node()) { + if (lineDirectionPosition <= box->logicalLeft() + (box->logicalWidth() / 2)) + return createVisiblePosition(0, DOWNSTREAM); + return createVisiblePosition(1, DOWNSTREAM); + } + + return RenderBox::positionForPoint(point); +} + +IntRect RenderReplaced::selectionRectForRepaint(RenderBoxModelObject* repaintContainer, bool clipToVisibleContent) +{ + ASSERT(!needsLayout()); + + if (!isSelected()) + return IntRect(); + + IntRect rect = localSelectionRect(); + if (clipToVisibleContent) + computeRectForRepaint(repaintContainer, rect); + else + rect = localToContainerQuad(FloatRect(rect), repaintContainer).enclosingBoundingBox(); + + return rect; +} + +IntRect RenderReplaced::localSelectionRect(bool checkWhetherSelected) const +{ + if (checkWhetherSelected && !isSelected()) + return IntRect(); + + if (!m_inlineBoxWrapper) + // We're a block-level replaced element. Just return our own dimensions. + return IntRect(0, 0, width(), height()); + + RootInlineBox* root = m_inlineBoxWrapper->root(); + int newLogicalTop = root->block()->style()->isFlippedBlocksWritingMode() ? m_inlineBoxWrapper->logicalBottom() - root->selectionBottom() : root->selectionTop() - m_inlineBoxWrapper->logicalTop(); + if (root->block()->style()->isHorizontalWritingMode()) + return IntRect(0, newLogicalTop, width(), root->selectionHeight()); + return IntRect(newLogicalTop, 0, root->selectionHeight(), height()); +} + +void RenderReplaced::setSelectionState(SelectionState s) +{ + RenderBox::setSelectionState(s); // The selection state for our containing block hierarchy is updated by the base class call. + if (m_inlineBoxWrapper) { + RootInlineBox* line = m_inlineBoxWrapper->root(); + if (line) + line->setHasSelectedChildren(isSelected()); + } +} + +bool RenderReplaced::isSelected() const +{ + SelectionState s = selectionState(); + if (s == SelectionNone) + return false; + if (s == SelectionInside) + return true; + + int selectionStart, selectionEnd; + selectionStartEnd(selectionStart, selectionEnd); + if (s == SelectionStart) + return selectionStart == 0; + + int end = node()->hasChildNodes() ? node()->childNodeCount() : 1; + if (s == SelectionEnd) + return selectionEnd == end; + if (s == SelectionBoth) + return selectionStart == 0 && selectionEnd == end; + + ASSERT(0); + return false; +} + +IntSize RenderReplaced::intrinsicSize() const +{ + return m_intrinsicSize; +} + +void RenderReplaced::setIntrinsicSize(const IntSize& size) +{ + ASSERT(m_hasIntrinsicSize); + m_intrinsicSize = size; +} + +IntRect RenderReplaced::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) +{ + if (style()->visibility() != VISIBLE && !enclosingLayer()->hasVisibleContent()) + return IntRect(); + + // The selectionRect can project outside of the overflowRect, so take their union + // for repainting to avoid selection painting glitches. + IntRect r = unionRect(localSelectionRect(false), visualOverflowRect()); + + RenderView* v = view(); + if (v) { + // FIXME: layoutDelta needs to be applied in parts before/after transforms and + // repaint containers. https://bugs.webkit.org/show_bug.cgi?id=23308 + r.move(v->layoutDelta()); + } + + if (style()) { + if (style()->hasAppearance()) + // The theme may wish to inflate the rect used when repainting. + theme()->adjustRepaintRect(this, r); + if (v) + r.inflate(style()->outlineSize()); + } + computeRectForRepaint(repaintContainer, r); + return r; +} + +} diff --git a/Source/WebCore/rendering/RenderReplaced.h b/Source/WebCore/rendering/RenderReplaced.h new file mode 100644 index 0000000..d6ebba6 --- /dev/null +++ b/Source/WebCore/rendering/RenderReplaced.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderReplaced_h +#define RenderReplaced_h + +#include "RenderBox.h" + +namespace WebCore { + +class RenderReplaced : public RenderBox { +public: + RenderReplaced(Node*); + RenderReplaced(Node*, const IntSize& intrinsicSize); + virtual ~RenderReplaced(); + +protected: + virtual void layout(); + + virtual IntSize intrinsicSize() const; + + virtual int computeReplacedLogicalWidth(bool includeMaxWidth = true) const; + virtual int computeReplacedLogicalHeight() const; + virtual int minimumReplacedHeight() const { return 0; } + + virtual void setSelectionState(SelectionState); + + bool isSelected() const; + + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + void setIntrinsicSize(const IntSize&); + virtual void intrinsicSizeChanged(); + void setHasIntrinsicSize() { m_hasIntrinsicSize = true; } + + virtual void paint(PaintInfo&, int tx, int ty); + bool shouldPaint(PaintInfo&, int& tx, int& ty); + IntRect localSelectionRect(bool checkWhetherSelected = true) const; // This is in local coordinates, but it's a physical rect (so the top left corner is physical top left). + +private: + virtual const char* renderName() const { return "RenderReplaced"; } + + virtual bool canHaveChildren() const { return false; } + + virtual void computePreferredLogicalWidths(); + + int calcAspectRatioLogicalWidth() const; + int calcAspectRatioLogicalHeight() const; + + virtual void paintReplaced(PaintInfo&, int /*tx*/, int /*ty*/) { } + + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer); + + virtual unsigned caretMaxRenderedOffset() const; + virtual VisiblePosition positionForPoint(const IntPoint&); + + virtual bool canBeSelectionLeaf() const { return true; } + + virtual IntRect selectionRectForRepaint(RenderBoxModelObject* repaintContainer, bool clipToVisibleContent = true); + + IntSize m_intrinsicSize; + bool m_hasIntrinsicSize; +}; + +} + +#endif diff --git a/Source/WebCore/rendering/RenderReplica.cpp b/Source/WebCore/rendering/RenderReplica.cpp new file mode 100644 index 0000000..c099d9d --- /dev/null +++ b/Source/WebCore/rendering/RenderReplica.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderReplica.h" + +#include "RenderLayer.h" + +namespace WebCore { + +RenderReplica::RenderReplica(Node* n) +: RenderBox(n) +{ + // This is a hack. Replicas are synthetic, and don't pick up the attributes of the + // renderers being replicated, so they always report that they are inline, non-replaced. + // However, we need transforms to be applied to replicas for reflections, so have to pass + // the if (!isInline() || isReplaced()) check before setHasTransform(). + setReplaced(true); +} + +RenderReplica::~RenderReplica() +{} + +void RenderReplica::layout() +{ + setFrameRect(parentBox()->borderBoxRect()); + updateLayerTransform(); + setNeedsLayout(false); +} + +void RenderReplica::computePreferredLogicalWidths() +{ + m_minPreferredLogicalWidth = parentBox()->width(); + m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth; + setPreferredLogicalWidthsDirty(false); +} + +void RenderReplica::paint(PaintInfo& paintInfo, int tx, int ty) +{ + if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseMask) + return; + + tx += x(); + ty += y(); + + if (paintInfo.phase == PaintPhaseForeground) + // Turn around and paint the parent layer. Use temporary clipRects, so that the layer doesn't end up caching clip rects + // computing using the wrong rootLayer + layer()->parent()->paintLayer(layer()->transform() ? layer()->parent() : layer()->enclosingTransformedAncestor(), + paintInfo.context, paintInfo.rect, + PaintBehaviorNormal, 0, 0, + RenderLayer::PaintLayerHaveTransparency | RenderLayer::PaintLayerAppliedTransform | RenderLayer::PaintLayerTemporaryClipRects | RenderLayer::PaintLayerPaintingReflection); + else if (paintInfo.phase == PaintPhaseMask) + paintMask(paintInfo, tx, ty); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderReplica.h b/Source/WebCore/rendering/RenderReplica.h new file mode 100644 index 0000000..a7b03f6 --- /dev/null +++ b/Source/WebCore/rendering/RenderReplica.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderReplica_h +#define RenderReplica_h + +#include "RenderBox.h" + +namespace WebCore { + +class RenderReplica : public RenderBox { +public: + RenderReplica(Node*); + virtual ~RenderReplica(); + + virtual const char* renderName() const { return "RenderReplica"; } + + virtual bool requiresLayer() const { return true; } + + virtual void layout(); + virtual void computePreferredLogicalWidths(); + + virtual void paint(PaintInfo&, int tx, int ty); + +private: + virtual bool isReplica() const { return true; } + +}; + +} // namespace WebCore + +#endif // RenderReplica_h diff --git a/Source/WebCore/rendering/RenderRuby.cpp b/Source/WebCore/rendering/RenderRuby.cpp new file mode 100644 index 0000000..1c5cfaf --- /dev/null +++ b/Source/WebCore/rendering/RenderRuby.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include "RenderRuby.h" + +#include "RenderRubyRun.h" + +namespace WebCore { + +//=== generic helper functions to avoid excessive code duplication === + +static RenderRubyRun* lastRubyRun(const RenderObject* ruby) +{ + RenderObject* child = ruby->lastChild(); + if (child && ruby->isAfterContent(child)) + child = child->previousSibling(); + ASSERT(!child || child->isRubyRun() || child->isBeforeContent()); + return child && child->isRubyRun() ? static_cast<RenderRubyRun*>(child) : 0; +} + +static inline RenderRubyRun* findRubyRunParent(RenderObject* child) +{ + while (child && !child->isRubyRun()) + child = child->parent(); + return static_cast<RenderRubyRun*>(child); +} + +//=== ruby as inline object === + +RenderRubyAsInline::RenderRubyAsInline(Node* node) + : RenderInline(node) +{ +} + +RenderRubyAsInline::~RenderRubyAsInline() +{ +} + +bool RenderRubyAsInline::isChildAllowed(RenderObject* child, RenderStyle*) const +{ + return child->isRubyText() + || child->isRubyRun() + || child->isInline(); +} + +void RenderRubyAsInline::addChild(RenderObject* child, RenderObject* beforeChild) +{ + // Insert :before and :after content outside of ruby runs. + if (child->isBeforeContent() || child->isAfterContent()) { + RenderInline::addChild(child, beforeChild); + return; + } + + // If the child is a ruby run, just add it normally. + if (child->isRubyRun()) { + RenderInline::addChild(child, beforeChild); + return; + } + + if (beforeChild && !isAfterContent(beforeChild)) { + // insert child into run + ASSERT(!beforeChild->isRubyRun()); + RenderObject* run = beforeChild; + while (run && !run->isRubyRun()) + run = run->parent(); + if (run) { + run->addChild(child, beforeChild); + return; + } + ASSERT_NOT_REACHED(); // beforeChild should always have a run as parent! + // Emergency fallback: fall through and just append. + } + + // If the new child would be appended, try to add the child to the previous run + // if possible, or create a new run otherwise. + // (The RenderRubyRun object will handle the details) + RenderRubyRun* lastRun = lastRubyRun(this); + if (!lastRun || lastRun->hasRubyText()) { + lastRun = RenderRubyRun::staticCreateRubyRun(this); + RenderInline::addChild(lastRun); + } + lastRun->addChild(child); +} + +void RenderRubyAsInline::removeChild(RenderObject* child) +{ + // If the child's parent is *this (a ruby run or :before or :after content), + // just use the normal remove method. + if (child->isRubyRun() || child->isBeforeContent() || child->isAfterContent()) { + RenderInline::removeChild(child); + return; + } + + // Otherwise find the containing run and remove it from there. + ASSERT(child->parent() != this); + RenderRubyRun* run = findRubyRunParent(child); + ASSERT(run); + run->removeChild(child); +} + + +//=== ruby as block object === + +RenderRubyAsBlock::RenderRubyAsBlock(Node* node) + : RenderBlock(node) +{ +} + +RenderRubyAsBlock::~RenderRubyAsBlock() +{ +} + +bool RenderRubyAsBlock::isChildAllowed(RenderObject* child, RenderStyle*) const +{ + return child->isRubyText() + || child->isRubyRun() + || child->isInline(); +} + +void RenderRubyAsBlock::addChild(RenderObject* child, RenderObject* beforeChild) +{ + // Insert :before and :after content outside of ruby runs. + if (child->isBeforeContent() || child->isAfterContent()) { + RenderBlock::addChild(child, beforeChild); + return; + } + + // If the child is a ruby run, just add it normally. + if (child->isRubyRun()) { + RenderBlock::addChild(child, beforeChild); + return; + } + + if (beforeChild && !isAfterContent(beforeChild)) { + // insert child into run + ASSERT(!beforeChild->isRubyRun()); + RenderObject* run = beforeChild; + while (run && !run->isRubyRun()) + run = run->parent(); + if (run) { + run->addChild(child, beforeChild); + return; + } + ASSERT_NOT_REACHED(); // beforeChild should always have a run as parent! + // Emergency fallback: fall through and just append. + } + + // If the new child would be appended, try to add the child to the previous run + // if possible, or create a new run otherwise. + // (The RenderRubyRun object will handle the details) + RenderRubyRun* lastRun = lastRubyRun(this); + if (!lastRun || lastRun->hasRubyText()) { + lastRun = RenderRubyRun::staticCreateRubyRun(this); + RenderBlock::addChild(lastRun); + } + lastRun->addChild(child); +} + +void RenderRubyAsBlock::removeChild(RenderObject* child) +{ + // If the child's parent is *this (a ruby run or :before or :after content), + // just use the normal remove method. + if (child->isRubyRun() || child->isBeforeContent() || child->isAfterContent()) { + RenderBlock::removeChild(child); + return; + } + + // Otherwise find the containing run and remove it from there. + ASSERT(child->parent() != this); + RenderRubyRun* run = findRubyRunParent(child); + ASSERT(run); + run->removeChild(child); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderRuby.h b/Source/WebCore/rendering/RenderRuby.h new file mode 100644 index 0000000..49a84d8 --- /dev/null +++ b/Source/WebCore/rendering/RenderRuby.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderRuby_h +#define RenderRuby_h + +#include "RenderBlock.h" +#include "RenderInline.h" + +namespace WebCore { + +// Following the HTML 5 spec, the box object model for a <ruby> element allows several runs of ruby +// bases with their respective ruby texts looks as follows: +// +// 1 RenderRuby object, corresponding to the whole <ruby> HTML element +// 1+ RenderRubyRun (anonymous) +// 0 or 1 RenderRubyText - shuffled to the front in order to re-use existing block layouting +// 0-n inline object(s) +// 0 or 1 RenderRubyBase - contains the inline objects that make up the ruby base +// 1-n inline object(s) +// +// Note: <rp> elements are defined as having 'display:none' and thus normally are not assigned a renderer. + +// <ruby> when used as 'display:inline' +class RenderRubyAsInline : public RenderInline { +public: + RenderRubyAsInline(Node*); + virtual ~RenderRubyAsInline(); + + virtual bool isChildAllowed(RenderObject*, RenderStyle*) const; + virtual void addChild(RenderObject* child, RenderObject* beforeChild = 0); + virtual void removeChild(RenderObject* child); + +private: + virtual bool isRuby() const { return true; } + virtual const char* renderName() const { return "RenderRuby (inline)"; } + virtual bool createsAnonymousWrapper() const { return true; } + virtual void removeLeftoverAnonymousBlock(RenderBlock*) { ASSERT_NOT_REACHED(); } +}; + +// <ruby> when used as 'display:block' or 'display:inline-block' +class RenderRubyAsBlock : public RenderBlock { +public: + RenderRubyAsBlock(Node*); + virtual ~RenderRubyAsBlock(); + + virtual bool isChildAllowed(RenderObject*, RenderStyle*) const; + virtual void addChild(RenderObject* child, RenderObject* beforeChild = 0); + virtual void removeChild(RenderObject* child); + +private: + virtual bool isRuby() const { return true; } + virtual const char* renderName() const { return "RenderRuby (block)"; } + virtual bool createsAnonymousWrapper() const { return true; } + virtual void removeLeftoverAnonymousBlock(RenderBlock*) { ASSERT_NOT_REACHED(); } +}; + +} // namespace WebCore + +#endif // RenderRuby_h diff --git a/Source/WebCore/rendering/RenderRubyBase.cpp b/Source/WebCore/rendering/RenderRubyBase.cpp new file mode 100644 index 0000000..83399a1 --- /dev/null +++ b/Source/WebCore/rendering/RenderRubyBase.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include "RenderRubyBase.h" + +namespace WebCore { + +RenderRubyBase::RenderRubyBase(Node* node) + : RenderBlock(node) +{ + setInline(false); +} + +RenderRubyBase::~RenderRubyBase() +{ +} + +bool RenderRubyBase::isChildAllowed(RenderObject* child, RenderStyle*) const +{ + return child->isInline(); +} + +bool RenderRubyBase::hasOnlyWrappedInlineChildren(RenderObject* beforeChild) const +{ + // Tests whether all children in the base before beforeChild are either floated/positioned, + // or inline objects wrapped in anonymous blocks. + // Note that beforeChild may be 0, in which case all children are looked at. + for (RenderObject* child = firstChild(); child != beforeChild; child = child->nextSibling()) { + if (!child->isFloatingOrPositioned() && !(child->isAnonymousBlock() && child->childrenInline())) + return false; + } + return true; +} + +void RenderRubyBase::moveChildren(RenderRubyBase* toBase, RenderObject* fromBeforeChild) +{ + // This function removes all children that are before (!) beforeChild + // and appends them to toBase. + ASSERT(toBase); + + // First make sure that beforeChild (if set) is indeed a direct child of this. + // Inline children might be wrapped in an anonymous block if there's a continuation. + // Theoretically, in ruby bases, this can happen with only the first such a child, + // so it should be OK to just climb the tree. + while (fromBeforeChild && fromBeforeChild->parent() != this) + fromBeforeChild = fromBeforeChild->parent(); + + if (childrenInline()) + moveInlineChildren(toBase, fromBeforeChild); + else + moveBlockChildren(toBase, fromBeforeChild); + + setNeedsLayoutAndPrefWidthsRecalc(); + toBase->setNeedsLayoutAndPrefWidthsRecalc(); +} + +void RenderRubyBase::moveInlineChildren(RenderRubyBase* toBase, RenderObject* fromBeforeChild) +{ + RenderBlock* toBlock; + + if (toBase->childrenInline()) { + // The standard and easy case: move the children into the target base + toBlock = toBase; + } else { + // We need to wrap the inline objects into an anonymous block. + // If toBase has a suitable block, we re-use it, otherwise create a new one. + RenderObject* lastChild = toBase->lastChild(); + if (lastChild && lastChild->isAnonymousBlock() && lastChild->childrenInline()) + toBlock = toRenderBlock(lastChild); + else { + toBlock = toBase->createAnonymousBlock(); + toBase->children()->appendChildNode(toBase, toBlock); + } + } + // Move our inline children into the target block we determined above. + moveChildrenTo(toBlock, firstChild(), fromBeforeChild); +} + +void RenderRubyBase::moveBlockChildren(RenderRubyBase* toBase, RenderObject* fromBeforeChild) +{ + if (toBase->childrenInline()) { + // First check whether we move only wrapped inline objects. + if (hasOnlyWrappedInlineChildren(fromBeforeChild)) { + // The reason why the base is in block flow must be after beforeChild. + // We therefore can extract the inline objects and move them to toBase. + for (RenderObject* child = firstChild(); child != fromBeforeChild; child = firstChild()) { + if (child->isAnonymousBlock()) { + RenderBlock* anonBlock = toRenderBlock(child); + ASSERT(anonBlock->childrenInline()); + ASSERT(!anonBlock->inlineElementContinuation()); + anonBlock->moveAllChildrenTo(toBase, toBase->children()); + anonBlock->deleteLineBoxTree(); + anonBlock->destroy(); + } else { + ASSERT(child->isFloatingOrPositioned()); + moveChildTo(toBase, child); + } + } + } else { + // Moving block children -> have to set toBase as block flow + toBase->makeChildrenNonInline(); + // Move children, potentially collapsing anonymous block wrappers. + mergeBlockChildren(toBase, fromBeforeChild); + + // Now we need to check if the leftover children are all inline. + // If so, make this base inline again. + if (hasOnlyWrappedInlineChildren()) { + RenderObject* next = 0; + for (RenderObject* child = firstChild(); child; child = next) { + next = child->nextSibling(); + if (child->isFloatingOrPositioned()) + continue; + ASSERT(child->isAnonymousBlock()); + + RenderBlock* anonBlock = toRenderBlock(child); + ASSERT(anonBlock->childrenInline()); + ASSERT(!anonBlock->inlineElementContinuation()); + // Move inline children out of anonymous block. + anonBlock->moveAllChildrenTo(this, anonBlock); + anonBlock->deleteLineBoxTree(); + anonBlock->destroy(); + } + setChildrenInline(true); + } + } + } else + mergeBlockChildren(toBase, fromBeforeChild); +} + +void RenderRubyBase::mergeBlockChildren(RenderRubyBase* toBase, RenderObject* fromBeforeChild) +{ + // This function removes all children that are before fromBeforeChild and appends them to toBase. + ASSERT(!childrenInline()); + ASSERT(toBase); + ASSERT(!toBase->childrenInline()); + + // Quick check whether we have anything to do, to simplify the following code. + if (fromBeforeChild != firstChild()) + return; + + // If an anonymous block would be put next to another such block, then merge those. + RenderObject* firstChildHere = firstChild(); + RenderObject* lastChildThere = toBase->lastChild(); + if (firstChildHere && firstChildHere->isAnonymousBlock() && firstChildHere->childrenInline() + && lastChildThere && lastChildThere->isAnonymousBlock() && lastChildThere->childrenInline()) { + RenderBlock* anonBlockHere = toRenderBlock(firstChildHere); + RenderBlock* anonBlockThere = toRenderBlock(lastChildThere); + anonBlockHere->moveAllChildrenTo(anonBlockThere, anonBlockThere->children()); + anonBlockHere->deleteLineBoxTree(); + anonBlockHere->destroy(); + } + // Move all remaining children normally. + moveChildrenTo(toBase, firstChild(), fromBeforeChild); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderRubyBase.h b/Source/WebCore/rendering/RenderRubyBase.h new file mode 100644 index 0000000..c029bd5 --- /dev/null +++ b/Source/WebCore/rendering/RenderRubyBase.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderRubyBase_h +#define RenderRubyBase_h + +#include "RenderBlock.h" + +namespace WebCore { + +class RenderRubyBase : public RenderBlock { +public: + RenderRubyBase(Node*); + virtual ~RenderRubyBase(); + + virtual const char* renderName() const { return "RenderRubyBase (anonymous)"; } + + virtual bool isRubyBase() const { return true; } + + virtual bool isChildAllowed(RenderObject*, RenderStyle*) const; + +private: + bool hasOnlyWrappedInlineChildren(RenderObject* beforeChild = 0) const; + + void moveChildren(RenderRubyBase* toBase, RenderObject* fromBeforeChild = 0); + void moveInlineChildren(RenderRubyBase* toBase, RenderObject* fromBeforeChild = 0); + void moveBlockChildren(RenderRubyBase* toBase, RenderObject* fromBeforeChild = 0); + void mergeBlockChildren(RenderRubyBase* toBase, RenderObject* fromBeforeChild = 0); + + // Allow RenderRubyRun to manipulate the children within ruby bases. + friend class RenderRubyRun; +}; + +} // namespace WebCore + +#endif // RenderRubyBase_h diff --git a/Source/WebCore/rendering/RenderRubyRun.cpp b/Source/WebCore/rendering/RenderRubyRun.cpp new file mode 100644 index 0000000..c12e543 --- /dev/null +++ b/Source/WebCore/rendering/RenderRubyRun.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include "RenderRubyRun.h" + +#include "RenderRubyBase.h" +#include "RenderRubyText.h" +#include "RenderView.h" + +using namespace std; + +namespace WebCore { + +RenderRubyRun::RenderRubyRun(Node* node) + : RenderBlock(node) + , m_beingDestroyed(false) +{ + setReplaced(true); + setInline(true); +} + +RenderRubyRun::~RenderRubyRun() +{ +} + +void RenderRubyRun::destroy() +{ + // Mark if the run is being destroyed to avoid trouble in removeChild(). + m_beingDestroyed = true; + RenderBlock::destroy(); +} + +bool RenderRubyRun::hasRubyText() const +{ + // The only place where a ruby text can be is in the first position + // Note: As anonymous blocks, ruby runs do not have ':before' or ':after' content themselves. + return firstChild() && firstChild()->isRubyText(); +} + +bool RenderRubyRun::hasRubyBase() const +{ + // The only place where a ruby base can be is in the last position + // Note: As anonymous blocks, ruby runs do not have ':before' or ':after' content themselves. + return lastChild() && lastChild()->isRubyBase(); +} + +bool RenderRubyRun::isEmpty() const +{ + return !hasRubyText() && !hasRubyBase(); +} + +RenderRubyText* RenderRubyRun::rubyText() const +{ + RenderObject* child = firstChild(); + return child && child->isRubyText() ? static_cast<RenderRubyText*>(child) : 0; +} + +RenderRubyBase* RenderRubyRun::rubyBase() const +{ + RenderObject* child = lastChild(); + return child && child->isRubyBase() ? static_cast<RenderRubyBase*>(child) : 0; +} + +RenderRubyBase* RenderRubyRun::rubyBaseSafe() +{ + RenderRubyBase* base = rubyBase(); + if (!base) { + base = createRubyBase(); + RenderBlock::addChild(base); + } + return base; +} + +RenderBlock* RenderRubyRun::firstLineBlock() const +{ + return 0; +} + +void RenderRubyRun::updateFirstLetter() +{ +} + +bool RenderRubyRun::isChildAllowed(RenderObject* child, RenderStyle*) const +{ + return child->isRubyText() || child->isInline(); +} + +void RenderRubyRun::addChild(RenderObject* child, RenderObject* beforeChild) +{ + ASSERT(child); + + // If child is a ruby text + if (child->isRubyText()) { + if (!beforeChild) { + // RenderRuby has already ascertained that we can add the child here. + ASSERT(!hasRubyText()); + // prepend ruby texts as first child + RenderBlock::addChild(child, firstChild()); + } else if (beforeChild->isRubyText()) { + // New text is inserted just before another. + // In this case the new text takes the place of the old one, and + // the old text goes into a new run that is inserted as next sibling. + ASSERT(beforeChild->parent() == this); + RenderObject* ruby = parent(); + ASSERT(ruby->isRuby()); + RenderBlock* newRun = staticCreateRubyRun(ruby); + ruby->addChild(newRun, nextSibling()); + // Add the new ruby text and move the old one to the new run + // Note: Doing it in this order and not using RenderRubyRun's methods, + // in order to avoid automatic removal of the ruby run in case there is no + // other child besides the old ruby text. + RenderBlock::addChild(child, beforeChild); + RenderBlock::removeChild(beforeChild); + newRun->addChild(beforeChild); + } else { + if (hasRubyBase()) { + // Insertion before a ruby base object. + // In this case we need insert a new run before the current one and split the base. + RenderObject* ruby = parent(); + RenderRubyRun* newRun = staticCreateRubyRun(ruby); + ruby->addChild(newRun, this); + newRun->addChild(child); + rubyBaseSafe()->moveChildren(newRun->rubyBaseSafe(), beforeChild); + } + } + } else { + // child is not a text -> insert it into the base + // (append it instead if beforeChild is the ruby text) + if (beforeChild && beforeChild->isRubyText()) + beforeChild = 0; + rubyBaseSafe()->addChild(child, beforeChild); + } +} + +void RenderRubyRun::removeChild(RenderObject* child) +{ + // If the child is a ruby text, then merge the ruby base with the base of + // the right sibling run, if possible. + if (!m_beingDestroyed && !documentBeingDestroyed() && child->isRubyText()) { + RenderRubyBase* base = rubyBase(); + RenderObject* rightNeighbour = nextSibling(); + if (base && rightNeighbour && rightNeighbour->isRubyRun()) { + // Ruby run without a base can happen only at the first run. + RenderRubyRun* rightRun = static_cast<RenderRubyRun*>(rightNeighbour); + if (rightRun->hasRubyBase()) { + RenderRubyBase* rightBase = rightRun->rubyBaseSafe(); + // Collect all children in a single base, then swap the bases. + rightBase->moveChildren(base); + moveChildTo(rightRun, base); + rightRun->moveChildTo(this, rightBase); + // The now empty ruby base will be removed below. + } + } + } + + RenderBlock::removeChild(child); + + if (!m_beingDestroyed && !documentBeingDestroyed()) { + // Check if our base (if any) is now empty. If so, destroy it. + RenderBlock* base = rubyBase(); + if (base && !base->firstChild()) { + RenderBlock::removeChild(base); + base->deleteLineBoxTree(); + base->destroy(); + } + + // If any of the above leaves the run empty, destroy it as well. + if (isEmpty()) { + parent()->removeChild(this); + deleteLineBoxTree(); + destroy(); + } + } +} + +RenderRubyBase* RenderRubyRun::createRubyBase() const +{ + RenderRubyBase* rb = new (renderArena()) RenderRubyBase(document() /* anonymous */); + RefPtr<RenderStyle> newStyle = RenderStyle::create(); + newStyle->inheritFrom(style()); + newStyle->setDisplay(BLOCK); + newStyle->setTextAlign(CENTER); // FIXME: use WEBKIT_CENTER? + rb->setStyle(newStyle.release()); + return rb; +} + +RenderRubyRun* RenderRubyRun::staticCreateRubyRun(const RenderObject* parentRuby) +{ + ASSERT(parentRuby && parentRuby->isRuby()); + RenderRubyRun* rr = new (parentRuby->renderArena()) RenderRubyRun(parentRuby->document() /* anonymous */); + RefPtr<RenderStyle> newStyle = RenderStyle::create(); + newStyle->inheritFrom(parentRuby->style()); + newStyle->setDisplay(INLINE_BLOCK); + rr->setStyle(newStyle.release()); + return rr; +} + +RenderObject* RenderRubyRun::layoutSpecialExcludedChild(bool relayoutChildren) +{ + // Don't bother positioning the RenderRubyRun yet. + RenderRubyText* rt = rubyText(); + if (!rt) + return 0; + if (relayoutChildren) + rt->setChildNeedsLayout(true, false); + rt->layoutIfNeeded(); + return rt; +} + +void RenderRubyRun::layout() +{ + RenderBlock::layout(); + + // Place the RenderRubyText such that its bottom is flush with the lineTop of the first line of the RenderRubyBase. + RenderRubyText* rt = rubyText(); + if (!rt) + return; + + int lastLineRubyTextBottom = rt->logicalHeight(); + int firstLineRubyTextTop = 0; + RootInlineBox* rootBox = rt->lastRootBox(); + if (rootBox) { + // In order to align, we have to ignore negative leading. + firstLineRubyTextTop = rt->firstRootBox()->logicalTopLayoutOverflow(); + lastLineRubyTextBottom = rootBox->logicalBottomLayoutOverflow(); + } + + if (!style()->isFlippedLinesWritingMode()) { + int firstLineTop = 0; + if (RenderRubyBase* rb = rubyBase()) { + RootInlineBox* rootBox = rb->firstRootBox(); + if (rootBox) + firstLineTop = rootBox->logicalTopLayoutOverflow(); + firstLineTop += rb->logicalTop(); + } + + rt->setLogicalTop(-lastLineRubyTextBottom + firstLineTop); + } else { + int lastLineBottom = logicalHeight(); + if (RenderRubyBase* rb = rubyBase()) { + RootInlineBox* rootBox = rb->lastRootBox(); + if (rootBox) + lastLineBottom = rootBox->logicalBottomLayoutOverflow(); + lastLineBottom += rb->logicalTop(); + } + + rt->setLogicalTop(-firstLineRubyTextTop + lastLineBottom); + } + + // Update our overflow to account for the new RenderRubyText position. + m_overflow.clear(); + computeOverflow(clientLogicalBottom()); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderRubyRun.h b/Source/WebCore/rendering/RenderRubyRun.h new file mode 100644 index 0000000..d844bff --- /dev/null +++ b/Source/WebCore/rendering/RenderRubyRun.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderRubyRun_h +#define RenderRubyRun_h + +#include "RenderBlock.h" + +namespace WebCore { + +class RenderRubyBase; +class RenderRubyText; + +// RenderRubyRun are 'inline-block/table' like objects,and wrap a single pairing of a ruby base with its ruby text(s). +// See RenderRuby.h for further comments on the structure + +class RenderRubyRun : public RenderBlock { +public: + RenderRubyRun(Node*); + virtual ~RenderRubyRun(); + + virtual void destroy(); + + bool hasRubyText() const; + bool hasRubyBase() const; + bool isEmpty() const; + RenderRubyText* rubyText() const; + RenderRubyBase* rubyBase() const; + RenderRubyBase* rubyBaseSafe(); // creates the base if it doesn't already exist + + virtual RenderObject* layoutSpecialExcludedChild(bool relayoutChildren); + virtual void layout(); + + virtual bool isChildAllowed(RenderObject*, RenderStyle*) const; + virtual void addChild(RenderObject* child, RenderObject* beforeChild = 0); + virtual void removeChild(RenderObject* child); + + virtual RenderBlock* firstLineBlock() const; + virtual void updateFirstLetter(); + + static RenderRubyRun* staticCreateRubyRun(const RenderObject* parentRuby); + +protected: + RenderRubyBase* createRubyBase() const; + +private: + virtual bool isRubyRun() const { return true; } + virtual const char* renderName() const { return "RenderRubyRun (anonymous)"; } + virtual bool createsAnonymousWrapper() const { return true; } + virtual void removeLeftoverAnonymousBlock(RenderBlock*) { } + + bool m_beingDestroyed; +}; + +} // namespace WebCore + +#endif // RenderRubyRun_h diff --git a/Source/WebCore/rendering/RenderRubyText.cpp b/Source/WebCore/rendering/RenderRubyText.cpp new file mode 100644 index 0000000..14cf7fc --- /dev/null +++ b/Source/WebCore/rendering/RenderRubyText.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include "RenderRubyText.h" + +namespace WebCore { + +RenderRubyText::RenderRubyText(Node* node) + : RenderBlock(node) +{ +} + +RenderRubyText::~RenderRubyText() +{ +} + +bool RenderRubyText::isChildAllowed(RenderObject* child, RenderStyle*) const +{ + return child->isInline(); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderRubyText.h b/Source/WebCore/rendering/RenderRubyText.h new file mode 100644 index 0000000..e475914 --- /dev/null +++ b/Source/WebCore/rendering/RenderRubyText.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderRubyText_h +#define RenderRubyText_h + +#include "RenderBlock.h" + +namespace WebCore { + +class RenderRubyText : public RenderBlock { +public: + RenderRubyText(Node*); + virtual ~RenderRubyText(); + + virtual const char* renderName() const { return "RenderRubyText"; } + + virtual bool isRubyText() const { return true; } + + virtual bool isChildAllowed(RenderObject*, RenderStyle*) const; +}; + +} // namespace WebCore + +#endif // RenderRubyText_h diff --git a/Source/WebCore/rendering/RenderSVGAllInOne.cpp b/Source/WebCore/rendering/RenderSVGAllInOne.cpp new file mode 100644 index 0000000..7002747 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGAllInOne.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2009, 2010 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// This all-in-one cpp file cuts down on template bloat to allow us to build our Windows release build. + +#include "RenderSVGBlock.cpp" +#include "RenderSVGContainer.cpp" +#include "RenderSVGGradientStop.cpp" +#include "RenderSVGHiddenContainer.cpp" +#include "RenderSVGImage.cpp" +#include "RenderSVGModelObject.cpp" +#include "RenderSVGResource.cpp" +#include "RenderSVGResourceClipper.cpp" +#include "RenderSVGResourceContainer.cpp" +#include "RenderSVGResourceFilter.cpp" +#include "RenderSVGResourceFilterPrimitive.cpp" +#include "RenderSVGResourceGradient.cpp" +#include "RenderSVGResourceLinearGradient.cpp" +#include "RenderSVGResourceMarker.cpp" +#include "RenderSVGResourceMasker.cpp" +#include "RenderSVGResourcePattern.cpp" +#include "RenderSVGResourceRadialGradient.cpp" +#include "RenderSVGResourceSolidColor.cpp" +#include "RenderSVGRoot.cpp" +#include "RenderSVGShadowTreeRootContainer.cpp" +#include "RenderSVGTransformableContainer.cpp" +#include "RenderSVGViewportContainer.cpp" +#include "SVGImageBufferTools.cpp" +#include "SVGMarkerLayoutInfo.cpp" +#include "SVGRenderSupport.cpp" +#include "SVGRenderTreeAsText.cpp" +#include "SVGResources.cpp" +#include "SVGResourcesCache.cpp" +#include "SVGResourcesCycleSolver.cpp" +#include "SVGShadowTreeElements.cpp" + +// FIXME: As soon as all SVG renderers live in rendering/svg, this file should be moved there as well, removing the need for the svg/ includes below. +#include "svg/RenderSVGInline.cpp" +#include "svg/RenderSVGInlineText.cpp" +#include "svg/RenderSVGTSpan.cpp" +#include "svg/RenderSVGText.cpp" +#include "svg/RenderSVGTextPath.cpp" +#include "svg/SVGInlineFlowBox.cpp" +#include "svg/SVGInlineTextBox.cpp" +#include "svg/SVGRootInlineBox.cpp" +#include "svg/SVGTextChunk.cpp" +#include "svg/SVGTextChunkBuilder.cpp" +#include "svg/SVGTextLayoutAttributes.cpp" +#include "svg/SVGTextLayoutAttributesBuilder.cpp" +#include "svg/SVGTextLayoutEngine.cpp" +#include "svg/SVGTextLayoutEngineBaseline.cpp" +#include "svg/SVGTextLayoutEngineSpacing.cpp" +#include "svg/SVGTextMetrics.cpp" +#include "svg/SVGTextQuery.cpp" diff --git a/Source/WebCore/rendering/RenderSVGBlock.cpp b/Source/WebCore/rendering/RenderSVGBlock.cpp new file mode 100644 index 0000000..b2d727a --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGBlock.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. + * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGBlock.h" + +#include "RenderSVGResource.h" +#include "SVGElement.h" + +namespace WebCore { + +RenderSVGBlock::RenderSVGBlock(SVGElement* node) + : RenderBlock(node) +{ +} + +void RenderSVGBlock::setStyle(PassRefPtr<RenderStyle> style) +{ + RefPtr<RenderStyle> useStyle = style; + + // SVG text layout code expects us to be a block-level style element. + if (useStyle->isDisplayInlineType()) { + RefPtr<RenderStyle> newStyle = RenderStyle::create(); + newStyle->inheritFrom(useStyle.get()); + newStyle->setDisplay(BLOCK); + useStyle = newStyle.release(); + } + + RenderBlock::setStyle(useStyle.release()); +} + +void RenderSVGBlock::updateBoxModelInfoFromStyle() +{ + RenderBlock::updateBoxModelInfoFromStyle(); + + // RenderSVGlock, used by Render(SVGText|ForeignObject), is not allowed to call setHasOverflowClip(true). + // RenderBlock assumes a layer to be present when the overflow clip functionality is requested. Both + // Render(SVGText|ForeignObject) return 'false' on 'requiresLayer'. Fine for RenderSVGText. + // + // If we want to support overflow rules for <foreignObject> we can choose between two solutions: + // a) make RenderForeignObject require layers and SVG layer aware + // b) reactor overflow logic out of RenderLayer (as suggested by dhyatt), which is a large task + // + // Until this is resolved, disable overflow support. Opera/FF don't support it as well at the moment (Feb 2010). + // + // Note: This does NOT affect overflow handling on outer/inner <svg> elements - this is handled + // manually by RenderSVGRoot - which owns the documents enclosing root layer and thus works fine. + setHasOverflowClip(false); +} + +void RenderSVGBlock::absoluteRects(Vector<IntRect>&, int, int) +{ + // This code path should never be taken for SVG, as we're assuming useTransforms=true everywhere, absoluteQuads should be used. + ASSERT_NOT_REACHED(); +} + +void RenderSVGBlock::destroy() +{ + SVGResourcesCache::clientDestroyed(this); + RenderBlock::destroy(); +} + +void RenderSVGBlock::styleWillChange(StyleDifference diff, const RenderStyle* newStyle) +{ + if (diff == StyleDifferenceLayout) + setNeedsBoundariesUpdate(); + RenderBlock::styleWillChange(diff, newStyle); +} + +void RenderSVGBlock::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBlock::styleDidChange(diff, oldStyle); + SVGResourcesCache::clientStyleChanged(this, diff, style()); +} + +void RenderSVGBlock::updateFromElement() +{ + RenderBlock::updateFromElement(); + SVGResourcesCache::clientUpdatedFromElement(this, style()); +} + +} + +#endif diff --git a/Source/WebCore/rendering/RenderSVGBlock.h b/Source/WebCore/rendering/RenderSVGBlock.h new file mode 100644 index 0000000..a9dd5db --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGBlock.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. + * + * 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. + * + */ + +#ifndef RenderSVGBlock_h +#define RenderSVGBlock_h + +#if ENABLE(SVG) +#include "RenderBlock.h" +#include "SVGRenderSupport.h" + +namespace WebCore { + +class SVGElement; + +class RenderSVGBlock : public RenderBlock { +public: + explicit RenderSVGBlock(SVGElement*); + +private: + virtual void setStyle(PassRefPtr<RenderStyle>); + virtual void updateBoxModelInfoFromStyle(); + + virtual void absoluteRects(Vector<IntRect>&, int tx, int ty); + + virtual void destroy(); + virtual void styleWillChange(StyleDifference, const RenderStyle* newStyle); + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + virtual void updateFromElement(); +}; + +} +#endif +#endif diff --git a/Source/WebCore/rendering/RenderSVGContainer.cpp b/Source/WebCore/rendering/RenderSVGContainer.cpp new file mode 100644 index 0000000..32e0598 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGContainer.cpp @@ -0,0 +1,184 @@ +/* + Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> + 2004, 2005, 2007, 2008 Rob Buis <buis@kde.org> + 2007 Eric Seidel <eric@webkit.org> + Copyright (C) 2009 Google, Inc. All rights reserved. + 2009 Dirk Schulze <krit@webkit.org> + + 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 + aint 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" + +#if ENABLE(SVG) +#include "RenderSVGContainer.h" + +#include "GraphicsContext.h" +#include "RenderSVGResource.h" +#include "RenderSVGResourceFilter.h" +#include "RenderView.h" +#include "SVGRenderSupport.h" +#include "SVGResources.h" +#include "SVGStyledElement.h" + +namespace WebCore { + +RenderSVGContainer::RenderSVGContainer(SVGStyledElement* node) + : RenderSVGModelObject(node) + , m_needsBoundariesUpdate(true) +{ +} + +RenderSVGContainer::~RenderSVGContainer() +{ +} + +void RenderSVGContainer::layout() +{ + ASSERT(needsLayout()); + + // RenderSVGRoot disables layoutState for the SVG rendering tree. + ASSERT(!view()->layoutStateEnabled()); + + // Allow RenderSVGViewportContainer to update its viewport. + calcViewport(); + + LayoutRepainter repainter(*this, checkForRepaintDuringLayout() || selfWillPaint()); + + // Allow RenderSVGTransformableContainer to update its transform. + bool updatedTransform = calculateLocalTransform(); + + SVGRenderSupport::layoutChildren(this, selfNeedsLayout()); + + // Invalidate all resources of this client if our layout changed. + if (m_everHadLayout && selfNeedsLayout()) + SVGResourcesCache::clientLayoutChanged(this); + + // At this point LayoutRepainter already grabbed the old bounds, + // recalculate them now so repaintAfterLayout() uses the new bounds. + if (m_needsBoundariesUpdate || updatedTransform) { + updateCachedBoundaries(); + m_needsBoundariesUpdate = false; + + // If our bounds changed, notify the parents. + RenderSVGModelObject::setNeedsBoundariesUpdate(); + } + + repainter.repaintAfterLayout(); + setNeedsLayout(false); +} + +bool RenderSVGContainer::selfWillPaint() +{ +#if ENABLE(FILTERS) + SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this); + return resources && resources->filter(); +#else + return false; +#endif +} + +void RenderSVGContainer::paint(PaintInfo& paintInfo, int, int) +{ + if (paintInfo.context->paintingDisabled()) + return; + + // Spec: groups w/o children still may render filter content. + if (!firstChild() && !selfWillPaint()) + return; + + FloatRect repaintRect = repaintRectInLocalCoordinates(); + if (!SVGRenderSupport::paintInfoIntersectsRepaintRect(repaintRect, localToParentTransform(), paintInfo)) + return; + + PaintInfo childPaintInfo(paintInfo); + childPaintInfo.context->save(); + + // Let the RenderSVGViewportContainer subclass clip if necessary + applyViewportClip(childPaintInfo); + + childPaintInfo.applyTransform(localToParentTransform()); + + bool continueRendering = true; + if (childPaintInfo.phase == PaintPhaseForeground) + continueRendering = SVGRenderSupport::prepareToRenderSVGContent(this, childPaintInfo); + + if (continueRendering) { + childPaintInfo.updatePaintingRootForChildren(this); + for (RenderObject* child = firstChild(); child; child = child->nextSibling()) + child->paint(childPaintInfo, 0, 0); + } + + if (paintInfo.phase == PaintPhaseForeground) + SVGRenderSupport::finishRenderSVGContent(this, childPaintInfo, paintInfo.context); + + childPaintInfo.context->restore(); + + // FIXME: This really should be drawn from local coordinates, but currently we hack it + // to avoid our clip killing our outline rect. Thus we translate our + // outline rect into parent coords before drawing. + // FIXME: This means our focus ring won't share our rotation like it should. + // We should instead disable our clip during PaintPhaseOutline + if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth() && style()->visibility() == VISIBLE) { + IntRect paintRectInParent = enclosingIntRect(localToParentTransform().mapRect(repaintRect)); + paintOutline(paintInfo.context, paintRectInParent.x(), paintRectInParent.y(), paintRectInParent.width(), paintRectInParent.height()); + } +} + +// addFocusRingRects is called from paintOutline and needs to be in the same coordinates as the paintOuline call +void RenderSVGContainer::addFocusRingRects(Vector<IntRect>& rects, int, int) +{ + IntRect paintRectInParent = enclosingIntRect(localToParentTransform().mapRect(repaintRectInLocalCoordinates())); + if (!paintRectInParent.isEmpty()) + rects.append(paintRectInParent); +} + +void RenderSVGContainer::updateCachedBoundaries() +{ + m_objectBoundingBox = FloatRect(); + m_strokeBoundingBox = FloatRect(); + m_repaintBoundingBox = FloatRect(); + + SVGRenderSupport::computeContainerBoundingBoxes(this, m_objectBoundingBox, m_strokeBoundingBox, m_repaintBoundingBox); + SVGRenderSupport::intersectRepaintRectWithResources(this, m_repaintBoundingBox); +} + +bool RenderSVGContainer::nodeAtFloatPoint(const HitTestRequest& request, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction) +{ + // Give RenderSVGViewportContainer a chance to apply its viewport clip + if (!pointIsInsideViewportClip(pointInParent)) + return false; + + FloatPoint localPoint = localToParentTransform().inverse().mapPoint(pointInParent); + + if (!SVGRenderSupport::pointInClippingArea(this, localPoint)) + return false; + + for (RenderObject* child = lastChild(); child; child = child->previousSibling()) { + if (child->nodeAtFloatPoint(request, result, localPoint, hitTestAction)) { + updateHitTestResult(result, roundedIntPoint(localPoint)); + return true; + } + } + + // Spec: Only graphical elements can be targeted by the mouse, period. + // 16.4: "If there are no graphics elements whose relevant graphics content is under the pointer (i.e., there is no target element), the event is not dispatched." + return false; +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/RenderSVGContainer.h b/Source/WebCore/rendering/RenderSVGContainer.h new file mode 100644 index 0000000..3bd5346 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGContainer.h @@ -0,0 +1,101 @@ +/* + Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> + 2004, 2005, 2007 Rob Buis <buis@kde.org> + Copyright (C) 2009 Google, Inc. All rights reserved. + Copyright (C) 2009 Apple Inc. All rights reserved. + + 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 + aint 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. +*/ + +#ifndef RenderSVGContainer_h +#define RenderSVGContainer_h + +#if ENABLE(SVG) + +#include "RenderSVGModelObject.h" + +namespace WebCore { + +class SVGElement; + +class RenderSVGContainer : public RenderSVGModelObject { +public: + explicit RenderSVGContainer(SVGStyledElement*); + virtual ~RenderSVGContainer(); + + const RenderObjectChildList* children() const { return &m_children; } + RenderObjectChildList* children() { return &m_children; } + + virtual void paint(PaintInfo&, int parentX, int parentY); + virtual void setNeedsBoundariesUpdate() { m_needsBoundariesUpdate = true; } + +protected: + virtual RenderObjectChildList* virtualChildren() { return children(); } + virtual const RenderObjectChildList* virtualChildren() const { return children(); } + + virtual bool isSVGContainer() const { return true; } + virtual const char* renderName() const { return "RenderSVGContainer"; } + + virtual void layout(); + + virtual void addFocusRingRects(Vector<IntRect>&, int tx, int ty); + + virtual FloatRect objectBoundingBox() const { return m_objectBoundingBox; } + virtual FloatRect strokeBoundingBox() const { return m_strokeBoundingBox; } + virtual FloatRect repaintRectInLocalCoordinates() const { return m_repaintBoundingBox; } + + virtual bool nodeAtFloatPoint(const HitTestRequest&, HitTestResult&, const FloatPoint& pointInParent, HitTestAction); + + // Allow RenderSVGTransformableContainer to hook in at the right time in layout() + virtual bool calculateLocalTransform() { return false; } + + // Allow RenderSVGViewportContainer to hook in at the right times in layout(), paint() and nodeAtFloatPoint() + virtual void calcViewport() { } + virtual void applyViewportClip(PaintInfo&) { } + virtual bool pointIsInsideViewportClip(const FloatPoint& /*pointInParent*/) { return true; } + + bool selfWillPaint(); + void updateCachedBoundaries(); + +private: + RenderObjectChildList m_children; + FloatRect m_objectBoundingBox; + FloatRect m_strokeBoundingBox; + FloatRect m_repaintBoundingBox; + bool m_needsBoundariesUpdate : 1; +}; + +inline RenderSVGContainer* toRenderSVGContainer(RenderObject* object) +{ + // Note: isSVGContainer is also true for RenderSVGViewportContainer, which is not derived from this. + ASSERT(!object || (object->isSVGContainer() && strcmp(object->renderName(), "RenderSVGViewportContainer"))); + return static_cast<RenderSVGContainer*>(object); +} + +inline const RenderSVGContainer* toRenderSVGContainer(const RenderObject* object) +{ + // Note: isSVGContainer is also true for RenderSVGViewportContainer, which is not derived from this. + ASSERT(!object || (object->isSVGContainer() && strcmp(object->renderName(), "RenderSVGViewportContainer"))); + return static_cast<const RenderSVGContainer*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderSVGContainer(const RenderSVGContainer*); + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif // RenderSVGContainer_h diff --git a/Source/WebCore/rendering/RenderSVGGradientStop.cpp b/Source/WebCore/rendering/RenderSVGGradientStop.cpp new file mode 100644 index 0000000..094fc7f --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGGradientStop.cpp @@ -0,0 +1,83 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2007 Eric Seidel <eric@webkit.org> + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGGradientStop.h" + +#include "RenderSVGResourceContainer.h" +#include "SVGGradientElement.h" +#include "SVGNames.h" +#include "SVGResourcesCache.h" +#include "SVGStopElement.h" + +namespace WebCore { + +using namespace SVGNames; + +RenderSVGGradientStop::RenderSVGGradientStop(SVGStopElement* element) + : RenderObject(element) +{ +} + +RenderSVGGradientStop::~RenderSVGGradientStop() +{ +} + +void RenderSVGGradientStop::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderObject::styleDidChange(diff, oldStyle); + if (diff == StyleDifferenceEqual) + return; + + // <stop> elements should only be allowed to make renderers under gradient elements + // but I can imagine a few cases we might not be catching, so let's not crash if our parent isn't a gradient. + SVGGradientElement* gradient = gradientElement(); + if (!gradient) + return; + + RenderObject* renderer = gradient->renderer(); + if (!renderer) + return; + + ASSERT(renderer->isSVGResourceContainer()); + RenderSVGResourceContainer* container = renderer->toRenderSVGResourceContainer(); + container->removeAllClientsFromCache(); +} + +void RenderSVGGradientStop::layout() +{ + setNeedsLayout(false); +} + +SVGGradientElement* RenderSVGGradientStop::gradientElement() const +{ + ContainerNode* parentNode = node()->parentNode(); + if (parentNode->hasTagName(linearGradientTag) || parentNode->hasTagName(radialGradientTag)) + return static_cast<SVGGradientElement*>(parentNode); + return 0; +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/RenderSVGGradientStop.h b/Source/WebCore/rendering/RenderSVGGradientStop.h new file mode 100644 index 0000000..f06a9a5 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGGradientStop.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2007 Eric Seidel <eric@webkit.org> + * Copyright (C) 2009 Google, Inc. + * + * 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. + * + */ + +#ifndef RenderSVGGradientStop_h +#define RenderSVGGradientStop_h + +#if ENABLE(SVG) +#include "RenderObject.h" + +namespace WebCore { + +class SVGGradientElement; +class SVGStopElement; + +// This class exists mostly so we can hear about gradient stop style changes +class RenderSVGGradientStop : public RenderObject { +public: + RenderSVGGradientStop(SVGStopElement*); + virtual ~RenderSVGGradientStop(); + + virtual bool isSVGGradientStop() const { return true; } + virtual const char* renderName() const { return "RenderSVGGradientStop"; } + + virtual void layout(); + + // This overrides are needed to prevent ASSERTs on <svg><stop /></svg> + // RenderObject's default implementations ASSERT_NOT_REACHED() + // https://bugs.webkit.org/show_bug.cgi?id=20400 + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject*) { return IntRect(); } + virtual FloatRect objectBoundingBox() const { return FloatRect(); } + virtual FloatRect strokeBoundingBox() const { return FloatRect(); } + virtual FloatRect repaintRectInLocalCoordinates() const { return FloatRect(); } + +protected: + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + +private: + SVGGradientElement* gradientElement() const; +}; + +inline const RenderSVGGradientStop* toRenderSVGGradientStop(const RenderObject* object) +{ + ASSERT(!object || object->isSVGGradientStop()); + return static_cast<const RenderSVGGradientStop*>(object); +} + +} + +#endif // ENABLE(SVG) +#endif // RenderSVGGradientStop_h diff --git a/Source/WebCore/rendering/RenderSVGHiddenContainer.cpp b/Source/WebCore/rendering/RenderSVGHiddenContainer.cpp new file mode 100644 index 0000000..fb14ffe --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGHiddenContainer.cpp @@ -0,0 +1,62 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2007 Eric Seidel <eric@webkit.org> + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGHiddenContainer.h" + +#include "RenderSVGPath.h" +#include "SVGStyledElement.h" + +namespace WebCore { + +RenderSVGHiddenContainer::RenderSVGHiddenContainer(SVGStyledElement* element) + : RenderSVGContainer(element) +{ +} + +void RenderSVGHiddenContainer::layout() +{ + ASSERT(needsLayout()); + SVGRenderSupport::layoutChildren(this, selfNeedsLayout()); + setNeedsLayout(false); +} + +void RenderSVGHiddenContainer::paint(PaintInfo&, int, int) +{ + // This subtree does not paint. +} + +void RenderSVGHiddenContainer::absoluteQuads(Vector<FloatQuad>&) +{ + // This subtree does not take up space or paint +} + +bool RenderSVGHiddenContainer::nodeAtFloatPoint(const HitTestRequest&, HitTestResult&, const FloatPoint&, HitTestAction) +{ + return false; +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/RenderSVGHiddenContainer.h b/Source/WebCore/rendering/RenderSVGHiddenContainer.h new file mode 100644 index 0000000..c591a88 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGHiddenContainer.h @@ -0,0 +1,58 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2007 Eric Seidel <eric@webkit.org> + * + * 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. + * + */ + +#ifndef RenderSVGHiddenContainer_h +#define RenderSVGHiddenContainer_h + +#if ENABLE(SVG) +#include "RenderSVGContainer.h" + +namespace WebCore { + + class SVGStyledElement; + + // This class is for containers which are never drawn, but do need to support style + // <defs>, <linearGradient>, <radialGradient> are all good examples + class RenderSVGHiddenContainer : public RenderSVGContainer { + public: + explicit RenderSVGHiddenContainer(SVGStyledElement*); + + virtual const char* renderName() const { return "RenderSVGHiddenContainer"; } + + protected: + virtual void layout(); + + private: + virtual bool isSVGHiddenContainer() const { return true; } + virtual bool requiresLayer() const { return false; } + + virtual void paint(PaintInfo&, int parentX, int parentY); + + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject*) { return IntRect(); } + virtual void absoluteQuads(Vector<FloatQuad>&); + + virtual bool nodeAtFloatPoint(const HitTestRequest&, HitTestResult&, const FloatPoint& pointInParent, HitTestAction); + }; +} + +#endif // ENABLE(SVG) +#endif // RenderSVGHiddenContainer_h diff --git a/Source/WebCore/rendering/RenderSVGImage.cpp b/Source/WebCore/rendering/RenderSVGImage.cpp new file mode 100644 index 0000000..237dbaa --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGImage.cpp @@ -0,0 +1,195 @@ +/* + Copyright (C) 2006 Alexander Kellett <lypanov@kde.org> + Copyright (C) 2006 Apple Computer, Inc. + Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + Copyright (C) 2007, 2008, 2009 Rob Buis <buis@kde.org> + Copyright (C) 2009, Google, Inc. + Copyright (C) 2009 Dirk Schulze <krit@webkit.org> + Copyright (C) 2010 Patrick Gansterer <paroga@paroga.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" + +#if ENABLE(SVG) +#include "RenderSVGImage.h" + +#include "Attr.h" +#include "FloatConversion.h" +#include "FloatQuad.h" +#include "GraphicsContext.h" +#include "PointerEventsHitRules.h" +#include "RenderImageResource.h" +#include "RenderLayer.h" +#include "RenderSVGResourceContainer.h" +#include "RenderSVGResourceFilter.h" +#include "SVGImageElement.h" +#include "SVGLength.h" +#include "SVGPreserveAspectRatio.h" +#include "SVGRenderSupport.h" +#include "SVGResources.h" + +namespace WebCore { + +RenderSVGImage::RenderSVGImage(SVGImageElement* impl) + : RenderSVGModelObject(impl) + , m_updateCachedRepaintRect(true) + , m_needsTransformUpdate(true) + , m_imageResource(RenderImageResource::create()) +{ + m_imageResource->initialize(this); +} + +RenderSVGImage::~RenderSVGImage() +{ + m_imageResource->shutdown(); +} + +void RenderSVGImage::layout() +{ + ASSERT(needsLayout()); + + LayoutRepainter repainter(*this, checkForRepaintDuringLayout() && selfNeedsLayout()); + SVGImageElement* image = static_cast<SVGImageElement*>(node()); + + bool transformOrBoundariesUpdate = m_needsTransformUpdate || m_updateCachedRepaintRect; + if (m_needsTransformUpdate) { + m_localTransform = image->animatedLocalTransform(); + m_needsTransformUpdate = false; + } + + if (m_updateCachedRepaintRect) { + m_repaintBoundingBox = m_objectBoundingBox; + SVGRenderSupport::intersectRepaintRectWithResources(this, m_repaintBoundingBox); + m_updateCachedRepaintRect = false; + } + + // Invalidate all resources of this client if our layout changed. + if (m_everHadLayout && selfNeedsLayout()) + SVGResourcesCache::clientLayoutChanged(this); + + // If our bounds changed, notify the parents. + if (transformOrBoundariesUpdate) + RenderSVGModelObject::setNeedsBoundariesUpdate(); + + repainter.repaintAfterLayout(); + setNeedsLayout(false); +} + +void RenderSVGImage::updateFromElement() +{ + SVGImageElement* image = static_cast<SVGImageElement*>(node()); + + FloatRect oldBoundaries = m_objectBoundingBox; + m_objectBoundingBox = FloatRect(image->x().value(image), image->y().value(image), image->width().value(image), image->height().value(image)); + if (m_objectBoundingBox != oldBoundaries) { + m_updateCachedRepaintRect = true; + setNeedsLayout(true); + } + RenderSVGModelObject::updateFromElement(); +} + +void RenderSVGImage::paint(PaintInfo& paintInfo, int, int) +{ + if (paintInfo.context->paintingDisabled() || style()->visibility() == HIDDEN || !m_imageResource->hasImage()) + return; + + FloatRect boundingBox = repaintRectInLocalCoordinates(); + if (!SVGRenderSupport::paintInfoIntersectsRepaintRect(boundingBox, m_localTransform, paintInfo)) + return; + + PaintInfo childPaintInfo(paintInfo); + bool drawsOutline = style()->outlineWidth() && (childPaintInfo.phase == PaintPhaseOutline || childPaintInfo.phase == PaintPhaseSelfOutline); + if (drawsOutline || childPaintInfo.phase == PaintPhaseForeground) { + childPaintInfo.context->save(); + childPaintInfo.applyTransform(m_localTransform); + + if (childPaintInfo.phase == PaintPhaseForeground) { + PaintInfo savedInfo(childPaintInfo); + + if (SVGRenderSupport::prepareToRenderSVGContent(this, childPaintInfo)) { + Image* image = m_imageResource->image(); + FloatRect destRect = m_objectBoundingBox; + FloatRect srcRect(0, 0, image->width(), image->height()); + + SVGImageElement* imageElement = static_cast<SVGImageElement*>(node()); + if (imageElement->preserveAspectRatio().align() != SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_NONE) + imageElement->preserveAspectRatio().transformRect(destRect, srcRect); + + childPaintInfo.context->drawImage(image, ColorSpaceDeviceRGB, destRect, srcRect); + } + + SVGRenderSupport::finishRenderSVGContent(this, childPaintInfo, savedInfo.context); + } + + if (drawsOutline) + paintOutline(childPaintInfo.context, static_cast<int>(boundingBox.x()), static_cast<int>(boundingBox.y()), + static_cast<int>(boundingBox.width()), static_cast<int>(boundingBox.height())); + + childPaintInfo.context->restore(); + } +} + +bool RenderSVGImage::nodeAtFloatPoint(const HitTestRequest& request, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction) +{ + // We only draw in the forground phase, so we only hit-test then. + if (hitTestAction != HitTestForeground) + return false; + + PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_IMAGE_HITTESTING, request, style()->pointerEvents()); + bool isVisible = (style()->visibility() == VISIBLE); + if (isVisible || !hitRules.requireVisible) { + FloatPoint localPoint = localToParentTransform().inverse().mapPoint(pointInParent); + + if (!SVGRenderSupport::pointInClippingArea(this, localPoint)) + return false; + + if (hitRules.canHitFill) { + if (m_objectBoundingBox.contains(localPoint)) { + updateHitTestResult(result, roundedIntPoint(localPoint)); + return true; + } + } + } + + return false; +} + +void RenderSVGImage::imageChanged(WrappedImagePtr, const IntRect*) +{ + // The image resource defaults to nullImage until the resource arrives. + // This empty image may be cached by SVG resources which must be invalidated. + if (SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this)) + resources->removeClientFromCache(this); + + // Eventually notify parent resources, that we've changed. + RenderSVGResource::markForLayoutAndParentResourceInvalidation(this, false); + + repaint(); +} + +void RenderSVGImage::addFocusRingRects(Vector<IntRect>& rects, int, int) +{ + // this is called from paint() after the localTransform has already been applied + IntRect contentRect = enclosingIntRect(repaintRectInLocalCoordinates()); + if (!contentRect.isEmpty()) + rects.append(contentRect); +} + +} // namespace WebCore + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/RenderSVGImage.h b/Source/WebCore/rendering/RenderSVGImage.h new file mode 100644 index 0000000..485d6ab --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGImage.h @@ -0,0 +1,99 @@ +/* + Copyright (C) 2006 Alexander Kellett <lypanov@kde.org> + Copyright (C) 2006, 2009 Apple Inc. All rights reserved. + Copyright (C) 2007 Rob Buis <buis@kde.org> + Copyright (C) 2009 Google, Inc. + Copyright (C) 2010 Patrick Gansterer <paroga@paroga.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. +*/ + +#ifndef RenderSVGImage_h +#define RenderSVGImage_h + +#if ENABLE(SVG) +#include "AffineTransform.h" +#include "FloatRect.h" +#include "RenderSVGModelObject.h" +#include "SVGPreserveAspectRatio.h" +#include "SVGRenderSupport.h" + +namespace WebCore { + +class RenderImageResource; +class SVGImageElement; + +class RenderSVGImage : public RenderSVGModelObject { +public: + RenderSVGImage(SVGImageElement*); + virtual ~RenderSVGImage(); + + virtual void setNeedsTransformUpdate() { m_needsTransformUpdate = true; } + virtual void updateFromElement(); + + RenderImageResource* imageResource() { return m_imageResource.get(); } + const RenderImageResource* imageResource() const { return m_imageResource.get(); } + +private: + virtual const char* renderName() const { return "RenderSVGImage"; } + virtual bool isSVGImage() const { return true; } + + virtual const AffineTransform& localToParentTransform() const { return m_localTransform; } + + virtual FloatRect objectBoundingBox() const { return m_objectBoundingBox; } + virtual FloatRect strokeBoundingBox() const { return m_objectBoundingBox; } + virtual FloatRect repaintRectInLocalCoordinates() const { return m_repaintBoundingBox; } + + virtual void addFocusRingRects(Vector<IntRect>&, int tx, int ty); + + virtual void imageChanged(WrappedImagePtr, const IntRect* = 0); + + virtual void layout(); + virtual void paint(PaintInfo&, int parentX, int parentY); + + virtual bool requiresLayer() const { return false; } + + virtual bool nodeAtFloatPoint(const HitTestRequest&, HitTestResult&, const FloatPoint& pointInParent, HitTestAction); + + virtual AffineTransform localTransform() const { return m_localTransform; } + + bool m_updateCachedRepaintRect : 1; + bool m_needsTransformUpdate : 1; + AffineTransform m_localTransform; + FloatRect m_objectBoundingBox; + FloatRect m_repaintBoundingBox; + OwnPtr<RenderImageResource> m_imageResource; +}; + +inline RenderSVGImage* toRenderSVGImage(RenderObject* object) +{ + ASSERT(!object || object->isSVGImage()); + return static_cast<RenderSVGImage*>(object); +} + +inline const RenderSVGImage* toRenderSVGImage(const RenderObject* object) +{ + ASSERT(!object || object->isSVGImage()); + return static_cast<const RenderSVGImage*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderSVGImage(const RenderSVGImage*); + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif // RenderSVGImage_h diff --git a/Source/WebCore/rendering/RenderSVGModelObject.cpp b/Source/WebCore/rendering/RenderSVGModelObject.cpp new file mode 100644 index 0000000..28760a0 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGModelObject.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2009, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if ENABLE(SVG) +#include "RenderSVGModelObject.h" + +#include "RenderSVGResource.h" +#include "SVGStyledElement.h" + +namespace WebCore { + +RenderSVGModelObject::RenderSVGModelObject(SVGStyledElement* node) + : RenderObject(node) +{ +} + +IntRect RenderSVGModelObject::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) +{ + return SVGRenderSupport::clippedOverflowRectForRepaint(this, repaintContainer); +} + +void RenderSVGModelObject::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& repaintRect, bool fixed) +{ + SVGRenderSupport::computeRectForRepaint(this, repaintContainer, repaintRect, fixed); +} + +void RenderSVGModelObject::mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool fixed , bool useTransforms, TransformState& transformState) const +{ + SVGRenderSupport::mapLocalToContainer(this, repaintContainer, fixed, useTransforms, transformState); +} + +// Copied from RenderBox, this method likely requires further refactoring to work easily for both SVG and CSS Box Model content. +// FIXME: This may also need to move into SVGRenderSupport as the RenderBox version depends +// on borderBoundingBox() which SVG RenderBox subclases (like SVGRenderBlock) do not implement. +IntRect RenderSVGModelObject::outlineBoundsForRepaint(RenderBoxModelObject* repaintContainer, IntPoint*) const +{ + IntRect box = enclosingIntRect(repaintRectInLocalCoordinates()); + adjustRectForOutlineAndShadow(box); + + FloatQuad containerRelativeQuad = localToContainerQuad(FloatRect(box), repaintContainer); + return containerRelativeQuad.enclosingBoundingBox(); +} + +void RenderSVGModelObject::absoluteRects(Vector<IntRect>&, int, int) +{ + // This code path should never be taken for SVG, as we're assuming useTransforms=true everywhere, absoluteQuads should be used. + ASSERT_NOT_REACHED(); +} + +void RenderSVGModelObject::absoluteQuads(Vector<FloatQuad>& quads) +{ + quads.append(localToAbsoluteQuad(strokeBoundingBox())); +} + +void RenderSVGModelObject::destroy() +{ + SVGResourcesCache::clientDestroyed(this); + RenderObject::destroy(); +} + +void RenderSVGModelObject::styleWillChange(StyleDifference diff, const RenderStyle* newStyle) +{ + if (diff == StyleDifferenceLayout) + setNeedsBoundariesUpdate(); + RenderObject::styleWillChange(diff, newStyle); +} + +void RenderSVGModelObject::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderObject::styleDidChange(diff, oldStyle); + SVGResourcesCache::clientStyleChanged(this, diff, style()); +} + +void RenderSVGModelObject::updateFromElement() +{ + RenderObject::updateFromElement(); + SVGResourcesCache::clientUpdatedFromElement(this, style()); +} + +bool RenderSVGModelObject::nodeAtPoint(const HitTestRequest&, HitTestResult&, int, int, int, int, HitTestAction) +{ + ASSERT_NOT_REACHED(); + return false; +} + +} // namespace WebCore + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/RenderSVGModelObject.h b/Source/WebCore/rendering/RenderSVGModelObject.h new file mode 100644 index 0000000..fb8f89f --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGModelObject.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2009, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderSVGModelObject_h +#define RenderSVGModelObject_h + +#if ENABLE(SVG) + +#include "RenderObject.h" +#include "SVGRenderSupport.h" + +namespace WebCore { + +// Most renderers in the SVG rendering tree will inherit from this class +// but not all. (e.g. RenderSVGForeignObject, RenderSVGBlock) thus methods +// required by SVG renders need to be declared on RenderObject, but shared +// logic can go in this class or in SVGRenderSupport. + +class SVGStyledElement; + +class RenderSVGModelObject : public RenderObject { +public: + explicit RenderSVGModelObject(SVGStyledElement*); + + virtual bool requiresLayer() const { return false; } + + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer); + virtual void computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect&, bool fixed = false); + virtual IntRect outlineBoundsForRepaint(RenderBoxModelObject* repaintContainer, IntPoint*) const; + + virtual void absoluteRects(Vector<IntRect>&, int tx, int ty); + virtual void absoluteQuads(Vector<FloatQuad>&); + + virtual void destroy(); + + virtual void mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool useTransforms, bool fixed, TransformState&) const; + virtual void styleWillChange(StyleDifference, const RenderStyle* newStyle); + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + virtual void updateFromElement(); + +private: + // This method should never be called, SVG uses a different nodeAtPoint method + bool nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int xInContainer, int yInContainer, int dxParentToContainer, int dyParentToContainer, HitTestAction hitTestAction); +}; + +} + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/RenderSVGResource.cpp b/Source/WebCore/rendering/RenderSVGResource.cpp new file mode 100644 index 0000000..f4c65d5 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResource.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) 2007 Rob Buis <buis@kde.org> + * Copyright (C) 2008 Dirk Schulze <krit@webkit.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGResource.h" + +#include "RenderSVGResourceContainer.h" +#include "RenderSVGResourceSolidColor.h" +#include "SVGResources.h" +#include "SVGURIReference.h" + +namespace WebCore { + +static inline RenderSVGResource* requestPaintingResource(RenderSVGResourceMode mode, RenderObject* object, const RenderStyle* style, Color& fallbackColor) +{ + ASSERT(object); + ASSERT(style); + + // If we have no style at all, ignore it. + const SVGRenderStyle* svgStyle = style->svgStyle(); + if (!svgStyle) + return 0; + + // If we have no fill/stroke, return 0. + if (mode == ApplyToFillMode) { + if (!svgStyle->hasFill()) + return 0; + } else { + if (!svgStyle->hasStroke()) + return 0; + } + + SVGPaint* paint = mode == ApplyToFillMode ? svgStyle->fillPaint() : svgStyle->strokePaint(); + ASSERT(paint); + + SVGPaint::SVGPaintType paintType = paint->paintType(); + if (paintType == SVGPaint::SVG_PAINTTYPE_NONE) + return 0; + + Color color; + if (paintType == SVGPaint::SVG_PAINTTYPE_RGBCOLOR + || paintType == SVGPaint::SVG_PAINTTYPE_RGBCOLOR_ICCCOLOR + || paintType == SVGPaint::SVG_PAINTTYPE_URI_RGBCOLOR + || paintType == SVGPaint::SVG_PAINTTYPE_URI_RGBCOLOR_ICCCOLOR) + color = paint->color(); + else if (paintType == SVGPaint::SVG_PAINTTYPE_CURRENTCOLOR || paintType == SVGPaint::SVG_PAINTTYPE_URI_CURRENTCOLOR) + color = style->visitedDependentColor(CSSPropertyColor); + + if (style->insideLink() == InsideVisitedLink) { + RenderStyle* visitedStyle = style->getCachedPseudoStyle(VISITED_LINK); + ASSERT(visitedStyle); + + if (SVGPaint* visitedPaint = mode == ApplyToFillMode ? visitedStyle->svgStyle()->fillPaint() : visitedStyle->svgStyle()->strokePaint()) { + // For SVG_PAINTTYPE_CURRENTCOLOR, 'color' already contains the 'visitedColor'. + if (visitedPaint->paintType() < SVGPaint::SVG_PAINTTYPE_URI_NONE && visitedPaint->paintType() != SVGPaint::SVG_PAINTTYPE_CURRENTCOLOR) { + const Color& visitedColor = visitedPaint->color(); + if (visitedColor.isValid()) + color = Color(visitedColor.red(), visitedColor.green(), visitedColor.blue(), color.alpha()); + } + } + } + + // If the primary resource is just a color, return immediately. + RenderSVGResourceSolidColor* colorResource = RenderSVGResource::sharedSolidPaintingResource(); + if (paintType < SVGPaint::SVG_PAINTTYPE_URI_NONE) { + // If an invalid fill color is specified, fallback to fill/stroke="none". + if (!color.isValid()) + return 0; + + colorResource->setColor(color); + return colorResource; + } + + // If no resources are associated with the given renderer, return the color resource. + SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(object); + if (!resources) { + // If a paint server is specified, and no or an invalid fallback color is given, default to fill/stroke="black". + if (!color.isValid()) + color = Color::black; + + colorResource->setColor(color); + return colorResource; + } + + // If the requested resource is not available, return the color resource. + RenderSVGResource* uriResource = mode == ApplyToFillMode ? resources->fill() : resources->stroke(); + if (!uriResource) { + // If a paint server is specified, and no or an invalid fallback color is given, default to fill/stroke="black". + if (!color.isValid()) + color = Color::black; + + colorResource->setColor(color); + return colorResource; + } + + // The paint server resource exists, though it may be invalid (pattern with width/height=0). Pass the fallback color to our caller + // so it can use the solid color painting resource, if applyResource() on the URI resource failed. + fallbackColor = color; + return uriResource; +} + +RenderSVGResource* RenderSVGResource::fillPaintingResource(RenderObject* object, const RenderStyle* style, Color& fallbackColor) +{ + return requestPaintingResource(ApplyToFillMode, object, style, fallbackColor); +} + +RenderSVGResource* RenderSVGResource::strokePaintingResource(RenderObject* object, const RenderStyle* style, Color& fallbackColor) +{ + return requestPaintingResource(ApplyToStrokeMode, object, style, fallbackColor); +} + +RenderSVGResourceSolidColor* RenderSVGResource::sharedSolidPaintingResource() +{ + static RenderSVGResourceSolidColor* s_sharedSolidPaintingResource = 0; + if (!s_sharedSolidPaintingResource) + s_sharedSolidPaintingResource = new RenderSVGResourceSolidColor; + return s_sharedSolidPaintingResource; +} + +void RenderSVGResource::markForLayoutAndParentResourceInvalidation(RenderObject* object, bool needsLayout) +{ + ASSERT(object); + if (needsLayout) + object->setNeedsLayout(true); + + // Invalidate resources in ancestor chain, if needed. + RenderObject* current = object->parent(); + while (current) { + if (current->isSVGResourceContainer()) { + current->toRenderSVGResourceContainer()->removeAllClientsFromCache(); + break; + } + + current = current->parent(); + } +} + +} + +#endif diff --git a/Source/WebCore/rendering/RenderSVGResource.h b/Source/WebCore/rendering/RenderSVGResource.h new file mode 100644 index 0000000..6fc2dae --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResource.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSVGResource_h +#define RenderSVGResource_h + +#if ENABLE(SVG) +#include "RenderStyleConstants.h" +#include "SVGDocumentExtensions.h" + +namespace WebCore { + +enum RenderSVGResourceType { + MaskerResourceType, + MarkerResourceType, + PatternResourceType, + LinearGradientResourceType, + RadialGradientResourceType, + SolidColorResourceType, + FilterResourceType, + ClipperResourceType +}; + +enum RenderSVGResourceMode { + ApplyToDefaultMode = 1 << 0, // used for all resources except gradient/pattern + ApplyToFillMode = 1 << 1, + ApplyToStrokeMode = 1 << 2, + ApplyToTextMode = 1 << 3 // used in combination with ApplyTo{Fill|Stroke}Mode +}; + +class Color; +class FloatRect; +class GraphicsContext; +class Path; +class RenderObject; +class RenderStyle; +class RenderSVGResourceSolidColor; + +class RenderSVGResource { +public: + RenderSVGResource() { } + virtual ~RenderSVGResource() { } + + virtual void removeAllClientsFromCache(bool markForInvalidation = true) = 0; + virtual void removeClientFromCache(RenderObject*, bool markForInvalidation = true) = 0; + + virtual bool applyResource(RenderObject*, RenderStyle*, GraphicsContext*&, unsigned short resourceMode) = 0; + virtual void postApplyResource(RenderObject*, GraphicsContext*&, unsigned short, const Path*) { } + virtual FloatRect resourceBoundingBox(RenderObject*) = 0; + + virtual RenderSVGResourceType resourceType() const = 0; + + template<class Renderer> + Renderer* cast() + { + if (Renderer::s_resourceType == resourceType()) + return static_cast<Renderer*>(this); + + return 0; + } + + // Helper utilities used in the render tree to access resources used for painting shapes/text (gradients & patterns & solid colors only) + static RenderSVGResource* fillPaintingResource(RenderObject*, const RenderStyle*, Color& fallbackColor); + static RenderSVGResource* strokePaintingResource(RenderObject*, const RenderStyle*, Color& fallbackColor); + static RenderSVGResourceSolidColor* sharedSolidPaintingResource(); + + static void markForLayoutAndParentResourceInvalidation(RenderObject*, bool needsLayout = true); +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourceClipper.cpp b/Source/WebCore/rendering/RenderSVGResourceClipper.cpp new file mode 100644 index 0000000..190c1a4 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceClipper.cpp @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org> + * 2004, 2005, 2006, 2007, 2008 Rob Buis <buis@kde.org> + * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGResourceClipper.h" + +#include "AffineTransform.h" +#include "FloatRect.h" +#include "GraphicsContext.h" +#include "HitTestRequest.h" +#include "HitTestResult.h" +#include "ImageBuffer.h" +#include "IntRect.h" +#include "RenderObject.h" +#include "RenderSVGResource.h" +#include "RenderStyle.h" +#include "SVGClipPathElement.h" +#include "SVGElement.h" +#include "SVGImageBufferTools.h" +#include "SVGNames.h" +#include "SVGRenderSupport.h" +#include "SVGResources.h" +#include "SVGStyledElement.h" +#include "SVGStyledTransformableElement.h" +#include "SVGUnitTypes.h" +#include "SVGUseElement.h" +#include <wtf/UnusedParam.h> + +namespace WebCore { + +RenderSVGResourceType RenderSVGResourceClipper::s_resourceType = ClipperResourceType; + +RenderSVGResourceClipper::RenderSVGResourceClipper(SVGClipPathElement* node) + : RenderSVGResourceContainer(node) + , m_invalidationBlocked(false) +{ +} + +RenderSVGResourceClipper::~RenderSVGResourceClipper() +{ + if (m_clipper.isEmpty()) + return; + + deleteAllValues(m_clipper); + m_clipper.clear(); +} + +void RenderSVGResourceClipper::removeAllClientsFromCache(bool markForInvalidation) +{ + if (m_invalidationBlocked) + return; + + m_clipBoundaries = FloatRect(); + if (!m_clipper.isEmpty()) { + deleteAllValues(m_clipper); + m_clipper.clear(); + } + + markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation); +} + +void RenderSVGResourceClipper::removeClientFromCache(RenderObject* client, bool markForInvalidation) +{ + ASSERT(client); + if (m_invalidationBlocked) + return; + + if (m_clipper.contains(client)) + delete m_clipper.take(client); + + markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation); +} + +bool RenderSVGResourceClipper::applyResource(RenderObject* object, RenderStyle*, GraphicsContext*& context, unsigned short resourceMode) +{ + ASSERT(object); + ASSERT(context); +#ifndef NDEBUG + ASSERT(resourceMode == ApplyToDefaultMode); +#else + UNUSED_PARAM(resourceMode); +#endif + + return applyClippingToContext(object, object->objectBoundingBox(), object->repaintRectInLocalCoordinates(), context); +} + +bool RenderSVGResourceClipper::pathOnlyClipping(GraphicsContext* context, const FloatRect& objectBoundingBox) +{ + // If the current clip-path gets clipped itself, we have to fallback to masking. + if (!style()->svgStyle()->clipperResource().isEmpty()) + return false; + WindRule clipRule = RULE_NONZERO; + Path clipPath = Path(); + + // If clip-path only contains one visible shape or path, we can use path-based clipping. Invisible + // shapes don't affect the clipping and can be ignored. If clip-path contains more than one + // visible shape, the additive clipping may not work, caused by the clipRule. EvenOdd + // as well as NonZero can cause self-clipping of the elements. + // See also http://www.w3.org/TR/SVG/painting.html#FillRuleProperty + for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { + RenderObject* renderer = childNode->renderer(); + if (!renderer) + continue; + // Only shapes or paths are supported for direct clipping. We need to fallback to masking for texts. + if (renderer->isSVGText()) + return false; + if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyledTransformable()) + continue; + SVGStyledTransformableElement* styled = static_cast<SVGStyledTransformableElement*>(childNode); + RenderStyle* style = renderer->style(); + if (!style || style->display() == NONE || style->visibility() != VISIBLE) + continue; + const SVGRenderStyle* svgStyle = style->svgStyle(); + // Current shape in clip-path gets clipped too. Fallback to masking. + if (!svgStyle->clipperResource().isEmpty()) + return false; + // Fallback to masking, if there is more than one clipping path. + if (clipPath.isEmpty()) { + styled->toClipPath(clipPath); + clipRule = svgStyle->clipRule(); + } else + return false; + } + // Only one visible shape/path was found. Directly continue clipping and transform the content to userspace if necessary. + if (static_cast<SVGClipPathElement*>(node())->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + AffineTransform transform; + transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); + transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); + clipPath.transform(transform); + } + // The SVG specification wants us to clip everything, if clip-path doesn't have a child. + if (clipPath.isEmpty()) + clipPath.addRect(FloatRect()); + context->clipPath(clipPath, clipRule); + return true; +} + +bool RenderSVGResourceClipper::applyClippingToContext(RenderObject* object, const FloatRect& objectBoundingBox, + const FloatRect& repaintRect, GraphicsContext* context) +{ + if (!m_clipper.contains(object)) + m_clipper.set(object, new ClipperData); + + bool shouldCreateClipData = false; + ClipperData* clipperData = m_clipper.get(object); + if (!clipperData->clipMaskImage) { + if (pathOnlyClipping(context, objectBoundingBox)) + return true; + shouldCreateClipData = true; + } + + AffineTransform absoluteTransform; + SVGImageBufferTools::calculateTransformationToOutermostSVGCoordinateSystem(object, absoluteTransform); + + FloatRect absoluteTargetRect = absoluteTransform.mapRect(repaintRect); + FloatRect clampedAbsoluteTargetRect = SVGImageBufferTools::clampedAbsoluteTargetRectForRenderer(object, absoluteTargetRect); + + if (shouldCreateClipData && !clampedAbsoluteTargetRect.isEmpty()) { + if (!SVGImageBufferTools::createImageBuffer(absoluteTargetRect, clampedAbsoluteTargetRect, clipperData->clipMaskImage, ColorSpaceDeviceRGB)) + return false; + + GraphicsContext* maskContext = clipperData->clipMaskImage->context(); + ASSERT(maskContext); + + // The save/restore pair is needed for clipToImageBuffer - it doesn't work without it on non-Cg platforms. + maskContext->save(); + maskContext->translate(-clampedAbsoluteTargetRect.x(), -clampedAbsoluteTargetRect.y()); + maskContext->concatCTM(absoluteTransform); + + // clipPath can also be clipped by another clipPath. + if (SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this)) { + if (RenderSVGResourceClipper* clipper = resources->clipper()) { + if (!clipper->applyClippingToContext(this, objectBoundingBox, repaintRect, maskContext)) { + maskContext->restore(); + return false; + } + } + } + + drawContentIntoMaskImage(clipperData, objectBoundingBox); + maskContext->restore(); + } + + if (!clipperData->clipMaskImage) + return false; + + SVGImageBufferTools::clipToImageBuffer(context, absoluteTransform, clampedAbsoluteTargetRect, clipperData->clipMaskImage); + return true; +} + +bool RenderSVGResourceClipper::drawContentIntoMaskImage(ClipperData* clipperData, const FloatRect& objectBoundingBox) +{ + ASSERT(clipperData); + ASSERT(clipperData->clipMaskImage); + + GraphicsContext* maskContext = clipperData->clipMaskImage->context(); + ASSERT(maskContext); + + AffineTransform maskContentTransformation; + SVGClipPathElement* clipPath = static_cast<SVGClipPathElement*>(node()); + if (clipPath->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + maskContentTransformation.translate(objectBoundingBox.x(), objectBoundingBox.y()); + maskContentTransformation.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); + maskContext->concatCTM(maskContentTransformation); + } + + // Draw all clipPath children into a global mask. + for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { + RenderObject* renderer = childNode->renderer(); + if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyled() || !renderer) + continue; + RenderStyle* style = renderer->style(); + if (!style || style->display() == NONE || style->visibility() != VISIBLE) + continue; + + WindRule newClipRule = style->svgStyle()->clipRule(); + bool isUseElement = renderer->isSVGShadowTreeRootContainer(); + if (isUseElement) { + SVGUseElement* useElement = static_cast<SVGUseElement*>(childNode); + renderer = useElement->rendererClipChild(); + if (!renderer) + continue; + if (!useElement->hasAttribute(SVGNames::clip_ruleAttr)) + newClipRule = renderer->style()->svgStyle()->clipRule(); + } + + // Only shapes, paths and texts are allowed for clipping. + if (!renderer->isSVGPath() && !renderer->isSVGText()) + continue; + + // Save the old RenderStyle of the current object for restoring after drawing + // it to the MaskImage. The new intermediate RenderStyle needs to inherit from + // the old one. + RefPtr<RenderStyle> oldRenderStyle = renderer->style(); + RefPtr<RenderStyle> newRenderStyle = RenderStyle::clone(oldRenderStyle.get()); + SVGRenderStyle* svgStyle = newRenderStyle.get()->accessSVGStyle(); + svgStyle->setFillPaint(SVGPaint::defaultFill()); + svgStyle->setStrokePaint(SVGPaint::defaultStroke()); + svgStyle->setFillRule(newClipRule); + newRenderStyle.get()->setOpacity(1.0f); + svgStyle->setFillOpacity(1.0f); + svgStyle->setStrokeOpacity(1.0f); + svgStyle->setFilterResource(String()); + svgStyle->setMaskerResource(String()); + + // The setStyle() call results in a styleDidChange() call, which in turn invalidations the resources. + // As we're mutating the resource on purpose, block updates until we've resetted the style again. + m_invalidationBlocked = true; + renderer->setStyle(newRenderStyle.release()); + + // In the case of a <use> element, we obtained its renderere above, to retrieve its clipRule. + // We have to pass the <use> renderer itself to renderSubtreeToImageBuffer() to apply it's x/y/transform/etc. values when rendering. + // So if isUseElement is true, refetch the childNode->renderer(), as renderer got overriden above. + SVGImageBufferTools::renderSubtreeToImageBuffer(clipperData->clipMaskImage.get(), isUseElement ? childNode->renderer() : renderer, maskContentTransformation); + + renderer->setStyle(oldRenderStyle.release()); + m_invalidationBlocked = false; + } + + return true; +} + +void RenderSVGResourceClipper::calculateClipContentRepaintRect() +{ + // This is a rough heuristic to appraise the clip size and doesn't consider clip on clip. + for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { + RenderObject* renderer = childNode->renderer(); + if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyled() || !renderer) + continue; + if (!renderer->isSVGPath() && !renderer->isSVGText() && !renderer->isSVGShadowTreeRootContainer()) + continue; + RenderStyle* style = renderer->style(); + if (!style || style->display() == NONE || style->visibility() != VISIBLE) + continue; + m_clipBoundaries.unite(renderer->localToParentTransform().mapRect(renderer->repaintRectInLocalCoordinates())); + } +} + +bool RenderSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint) +{ + FloatPoint point = nodeAtPoint; + if (!SVGRenderSupport::pointInClippingArea(this, point)) + return false; + + if (static_cast<SVGClipPathElement*>(node())->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + AffineTransform transform; + transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); + transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); + point = transform.inverse().mapPoint(point); + } + + for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { + RenderObject* renderer = childNode->renderer(); + if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyled() || !renderer) + continue; + if (!renderer->isSVGPath() && !renderer->isSVGText() && !renderer->isSVGShadowTreeRootContainer()) + continue; + IntPoint hitPoint; + HitTestResult result(hitPoint); + if (renderer->nodeAtFloatPoint(HitTestRequest(HitTestRequest::SVGClipContent), result, point, HitTestForeground)) + return true; + } + + return false; +} + +FloatRect RenderSVGResourceClipper::resourceBoundingBox(RenderObject* object) +{ + // Resource was not layouted yet. Give back the boundingBox of the object. + if (selfNeedsLayout()) + return object->objectBoundingBox(); + + if (m_clipBoundaries.isEmpty()) + calculateClipContentRepaintRect(); + + if (static_cast<SVGClipPathElement*>(node())->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + FloatRect objectBoundingBox = object->objectBoundingBox(); + AffineTransform transform; + transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); + transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); + return transform.mapRect(m_clipBoundaries); + } + + return m_clipBoundaries; +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/RenderSVGResourceClipper.h b/Source/WebCore/rendering/RenderSVGResourceClipper.h new file mode 100644 index 0000000..20153e9 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceClipper.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSVGResourceClipper_h +#define RenderSVGResourceClipper_h + +#if ENABLE(SVG) +#include "FloatRect.h" +#include "GraphicsContext.h" +#include "ImageBuffer.h" +#include "IntSize.h" +#include "Path.h" +#include "RenderSVGResourceContainer.h" +#include "SVGClipPathElement.h" +#include "SVGUnitTypes.h" + +#include <wtf/HashMap.h> +#include <wtf/OwnPtr.h> + +namespace WebCore { + +struct ClipperData : FastAllocBase { + OwnPtr<ImageBuffer> clipMaskImage; +}; + +class RenderSVGResourceClipper : public RenderSVGResourceContainer { +public: + RenderSVGResourceClipper(SVGClipPathElement*); + virtual ~RenderSVGResourceClipper(); + + virtual const char* renderName() const { return "RenderSVGResourceClipper"; } + + virtual void removeAllClientsFromCache(bool markForInvalidation = true); + virtual void removeClientFromCache(RenderObject*, bool markForInvalidation = true); + + virtual bool applyResource(RenderObject*, RenderStyle*, GraphicsContext*&, unsigned short resourceMode); + virtual FloatRect resourceBoundingBox(RenderObject*); + + virtual RenderSVGResourceType resourceType() const { return ClipperResourceType; } + + bool hitTestClipContent(const FloatRect&, const FloatPoint&); + + SVGUnitTypes::SVGUnitType clipPathUnits() const { return toUnitType(static_cast<SVGClipPathElement*>(node())->clipPathUnits()); } + + static RenderSVGResourceType s_resourceType; +private: + // clipPath can be clipped too, but don't have a boundingBox or repaintRect. So we can't call + // applyResource directly and use the rects from the object, since they are empty for RenderSVGResources + bool applyClippingToContext(RenderObject*, const FloatRect&, const FloatRect&, GraphicsContext*); + bool pathOnlyClipping(GraphicsContext*, const FloatRect&); + bool drawContentIntoMaskImage(ClipperData*, const FloatRect& objectBoundingBox); + void calculateClipContentRepaintRect(); + + bool m_invalidationBlocked; + FloatRect m_clipBoundaries; + HashMap<RenderObject*, ClipperData*> m_clipper; +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourceContainer.cpp b/Source/WebCore/rendering/RenderSVGResourceContainer.cpp new file mode 100644 index 0000000..fb30efd --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceContainer.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGResourceContainer.h" + +#include "RenderSVGShadowTreeRootContainer.h" +#include "SVGStyledTransformableElement.h" + +namespace WebCore { + +static inline SVGDocumentExtensions* svgExtensionsFromNode(Node* node) +{ + ASSERT(node); + ASSERT(node->document()); + return node->document()->accessSVGExtensions(); +} + +RenderSVGResourceContainer::RenderSVGResourceContainer(SVGStyledElement* node) + : RenderSVGHiddenContainer(node) + , m_id(node->hasID() ? node->getIdAttribute() : nullAtom) + , m_registered(false) +{ +} + +RenderSVGResourceContainer::~RenderSVGResourceContainer() +{ + if (m_registered) + svgExtensionsFromNode(node())->removeResource(m_id); +} + +void RenderSVGResourceContainer::layout() +{ + // Invalidate all resources if our layout changed. + if (m_everHadLayout && selfNeedsLayout()) + removeAllClientsFromCache(); + + RenderSVGHiddenContainer::layout(); +} + +void RenderSVGResourceContainer::destroy() +{ + SVGResourcesCache::resourceDestroyed(this); + RenderSVGHiddenContainer::destroy(); +} + +void RenderSVGResourceContainer::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderSVGHiddenContainer::styleDidChange(diff, oldStyle); + + if (!m_registered) { + m_registered = true; + registerResource(); + } +} + +void RenderSVGResourceContainer::idChanged() +{ + // Invalidate all our current clients. + removeAllClientsFromCache(); + + // Remove old id, that is guaranteed to be present in cache. + SVGDocumentExtensions* extensions = svgExtensionsFromNode(node()); + extensions->removeResource(m_id); + m_id = static_cast<Element*>(node())->getIdAttribute(); + + registerResource(); +} + +void RenderSVGResourceContainer::markAllClientsForInvalidation(InvalidationMode mode) +{ + if (m_clients.isEmpty()) + return; + + bool needsLayout = mode == LayoutAndBoundariesInvalidation; + bool markForInvalidation = mode != ParentOnlyInvalidation; + + HashSet<RenderObject*>::iterator end = m_clients.end(); + for (HashSet<RenderObject*>::iterator it = m_clients.begin(); it != end; ++it) { + RenderObject* client = *it; + if (client->isSVGResourceContainer()) { + client->toRenderSVGResourceContainer()->removeAllClientsFromCache(markForInvalidation); + continue; + } + + if (markForInvalidation) + markClientForInvalidation(client, mode); + + if (needsLayout) + client->setNeedsLayout(true); + + // Invalidate resources in ancestor chain, if needed. + RenderObject* current = client->parent(); + while (current) { + if (current->isSVGResourceContainer()) { + current->toRenderSVGResourceContainer()->removeAllClientsFromCache(markForInvalidation); + break; + } + + current = current->parent(); + } + } +} + +void RenderSVGResourceContainer::markClientForInvalidation(RenderObject* client, InvalidationMode mode) +{ + ASSERT(client); + ASSERT(!m_clients.isEmpty()); + + switch (mode) { + case LayoutAndBoundariesInvalidation: + case BoundariesInvalidation: + client->setNeedsBoundariesUpdate(); + break; + case RepaintInvalidation: + if (client->view()) + client->repaint(); + break; + case ParentOnlyInvalidation: + break; + } +} + +void RenderSVGResourceContainer::addClient(RenderObject* client) +{ + ASSERT(client); + m_clients.add(client); +} + +void RenderSVGResourceContainer::removeClient(RenderObject* client) +{ + ASSERT(client); + m_clients.remove(client); +} + +void RenderSVGResourceContainer::registerResource() +{ + SVGDocumentExtensions* extensions = svgExtensionsFromNode(node()); + if (!extensions->isPendingResource(m_id)) { + extensions->addResource(m_id, this); + return; + } + + OwnPtr<SVGDocumentExtensions::SVGPendingElements> clients(extensions->removePendingResource(m_id)); + + // Cache us with the new id. + extensions->addResource(m_id, this); + + // Update cached resources of pending clients. + const SVGDocumentExtensions::SVGPendingElements::const_iterator end = clients->end(); + for (SVGDocumentExtensions::SVGPendingElements::const_iterator it = clients->begin(); it != end; ++it) { + RenderObject* renderer = (*it)->renderer(); + if (!renderer) + continue; + SVGResourcesCache::clientUpdatedFromElement(renderer, renderer->style()); + renderer->setNeedsLayout(true); + } +} + +// FIXME: This does not belong here. +AffineTransform RenderSVGResourceContainer::transformOnNonScalingStroke(RenderObject* object, const AffineTransform& resourceTransform) +{ + if (!object->isSVGPath()) + return resourceTransform; + + SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(object->node()); + AffineTransform transform = resourceTransform; + transform.multiply(element->getScreenCTM(SVGLocatable::DisallowStyleUpdate)); + return transform; +} + +} + +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourceContainer.h b/Source/WebCore/rendering/RenderSVGResourceContainer.h new file mode 100644 index 0000000..4e5e475 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceContainer.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSVGResourceContainer_h +#define RenderSVGResourceContainer_h + +#if ENABLE(SVG) +#include "RenderSVGHiddenContainer.h" +#include "RenderSVGResource.h" + +namespace WebCore { + +class RenderSVGResourceContainer : public RenderSVGHiddenContainer, + public RenderSVGResource { +public: + RenderSVGResourceContainer(SVGStyledElement*); + virtual ~RenderSVGResourceContainer(); + + virtual void layout(); + virtual void destroy(); + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + virtual bool isSVGResourceContainer() const { return true; } + virtual RenderSVGResourceContainer* toRenderSVGResourceContainer() { return this; } + + static AffineTransform transformOnNonScalingStroke(RenderObject*, const AffineTransform& resourceTransform); + + void idChanged(); + +protected: + enum InvalidationMode { + LayoutAndBoundariesInvalidation, + BoundariesInvalidation, + RepaintInvalidation, + ParentOnlyInvalidation + }; + + // Used from the invalidateClient/invalidateClients methods from classes, inheriting from us. + void markAllClientsForInvalidation(InvalidationMode); + void markClientForInvalidation(RenderObject*, InvalidationMode); + +private: + friend class SVGResourcesCache; + void addClient(RenderObject*); + void removeClient(RenderObject*); + +private: + void registerResource(); + + AtomicString m_id; + bool m_registered; + HashSet<RenderObject*> m_clients; +}; + +inline RenderSVGResourceContainer* getRenderSVGResourceContainerById(Document* document, const AtomicString& id) +{ + if (id.isEmpty()) + return 0; + + if (RenderSVGResourceContainer* renderResource = document->accessSVGExtensions()->resourceById(id)) + return renderResource; + + return 0; +} + +template<typename Renderer> +Renderer* getRenderSVGResourceById(Document* document, const AtomicString& id) +{ + if (RenderSVGResourceContainer* container = getRenderSVGResourceContainerById(document, id)) + return container->cast<Renderer>(); + + return 0; +} + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourceFilter.cpp b/Source/WebCore/rendering/RenderSVGResourceFilter.cpp new file mode 100644 index 0000000..8aa9370 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceFilter.cpp @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2004, 2005, 2006, 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) 2004, 2005 Rob Buis <buis@kde.org> + * Copyright (C) 2005 Eric Seidel <eric@webkit.org> + * Copyright (C) 2009 Dirk Schulze <krit@webkit.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) && ENABLE(FILTERS) +#include "RenderSVGResourceFilter.h" + +#include "AffineTransform.h" +#include "FloatPoint.h" +#include "FloatRect.h" +#include "GraphicsContext.h" +#include "Image.h" +#include "ImageBuffer.h" +#include "ImageData.h" +#include "IntRect.h" +#include "RenderSVGResource.h" +#include "RenderSVGResourceFilterPrimitive.h" +#include "SVGElement.h" +#include "SVGFilter.h" +#include "SVGFilterElement.h" +#include "SVGFilterPrimitiveStandardAttributes.h" +#include "SVGImageBufferTools.h" +#include "SVGNames.h" +#include "SVGStyledElement.h" +#include "SVGUnitTypes.h" +#include <wtf/Vector.h> +#include <wtf/UnusedParam.h> + +static const float kMaxFilterSize = 5000.0f; + +using namespace std; + +namespace WebCore { + +RenderSVGResourceType RenderSVGResourceFilter::s_resourceType = FilterResourceType; + +RenderSVGResourceFilter::RenderSVGResourceFilter(SVGFilterElement* node) + : RenderSVGResourceContainer(node) +{ +} + +RenderSVGResourceFilter::~RenderSVGResourceFilter() +{ + if (m_filter.isEmpty()) + return; + + deleteAllValues(m_filter); + m_filter.clear(); +} + +void RenderSVGResourceFilter::removeAllClientsFromCache(bool markForInvalidation) +{ + if (!m_filter.isEmpty()) { + deleteAllValues(m_filter); + m_filter.clear(); + } + + markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation); +} + +void RenderSVGResourceFilter::removeClientFromCache(RenderObject* client, bool markForInvalidation) +{ + ASSERT(client); + + if (m_filter.contains(client)) + delete m_filter.take(client); + + markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation); +} + +PassRefPtr<SVGFilterBuilder> RenderSVGResourceFilter::buildPrimitives(Filter* filter) +{ + SVGFilterElement* filterElement = static_cast<SVGFilterElement*>(node()); + bool primitiveBoundingBoxMode = filterElement->primitiveUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX; + + // Add effects to the builder + RefPtr<SVGFilterBuilder> builder = SVGFilterBuilder::create(filter); + for (Node* node = filterElement->firstChild(); node; node = node->nextSibling()) { + if (!node->isSVGElement()) + continue; + + SVGElement* element = static_cast<SVGElement*>(node); + if (!element->isFilterEffect()) + continue; + + SVGFilterPrimitiveStandardAttributes* effectElement = static_cast<SVGFilterPrimitiveStandardAttributes*>(element); + RefPtr<FilterEffect> effect = effectElement->build(builder.get(), filter); + if (!effect) { + builder->clearEffects(); + return 0; + } + builder->appendEffectToEffectReferences(effect); + effectElement->setStandardAttributes(primitiveBoundingBoxMode, effect.get()); + builder->add(effectElement->result(), effect); + } + return builder.release(); +} + +bool RenderSVGResourceFilter::fitsInMaximumImageSize(const FloatSize& size, FloatSize& scale) +{ + bool matchesFilterSize = true; + if (size.width() > kMaxFilterSize) { + scale.setWidth(scale.width() * kMaxFilterSize / size.width()); + matchesFilterSize = false; + } + if (size.height() > kMaxFilterSize) { + scale.setHeight(scale.height() * kMaxFilterSize / size.height()); + matchesFilterSize = false; + } + + return matchesFilterSize; +} + +bool RenderSVGResourceFilter::applyResource(RenderObject* object, RenderStyle*, GraphicsContext*& context, unsigned short resourceMode) +{ + ASSERT(object); + ASSERT(context); +#ifndef NDEBUG + ASSERT(resourceMode == ApplyToDefaultMode); +#else + UNUSED_PARAM(resourceMode); +#endif + + // Returning false here, to avoid drawings onto the context. We just want to + // draw the stored filter output, not the unfiltered object as well. + if (m_filter.contains(object)) { + FilterData* filterData = m_filter.get(object); + if (filterData->builded) + return false; + + delete m_filter.take(object); // Oops, have to rebuild, go through normal code path + } + + OwnPtr<FilterData> filterData(new FilterData); + FloatRect targetBoundingBox = object->objectBoundingBox(); + + SVGFilterElement* filterElement = static_cast<SVGFilterElement*>(node()); + filterData->boundaries = filterElement->filterBoundingBox(targetBoundingBox); + if (filterData->boundaries.isEmpty()) + return false; + + // Determine absolute transformation matrix for filter. + AffineTransform absoluteTransform; + SVGImageBufferTools::calculateTransformationToOutermostSVGCoordinateSystem(object, absoluteTransform); + if (!absoluteTransform.isInvertible()) + return false; + + // Eliminate shear of the absolute transformation matrix, to be able to produce unsheared tile images for feTile. + filterData->shearFreeAbsoluteTransform = AffineTransform(absoluteTransform.xScale(), 0, 0, absoluteTransform.yScale(), absoluteTransform.e(), absoluteTransform.f()); + + // Determine absolute boundaries of the filter and the drawing region. + FloatRect absoluteFilterBoundaries = filterData->shearFreeAbsoluteTransform.mapRect(filterData->boundaries); + FloatRect drawingRegion = object->strokeBoundingBox(); + drawingRegion.intersect(filterData->boundaries); + FloatRect absoluteDrawingRegion = filterData->shearFreeAbsoluteTransform.mapRect(drawingRegion); + + // Create the SVGFilter object. + bool primitiveBoundingBoxMode = filterElement->primitiveUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX; + filterData->filter = SVGFilter::create(filterData->shearFreeAbsoluteTransform, absoluteDrawingRegion, targetBoundingBox, filterData->boundaries, primitiveBoundingBoxMode); + + // Create all relevant filter primitives. + filterData->builder = buildPrimitives(filterData->filter.get()); + if (!filterData->builder) + return false; + + // Calculate the scale factor for the use of filterRes. + // Also see http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion + FloatSize scale(1, 1); + if (filterElement->hasAttribute(SVGNames::filterResAttr)) { + scale.setWidth(filterElement->filterResX() / absoluteFilterBoundaries.width()); + scale.setHeight(filterElement->filterResY() / absoluteFilterBoundaries.height()); + } + + if (scale.isEmpty()) + return false; + + // Determine scale factor for filter. The size of intermediate ImageBuffers shouldn't be bigger than kMaxFilterSize. + FloatRect tempSourceRect = absoluteDrawingRegion; + tempSourceRect.scale(scale.width(), scale.height()); + fitsInMaximumImageSize(tempSourceRect.size(), scale); + + // Set the scale level in SVGFilter. + filterData->filter->setFilterResolution(scale); + + FilterEffect* lastEffect = filterData->builder->lastEffect(); + if (!lastEffect) + return false; + + RenderSVGResourceFilterPrimitive::determineFilterPrimitiveSubregion(lastEffect, filterData->filter.get()); + FloatRect subRegion = lastEffect->maxEffectRect(); + // At least one FilterEffect has a too big image size, + // recalculate the effect sizes with new scale factors. + if (!fitsInMaximumImageSize(subRegion.size(), scale)) { + filterData->filter->setFilterResolution(scale); + RenderSVGResourceFilterPrimitive::determineFilterPrimitiveSubregion(lastEffect, filterData->filter.get()); + } + + // If the drawingRegion is empty, we have something like <g filter=".."/>. + // Even if the target objectBoundingBox() is empty, we still have to draw the last effect result image in postApplyResource. + if (drawingRegion.isEmpty()) { + ASSERT(!m_filter.contains(object)); + filterData->savedContext = context; + m_filter.set(object, filterData.leakPtr()); + return false; + } + + absoluteDrawingRegion.scale(scale.width(), scale.height()); + + OwnPtr<ImageBuffer> sourceGraphic; + if (!SVGImageBufferTools::createImageBuffer(absoluteDrawingRegion, absoluteDrawingRegion, sourceGraphic, ColorSpaceLinearRGB)) { + ASSERT(!m_filter.contains(object)); + filterData->savedContext = context; + m_filter.set(object, filterData.leakPtr()); + return false; + } + + GraphicsContext* sourceGraphicContext = sourceGraphic->context(); + ASSERT(sourceGraphicContext); + + sourceGraphicContext->translate(-absoluteDrawingRegion.x(), -absoluteDrawingRegion.y()); + if (scale.width() != 1 || scale.height() != 1) + sourceGraphicContext->scale(scale); + + sourceGraphicContext->concatCTM(filterData->shearFreeAbsoluteTransform); + sourceGraphicContext->clearRect(FloatRect(FloatPoint(), absoluteDrawingRegion.size())); + filterData->sourceGraphicBuffer = sourceGraphic.release(); + filterData->savedContext = context; + + context = sourceGraphicContext; + + ASSERT(!m_filter.contains(object)); + m_filter.set(object, filterData.leakPtr()); + + return true; +} + +void RenderSVGResourceFilter::postApplyResource(RenderObject* object, GraphicsContext*& context, unsigned short resourceMode, const Path*) +{ + ASSERT(object); + ASSERT(context); +#ifndef NDEBUG + ASSERT(resourceMode == ApplyToDefaultMode); +#else + UNUSED_PARAM(resourceMode); +#endif + + if (!m_filter.contains(object)) + return; + + FilterData* filterData = m_filter.get(object); + if (!filterData->builded) { + if (!filterData->savedContext) { + removeClientFromCache(object); + return; + } + + context = filterData->savedContext; + filterData->savedContext = 0; +#if !PLATFORM(CG) + if (filterData->sourceGraphicBuffer) + filterData->sourceGraphicBuffer->transformColorSpace(ColorSpaceDeviceRGB, ColorSpaceLinearRGB); +#endif + } + + FilterEffect* lastEffect = filterData->builder->lastEffect(); + + if (lastEffect && !filterData->boundaries.isEmpty() && !lastEffect->filterPrimitiveSubregion().isEmpty()) { + // This is the real filtering of the object. It just needs to be called on the + // initial filtering process. We just take the stored filter result on a + // second drawing. + if (!filterData->builded) { + filterData->filter->setSourceImage(filterData->sourceGraphicBuffer.release()); + lastEffect->apply(); +#if !PLATFORM(CG) + ImageBuffer* resultImage = lastEffect->asImageBuffer(); + if (resultImage) + resultImage->transformColorSpace(ColorSpaceLinearRGB, ColorSpaceDeviceRGB); +#endif + filterData->builded = true; + } + + ImageBuffer* resultImage = lastEffect->asImageBuffer(); + if (resultImage) { + context->concatCTM(filterData->shearFreeAbsoluteTransform.inverse()); + + context->scale(FloatSize(1 / filterData->filter->filterResolution().width(), 1 / filterData->filter->filterResolution().height())); + context->clip(lastEffect->maxEffectRect()); + context->drawImageBuffer(resultImage, object->style()->colorSpace(), lastEffect->absolutePaintRect()); + context->scale(filterData->filter->filterResolution()); + + context->concatCTM(filterData->shearFreeAbsoluteTransform); + } + } + filterData->sourceGraphicBuffer.clear(); +} + +FloatRect RenderSVGResourceFilter::resourceBoundingBox(RenderObject* object) +{ + if (SVGFilterElement* element = static_cast<SVGFilterElement*>(node())) + return element->filterBoundingBox(object->objectBoundingBox()); + + return FloatRect(); +} + +} +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourceFilter.h b/Source/WebCore/rendering/RenderSVGResourceFilter.h new file mode 100644 index 0000000..5950c44 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceFilter.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2004, 2005, 2006, 2007 Nikolas Zimmermann <zimmermann@kde.org> + * 2004, 2005 Rob Buis <buis@kde.org> + * 2005 Eric Seidel <eric@webkit.org> + * 2009 Dirk Schulze <krit@webkit.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSVGResourceFilter_h +#define RenderSVGResourceFilter_h + +#if ENABLE(SVG) && ENABLE(FILTERS) +#include "FloatRect.h" +#include "ImageBuffer.h" +#include "RenderSVGResourceContainer.h" +#include "SVGFilter.h" +#include "SVGFilterBuilder.h" +#include "SVGFilterElement.h" +#include "SVGUnitTypes.h" + +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +struct FilterData { + FilterData() + : savedContext(0) + , builded(false) + { + } + + RefPtr<SVGFilter> filter; + RefPtr<SVGFilterBuilder> builder; + OwnPtr<ImageBuffer> sourceGraphicBuffer; + GraphicsContext* savedContext; + AffineTransform shearFreeAbsoluteTransform; + FloatRect boundaries; + FloatSize scale; + bool builded; +}; + +class GraphicsContext; + +class RenderSVGResourceFilter : public RenderSVGResourceContainer { +public: + RenderSVGResourceFilter(SVGFilterElement*); + virtual ~RenderSVGResourceFilter(); + + virtual const char* renderName() const { return "RenderSVGResourceFilter"; } + + virtual void removeAllClientsFromCache(bool markForInvalidation = true); + virtual void removeClientFromCache(RenderObject*, bool markForInvalidation = true); + + virtual bool applyResource(RenderObject*, RenderStyle*, GraphicsContext*&, unsigned short resourceMode); + virtual void postApplyResource(RenderObject*, GraphicsContext*&, unsigned short resourceMode, const Path*); + + virtual FloatRect resourceBoundingBox(RenderObject*); + + PassRefPtr<SVGFilterBuilder> buildPrimitives(Filter*); + + SVGUnitTypes::SVGUnitType filterUnits() const { return toUnitType(static_cast<SVGFilterElement*>(node())->filterUnits()); } + SVGUnitTypes::SVGUnitType primitiveUnits() const { return toUnitType(static_cast<SVGFilterElement*>(node())->primitiveUnits()); } + + virtual RenderSVGResourceType resourceType() const { return s_resourceType; } + static RenderSVGResourceType s_resourceType; + +private: + bool fitsInMaximumImageSize(const FloatSize&, FloatSize&); + + HashMap<RenderObject*, FilterData*> m_filter; +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourceFilterPrimitive.cpp b/Source/WebCore/rendering/RenderSVGResourceFilterPrimitive.cpp new file mode 100644 index 0000000..bf2ef6e --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceFilterPrimitive.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2010 University of Szeged + * Copyright (C) 2010 Zoltan Herczeg + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF SZEGED ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UNIVERSITY OF SZEGED OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if ENABLE(SVG) && ENABLE(FILTERS) +#include "RenderSVGResourceFilterPrimitive.h" +#include "SVGFEImage.h" + +namespace WebCore { + +FloatRect RenderSVGResourceFilterPrimitive::determineFilterPrimitiveSubregion(FilterEffect* effect, SVGFilter* filter) +{ + FloatRect uniteRect; + FloatRect subregionBoundingBox = effect->effectBoundaries(); + FloatRect subregion = subregionBoundingBox; + + if (effect->filterEffectType() != FilterEffectTypeTile) { + // FETurbulence, FEImage and FEFlood don't have input effects, take the filter region as unite rect. + if (unsigned numberOfInputEffects = effect->inputEffects().size()) { + for (unsigned i = 0; i < numberOfInputEffects; ++i) + uniteRect.unite(determineFilterPrimitiveSubregion(effect->inputEffect(i), filter)); + } else + uniteRect = filter->filterRegionInUserSpace(); + } else { + determineFilterPrimitiveSubregion(effect->inputEffect(0), filter); + uniteRect = filter->filterRegionInUserSpace(); + } + + if (filter->effectBoundingBoxMode()) { + subregion = uniteRect; + // Avoid the calling of a virtual method several times. + FloatRect targetBoundingBox = filter->targetBoundingBox(); + + if (effect->hasX()) + subregion.setX(targetBoundingBox.x() + subregionBoundingBox.x() * targetBoundingBox.width()); + + if (effect->hasY()) + subregion.setY(targetBoundingBox.y() + subregionBoundingBox.y() * targetBoundingBox.height()); + + if (effect->hasWidth()) + subregion.setWidth(subregionBoundingBox.width() * targetBoundingBox.width()); + + if (effect->hasHeight()) + subregion.setHeight(subregionBoundingBox.height() * targetBoundingBox.height()); + } else { + if (!effect->hasX()) + subregion.setX(uniteRect.x()); + + if (!effect->hasY()) + subregion.setY(uniteRect.y()); + + if (!effect->hasWidth()) + subregion.setWidth(uniteRect.width()); + + if (!effect->hasHeight()) + subregion.setHeight(uniteRect.height()); + } + + effect->setFilterPrimitiveSubregion(subregion); + + FloatRect absoluteSubregion = filter->mapLocalRectToAbsoluteRect(subregion); + FloatSize filterResolution = filter->filterResolution(); + absoluteSubregion.scale(filterResolution.width(), filterResolution.height()); + + // FEImage needs the unclipped subregion in absolute coordinates to determine the correct + // destination rect in combination with preserveAspectRatio. + if (effect->filterEffectType() == FilterEffectTypeImage) + reinterpret_cast<FEImage*>(effect)->setAbsoluteSubregion(absoluteSubregion); + + // Clip every filter effect to the filter region. + FloatRect absoluteScaledFilterRegion = filter->filterRegion(); + absoluteScaledFilterRegion.scale(filterResolution.width(), filterResolution.height()); + absoluteSubregion.intersect(absoluteScaledFilterRegion); + + effect->setMaxEffectRect(enclosingIntRect(absoluteSubregion)); + return subregion; +} + +} // namespace WebCore + +#endif // ENABLE(SVG) && ENABLE(FILTERS) diff --git a/Source/WebCore/rendering/RenderSVGResourceFilterPrimitive.h b/Source/WebCore/rendering/RenderSVGResourceFilterPrimitive.h new file mode 100644 index 0000000..c890ffe --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceFilterPrimitive.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2010 University of Szeged + * Copyright (C) 2010 Zoltan Herczeg + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF SZEGED ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UNIVERSITY OF SZEGED OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderSVGResourceFilterPrimitive_h +#define RenderSVGResourceFilterPrimitive_h + +#if ENABLE(SVG) && ENABLE(FILTERS) + +#include "RenderSVGHiddenContainer.h" +#include "SVGFilterPrimitiveStandardAttributes.h" +#include "SVGFilter.h" + +namespace WebCore { + +class RenderSVGResourceFilterPrimitive : public RenderSVGHiddenContainer { +public: + + explicit RenderSVGResourceFilterPrimitive(SVGFilterPrimitiveStandardAttributes* filterPrimitiveElement) + : RenderSVGHiddenContainer(filterPrimitiveElement) + { + } + + // They depend on the RenderObject argument of RenderSVGResourceFilter::applyResource. + static FloatRect determineFilterPrimitiveSubregion(FilterEffect* effect, SVGFilter* filter); + +private: + virtual const char* renderName() const { return "RenderSVGResourceFilterPrimitive"; } + virtual bool isSVGResourceFilterPrimitive() const { return true; } +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) && ENABLE(FILTERS) + +#endif // RenderSVGResourceFilterPrimitive_h diff --git a/Source/WebCore/rendering/RenderSVGResourceGradient.cpp b/Source/WebCore/rendering/RenderSVGResourceGradient.cpp new file mode 100644 index 0000000..7c383d0 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceGradient.cpp @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) 2008 Eric Seidel <eric@webkit.org> + * Copyright (C) 2008 Dirk Schulze <krit@webkit.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGResourceGradient.h" + +#include "GradientAttributes.h" +#include "GraphicsContext.h" +#include "RenderSVGText.h" +#include "SVGImageBufferTools.h" +#include "SVGRenderSupport.h" +#include <wtf/UnusedParam.h> + +namespace WebCore { + +RenderSVGResourceGradient::RenderSVGResourceGradient(SVGGradientElement* node) + : RenderSVGResourceContainer(node) + , m_shouldCollectGradientAttributes(true) +#if PLATFORM(CG) + , m_savedContext(0) +#endif +{ +} + +RenderSVGResourceGradient::~RenderSVGResourceGradient() +{ + if (m_gradient.isEmpty()) + return; + + deleteAllValues(m_gradient); + m_gradient.clear(); +} + +void RenderSVGResourceGradient::removeAllClientsFromCache(bool markForInvalidation) +{ + if (!m_gradient.isEmpty()) { + deleteAllValues(m_gradient); + m_gradient.clear(); + } + + m_shouldCollectGradientAttributes = true; + markAllClientsForInvalidation(markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation); +} + +void RenderSVGResourceGradient::removeClientFromCache(RenderObject* client, bool markForInvalidation) +{ + ASSERT(client); + + if (m_gradient.contains(client)) + delete m_gradient.take(client); + + markClientForInvalidation(client, markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation); +} + +#if PLATFORM(CG) +static inline bool createMaskAndSwapContextForTextGradient(GraphicsContext*& context, + GraphicsContext*& savedContext, + OwnPtr<ImageBuffer>& imageBuffer, + RenderObject* object) +{ + RenderObject* textRootBlock = RenderSVGText::locateRenderSVGTextAncestor(object); + ASSERT(textRootBlock); + + AffineTransform absoluteTransform; + SVGImageBufferTools::calculateTransformationToOutermostSVGCoordinateSystem(textRootBlock, absoluteTransform); + + FloatRect absoluteTargetRect = absoluteTransform.mapRect(textRootBlock->repaintRectInLocalCoordinates()); + FloatRect clampedAbsoluteTargetRect = SVGImageBufferTools::clampedAbsoluteTargetRectForRenderer(textRootBlock, absoluteTargetRect); + if (clampedAbsoluteTargetRect.isEmpty()) + return false; + + OwnPtr<ImageBuffer> maskImage; + if (!SVGImageBufferTools::createImageBuffer(absoluteTargetRect, clampedAbsoluteTargetRect, maskImage, ColorSpaceDeviceRGB)) + return false; + + GraphicsContext* maskImageContext = maskImage->context(); + ASSERT(maskImageContext); + + maskImageContext->translate(-clampedAbsoluteTargetRect.x(), -clampedAbsoluteTargetRect.y()); + maskImageContext->concatCTM(absoluteTransform); + + ASSERT(maskImage); + savedContext = context; + context = maskImageContext; + imageBuffer = maskImage.release(); + return true; +} + +static inline AffineTransform clipToTextMask(GraphicsContext* context, + OwnPtr<ImageBuffer>& imageBuffer, + FloatRect& targetRect, + RenderObject* object, + bool boundingBoxMode, + const AffineTransform& gradientTransform) +{ + RenderObject* textRootBlock = RenderSVGText::locateRenderSVGTextAncestor(object); + ASSERT(textRootBlock); + + targetRect = textRootBlock->repaintRectInLocalCoordinates(); + + AffineTransform absoluteTransform; + SVGImageBufferTools::calculateTransformationToOutermostSVGCoordinateSystem(textRootBlock, absoluteTransform); + + FloatRect absoluteTargetRect = absoluteTransform.mapRect(targetRect); + FloatRect clampedAbsoluteTargetRect = SVGImageBufferTools::clampedAbsoluteTargetRectForRenderer(textRootBlock, absoluteTargetRect); + + SVGImageBufferTools::clipToImageBuffer(context, absoluteTransform, clampedAbsoluteTargetRect, imageBuffer); + + AffineTransform matrix; + if (boundingBoxMode) { + FloatRect maskBoundingBox = textRootBlock->objectBoundingBox(); + matrix.translate(maskBoundingBox.x(), maskBoundingBox.y()); + matrix.scaleNonUniform(maskBoundingBox.width(), maskBoundingBox.height()); + } + matrix.multLeft(gradientTransform); + return matrix; +} +#endif + +bool RenderSVGResourceGradient::applyResource(RenderObject* object, RenderStyle* style, GraphicsContext*& context, unsigned short resourceMode) +{ + ASSERT(object); + ASSERT(style); + ASSERT(context); + ASSERT(resourceMode != ApplyToDefaultMode); + + // Be sure to synchronize all SVG properties on the gradientElement _before_ processing any further. + // Otherwhise the call to collectGradientAttributes() in createTileImage(), may cause the SVG DOM property + // synchronization to kick in, which causes removeAllClientsFromCache() to be called, which in turn deletes our + // GradientData object! Leaving out the line below will cause svg/dynamic-updates/SVG*GradientElement-svgdom* to crash. + SVGGradientElement* gradientElement = static_cast<SVGGradientElement*>(node()); + if (!gradientElement) + return false; + + if (m_shouldCollectGradientAttributes) { + gradientElement->updateAnimatedSVGAttribute(anyQName()); + collectGradientAttributes(gradientElement); + m_shouldCollectGradientAttributes = false; + } + + // Spec: When the geometry of the applicable element has no width or height and objectBoundingBox is specified, + // then the given effect (e.g. a gradient or a filter) will be ignored. + FloatRect objectBoundingBox = object->objectBoundingBox(); + if (boundingBoxMode() && objectBoundingBox.isEmpty()) + return false; + + if (!m_gradient.contains(object)) + m_gradient.set(object, new GradientData); + + GradientData* gradientData = m_gradient.get(object); + bool isPaintingText = resourceMode & ApplyToTextMode; + + // Create gradient object + if (!gradientData->gradient) { + buildGradient(gradientData, gradientElement); + + // CG platforms will handle the gradient space transform for text after applying the + // resource, so don't apply it here. For non-CG platforms, we want the text bounding + // box applied to the gradient space transform now, so the gradient shader can use it. +#if PLATFORM(CG) + if (boundingBoxMode() && !objectBoundingBox.isEmpty() && !isPaintingText) { +#else + if (boundingBoxMode() && !objectBoundingBox.isEmpty()) { +#endif + gradientData->userspaceTransform.translate(objectBoundingBox.x(), objectBoundingBox.y()); + gradientData->userspaceTransform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); + } + + AffineTransform gradientTransform; + calculateGradientTransform(gradientTransform); + + gradientData->userspaceTransform.multLeft(gradientTransform); + gradientData->gradient->setGradientSpaceTransform(gradientData->userspaceTransform); + } + + if (!gradientData->gradient) + return false; + + // Draw gradient + context->save(); + + if (isPaintingText) { +#if PLATFORM(CG) + if (!createMaskAndSwapContextForTextGradient(context, m_savedContext, m_imageBuffer, object)) { + context->restore(); + return false; + } +#endif + + context->setTextDrawingMode(resourceMode & ApplyToFillMode ? TextModeFill : TextModeStroke); + } + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + + if (resourceMode & ApplyToFillMode) { + context->setAlpha(svgStyle->fillOpacity()); + context->setFillGradient(gradientData->gradient); + context->setFillRule(svgStyle->fillRule()); + } else if (resourceMode & ApplyToStrokeMode) { + if (svgStyle->vectorEffect() == VE_NON_SCALING_STROKE) + gradientData->gradient->setGradientSpaceTransform(transformOnNonScalingStroke(object, gradientData->userspaceTransform)); + context->setAlpha(svgStyle->strokeOpacity()); + context->setStrokeGradient(gradientData->gradient); + SVGRenderSupport::applyStrokeStyleToContext(context, style, object); + } + + return true; +} + +void RenderSVGResourceGradient::postApplyResource(RenderObject* object, GraphicsContext*& context, unsigned short resourceMode, const Path* path) +{ + ASSERT(context); + ASSERT(resourceMode != ApplyToDefaultMode); + + if (resourceMode & ApplyToTextMode) { +#if PLATFORM(CG) + // CG requires special handling for gradient on text + if (m_savedContext && m_gradient.contains(object)) { + GradientData* gradientData = m_gradient.get(object); + + // Restore on-screen drawing context + context = m_savedContext; + m_savedContext = 0; + + AffineTransform gradientTransform; + calculateGradientTransform(gradientTransform); + + FloatRect targetRect; + gradientData->gradient->setGradientSpaceTransform(clipToTextMask(context, m_imageBuffer, targetRect, object, boundingBoxMode(), gradientTransform)); + context->setFillGradient(gradientData->gradient); + + context->fillRect(targetRect); + m_imageBuffer.clear(); + } +#else + UNUSED_PARAM(object); +#endif + } else if (path) { + if (resourceMode & ApplyToFillMode) + context->fillPath(*path); + else if (resourceMode & ApplyToStrokeMode) + context->strokePath(*path); + } + + context->restore(); +} + +void RenderSVGResourceGradient::addStops(GradientData* gradientData, const Vector<Gradient::ColorStop>& stops) const +{ + ASSERT(gradientData->gradient); + + const Vector<Gradient::ColorStop>::const_iterator end = stops.end(); + for (Vector<Gradient::ColorStop>::const_iterator it = stops.begin(); it != end; ++it) + gradientData->gradient->addColorStop(*it); +} + +} + +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourceGradient.h b/Source/WebCore/rendering/RenderSVGResourceGradient.h new file mode 100644 index 0000000..1c7f52d --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceGradient.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> + * 2008 Eric Seidel <eric@webkit.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSVGResourceGradient_h +#define RenderSVGResourceGradient_h + +#if ENABLE(SVG) +#include "AffineTransform.h" +#include "FloatRect.h" +#include "Gradient.h" +#include "ImageBuffer.h" +#include "RenderSVGResourceContainer.h" +#include "SVGGradientElement.h" + +#include <wtf/HashMap.h> + +namespace WebCore { + +struct GradientData { + RefPtr<Gradient> gradient; + AffineTransform userspaceTransform; +}; + +class GraphicsContext; + +class RenderSVGResourceGradient : public RenderSVGResourceContainer { +public: + RenderSVGResourceGradient(SVGGradientElement*); + virtual ~RenderSVGResourceGradient(); + + virtual void removeAllClientsFromCache(bool markForInvalidation = true); + virtual void removeClientFromCache(RenderObject*, bool markForInvalidation = true); + + virtual bool applyResource(RenderObject*, RenderStyle*, GraphicsContext*&, unsigned short resourceMode); + virtual void postApplyResource(RenderObject*, GraphicsContext*&, unsigned short resourceMode, const Path*); + virtual FloatRect resourceBoundingBox(RenderObject*) { return FloatRect(); } + +protected: + void addStops(GradientData*, const Vector<Gradient::ColorStop>&) const; + + virtual bool boundingBoxMode() const = 0; + virtual void calculateGradientTransform(AffineTransform&) = 0; + virtual void collectGradientAttributes(SVGGradientElement*) = 0; + virtual void buildGradient(GradientData*, SVGGradientElement*) const = 0; + +private: + bool m_shouldCollectGradientAttributes : 1; + HashMap<RenderObject*, GradientData*> m_gradient; + +#if PLATFORM(CG) + GraphicsContext* m_savedContext; + OwnPtr<ImageBuffer> m_imageBuffer; +#endif +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourceLinearGradient.cpp b/Source/WebCore/rendering/RenderSVGResourceLinearGradient.cpp new file mode 100644 index 0000000..ce8d69e --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceLinearGradient.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGResourceLinearGradient.h" + +#include "LinearGradientAttributes.h" +#include "SVGLinearGradientElement.h" + +namespace WebCore { + +RenderSVGResourceType RenderSVGResourceLinearGradient::s_resourceType = LinearGradientResourceType; + +RenderSVGResourceLinearGradient::RenderSVGResourceLinearGradient(SVGLinearGradientElement* node) + : RenderSVGResourceGradient(node) +{ +} + +RenderSVGResourceLinearGradient::~RenderSVGResourceLinearGradient() +{ +} + +void RenderSVGResourceLinearGradient::collectGradientAttributes(SVGGradientElement* gradientElement) +{ + m_attributes = LinearGradientAttributes(); + static_cast<SVGLinearGradientElement*>(gradientElement)->collectGradientAttributes(m_attributes); +} + +void RenderSVGResourceLinearGradient::buildGradient(GradientData* gradientData, SVGGradientElement* gradientElement) const +{ + SVGLinearGradientElement* linearGradientElement = static_cast<SVGLinearGradientElement*>(gradientElement); + + // Determine gradient start/end points + FloatPoint startPoint; + FloatPoint endPoint; + linearGradientElement->calculateStartEndPoints(m_attributes, startPoint, endPoint); + + gradientData->gradient = Gradient::create(startPoint, endPoint); + gradientData->gradient->setSpreadMethod(m_attributes.spreadMethod()); + + // Add stops + addStops(gradientData, m_attributes.stops()); +} + +} + +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourceLinearGradient.h b/Source/WebCore/rendering/RenderSVGResourceLinearGradient.h new file mode 100644 index 0000000..9e4530d --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceLinearGradient.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSVGResourceLinearGradient_h +#define RenderSVGResourceLinearGradient_h + +#if ENABLE(SVG) +#include "LinearGradientAttributes.h" +#include "RenderSVGResourceGradient.h" + +namespace WebCore { + +class SVGLinearGradientElement; + +class RenderSVGResourceLinearGradient : public RenderSVGResourceGradient { +public: + RenderSVGResourceLinearGradient(SVGLinearGradientElement*); + virtual ~RenderSVGResourceLinearGradient(); + + virtual const char* renderName() const { return "RenderSVGResourceLinearGradient"; } + + virtual RenderSVGResourceType resourceType() const { return s_resourceType; } + static RenderSVGResourceType s_resourceType; + + virtual bool boundingBoxMode() const { return m_attributes.boundingBoxMode(); } + virtual void calculateGradientTransform(AffineTransform& transform) { transform = m_attributes.gradientTransform(); } + virtual void collectGradientAttributes(SVGGradientElement*); + virtual void buildGradient(GradientData*, SVGGradientElement*) const; + +private: + LinearGradientAttributes m_attributes; +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourceMarker.cpp b/Source/WebCore/rendering/RenderSVGResourceMarker.cpp new file mode 100644 index 0000000..1d5663b --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceMarker.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org> + * 2004, 2005, 2006, 2007, 2008 Rob Buis <buis@kde.org> + * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGResourceMarker.h" + +#include "GraphicsContext.h" +#include "RenderSVGContainer.h" +#include "SVGElement.h" +#include "SVGMarkerElement.h" +#include "SVGRenderSupport.h" +#include "SVGStyledElement.h" +#include "SVGStyledTransformableElement.h" + +namespace WebCore { + +RenderSVGResourceType RenderSVGResourceMarker::s_resourceType = MarkerResourceType; + +RenderSVGResourceMarker::RenderSVGResourceMarker(SVGMarkerElement* node) + : RenderSVGResourceContainer(node) +{ +} + +RenderSVGResourceMarker::~RenderSVGResourceMarker() +{ +} + +void RenderSVGResourceMarker::layout() +{ + // Invalidate all resources if our layout changed. + if (m_everHadLayout && selfNeedsLayout()) + removeAllClientsFromCache(); + + // RenderSVGHiddenContainer overwrites layout(). We need the + // layouting of RenderSVGContainer for calculating local + // transformations and repaint. + RenderSVGContainer::layout(); +} + +void RenderSVGResourceMarker::removeAllClientsFromCache(bool markForInvalidation) +{ + markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation); +} + +void RenderSVGResourceMarker::removeClientFromCache(RenderObject* client, bool markForInvalidation) +{ + ASSERT(client); + markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation); +} + +void RenderSVGResourceMarker::applyViewportClip(PaintInfo& paintInfo) +{ + if (SVGRenderSupport::isOverflowHidden(this)) + paintInfo.context->clip(m_viewport); +} + +FloatRect RenderSVGResourceMarker::markerBoundaries(const AffineTransform& markerTransformation) const +{ + FloatRect coordinates = RenderSVGContainer::repaintRectInLocalCoordinates(); + + // Map repaint rect into parent coordinate space, in which the marker boundaries have to be evaluated + coordinates = localToParentTransform().mapRect(coordinates); + + return markerTransformation.mapRect(coordinates); +} + +const AffineTransform& RenderSVGResourceMarker::localToParentTransform() const +{ + AffineTransform viewportTranslation(viewportTransform()); + m_localToParentTransform = viewportTranslation.translateRight(m_viewport.x(), m_viewport.y()); + return m_localToParentTransform; + // If this class were ever given a localTransform(), then the above would read: + // return viewportTransform() * localTransform() * viewportTranslation; +} + +FloatPoint RenderSVGResourceMarker::referencePoint() const +{ + SVGMarkerElement* marker = static_cast<SVGMarkerElement*>(node()); + ASSERT(marker); + + return FloatPoint(marker->refX().value(marker), marker->refY().value(marker)); +} + +float RenderSVGResourceMarker::angle() const +{ + SVGMarkerElement* marker = static_cast<SVGMarkerElement*>(node()); + ASSERT(marker); + + float angle = -1; + if (marker->orientType() == SVGMarkerElement::SVG_MARKER_ORIENT_ANGLE) + angle = marker->orientAngle().value(); + + return angle; +} + +AffineTransform RenderSVGResourceMarker::markerTransformation(const FloatPoint& origin, float autoAngle, float strokeWidth) const +{ + SVGMarkerElement* marker = static_cast<SVGMarkerElement*>(node()); + ASSERT(marker); + + float markerAngle = angle(); + bool useStrokeWidth = (marker->markerUnits() == SVGMarkerElement::SVG_MARKERUNITS_STROKEWIDTH); + + AffineTransform transform; + transform.translate(origin.x(), origin.y()); + transform.rotate(markerAngle == -1 ? autoAngle : markerAngle); + transform = markerContentTransformation(transform, referencePoint(), useStrokeWidth ? strokeWidth : -1); + return transform; +} + +void RenderSVGResourceMarker::draw(PaintInfo& paintInfo, const AffineTransform& transform) +{ + PaintInfo info(paintInfo); + info.context->save(); + info.applyTransform(transform); + RenderSVGContainer::paint(info, 0, 0); + info.context->restore(); +} + +AffineTransform RenderSVGResourceMarker::markerContentTransformation(const AffineTransform& contentTransformation, const FloatPoint& origin, float strokeWidth) const +{ + // The 'origin' coordinate maps to SVGs refX/refY, given in coordinates relative to the viewport established by the marker + FloatPoint mappedOrigin = viewportTransform().mapPoint(origin); + + AffineTransform transformation = contentTransformation; + if (strokeWidth != -1) + transformation.scaleNonUniform(strokeWidth, strokeWidth); + + transformation.translate(-mappedOrigin.x(), -mappedOrigin.y()); + return transformation; +} + +AffineTransform RenderSVGResourceMarker::viewportTransform() const +{ + SVGMarkerElement* marker = static_cast<SVGMarkerElement*>(node()); + ASSERT(marker); + + return marker->viewBoxToViewTransform(m_viewport.width(), m_viewport.height()); +} + +void RenderSVGResourceMarker::calcViewport() +{ + if (!selfNeedsLayout()) + return; + + SVGMarkerElement* marker = static_cast<SVGMarkerElement*>(node()); + ASSERT(marker); + + float w = marker->markerWidth().value(marker); + float h = marker->markerHeight().value(marker); + m_viewport = FloatRect(0, 0, w, h); +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/RenderSVGResourceMarker.h b/Source/WebCore/rendering/RenderSVGResourceMarker.h new file mode 100644 index 0000000..e41096e --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceMarker.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSVGResourceMarker_h +#define RenderSVGResourceMarker_h + +#if ENABLE(SVG) +#include "FloatRect.h" +#include "RenderObject.h" +#include "RenderSVGResourceContainer.h" +#include "SVGMarkerElement.h" +#include "SVGStyledElement.h" + +#include <wtf/HashSet.h> + +namespace WebCore { + +class AffineTransform; + +class RenderSVGResourceMarker : public RenderSVGResourceContainer { +public: + RenderSVGResourceMarker(SVGMarkerElement*); + virtual ~RenderSVGResourceMarker(); + + virtual const char* renderName() const { return "RenderSVGResourceMarker"; } + + virtual void removeAllClientsFromCache(bool markForInvalidation = true); + virtual void removeClientFromCache(RenderObject*, bool markForInvalidation = true); + + void draw(PaintInfo&, const AffineTransform&); + + // Calculates marker boundaries, mapped to the target element's coordinate space + FloatRect markerBoundaries(const AffineTransform& markerTransformation) const; + + virtual void applyViewportClip(PaintInfo&); + virtual void layout(); + virtual void calcViewport(); + + virtual const AffineTransform& localToParentTransform() const; + AffineTransform markerTransformation(const FloatPoint& origin, float angle, float strokeWidth) const; + + virtual bool applyResource(RenderObject*, RenderStyle*, GraphicsContext*&, unsigned short) { return false; } + virtual FloatRect resourceBoundingBox(RenderObject*) { return FloatRect(); } + + FloatPoint referencePoint() const; + float angle() const; + SVGMarkerElement::SVGMarkerUnitsType markerUnits() const { return static_cast<SVGMarkerElement::SVGMarkerUnitsType>(static_cast<SVGMarkerElement*>(node())->markerUnits()); } + + virtual RenderSVGResourceType resourceType() const { return s_resourceType; } + static RenderSVGResourceType s_resourceType; + +private: + // Generates a transformation matrix usable to render marker content. Handles scaling the marker content + // acording to SVGs markerUnits="strokeWidth" concept, when a strokeWidth value != -1 is passed in. + AffineTransform markerContentTransformation(const AffineTransform& contentTransformation, const FloatPoint& origin, float strokeWidth = -1) const; + + AffineTransform viewportTransform() const; + + mutable AffineTransform m_localToParentTransform; + FloatRect m_viewport; +}; + +} +#endif + +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourceMasker.cpp b/Source/WebCore/rendering/RenderSVGResourceMasker.cpp new file mode 100644 index 0000000..ebbbe93 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceMasker.cpp @@ -0,0 +1,224 @@ +/* + * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGResourceMasker.h" + +#include "AffineTransform.h" +#include "Element.h" +#include "FloatPoint.h" +#include "FloatRect.h" +#include "GraphicsContext.h" +#include "Image.h" +#include "ImageBuffer.h" +#include "IntRect.h" +#include "RenderSVGResource.h" +#include "SVGElement.h" +#include "SVGImageBufferTools.h" +#include "SVGMaskElement.h" +#include "SVGStyledElement.h" +#include "SVGUnitTypes.h" + +#include <wtf/ByteArray.h> +#include <wtf/Vector.h> +#include <wtf/UnusedParam.h> + +namespace WebCore { + +RenderSVGResourceType RenderSVGResourceMasker::s_resourceType = MaskerResourceType; + +RenderSVGResourceMasker::RenderSVGResourceMasker(SVGMaskElement* node) + : RenderSVGResourceContainer(node) +{ +} + +RenderSVGResourceMasker::~RenderSVGResourceMasker() +{ + if (m_masker.isEmpty()) + return; + + deleteAllValues(m_masker); + m_masker.clear(); +} + +void RenderSVGResourceMasker::removeAllClientsFromCache(bool markForInvalidation) +{ + m_maskContentBoundaries = FloatRect(); + if (!m_masker.isEmpty()) { + deleteAllValues(m_masker); + m_masker.clear(); + } + + markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation); +} + +void RenderSVGResourceMasker::removeClientFromCache(RenderObject* client, bool markForInvalidation) +{ + ASSERT(client); + + if (m_masker.contains(client)) + delete m_masker.take(client); + + markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation); +} + +bool RenderSVGResourceMasker::applyResource(RenderObject* object, RenderStyle*, GraphicsContext*& context, unsigned short resourceMode) +{ + ASSERT(object); + ASSERT(context); +#ifndef NDEBUG + ASSERT(resourceMode == ApplyToDefaultMode); +#else + UNUSED_PARAM(resourceMode); +#endif + + if (!m_masker.contains(object)) + m_masker.set(object, new MaskerData); + + MaskerData* maskerData = m_masker.get(object); + + AffineTransform absoluteTransform; + SVGImageBufferTools::calculateTransformationToOutermostSVGCoordinateSystem(object, absoluteTransform); + + FloatRect absoluteTargetRect = absoluteTransform.mapRect(object->repaintRectInLocalCoordinates()); + FloatRect clampedAbsoluteTargetRect = SVGImageBufferTools::clampedAbsoluteTargetRectForRenderer(object, absoluteTargetRect); + + if (!maskerData->maskImage && !clampedAbsoluteTargetRect.isEmpty()) { + SVGMaskElement* maskElement = static_cast<SVGMaskElement*>(node()); + if (!maskElement) + return false; + + if (!SVGImageBufferTools::createImageBuffer(absoluteTargetRect, clampedAbsoluteTargetRect, maskerData->maskImage, ColorSpaceLinearRGB)) + return false; + + GraphicsContext* maskImageContext = maskerData->maskImage->context(); + ASSERT(maskImageContext); + + // The save/restore pair is needed for clipToImageBuffer - it doesn't work without it on non-Cg platforms. + maskImageContext->save(); + maskImageContext->translate(-clampedAbsoluteTargetRect.x(), -clampedAbsoluteTargetRect.y()); + maskImageContext->concatCTM(absoluteTransform); + + drawContentIntoMaskImage(maskerData, maskElement, object); + } + + if (!maskerData->maskImage) + return false; + + SVGImageBufferTools::clipToImageBuffer(context, absoluteTransform, clampedAbsoluteTargetRect, maskerData->maskImage); + return true; +} + +void RenderSVGResourceMasker::drawContentIntoMaskImage(MaskerData* maskerData, const SVGMaskElement* maskElement, RenderObject* object) +{ + GraphicsContext* maskImageContext = maskerData->maskImage->context(); + ASSERT(maskImageContext); + + // Eventually adjust the mask image context according to the target objectBoundingBox. + AffineTransform maskContentTransformation; + if (maskElement->maskContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + FloatRect objectBoundingBox = object->objectBoundingBox(); + maskContentTransformation.translate(objectBoundingBox.x(), objectBoundingBox.y()); + maskContentTransformation.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); + maskImageContext->concatCTM(maskContentTransformation); + } + + // Draw the content into the ImageBuffer. + for (Node* node = maskElement->firstChild(); node; node = node->nextSibling()) { + RenderObject* renderer = node->renderer(); + if (!node->isSVGElement() || !static_cast<SVGElement*>(node)->isStyled() || !renderer) + continue; + RenderStyle* style = renderer->style(); + if (!style || style->display() == NONE || style->visibility() != VISIBLE) + continue; + SVGImageBufferTools::renderSubtreeToImageBuffer(maskerData->maskImage.get(), renderer, maskContentTransformation); + } + + maskImageContext->restore(); + +#if !PLATFORM(CG) + maskerData->maskImage->transformColorSpace(ColorSpaceDeviceRGB, ColorSpaceLinearRGB); +#endif + + // Create the luminance mask. + IntRect maskImageRect(IntPoint(), maskerData->maskImage->size()); + RefPtr<ByteArray> srcPixelArray = maskerData->maskImage->getUnmultipliedImageData(maskImageRect); + + unsigned pixelArrayLength = srcPixelArray->length(); + for (unsigned pixelOffset = 0; pixelOffset < pixelArrayLength; pixelOffset += 4) { + unsigned char a = srcPixelArray->get(pixelOffset + 3); + if (!a) + continue; + unsigned char r = srcPixelArray->get(pixelOffset); + unsigned char g = srcPixelArray->get(pixelOffset + 1); + unsigned char b = srcPixelArray->get(pixelOffset + 2); + + double luma = (r * 0.2125 + g * 0.7154 + b * 0.0721) * ((double)a / 255.0); + srcPixelArray->set(pixelOffset + 3, luma); + } + + maskerData->maskImage->putUnmultipliedImageData(srcPixelArray.get(), maskImageRect.size(), maskImageRect, IntPoint()); +} + +void RenderSVGResourceMasker::calculateMaskContentRepaintRect() +{ + for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { + RenderObject* renderer = childNode->renderer(); + if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyled() || !renderer) + continue; + RenderStyle* style = renderer->style(); + if (!style || style->display() == NONE || style->visibility() != VISIBLE) + continue; + m_maskContentBoundaries.unite(renderer->localToParentTransform().mapRect(renderer->repaintRectInLocalCoordinates())); + } +} + +FloatRect RenderSVGResourceMasker::resourceBoundingBox(RenderObject* object) +{ + SVGMaskElement* maskElement = static_cast<SVGMaskElement*>(node()); + ASSERT(maskElement); + + FloatRect objectBoundingBox = object->objectBoundingBox(); + FloatRect maskBoundaries = maskElement->maskBoundingBox(objectBoundingBox); + + // Resource was not layouted yet. Give back clipping rect of the mask. + if (selfNeedsLayout()) + return maskBoundaries; + + if (m_maskContentBoundaries.isEmpty()) + calculateMaskContentRepaintRect(); + + FloatRect maskRect = m_maskContentBoundaries; + if (maskElement->maskContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + AffineTransform transform; + transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); + transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); + maskRect = transform.mapRect(maskRect); + } + + maskRect.intersect(maskBoundaries); + return maskRect; +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/RenderSVGResourceMasker.h b/Source/WebCore/rendering/RenderSVGResourceMasker.h new file mode 100644 index 0000000..fddecd0 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceMasker.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSVGResourceMasker_h +#define RenderSVGResourceMasker_h + +#if ENABLE(SVG) +#include "FloatRect.h" +#include "GraphicsContext.h" +#include "ImageBuffer.h" +#include "IntSize.h" +#include "RenderSVGResourceContainer.h" +#include "SVGMaskElement.h" +#include "SVGUnitTypes.h" + +#include <wtf/HashMap.h> +#include <wtf/OwnPtr.h> + +namespace WebCore { + +struct MaskerData { + OwnPtr<ImageBuffer> maskImage; +}; + +class RenderSVGResourceMasker : public RenderSVGResourceContainer { +public: + RenderSVGResourceMasker(SVGMaskElement*); + virtual ~RenderSVGResourceMasker(); + + virtual const char* renderName() const { return "RenderSVGResourceMasker"; } + + virtual void removeAllClientsFromCache(bool markForInvalidation = true); + virtual void removeClientFromCache(RenderObject*, bool markForInvalidation = true); + virtual bool applyResource(RenderObject*, RenderStyle*, GraphicsContext*&, unsigned short resourceMode); + virtual FloatRect resourceBoundingBox(RenderObject*); + + SVGUnitTypes::SVGUnitType maskUnits() const { return toUnitType(static_cast<SVGMaskElement*>(node())->maskUnits()); } + SVGUnitTypes::SVGUnitType maskContentUnits() const { return toUnitType(static_cast<SVGMaskElement*>(node())->maskContentUnits()); } + + virtual RenderSVGResourceType resourceType() const { return s_resourceType; } + static RenderSVGResourceType s_resourceType; + +private: + void drawContentIntoMaskImage(MaskerData*, const SVGMaskElement*, RenderObject*); + void calculateMaskContentRepaintRect(); + + FloatRect m_maskContentBoundaries; + HashMap<RenderObject*, MaskerData*> m_masker; +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourcePattern.cpp b/Source/WebCore/rendering/RenderSVGResourcePattern.cpp new file mode 100644 index 0000000..cd64183 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourcePattern.cpp @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGResourcePattern.h" + +#include "FrameView.h" +#include "GraphicsContext.h" +#include "PatternAttributes.h" +#include "RenderSVGRoot.h" +#include "SVGImageBufferTools.h" +#include "SVGRenderSupport.h" + +namespace WebCore { + +RenderSVGResourceType RenderSVGResourcePattern::s_resourceType = PatternResourceType; + +RenderSVGResourcePattern::RenderSVGResourcePattern(SVGPatternElement* node) + : RenderSVGResourceContainer(node) + , m_shouldCollectPatternAttributes(true) +{ +} + +RenderSVGResourcePattern::~RenderSVGResourcePattern() +{ + if (m_pattern.isEmpty()) + return; + + deleteAllValues(m_pattern); + m_pattern.clear(); +} + +void RenderSVGResourcePattern::removeAllClientsFromCache(bool markForInvalidation) +{ + if (!m_pattern.isEmpty()) { + deleteAllValues(m_pattern); + m_pattern.clear(); + } + + m_shouldCollectPatternAttributes = true; + markAllClientsForInvalidation(markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation); +} + +void RenderSVGResourcePattern::removeClientFromCache(RenderObject* client, bool markForInvalidation) +{ + ASSERT(client); + + if (m_pattern.contains(client)) + delete m_pattern.take(client); + + markClientForInvalidation(client, markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation); +} + +bool RenderSVGResourcePattern::applyResource(RenderObject* object, RenderStyle* style, GraphicsContext*& context, unsigned short resourceMode) +{ + ASSERT(object); + ASSERT(style); + ASSERT(context); + ASSERT(resourceMode != ApplyToDefaultMode); + + // Be sure to synchronize all SVG properties on the patternElement _before_ processing any further. + // Otherwhise the call to collectPatternAttributes() below, may cause the SVG DOM property + // synchronization to kick in, which causes removeAllClientsFromCache() to be called, which in turn deletes our + // PatternData object! Leaving out the line below will cause svg/dynamic-updates/SVGPatternElement-svgdom* to crash. + SVGPatternElement* patternElement = static_cast<SVGPatternElement*>(node()); + if (!patternElement) + return false; + + if (m_shouldCollectPatternAttributes) { + patternElement->updateAnimatedSVGAttribute(anyQName()); + + m_attributes = PatternAttributes(); + patternElement->collectPatternAttributes(m_attributes); + m_shouldCollectPatternAttributes = false; + } + + // Spec: When the geometry of the applicable element has no width or height and objectBoundingBox is specified, + // then the given effect (e.g. a gradient or a filter) will be ignored. + FloatRect objectBoundingBox = object->objectBoundingBox(); + if (m_attributes.boundingBoxMode() && objectBoundingBox.isEmpty()) + return false; + + if (!m_pattern.contains(object)) + m_pattern.set(object, new PatternData); + + PatternData* patternData = m_pattern.get(object); + if (!patternData->pattern) { + // If we couldn't determine the pattern content element root, stop here. + if (!m_attributes.patternContentElement()) + return false; + + // Compute all necessary transformations to build the tile image & the pattern. + FloatRect tileBoundaries; + AffineTransform tileImageTransform; + if (!buildTileImageTransform(object, m_attributes, patternElement, tileBoundaries, tileImageTransform)) + return false; + + AffineTransform absoluteTransform; + SVGImageBufferTools::calculateTransformationToOutermostSVGCoordinateSystem(object, absoluteTransform); + + FloatRect absoluteTileBoundaries = absoluteTransform.mapRect(tileBoundaries); + + // Build tile image. + OwnPtr<ImageBuffer> tileImage = createTileImage(object, m_attributes, tileBoundaries, absoluteTileBoundaries, tileImageTransform); + if (!tileImage) + return false; + + RefPtr<Image> copiedImage = tileImage->copyImage(); + if (!copiedImage) + return false; + + // Build pattern. + patternData->pattern = Pattern::create(copiedImage, true, true); + if (!patternData->pattern) + return false; + + // Compute pattern space transformation. + patternData->transform.translate(tileBoundaries.x(), tileBoundaries.y()); + patternData->transform.scale(tileBoundaries.width() / absoluteTileBoundaries.width(), tileBoundaries.height() / absoluteTileBoundaries.height()); + + AffineTransform patternTransform = m_attributes.patternTransform(); + if (!patternTransform.isIdentity()) + patternData->transform.multiply(patternTransform); + + patternData->pattern->setPatternSpaceTransform(patternData->transform); + } + + // Draw pattern + context->save(); + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + + if (resourceMode & ApplyToFillMode) { + context->setAlpha(svgStyle->fillOpacity()); + context->setFillPattern(patternData->pattern); + context->setFillRule(svgStyle->fillRule()); + } else if (resourceMode & ApplyToStrokeMode) { + if (svgStyle->vectorEffect() == VE_NON_SCALING_STROKE) + patternData->pattern->setPatternSpaceTransform(transformOnNonScalingStroke(object, patternData->transform)); + context->setAlpha(svgStyle->strokeOpacity()); + context->setStrokePattern(patternData->pattern); + SVGRenderSupport::applyStrokeStyleToContext(context, style, object); + } + + if (resourceMode & ApplyToTextMode) { + if (resourceMode & ApplyToFillMode) { + context->setTextDrawingMode(TextModeFill); + +#if PLATFORM(CG) + context->applyFillPattern(); +#endif + } else if (resourceMode & ApplyToStrokeMode) { + context->setTextDrawingMode(TextModeStroke); + +#if PLATFORM(CG) + context->applyStrokePattern(); +#endif + } + } + + return true; +} + +void RenderSVGResourcePattern::postApplyResource(RenderObject*, GraphicsContext*& context, unsigned short resourceMode, const Path* path) +{ + ASSERT(context); + ASSERT(resourceMode != ApplyToDefaultMode); + + if (path && !(resourceMode & ApplyToTextMode)) { + if (resourceMode & ApplyToFillMode) + context->fillPath(*path); + else if (resourceMode & ApplyToStrokeMode) + context->strokePath(*path); + } + + context->restore(); +} + +static inline FloatRect calculatePatternBoundaries(const PatternAttributes& attributes, + const FloatRect& objectBoundingBox, + const SVGPatternElement* patternElement) +{ + ASSERT(patternElement); + + if (attributes.boundingBoxMode()) + return FloatRect(attributes.x().valueAsPercentage() * objectBoundingBox.width() + objectBoundingBox.x(), + attributes.y().valueAsPercentage() * objectBoundingBox.height() + objectBoundingBox.y(), + attributes.width().valueAsPercentage() * objectBoundingBox.width(), + attributes.height().valueAsPercentage() * objectBoundingBox.height()); + + return FloatRect(attributes.x().value(patternElement), + attributes.y().value(patternElement), + attributes.width().value(patternElement), + attributes.height().value(patternElement)); +} + +bool RenderSVGResourcePattern::buildTileImageTransform(RenderObject* renderer, + const PatternAttributes& attributes, + const SVGPatternElement* patternElement, + FloatRect& patternBoundaries, + AffineTransform& tileImageTransform) const +{ + ASSERT(renderer); + ASSERT(patternElement); + + FloatRect objectBoundingBox = renderer->objectBoundingBox(); + patternBoundaries = calculatePatternBoundaries(attributes, objectBoundingBox, patternElement); + if (patternBoundaries.width() <= 0 || patternBoundaries.height() <= 0) + return false; + + AffineTransform viewBoxCTM = patternElement->viewBoxToViewTransform(patternElement->viewBox(), patternElement->preserveAspectRatio(), patternBoundaries.width(), patternBoundaries.height()); + + // Apply viewBox/objectBoundingBox transformations. + if (!viewBoxCTM.isIdentity()) + tileImageTransform = viewBoxCTM; + else if (attributes.boundingBoxModeContent()) { + tileImageTransform.translate(objectBoundingBox.x(), objectBoundingBox.y()); + tileImageTransform.scale(objectBoundingBox.width(), objectBoundingBox.height()); + } + + return true; +} + +PassOwnPtr<ImageBuffer> RenderSVGResourcePattern::createTileImage(RenderObject* object, + const PatternAttributes& attributes, + const FloatRect& tileBoundaries, + const FloatRect& absoluteTileBoundaries, + const AffineTransform& tileImageTransform) const +{ + ASSERT(object); + + // Clamp tile image size against SVG viewport size, as last resort, to avoid allocating huge image buffers. + FloatRect contentBoxRect = SVGRenderSupport::findTreeRootObject(object)->contentBoxRect(); + + FloatRect clampedAbsoluteTileBoundaries = absoluteTileBoundaries; + if (clampedAbsoluteTileBoundaries.width() > contentBoxRect.width()) + clampedAbsoluteTileBoundaries.setWidth(contentBoxRect.width()); + + if (clampedAbsoluteTileBoundaries.height() > contentBoxRect.height()) + clampedAbsoluteTileBoundaries.setHeight(contentBoxRect.height()); + + OwnPtr<ImageBuffer> tileImage; + + if (!SVGImageBufferTools::createImageBuffer(absoluteTileBoundaries, clampedAbsoluteTileBoundaries, tileImage, ColorSpaceDeviceRGB)) + return PassOwnPtr<ImageBuffer>(); + + GraphicsContext* tileImageContext = tileImage->context(); + ASSERT(tileImageContext); + + // The image buffer represents the final rendered size, so the content has to be scaled (to avoid pixelation). + tileImageContext->scale(FloatSize(absoluteTileBoundaries.width() / tileBoundaries.width(), + absoluteTileBoundaries.height() / tileBoundaries.height())); + + // Apply tile image transformations. + if (!tileImageTransform.isIdentity()) + tileImageContext->concatCTM(tileImageTransform); + + AffineTransform contentTransformation; + + // Draw the content into the ImageBuffer. + for (Node* node = attributes.patternContentElement()->firstChild(); node; node = node->nextSibling()) { + if (!node->isSVGElement() || !static_cast<SVGElement*>(node)->isStyled() || !node->renderer()) + continue; + SVGImageBufferTools::renderSubtreeToImageBuffer(tileImage.get(), node->renderer(), contentTransformation); + } + + return tileImage.release(); +} + +} + +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourcePattern.h b/Source/WebCore/rendering/RenderSVGResourcePattern.h new file mode 100644 index 0000000..ee3fb23 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourcePattern.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSVGResourcePattern_h +#define RenderSVGResourcePattern_h + +#if ENABLE(SVG) +#include "AffineTransform.h" +#include "FloatRect.h" +#include "ImageBuffer.h" +#include "Pattern.h" +#include "PatternAttributes.h" +#include "RenderSVGResourceContainer.h" +#include "SVGPatternElement.h" +#include "SVGUnitTypes.h" + +#include <wtf/HashMap.h> +#include <wtf/OwnPtr.h> + +namespace WebCore { + +struct PatternData { + RefPtr<Pattern> pattern; + AffineTransform transform; +}; + +class RenderSVGResourcePattern : public RenderSVGResourceContainer { +public: + RenderSVGResourcePattern(SVGPatternElement*); + virtual ~RenderSVGResourcePattern(); + + virtual const char* renderName() const { return "RenderSVGResourcePattern"; } + + virtual void removeAllClientsFromCache(bool markForInvalidation = true); + virtual void removeClientFromCache(RenderObject*, bool markForInvalidation = true); + + virtual bool applyResource(RenderObject*, RenderStyle*, GraphicsContext*&, unsigned short resourceMode); + virtual void postApplyResource(RenderObject*, GraphicsContext*&, unsigned short resourceMode, const Path*); + virtual FloatRect resourceBoundingBox(RenderObject*) { return FloatRect(); } + + virtual RenderSVGResourceType resourceType() const { return s_resourceType; } + static RenderSVGResourceType s_resourceType; + +private: + bool buildTileImageTransform(RenderObject*, const PatternAttributes&, const SVGPatternElement*, FloatRect& patternBoundaries, AffineTransform& tileImageTransform) const; + + PassOwnPtr<ImageBuffer> createTileImage(RenderObject*, const PatternAttributes&, const FloatRect& tileBoundaries, + const FloatRect& absoluteTileBoundaries, const AffineTransform& tileImageTransform) const; + + bool m_shouldCollectPatternAttributes : 1; + PatternAttributes m_attributes; + HashMap<RenderObject*, PatternData*> m_pattern; +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourceRadialGradient.cpp b/Source/WebCore/rendering/RenderSVGResourceRadialGradient.cpp new file mode 100644 index 0000000..300afcb --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceRadialGradient.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGResourceRadialGradient.h" + +#include "RadialGradientAttributes.h" +#include "SVGRadialGradientElement.h" + +namespace WebCore { + +RenderSVGResourceType RenderSVGResourceRadialGradient::s_resourceType = RadialGradientResourceType; + +RenderSVGResourceRadialGradient::RenderSVGResourceRadialGradient(SVGRadialGradientElement* node) + : RenderSVGResourceGradient(node) +{ +} + +RenderSVGResourceRadialGradient::~RenderSVGResourceRadialGradient() +{ +} + +void RenderSVGResourceRadialGradient::collectGradientAttributes(SVGGradientElement* gradientElement) +{ + m_attributes = RadialGradientAttributes(); + static_cast<SVGRadialGradientElement*>(gradientElement)->collectGradientAttributes(m_attributes); +} + +void RenderSVGResourceRadialGradient::buildGradient(GradientData* gradientData, SVGGradientElement* gradientElement) const +{ + SVGRadialGradientElement* radialGradientElement = static_cast<SVGRadialGradientElement*>(gradientElement); + + // Determine gradient focal/center points and radius + FloatPoint focalPoint; + FloatPoint centerPoint; + float radius; + radialGradientElement->calculateFocalCenterPointsAndRadius(m_attributes, focalPoint, centerPoint, radius); + + gradientData->gradient = Gradient::create(focalPoint, + 0, // SVG does not support a "focus radius" + centerPoint, + radius); + + gradientData->gradient->setSpreadMethod(m_attributes.spreadMethod()); + + // Add stops + addStops(gradientData, m_attributes.stops()); +} + +} + +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourceRadialGradient.h b/Source/WebCore/rendering/RenderSVGResourceRadialGradient.h new file mode 100644 index 0000000..6492ee3 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceRadialGradient.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSVGResourceRadialGradient_h +#define RenderSVGResourceRadialGradient_h + +#if ENABLE(SVG) +#include "RadialGradientAttributes.h" +#include "RenderSVGResourceGradient.h" + +namespace WebCore { + +class SVGRadialGradientElement; + +class RenderSVGResourceRadialGradient : public RenderSVGResourceGradient { +public: + RenderSVGResourceRadialGradient(SVGRadialGradientElement*); + virtual ~RenderSVGResourceRadialGradient(); + + virtual const char* renderName() const { return "RenderSVGResourceRadialGradient"; } + + virtual RenderSVGResourceType resourceType() const { return s_resourceType; } + static RenderSVGResourceType s_resourceType; + + virtual bool boundingBoxMode() const { return m_attributes.boundingBoxMode(); } + virtual void calculateGradientTransform(AffineTransform& transform) { transform = m_attributes.gradientTransform(); } + virtual void collectGradientAttributes(SVGGradientElement*); + virtual void buildGradient(GradientData*, SVGGradientElement*) const; + +private: + RadialGradientAttributes m_attributes; +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourceSolidColor.cpp b/Source/WebCore/rendering/RenderSVGResourceSolidColor.cpp new file mode 100644 index 0000000..9fbda40 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceSolidColor.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGResourceSolidColor.h" + +#include "GraphicsContext.h" +#include "RenderStyle.h" +#include "SVGRenderSupport.h" + +#if PLATFORM(SKIA) && !PLATFORM(ANDROID) +#include "PlatformContextSkia.h" +#endif + +namespace WebCore { + +RenderSVGResourceType RenderSVGResourceSolidColor::s_resourceType = SolidColorResourceType; + +RenderSVGResourceSolidColor::RenderSVGResourceSolidColor() +{ +} + +RenderSVGResourceSolidColor::~RenderSVGResourceSolidColor() +{ +} + +bool RenderSVGResourceSolidColor::applyResource(RenderObject* object, RenderStyle* style, GraphicsContext*& context, unsigned short resourceMode) +{ + // We are NOT allowed to ASSERT(object) here, unlike all other resources. + // RenderSVGResourceSolidColor is the only resource which may be used from HTML, when rendering + // SVG Fonts for a HTML document. This will be indicated by a null RenderObject pointer. + ASSERT(context); + ASSERT(resourceMode != ApplyToDefaultMode); + + const SVGRenderStyle* svgStyle = style ? style->svgStyle() : 0; + ColorSpace colorSpace = style ? style->colorSpace() : ColorSpaceDeviceRGB; + + if (resourceMode & ApplyToFillMode) { + context->setAlpha(svgStyle ? svgStyle->fillOpacity() : 1.0f); + context->setFillColor(m_color, colorSpace); + context->setFillRule(svgStyle ? svgStyle->fillRule() : RULE_NONZERO); + + if (resourceMode & ApplyToTextMode) + context->setTextDrawingMode(TextModeFill); + } else if (resourceMode & ApplyToStrokeMode) { + context->setAlpha(svgStyle ? svgStyle->strokeOpacity() : 1.0f); + context->setStrokeColor(m_color, colorSpace); + + if (style) + SVGRenderSupport::applyStrokeStyleToContext(context, style, object); + + if (resourceMode & ApplyToTextMode) + context->setTextDrawingMode(TextModeStroke); + } + + return true; +} + +void RenderSVGResourceSolidColor::postApplyResource(RenderObject*, GraphicsContext*& context, unsigned short resourceMode, const Path* path) +{ + ASSERT(context); + ASSERT(resourceMode != ApplyToDefaultMode); + + if (path && !(resourceMode & ApplyToTextMode)) { + if (resourceMode & ApplyToFillMode) + context->fillPath(*path); + else if (resourceMode & ApplyToStrokeMode) + context->strokePath(*path); + } +} + +} + +#endif diff --git a/Source/WebCore/rendering/RenderSVGResourceSolidColor.h b/Source/WebCore/rendering/RenderSVGResourceSolidColor.h new file mode 100644 index 0000000..72de3b2 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourceSolidColor.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSVGResourceSolidColor_h +#define RenderSVGResourceSolidColor_h + +#if ENABLE(SVG) +#include "Color.h" +#include "FloatRect.h" +#include "RenderSVGResource.h" + +namespace WebCore { + +class RenderSVGResourceSolidColor : public RenderSVGResource { +public: + RenderSVGResourceSolidColor(); + virtual ~RenderSVGResourceSolidColor(); + + virtual void removeAllClientsFromCache(bool = true) { } + virtual void removeClientFromCache(RenderObject*, bool = true) { } + + virtual bool applyResource(RenderObject*, RenderStyle*, GraphicsContext*&, unsigned short resourceMode); + virtual void postApplyResource(RenderObject*, GraphicsContext*&, unsigned short resourceMode, const Path*); + virtual FloatRect resourceBoundingBox(RenderObject*) { return FloatRect(); } + + virtual RenderSVGResourceType resourceType() const { return s_resourceType; } + static RenderSVGResourceType s_resourceType; + + const Color& color() const { return m_color; } + void setColor(const Color& color) { m_color = color; } + +private: + Color m_color; +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/RenderSVGRoot.cpp b/Source/WebCore/rendering/RenderSVGRoot.cpp new file mode 100644 index 0000000..215aac7 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGRoot.cpp @@ -0,0 +1,361 @@ +/* + Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> + 2004, 2005, 2007, 2008, 2009 Rob Buis <buis@kde.org> + 2007 Eric Seidel <eric@webkit.org> + 2009 Google, Inc. + + 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 + aint 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" + +#if ENABLE(SVG) +#include "RenderSVGRoot.h" + +#include "GraphicsContext.h" +#include "HitTestResult.h" +#include "RenderSVGContainer.h" +#include "RenderSVGResource.h" +#include "RenderView.h" +#include "SVGLength.h" +#include "SVGRenderSupport.h" +#include "SVGResources.h" +#include "SVGSVGElement.h" +#include "SVGStyledElement.h" +#include "TransformState.h" + +#if ENABLE(FILTERS) +#include "RenderSVGResourceFilter.h" +#endif + +using namespace std; + +namespace WebCore { + +RenderSVGRoot::RenderSVGRoot(SVGStyledElement* node) + : RenderBox(node) + , m_isLayoutSizeChanged(false) + , m_needsBoundariesOrTransformUpdate(true) +{ + setReplaced(true); +} + +RenderSVGRoot::~RenderSVGRoot() +{ +} + +void RenderSVGRoot::computePreferredLogicalWidths() +{ + ASSERT(preferredLogicalWidthsDirty()); + + int borderAndPadding = borderAndPaddingWidth(); + int width = computeReplacedLogicalWidth(false) + borderAndPadding; + + if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) + width = min(width, style()->maxWidth().value() + (style()->boxSizing() == CONTENT_BOX ? borderAndPadding : 0)); + + if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent())) { + m_minPreferredLogicalWidth = 0; + m_maxPreferredLogicalWidth = width; + } else + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = width; + + setPreferredLogicalWidthsDirty(false); +} + +int RenderSVGRoot::computeReplacedLogicalWidth(bool includeMaxWidth) const +{ + int replacedWidth = RenderBox::computeReplacedLogicalWidth(includeMaxWidth); + if (!style()->logicalWidth().isPercent()) + return replacedWidth; + + // FIXME: Investigate in size rounding issues + SVGSVGElement* svg = static_cast<SVGSVGElement*>(node()); + return static_cast<int>(roundf(replacedWidth * svg->currentScale())); +} + +int RenderSVGRoot::computeReplacedLogicalHeight() const +{ + int replacedHeight = RenderBox::computeReplacedLogicalHeight(); + if (!style()->logicalHeight().isPercent()) + return replacedHeight; + + // FIXME: Investigate in size rounding issues + SVGSVGElement* svg = static_cast<SVGSVGElement*>(node()); + return static_cast<int>(roundf(replacedHeight * svg->currentScale())); +} + +void RenderSVGRoot::layout() +{ + ASSERT(needsLayout()); + + // Arbitrary affine transforms are incompatible with LayoutState. + view()->disableLayoutState(); + + bool needsLayout = selfNeedsLayout(); + LayoutRepainter repainter(*this, checkForRepaintDuringLayout() && needsLayout); + + IntSize oldSize(width(), height()); + computeLogicalWidth(); + computeLogicalHeight(); + calcViewport(); + + SVGSVGElement* svg = static_cast<SVGSVGElement*>(node()); + m_isLayoutSizeChanged = svg->hasRelativeLengths() && oldSize != size(); + + SVGRenderSupport::layoutChildren(this, needsLayout); + m_isLayoutSizeChanged = false; + + // At this point LayoutRepainter already grabbed the old bounds, + // recalculate them now so repaintAfterLayout() uses the new bounds. + if (m_needsBoundariesOrTransformUpdate) { + updateCachedBoundaries(); + m_needsBoundariesOrTransformUpdate = false; + } + + repainter.repaintAfterLayout(); + + view()->enableLayoutState(); + setNeedsLayout(false); +} + +bool RenderSVGRoot::selfWillPaint() +{ +#if ENABLE(FILTERS) + SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this); + return resources && resources->filter(); +#else + return false; +#endif +} + +void RenderSVGRoot::paint(PaintInfo& paintInfo, int parentX, int parentY) +{ + if (paintInfo.context->paintingDisabled()) + return; + + bool isVisible = style()->visibility() == VISIBLE; + IntPoint parentOriginInContainer(parentX, parentY); + IntPoint borderBoxOriginInContainer = parentOriginInContainer + parentOriginToBorderBox(); + + if (hasBoxDecorations() && (paintInfo.phase == PaintPhaseBlockBackground || paintInfo.phase == PaintPhaseChildBlockBackground) && isVisible) + paintBoxDecorations(paintInfo, borderBoxOriginInContainer.x(), borderBoxOriginInContainer.y()); + + if (paintInfo.phase == PaintPhaseBlockBackground) + return; + + // An empty viewport disables rendering. FIXME: Should we still render filters? + if (m_viewportSize.isEmpty()) + return; + + // Don't paint if we don't have kids, except if we have filters we should paint those. + if (!firstChild() && !selfWillPaint()) + return; + + // Make a copy of the PaintInfo because applyTransform will modify the damage rect. + PaintInfo childPaintInfo(paintInfo); + childPaintInfo.context->save(); + + // Apply initial viewport clip - not affected by overflow handling + childPaintInfo.context->clip(overflowClipRect(borderBoxOriginInContainer.x(), borderBoxOriginInContainer.y())); + + // Convert from container offsets (html renderers) to a relative transform (svg renderers). + // Transform from our paint container's coordinate system to our local coords. + childPaintInfo.applyTransform(localToRepaintContainerTransform(parentOriginInContainer)); + + bool continueRendering = true; + if (childPaintInfo.phase == PaintPhaseForeground) + continueRendering = SVGRenderSupport::prepareToRenderSVGContent(this, childPaintInfo); + + if (continueRendering) + RenderBox::paint(childPaintInfo, 0, 0); + + if (childPaintInfo.phase == PaintPhaseForeground) + SVGRenderSupport::finishRenderSVGContent(this, childPaintInfo, paintInfo.context); + + childPaintInfo.context->restore(); + + if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth() && isVisible) + paintOutline(paintInfo.context, borderBoxOriginInContainer.x(), borderBoxOriginInContainer.y(), width(), height()); +} + +void RenderSVGRoot::destroy() +{ + SVGResourcesCache::clientDestroyed(this); + RenderBox::destroy(); +} + +void RenderSVGRoot::styleWillChange(StyleDifference diff, const RenderStyle* newStyle) +{ + if (diff == StyleDifferenceLayout) + setNeedsBoundariesUpdate(); + RenderBox::styleWillChange(diff, newStyle); +} + +void RenderSVGRoot::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBox::styleDidChange(diff, oldStyle); + SVGResourcesCache::clientStyleChanged(this, diff, style()); +} + +void RenderSVGRoot::updateFromElement() +{ + RenderBox::updateFromElement(); + SVGResourcesCache::clientUpdatedFromElement(this, style()); +} + +void RenderSVGRoot::calcViewport() +{ + SVGSVGElement* svg = static_cast<SVGSVGElement*>(node()); + + if (!svg->hasSetContainerSize()) { + // In the normal case of <svg> being stand-alone or in a CSSBoxModel object we use + // RenderBox::width()/height() (which pulls data from RenderStyle) + m_viewportSize = FloatSize(width(), height()); + return; + } + + // In the SVGImage case grab the SVGLength values off of SVGSVGElement and use + // the special relativeWidthValue accessors which respect the specified containerSize + // FIXME: Check how SVGImage + zooming is supposed to be handled? + SVGLength width = svg->width(); + SVGLength height = svg->height(); + m_viewportSize = FloatSize(width.unitType() == LengthTypePercentage ? svg->relativeWidthValue() : width.value(svg), + height.unitType() == LengthTypePercentage ? svg->relativeHeightValue() : height.value(svg)); +} + +// RenderBox methods will expect coordinates w/o any transforms in coordinates +// relative to our borderBox origin. This method gives us exactly that. +AffineTransform RenderSVGRoot::localToBorderBoxTransform() const +{ + IntSize borderAndPadding = borderOriginToContentBox(); + SVGSVGElement* svg = static_cast<SVGSVGElement*>(node()); + float scale = svg->currentScale(); + FloatPoint translate = svg->currentTranslate(); + AffineTransform ctm(scale, 0, 0, scale, borderAndPadding.width() + translate.x(), borderAndPadding.height() + translate.y()); + return svg->viewBoxToViewTransform(width() / scale, height() / scale) * ctm; +} + +IntSize RenderSVGRoot::parentOriginToBorderBox() const +{ + return IntSize(x(), y()); +} + +IntSize RenderSVGRoot::borderOriginToContentBox() const +{ + return IntSize(borderLeft() + paddingLeft(), borderTop() + paddingTop()); +} + +AffineTransform RenderSVGRoot::localToRepaintContainerTransform(const IntPoint& parentOriginInContainer) const +{ + AffineTransform parentToContainer(localToParentTransform()); + return parentToContainer.translateRight(parentOriginInContainer.x(), parentOriginInContainer.y()); +} + +const AffineTransform& RenderSVGRoot::localToParentTransform() const +{ + IntSize parentToBorderBoxOffset = parentOriginToBorderBox(); + + AffineTransform borderBoxOriginToParentOrigin(localToBorderBoxTransform()); + borderBoxOriginToParentOrigin.translateRight(parentToBorderBoxOffset.width(), parentToBorderBoxOffset.height()); + + m_localToParentTransform = borderBoxOriginToParentOrigin; + return m_localToParentTransform; +} + +IntRect RenderSVGRoot::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) +{ + return SVGRenderSupport::clippedOverflowRectForRepaint(this, repaintContainer); +} + +void RenderSVGRoot::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& repaintRect, bool fixed) +{ + // Apply our local transforms (except for x/y translation), then our shadow, + // and then call RenderBox's method to handle all the normal CSS Box model bits + repaintRect = localToBorderBoxTransform().mapRect(repaintRect); + + // Apply initial viewport clip - not affected by overflow settings + repaintRect.intersect(enclosingIntRect(FloatRect(FloatPoint(), m_viewportSize))); + + const SVGRenderStyle* svgStyle = style()->svgStyle(); + if (const ShadowData* shadow = svgStyle->shadow()) + shadow->adjustRectForShadow(repaintRect); + + RenderBox::computeRectForRepaint(repaintContainer, repaintRect, fixed); +} + +void RenderSVGRoot::mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool fixed , bool useTransforms, TransformState& transformState) const +{ + ASSERT(!fixed); // We should have no fixed content in the SVG rendering tree. + ASSERT(useTransforms); // mapping a point through SVG w/o respecting trasnforms is useless. + + // Transform to our border box and let RenderBox transform the rest of the way. + transformState.applyTransform(localToBorderBoxTransform()); + RenderBox::mapLocalToContainer(repaintContainer, fixed, useTransforms, transformState); +} + +void RenderSVGRoot::updateCachedBoundaries() +{ + m_objectBoundingBox = FloatRect(); + m_strokeBoundingBox = FloatRect(); + m_repaintBoundingBox = FloatRect(); + + SVGRenderSupport::computeContainerBoundingBoxes(this, m_objectBoundingBox, m_strokeBoundingBox, m_repaintBoundingBox); + SVGRenderSupport::intersectRepaintRectWithResources(this, m_repaintBoundingBox); + m_repaintBoundingBox.inflate(borderAndPaddingWidth()); +} + +bool RenderSVGRoot::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction) +{ + IntPoint pointInContainer(_x, _y); + IntSize containerToParentOffset(_tx, _ty); + + IntPoint pointInParent = pointInContainer - containerToParentOffset; + IntPoint pointInBorderBox = pointInParent - parentOriginToBorderBox(); + + // Note: For now, we're ignoring hits to border and padding for <svg> + IntPoint pointInContentBox = pointInBorderBox - borderOriginToContentBox(); + if (!contentBoxRect().contains(pointInContentBox)) + return false; + + IntPoint localPoint = localToParentTransform().inverse().mapPoint(pointInParent); + + for (RenderObject* child = lastChild(); child; child = child->previousSibling()) { + if (child->nodeAtFloatPoint(request, result, localPoint, hitTestAction)) { + // FIXME: CSS/HTML assumes the local point is relative to the border box, right? + updateHitTestResult(result, pointInBorderBox); + // FIXME: nodeAtFloatPoint() doesn't handle rect-based hit tests yet. + result.addNodeToRectBasedTestResult(child->node(), _x, _y); + return true; + } + } + + // If we didn't early exit above, we've just hit the container <svg> element. Unlike SVG 1.1, 2nd Edition allows container elements to be hit. + if (hitTestAction == HitTestBlockBackground) { + // Only return true here, if the last hit testing phase 'BlockBackground' is executed. If we'd return true in the 'Foreground' phase, + // hit testing would stop immediately. For SVG only trees this doesn't matter. Though when we have a <foreignObject> subtree we need + // to be able to detect hits on the background of a <div> element. If we'd return true here in the 'Foreground' phase, we are not able + // to detect these hits anymore. + updateHitTestResult(result, roundedIntPoint(localPoint)); + return true; + } + + return false; +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/RenderSVGRoot.h b/Source/WebCore/rendering/RenderSVGRoot.h new file mode 100644 index 0000000..1b6aa22 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGRoot.h @@ -0,0 +1,120 @@ +/* + Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> + 2004, 2005, 2007 Rob Buis <buis@kde.org> + Copyright (C) 2009 Google, Inc. All rights reserved. + Copyright (C) 2009 Apple Inc. All rights reserved. + + 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 + aint 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. +*/ + +#ifndef RenderSVGRoot_h +#define RenderSVGRoot_h + +#if ENABLE(SVG) +#include "RenderBox.h" +#include "FloatRect.h" +#include "SVGRenderSupport.h" + +namespace WebCore { + +class SVGStyledElement; +class AffineTransform; + +class RenderSVGRoot : public RenderBox { +public: + explicit RenderSVGRoot(SVGStyledElement*); + virtual ~RenderSVGRoot(); + + const RenderObjectChildList* children() const { return &m_children; } + RenderObjectChildList* children() { return &m_children; } + + bool isLayoutSizeChanged() const { return m_isLayoutSizeChanged; } + virtual void setNeedsBoundariesUpdate() { m_needsBoundariesOrTransformUpdate = true; } + virtual void setNeedsTransformUpdate() { m_needsBoundariesOrTransformUpdate = true; } + +private: + virtual RenderObjectChildList* virtualChildren() { return children(); } + virtual const RenderObjectChildList* virtualChildren() const { return children(); } + + virtual bool isSVGRoot() const { return true; } + virtual const char* renderName() const { return "RenderSVGRoot"; } + + virtual void computePreferredLogicalWidths(); + virtual int computeReplacedLogicalWidth(bool includeMaxWidth = true) const; + virtual int computeReplacedLogicalHeight() const; + virtual void layout(); + virtual void paint(PaintInfo&, int parentX, int parentY); + + virtual void destroy(); + virtual void styleWillChange(StyleDifference, const RenderStyle* newStyle); + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + virtual void updateFromElement(); + + virtual const AffineTransform& localToParentTransform() const; + + bool fillContains(const FloatPoint&) const; + bool strokeContains(const FloatPoint&) const; + + virtual FloatRect objectBoundingBox() const { return m_objectBoundingBox; } + virtual FloatRect strokeBoundingBox() const { return m_strokeBoundingBox; } + virtual FloatRect repaintRectInLocalCoordinates() const { return m_repaintBoundingBox; } + + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer); + virtual void computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& repaintRect, bool fixed); + + virtual void mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool useTransforms, bool fixed, TransformState&) const; + + void calcViewport(); + + bool selfWillPaint(); + void updateCachedBoundaries(); + + IntSize parentOriginToBorderBox() const; + IntSize borderOriginToContentBox() const; + AffineTransform localToRepaintContainerTransform(const IntPoint& parentOriginInContainer) const; + AffineTransform localToBorderBoxTransform() const; + + RenderObjectChildList m_children; + FloatSize m_viewportSize; + FloatRect m_objectBoundingBox; + FloatRect m_strokeBoundingBox; + FloatRect m_repaintBoundingBox; + mutable AffineTransform m_localToParentTransform; + bool m_isLayoutSizeChanged : 1; + bool m_needsBoundariesOrTransformUpdate : 1; +}; + +inline RenderSVGRoot* toRenderSVGRoot(RenderObject* object) +{ + ASSERT(!object || object->isSVGRoot()); + return static_cast<RenderSVGRoot*>(object); +} + +inline const RenderSVGRoot* toRenderSVGRoot(const RenderObject* object) +{ + ASSERT(!object || object->isSVGRoot()); + return static_cast<const RenderSVGRoot*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderSVGRoot(const RenderSVGRoot*); + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif // RenderSVGRoot_h diff --git a/Source/WebCore/rendering/RenderSVGShadowTreeRootContainer.cpp b/Source/WebCore/rendering/RenderSVGShadowTreeRootContainer.cpp new file mode 100644 index 0000000..11b398a --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGShadowTreeRootContainer.cpp @@ -0,0 +1,108 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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 + aint 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" + +#if ENABLE(SVG) +#include "RenderSVGShadowTreeRootContainer.h" + +#include "MouseEvent.h" +#include "SVGShadowTreeElements.h" +#include "SVGUseElement.h" + +namespace WebCore { + +RenderSVGShadowTreeRootContainer::RenderSVGShadowTreeRootContainer(SVGUseElement* node) + : RenderSVGTransformableContainer(node) + , m_recreateTree(false) +{ +} + +RenderSVGShadowTreeRootContainer::~RenderSVGShadowTreeRootContainer() +{ + if (m_shadowRoot && m_shadowRoot->attached()) { + m_shadowRoot->detach(); + m_shadowRoot->clearShadowHost(); + } +} + +void RenderSVGShadowTreeRootContainer::updateStyle(Node::StyleChange change) +{ + if (m_shadowRoot && m_shadowRoot->attached()) + m_shadowRoot->recalcStyle(change); +} + +void RenderSVGShadowTreeRootContainer::updateFromElement() +{ + bool hadExistingTree = m_shadowRoot; + + SVGUseElement* useElement = static_cast<SVGUseElement*>(node()); + if (!m_shadowRoot) { + ASSERT(!m_recreateTree); + m_shadowRoot = SVGShadowTreeRootElement::create(document(), useElement); + useElement->buildPendingResource(); + } + + ASSERT(m_shadowRoot->shadowHost() == useElement); + + bool shouldRecreateTree = m_recreateTree; + if (m_recreateTree) { + ASSERT(hadExistingTree); + + if (m_shadowRoot->attached()) + m_shadowRoot->detach(); + + m_shadowRoot->removeAllChildren(); + m_recreateTree = false; + } + + // Only rebuild the shadow tree, if we a) never had a tree or b) we were specifically asked to do so + // If the use element is a pending resource, and a) or b) is true, do nothing, and wait for the use + // element to be asked to buildPendingResource(), this will call us again, with m_recreateTrue=true. + if ((shouldRecreateTree || !hadExistingTree) && !useElement->isPendingResource()) { + useElement->buildShadowAndInstanceTree(m_shadowRoot.get()); + + // Attach shadow root element + m_shadowRoot->attachElement(style(), renderArena()); + + // Attach subtree, as if it was a regular non-shadow tree + for (Node* child = m_shadowRoot->firstChild(); child; child = child->nextSibling()) + child->attach(); + } + + ASSERT(!m_recreateTree); + RenderSVGTransformableContainer::updateFromElement(); +} + +void RenderSVGShadowTreeRootContainer::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderSVGTransformableContainer::styleDidChange(diff, oldStyle); + + if (RenderObject* shadowRootRenderer = m_shadowRoot ? m_shadowRoot->renderer() : 0) + shadowRootRenderer->setStyle(style()); +} + +Node* RenderSVGShadowTreeRootContainer::rootElement() const +{ + return m_shadowRoot.get(); +} + +} + +#endif diff --git a/Source/WebCore/rendering/RenderSVGShadowTreeRootContainer.h b/Source/WebCore/rendering/RenderSVGShadowTreeRootContainer.h new file mode 100644 index 0000000..bff2a87 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGShadowTreeRootContainer.h @@ -0,0 +1,54 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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 + aint 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. +*/ + +#ifndef RenderSVGShadowTreeRootContainer_h +#define RenderSVGShadowTreeRootContainer_h + +#if ENABLE(SVG) +#include "RenderSVGTransformableContainer.h" + +namespace WebCore { + +class SVGUseElement; +class SVGShadowTreeRootElement; + +class RenderSVGShadowTreeRootContainer : public RenderSVGTransformableContainer { +public: + RenderSVGShadowTreeRootContainer(SVGUseElement*); + virtual ~RenderSVGShadowTreeRootContainer(); + + virtual bool isSVGShadowTreeRootContainer() const { return true; } + + void markShadowTreeForRecreation() { m_recreateTree = true; } + void updateStyle(Node::StyleChange); + virtual void updateFromElement(); + + Node* rootElement() const; + +private: + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + bool m_recreateTree; + RefPtr<SVGShadowTreeRootElement> m_shadowRoot; +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/RenderSVGTransformableContainer.cpp b/Source/WebCore/rendering/RenderSVGTransformableContainer.cpp new file mode 100644 index 0000000..3c91170 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGTransformableContainer.cpp @@ -0,0 +1,66 @@ +/* + Copyright (C) 2004, 2005 Nikolas Zimmermann <zimmermann@kde.org> + 2004, 2005, 2006 Rob Buis <buis@kde.org> + 2009 Google, Inc. + + 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" + +#if ENABLE(SVG) +#include "RenderSVGTransformableContainer.h" + +#include "SVGNames.h" +#include "SVGShadowTreeElements.h" +#include "SVGStyledTransformableElement.h" + +namespace WebCore { + +RenderSVGTransformableContainer::RenderSVGTransformableContainer(SVGStyledTransformableElement* node) + : RenderSVGContainer(node) + , m_needsTransformUpdate(true) +{ +} + +bool RenderSVGTransformableContainer::calculateLocalTransform() +{ + SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node()); + + bool needsUpdate = m_needsTransformUpdate; + if (needsUpdate) { + m_localTransform = element->animatedLocalTransform(); + m_needsTransformUpdate = false; + } + + if (!element->hasTagName(SVGNames::gTag) || !static_cast<SVGGElement*>(element)->isShadowTreeContainerElement()) + return needsUpdate; + + FloatSize translation = static_cast<SVGShadowTreeContainerElement*>(element)->containerTranslation(); + if (translation.width() == 0 && translation.height() == 0) + return needsUpdate; + + // FIXME: Could optimize this case for use to avoid refetching the animatedLocalTransform() here, if only the containerTranslation() changed. + if (!needsUpdate) + m_localTransform = element->animatedLocalTransform(); + + m_localTransform.translate(translation.width(), translation.height()); + return true; +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/RenderSVGTransformableContainer.h b/Source/WebCore/rendering/RenderSVGTransformableContainer.h new file mode 100644 index 0000000..b49a403 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGTransformableContainer.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2007 Eric Seidel <eric@webkit.org> + 2009 Google, Inc. + + 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 + aint 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. + */ + +#ifndef RenderSVGTransformableContainer_h +#define RenderSVGTransformableContainer_h + +#if ENABLE(SVG) +#include "RenderSVGContainer.h" + +namespace WebCore { + + class SVGStyledTransformableElement; + class RenderSVGTransformableContainer : public RenderSVGContainer { + public: + explicit RenderSVGTransformableContainer(SVGStyledTransformableElement*); + + virtual const AffineTransform& localToParentTransform() const { return m_localTransform; } + virtual void setNeedsTransformUpdate() { m_needsTransformUpdate = true; } + + private: + virtual bool calculateLocalTransform(); + virtual AffineTransform localTransform() const { return m_localTransform; } + + bool m_needsTransformUpdate : 1; + AffineTransform m_localTransform; + }; +} + +#endif // ENABLE(SVG) +#endif // RenderSVGTransformableContainer_h diff --git a/Source/WebCore/rendering/RenderSVGViewportContainer.cpp b/Source/WebCore/rendering/RenderSVGViewportContainer.cpp new file mode 100644 index 0000000..8031328 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGViewportContainer.cpp @@ -0,0 +1,94 @@ +/* + Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> + 2004, 2005, 2007 Rob Buis <buis@kde.org> + 2007 Eric Seidel <eric@webkit.org> + 2009 Google, Inc. + Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. + + 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 + aint 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" + +#if ENABLE(SVG) +#include "RenderSVGViewportContainer.h" + +#include "GraphicsContext.h" +#include "RenderView.h" +#include "SVGNames.h" +#include "SVGSVGElement.h" + +namespace WebCore { + +RenderSVGViewportContainer::RenderSVGViewportContainer(SVGStyledElement* node) + : RenderSVGContainer(node) +{ +} + +void RenderSVGViewportContainer::applyViewportClip(PaintInfo& paintInfo) +{ + if (SVGRenderSupport::isOverflowHidden(this)) + paintInfo.context->clip(m_viewport); +} + +void RenderSVGViewportContainer::calcViewport() +{ + SVGElement* element = static_cast<SVGElement*>(node()); + if (element->hasTagName(SVGNames::svgTag)) { + SVGSVGElement* svg = static_cast<SVGSVGElement*>(element); + + FloatRect oldViewport = m_viewport; + m_viewport = FloatRect(svg->x().value(svg) + , svg->y().value(svg) + , svg->width().value(svg) + , svg->height().value(svg)); + + if (oldViewport != m_viewport) + setNeedsBoundariesUpdate(); + } +} + +AffineTransform RenderSVGViewportContainer::viewportTransform() const +{ + if (node()->hasTagName(SVGNames::svgTag)) { + SVGSVGElement* svg = static_cast<SVGSVGElement*>(node()); + return svg->viewBoxToViewTransform(m_viewport.width(), m_viewport.height()); + } + + return AffineTransform(); +} + +const AffineTransform& RenderSVGViewportContainer::localToParentTransform() const +{ + AffineTransform viewportTranslation(viewportTransform()); + m_localToParentTransform = viewportTranslation.translateRight(m_viewport.x(), m_viewport.y()); + return m_localToParentTransform; + // If this class were ever given a localTransform(), then the above would read: + // return viewportTransform() * localTransform() * viewportTranslation; +} + +bool RenderSVGViewportContainer::pointIsInsideViewportClip(const FloatPoint& pointInParent) +{ + // Respect the viewport clip (which is in parent coords) + if (!SVGRenderSupport::isOverflowHidden(this)) + return true; + + return m_viewport.contains(pointInParent); +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/RenderSVGViewportContainer.h b/Source/WebCore/rendering/RenderSVGViewportContainer.h new file mode 100644 index 0000000..5373ca8 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGViewportContainer.h @@ -0,0 +1,66 @@ +/* + Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> + 2004, 2005, 2007 Rob Buis <buis@kde.org> + 2009 Google, Inc. + Copyright (C) 2009 Apple Inc. All rights reserved. + + 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 + aint 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. +*/ + +#ifndef RenderSVGViewportContainer_h +#define RenderSVGViewportContainer_h + +#if ENABLE(SVG) +#include "RenderSVGContainer.h" + +namespace WebCore { + +// This is used for non-root <svg> elements and <marker> elements, neither of which are SVGTransformable +// thus we inherit from RenderSVGContainer instead of RenderSVGTransformableContainer +class RenderSVGViewportContainer : public RenderSVGContainer { +public: + explicit RenderSVGViewportContainer(SVGStyledElement*); + +private: + virtual bool isSVGContainer() const { return true; } + virtual bool isSVGViewportContainer() const { return true; } + virtual const char* renderName() const { return "RenderSVGViewportContainer"; } + + AffineTransform viewportTransform() const; + virtual const AffineTransform& localToParentTransform() const; + + virtual void calcViewport(); + + virtual void applyViewportClip(PaintInfo&); + virtual bool pointIsInsideViewportClip(const FloatPoint& pointInParent); + + FloatRect m_viewport; + mutable AffineTransform m_localToParentTransform; +}; + +inline RenderSVGViewportContainer* toRenderSVGViewportContainer(RenderObject* object) +{ + ASSERT(!object || !strcmp(object->renderName(), "RenderSVGViewportContainer")); + return static_cast<RenderSVGViewportContainer*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderSVGViewportContainer(const RenderSVGViewportContainer*); + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif // RenderSVGViewportContainer_h diff --git a/Source/WebCore/rendering/RenderScrollbar.cpp b/Source/WebCore/rendering/RenderScrollbar.cpp new file mode 100644 index 0000000..44a0126 --- /dev/null +++ b/Source/WebCore/rendering/RenderScrollbar.cpp @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2008, 2009 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderScrollbar.h" + +#include "Frame.h" +#include "FrameView.h" +#include "RenderPart.h" +#include "RenderScrollbarPart.h" +#include "RenderScrollbarTheme.h" + +namespace WebCore { + +PassRefPtr<Scrollbar> RenderScrollbar::createCustomScrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, RenderBox* renderer, Frame* owningFrame) +{ + return adoptRef(new RenderScrollbar(client, orientation, renderer, owningFrame)); +} + +RenderScrollbar::RenderScrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, RenderBox* renderer, Frame* owningFrame) + : Scrollbar(client, orientation, RegularScrollbar, RenderScrollbarTheme::renderScrollbarTheme()) + , m_owner(renderer) + , m_owningFrame(owningFrame) +{ + // FIXME: We need to do this because RenderScrollbar::styleChanged is called as soon as the scrollbar is created. + + // Update the scrollbar size. + int width = 0; + int height = 0; + updateScrollbarPart(ScrollbarBGPart); + if (RenderScrollbarPart* part = m_parts.get(ScrollbarBGPart)) { + part->layout(); + width = part->width(); + height = part->height(); + } else if (this->orientation() == HorizontalScrollbar) + width = this->width(); + else + height = this->height(); + + setFrameRect(IntRect(0, 0, width, height)); +} + +RenderScrollbar::~RenderScrollbar() +{ + ASSERT(m_parts.isEmpty()); +} + +RenderBox* RenderScrollbar::owningRenderer() const +{ + if (m_owningFrame) { + RenderBox* currentRenderer = m_owningFrame->ownerRenderer(); + return currentRenderer; + } + return m_owner; +} + +void RenderScrollbar::setParent(ScrollView* parent) +{ + Scrollbar::setParent(parent); + if (!parent) { + // Destroy all of the scrollbar's RenderBoxes. + updateScrollbarParts(true); + } +} + +void RenderScrollbar::setEnabled(bool e) +{ + bool wasEnabled = enabled(); + Scrollbar::setEnabled(e); + if (wasEnabled != e) + updateScrollbarParts(); +} + +void RenderScrollbar::styleChanged() +{ + updateScrollbarParts(); +} + +void RenderScrollbar::paint(GraphicsContext* context, const IntRect& damageRect) +{ + if (context->updatingControlTints()) { + updateScrollbarParts(); + return; + } + Scrollbar::paint(context, damageRect); +} + +void RenderScrollbar::setHoveredPart(ScrollbarPart part) +{ + if (part == m_hoveredPart) + return; + + ScrollbarPart oldPart = m_hoveredPart; + m_hoveredPart = part; + + updateScrollbarPart(oldPart); + updateScrollbarPart(m_hoveredPart); + + updateScrollbarPart(ScrollbarBGPart); + updateScrollbarPart(TrackBGPart); +} + +void RenderScrollbar::setPressedPart(ScrollbarPart part) +{ + ScrollbarPart oldPart = m_pressedPart; + Scrollbar::setPressedPart(part); + + updateScrollbarPart(oldPart); + updateScrollbarPart(part); + + updateScrollbarPart(ScrollbarBGPart); + updateScrollbarPart(TrackBGPart); +} + +static ScrollbarPart s_styleResolvePart; +static RenderScrollbar* s_styleResolveScrollbar; + +RenderScrollbar* RenderScrollbar::scrollbarForStyleResolve() +{ + return s_styleResolveScrollbar; +} + +ScrollbarPart RenderScrollbar::partForStyleResolve() +{ + return s_styleResolvePart; +} + +PassRefPtr<RenderStyle> RenderScrollbar::getScrollbarPseudoStyle(ScrollbarPart partType, PseudoId pseudoId) +{ + if (!m_owner) + return 0; + + s_styleResolvePart = partType; + s_styleResolveScrollbar = this; + RefPtr<RenderStyle> result = owningRenderer()->getUncachedPseudoStyle(pseudoId, owningRenderer()->style()); + s_styleResolvePart = NoPart; + s_styleResolveScrollbar = 0; + + // Scrollbars for root frames should always have background color + // unless explicitly specified as transparent. So we force it. + // This is because WebKit assumes scrollbar to be always painted and missing background + // causes visual artifact like non-repainted dirty region. + if (result && m_owningFrame && m_owningFrame->view() && !m_owningFrame->view()->isTransparent() && !result->hasBackground()) + result->setBackgroundColor(Color::white); + + return result; +} + +void RenderScrollbar::updateScrollbarParts(bool destroy) +{ + updateScrollbarPart(ScrollbarBGPart, destroy); + updateScrollbarPart(BackButtonStartPart, destroy); + updateScrollbarPart(ForwardButtonStartPart, destroy); + updateScrollbarPart(BackTrackPart, destroy); + updateScrollbarPart(ThumbPart, destroy); + updateScrollbarPart(ForwardTrackPart, destroy); + updateScrollbarPart(BackButtonEndPart, destroy); + updateScrollbarPart(ForwardButtonEndPart, destroy); + updateScrollbarPart(TrackBGPart, destroy); + + if (destroy) + return; + + // See if the scrollbar's thickness changed. If so, we need to mark our owning object as needing a layout. + bool isHorizontal = orientation() == HorizontalScrollbar; + int oldThickness = isHorizontal ? height() : width(); + int newThickness = 0; + RenderScrollbarPart* part = m_parts.get(ScrollbarBGPart); + if (part) { + part->layout(); + newThickness = isHorizontal ? part->height() : part->width(); + } + + if (newThickness != oldThickness) { + setFrameRect(IntRect(x(), y(), isHorizontal ? width() : newThickness, isHorizontal ? newThickness : height())); + owningRenderer()->setChildNeedsLayout(true); + } +} + +static PseudoId pseudoForScrollbarPart(ScrollbarPart part) +{ + switch (part) { + case BackButtonStartPart: + case ForwardButtonStartPart: + case BackButtonEndPart: + case ForwardButtonEndPart: + return SCROLLBAR_BUTTON; + case BackTrackPart: + case ForwardTrackPart: + return SCROLLBAR_TRACK_PIECE; + case ThumbPart: + return SCROLLBAR_THUMB; + case TrackBGPart: + return SCROLLBAR_TRACK; + case ScrollbarBGPart: + return SCROLLBAR; + case NoPart: + case AllParts: + break; + } + ASSERT_NOT_REACHED(); + return SCROLLBAR; +} + +void RenderScrollbar::updateScrollbarPart(ScrollbarPart partType, bool destroy) +{ + if (partType == NoPart) + return; + + RefPtr<RenderStyle> partStyle = !destroy ? getScrollbarPseudoStyle(partType, pseudoForScrollbarPart(partType)) : 0; + + bool needRenderer = !destroy && partStyle && partStyle->display() != NONE && partStyle->visibility() == VISIBLE; + + if (needRenderer && partStyle->display() != BLOCK) { + // See if we are a button that should not be visible according to OS settings. + ScrollbarButtonsPlacement buttonsPlacement = theme()->buttonsPlacement(); + switch (partType) { + case BackButtonStartPart: + needRenderer = (buttonsPlacement == ScrollbarButtonsSingle || buttonsPlacement == ScrollbarButtonsDoubleStart || + buttonsPlacement == ScrollbarButtonsDoubleBoth); + break; + case ForwardButtonStartPart: + needRenderer = (buttonsPlacement == ScrollbarButtonsDoubleStart || buttonsPlacement == ScrollbarButtonsDoubleBoth); + break; + case BackButtonEndPart: + needRenderer = (buttonsPlacement == ScrollbarButtonsDoubleEnd || buttonsPlacement == ScrollbarButtonsDoubleBoth); + break; + case ForwardButtonEndPart: + needRenderer = (buttonsPlacement == ScrollbarButtonsSingle || buttonsPlacement == ScrollbarButtonsDoubleEnd || + buttonsPlacement == ScrollbarButtonsDoubleBoth); + break; + default: + break; + } + } + + RenderScrollbarPart* partRenderer = m_parts.get(partType); + if (!partRenderer && needRenderer) { + partRenderer = new (owningRenderer()->renderArena()) RenderScrollbarPart(owningRenderer()->document(), this, partType); + m_parts.set(partType, partRenderer); + } else if (partRenderer && !needRenderer) { + m_parts.remove(partType); + partRenderer->destroy(); + partRenderer = 0; + } + + if (partRenderer) + partRenderer->setStyle(partStyle.release()); +} + +void RenderScrollbar::paintPart(GraphicsContext* graphicsContext, ScrollbarPart partType, const IntRect& rect) +{ + RenderScrollbarPart* partRenderer = m_parts.get(partType); + if (!partRenderer) + return; + partRenderer->paintIntoRect(graphicsContext, x(), y(), rect); +} + +IntRect RenderScrollbar::buttonRect(ScrollbarPart partType) +{ + RenderScrollbarPart* partRenderer = m_parts.get(partType); + if (!partRenderer) + return IntRect(); + + partRenderer->layout(); + + bool isHorizontal = orientation() == HorizontalScrollbar; + if (partType == BackButtonStartPart) + return IntRect(x(), y(), isHorizontal ? partRenderer->width() : width(), isHorizontal ? height() : partRenderer->height()); + if (partType == ForwardButtonEndPart) + return IntRect(isHorizontal ? x() + width() - partRenderer->width() : x(), + + isHorizontal ? y() : y() + height() - partRenderer->height(), + isHorizontal ? partRenderer->width() : width(), + isHorizontal ? height() : partRenderer->height()); + + if (partType == ForwardButtonStartPart) { + IntRect previousButton = buttonRect(BackButtonStartPart); + return IntRect(isHorizontal ? x() + previousButton.width() : x(), + isHorizontal ? y() : y() + previousButton.height(), + isHorizontal ? partRenderer->width() : width(), + isHorizontal ? height() : partRenderer->height()); + } + + IntRect followingButton = buttonRect(ForwardButtonEndPart); + return IntRect(isHorizontal ? x() + width() - followingButton.width() - partRenderer->width() : x(), + isHorizontal ? y() : y() + height() - followingButton.height() - partRenderer->height(), + isHorizontal ? partRenderer->width() : width(), + isHorizontal ? height() : partRenderer->height()); +} + +IntRect RenderScrollbar::trackRect(int startLength, int endLength) +{ + RenderScrollbarPart* part = m_parts.get(TrackBGPart); + if (part) + part->layout(); + + if (orientation() == HorizontalScrollbar) { + int marginLeft = part ? part->marginLeft() : 0; + int marginRight = part ? part->marginRight() : 0; + startLength += marginLeft; + endLength += marginRight; + int totalLength = startLength + endLength; + return IntRect(x() + startLength, y(), width() - totalLength, height()); + } + + int marginTop = part ? part->marginTop() : 0; + int marginBottom = part ? part->marginBottom() : 0; + startLength += marginTop; + endLength += marginBottom; + int totalLength = startLength + endLength; + + return IntRect(x(), y() + startLength, width(), height() - totalLength); +} + +IntRect RenderScrollbar::trackPieceRectWithMargins(ScrollbarPart partType, const IntRect& oldRect) +{ + RenderScrollbarPart* partRenderer = m_parts.get(partType); + if (!partRenderer) + return oldRect; + + partRenderer->layout(); + + IntRect rect = oldRect; + if (orientation() == HorizontalScrollbar) { + rect.setX(rect.x() + partRenderer->marginLeft()); + rect.setWidth(rect.width() - (partRenderer->marginLeft() + partRenderer->marginRight())); + } else { + rect.setY(rect.y() + partRenderer->marginTop()); + rect.setHeight(rect.height() - (partRenderer->marginTop() + partRenderer->marginBottom())); + } + return rect; +} + +int RenderScrollbar::minimumThumbLength() +{ + RenderScrollbarPart* partRenderer = m_parts.get(ThumbPart); + if (!partRenderer) + return 0; + partRenderer->layout(); + return orientation() == HorizontalScrollbar ? partRenderer->width() : partRenderer->height(); +} + +} diff --git a/Source/WebCore/rendering/RenderScrollbar.h b/Source/WebCore/rendering/RenderScrollbar.h new file mode 100644 index 0000000..de70624 --- /dev/null +++ b/Source/WebCore/rendering/RenderScrollbar.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2008, 2009 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderScrollbar_h +#define RenderScrollbar_h + +#include "RenderStyleConstants.h" +#include "Scrollbar.h" +#include <wtf/HashMap.h> + +namespace WebCore { + +class Frame; +class RenderBox; +class RenderScrollbarPart; +class RenderStyle; + +class RenderScrollbar : public Scrollbar { +protected: + RenderScrollbar(ScrollbarClient*, ScrollbarOrientation, RenderBox*, Frame*); + +public: + friend class Scrollbar; + static PassRefPtr<Scrollbar> createCustomScrollbar(ScrollbarClient*, ScrollbarOrientation, RenderBox*, Frame* owningFrame = 0); + virtual ~RenderScrollbar(); + + static ScrollbarPart partForStyleResolve(); + static RenderScrollbar* scrollbarForStyleResolve(); + + RenderBox* owningRenderer() const; + void clearOwningRenderer() { m_owner = 0; } + + void paintPart(GraphicsContext*, ScrollbarPart, const IntRect&); + + IntRect buttonRect(ScrollbarPart); + IntRect trackRect(int startLength, int endLength); + IntRect trackPieceRectWithMargins(ScrollbarPart, const IntRect&); + + int minimumThumbLength(); + +private: + virtual void setParent(ScrollView*); + virtual void setEnabled(bool); + + virtual void paint(GraphicsContext*, const IntRect& damageRect); + + virtual void setHoveredPart(ScrollbarPart); + virtual void setPressedPart(ScrollbarPart); + + virtual void styleChanged(); + + virtual bool isCustomScrollbar() const { return true; } + + void updateScrollbarParts(bool destroy = false); + + PassRefPtr<RenderStyle> getScrollbarPseudoStyle(ScrollbarPart, PseudoId); + void updateScrollbarPart(ScrollbarPart, bool destroy = false); + + RenderBox* m_owner; + Frame* m_owningFrame; + HashMap<unsigned, RenderScrollbarPart*> m_parts; +}; + +inline RenderScrollbar* toRenderScrollbar(Scrollbar* scrollbar) +{ + ASSERT(!scrollbar || scrollbar->isCustomScrollbar()); + return static_cast<RenderScrollbar*>(scrollbar); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderScrollbar(const RenderScrollbar*); + +} // namespace WebCore + +#endif // RenderScrollbar_h diff --git a/Source/WebCore/rendering/RenderScrollbarPart.cpp b/Source/WebCore/rendering/RenderScrollbarPart.cpp new file mode 100644 index 0000000..16cc204 --- /dev/null +++ b/Source/WebCore/rendering/RenderScrollbarPart.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderScrollbarPart.h" +#include "RenderScrollbar.h" +#include "RenderScrollbarTheme.h" +#include "RenderView.h" + +using namespace std; + +namespace WebCore { + +RenderScrollbarPart::RenderScrollbarPart(Node* node, RenderScrollbar* scrollbar, ScrollbarPart part) + : RenderBlock(node) + , m_scrollbar(scrollbar) + , m_part(part) +{ +} + +RenderScrollbarPart::~RenderScrollbarPart() +{ +} + +void RenderScrollbarPart::layout() +{ + setLocation(IntPoint()); // We don't worry about positioning ourselves. We're just determining our minimum width/height. + if (m_scrollbar->orientation() == HorizontalScrollbar) + layoutHorizontalPart(); + else + layoutVerticalPart(); + + setNeedsLayout(false); +} + +void RenderScrollbarPart::layoutHorizontalPart() +{ + if (m_part == ScrollbarBGPart) { + setWidth(m_scrollbar->width()); + computeScrollbarHeight(); + } else { + computeScrollbarWidth(); + setHeight(m_scrollbar->height()); + } +} + +void RenderScrollbarPart::layoutVerticalPart() +{ + if (m_part == ScrollbarBGPart) { + computeScrollbarWidth(); + setHeight(m_scrollbar->height()); + } else { + setWidth(m_scrollbar->width()); + computeScrollbarHeight(); + } +} + +static int calcScrollbarThicknessUsing(const Length& l, int containingLength) +{ + if (l.isIntrinsicOrAuto()) + return ScrollbarTheme::nativeTheme()->scrollbarThickness(); + return l.calcMinValue(containingLength); +} + +void RenderScrollbarPart::computeScrollbarWidth() +{ + if (!m_scrollbar->owningRenderer()) + return; + int visibleSize = m_scrollbar->owningRenderer()->width() - m_scrollbar->owningRenderer()->borderLeft() - m_scrollbar->owningRenderer()->borderRight(); + int w = calcScrollbarThicknessUsing(style()->width(), visibleSize); + int minWidth = calcScrollbarThicknessUsing(style()->minWidth(), visibleSize); + int maxWidth = style()->maxWidth().isUndefined() ? w : calcScrollbarThicknessUsing(style()->maxWidth(), visibleSize); + setWidth(max(minWidth, min(maxWidth, w))); + + // Buttons and track pieces can all have margins along the axis of the scrollbar. + m_marginLeft = style()->marginLeft().calcMinValue(visibleSize); + m_marginRight = style()->marginRight().calcMinValue(visibleSize); +} + +void RenderScrollbarPart::computeScrollbarHeight() +{ + if (!m_scrollbar->owningRenderer()) + return; + int visibleSize = m_scrollbar->owningRenderer()->height() - m_scrollbar->owningRenderer()->borderTop() - m_scrollbar->owningRenderer()->borderBottom(); + int h = calcScrollbarThicknessUsing(style()->height(), visibleSize); + int minHeight = calcScrollbarThicknessUsing(style()->minHeight(), visibleSize); + int maxHeight = style()->maxHeight().isUndefined() ? h : calcScrollbarThicknessUsing(style()->maxHeight(), visibleSize); + setHeight(max(minHeight, min(maxHeight, h))); + + // Buttons and track pieces can all have margins along the axis of the scrollbar. + m_marginTop = style()->marginTop().calcMinValue(visibleSize); + m_marginBottom = style()->marginBottom().calcMinValue(visibleSize); +} + +void RenderScrollbarPart::computePreferredLogicalWidths() +{ + if (!preferredLogicalWidthsDirty()) + return; + + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = 0; + + setPreferredLogicalWidthsDirty(false); +} + +void RenderScrollbarPart::styleWillChange(StyleDifference diff, const RenderStyle* newStyle) +{ + RenderBlock::styleWillChange(diff, newStyle); + setInline(false); +} + +void RenderScrollbarPart::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBlock::styleDidChange(diff, oldStyle); + setInline(false); + setPositioned(false); + setFloating(false); + setHasOverflowClip(false); + if (oldStyle && m_scrollbar && m_part != NoPart && diff >= StyleDifferenceRepaint) + m_scrollbar->theme()->invalidatePart(m_scrollbar, m_part); +} + +void RenderScrollbarPart::imageChanged(WrappedImagePtr image, const IntRect* rect) +{ + if (m_scrollbar && m_part != NoPart) + m_scrollbar->theme()->invalidatePart(m_scrollbar, m_part); + else { + if (FrameView* frameView = view()->frameView()) { + if (frameView->isFrameViewScrollCorner(this)) { + frameView->invalidateScrollCorner(); + return; + } + } + + RenderBlock::imageChanged(image, rect); + } +} + +void RenderScrollbarPart::paintIntoRect(GraphicsContext* graphicsContext, int tx, int ty, const IntRect& rect) +{ + // Make sure our dimensions match the rect. + setLocation(rect.x() - tx, rect.y() - ty); + setWidth(rect.width()); + setHeight(rect.height()); + + if (graphicsContext->paintingDisabled()) + return; + + // Now do the paint. + PaintInfo paintInfo(graphicsContext, rect, PaintPhaseBlockBackground, false, 0, 0); + paint(paintInfo, tx, ty); + paintInfo.phase = PaintPhaseChildBlockBackgrounds; + paint(paintInfo, tx, ty); + paintInfo.phase = PaintPhaseFloat; + paint(paintInfo, tx, ty); + paintInfo.phase = PaintPhaseForeground; + paint(paintInfo, tx, ty); + paintInfo.phase = PaintPhaseOutline; + paint(paintInfo, tx, ty); +} + +} diff --git a/Source/WebCore/rendering/RenderScrollbarPart.h b/Source/WebCore/rendering/RenderScrollbarPart.h new file mode 100644 index 0000000..24485d0 --- /dev/null +++ b/Source/WebCore/rendering/RenderScrollbarPart.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderScrollbarPart_h +#define RenderScrollbarPart_h + +#include "RenderBlock.h" +#include "ScrollTypes.h" + +namespace WebCore { + +class RenderScrollbar; + +class RenderScrollbarPart : public RenderBlock { +public: + RenderScrollbarPart(Node*, RenderScrollbar* = 0, ScrollbarPart = NoPart); + virtual ~RenderScrollbarPart(); + + virtual const char* renderName() const { return "RenderScrollbarPart"; } + + virtual bool requiresLayer() const { return false; } + + virtual void layout(); + virtual void computePreferredLogicalWidths(); + + void paintIntoRect(GraphicsContext*, int tx, int ty, const IntRect&); + +protected: + virtual void styleWillChange(StyleDifference diff, const RenderStyle* newStyle); + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + virtual void imageChanged(WrappedImagePtr, const IntRect* = 0); + +private: + void layoutHorizontalPart(); + void layoutVerticalPart(); + + void computeScrollbarWidth(); + void computeScrollbarHeight(); + + RenderScrollbar* m_scrollbar; + ScrollbarPart m_part; +}; + +} // namespace WebCore + +#endif // RenderScrollbarPart_h diff --git a/Source/WebCore/rendering/RenderScrollbarTheme.cpp b/Source/WebCore/rendering/RenderScrollbarTheme.cpp new file mode 100644 index 0000000..e32d87a --- /dev/null +++ b/Source/WebCore/rendering/RenderScrollbarTheme.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderScrollbarTheme.h" +#include "RenderScrollbar.h" +#include <wtf/StdLibExtras.h> + +namespace WebCore { + +RenderScrollbarTheme* RenderScrollbarTheme::renderScrollbarTheme() +{ + DEFINE_STATIC_LOCAL(RenderScrollbarTheme, theme, ()); + return &theme; +} + +void RenderScrollbarTheme::buttonSizesAlongTrackAxis(Scrollbar* scrollbar, int& beforeSize, int& afterSize) +{ + IntRect firstButton = backButtonRect(scrollbar, BackButtonStartPart); + IntRect secondButton = forwardButtonRect(scrollbar, ForwardButtonStartPart); + IntRect thirdButton = backButtonRect(scrollbar, BackButtonEndPart); + IntRect fourthButton = forwardButtonRect(scrollbar, ForwardButtonEndPart); + if (scrollbar->orientation() == HorizontalScrollbar) { + beforeSize = firstButton.width() + secondButton.width(); + afterSize = thirdButton.width() + fourthButton.width(); + } else { + beforeSize = firstButton.height() + secondButton.height(); + afterSize = thirdButton.height() + fourthButton.height(); + } +} + +bool RenderScrollbarTheme::hasButtons(Scrollbar* scrollbar) +{ + int startSize; + int endSize; + buttonSizesAlongTrackAxis(scrollbar, startSize, endSize); + return (startSize + endSize) <= (scrollbar->orientation() == HorizontalScrollbar ? scrollbar->width() : scrollbar->height()); +} + +bool RenderScrollbarTheme::hasThumb(Scrollbar* scrollbar) +{ + return trackLength(scrollbar) - thumbLength(scrollbar) >= 0; +} + +int RenderScrollbarTheme::minimumThumbLength(Scrollbar* scrollbar) +{ + return toRenderScrollbar(scrollbar)->minimumThumbLength(); +} + +IntRect RenderScrollbarTheme::backButtonRect(Scrollbar* scrollbar, ScrollbarPart partType, bool) +{ + return toRenderScrollbar(scrollbar)->buttonRect(partType); +} + +IntRect RenderScrollbarTheme::forwardButtonRect(Scrollbar* scrollbar, ScrollbarPart partType, bool) +{ + return toRenderScrollbar(scrollbar)->buttonRect(partType); +} + +IntRect RenderScrollbarTheme::trackRect(Scrollbar* scrollbar, bool) +{ + if (!hasButtons(scrollbar)) + return scrollbar->frameRect(); + + int startLength; + int endLength; + buttonSizesAlongTrackAxis(scrollbar, startLength, endLength); + + return toRenderScrollbar(scrollbar)->trackRect(startLength, endLength); +} + +IntRect RenderScrollbarTheme::constrainTrackRectToTrackPieces(Scrollbar* scrollbar, const IntRect& rect) +{ + IntRect backRect = toRenderScrollbar(scrollbar)->trackPieceRectWithMargins(BackTrackPart, rect); + IntRect forwardRect = toRenderScrollbar(scrollbar)->trackPieceRectWithMargins(ForwardTrackPart, rect); + IntRect result = rect; + if (scrollbar->orientation() == HorizontalScrollbar) { + result.setX(backRect.x()); + result.setWidth(forwardRect.right() - backRect.x()); + } else { + result.setY(backRect.y()); + result.setHeight(forwardRect.bottom() - backRect.y()); + } + return result; +} + +void RenderScrollbarTheme::paintScrollCorner(ScrollView*, GraphicsContext* context, const IntRect& cornerRect) +{ + // FIXME: Implement. + context->fillRect(cornerRect, Color::white, ColorSpaceDeviceRGB); +} + +void RenderScrollbarTheme::paintScrollbarBackground(GraphicsContext* context, Scrollbar* scrollbar) +{ + toRenderScrollbar(scrollbar)->paintPart(context, ScrollbarBGPart, scrollbar->frameRect()); +} + +void RenderScrollbarTheme::paintTrackBackground(GraphicsContext* context, Scrollbar* scrollbar, const IntRect& rect) +{ + toRenderScrollbar(scrollbar)->paintPart(context, TrackBGPart, rect); +} + +void RenderScrollbarTheme::paintTrackPiece(GraphicsContext* context, Scrollbar* scrollbar, const IntRect& rect, ScrollbarPart part) +{ + toRenderScrollbar(scrollbar)->paintPart(context, part, rect); +} + +void RenderScrollbarTheme::paintButton(GraphicsContext* context, Scrollbar* scrollbar, const IntRect& rect, ScrollbarPart part) +{ + toRenderScrollbar(scrollbar)->paintPart(context, part, rect); +} + +void RenderScrollbarTheme::paintThumb(GraphicsContext* context, Scrollbar* scrollbar, const IntRect& rect) +{ + toRenderScrollbar(scrollbar)->paintPart(context, ThumbPart, rect); +} + +} diff --git a/Source/WebCore/rendering/RenderScrollbarTheme.h b/Source/WebCore/rendering/RenderScrollbarTheme.h new file mode 100644 index 0000000..9b8b2c1 --- /dev/null +++ b/Source/WebCore/rendering/RenderScrollbarTheme.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderScrollbarTheme_h +#define RenderScrollbarTheme_h + +#include "ScrollbarThemeComposite.h" + +namespace WebCore { + +class PlatformMouseEvent; +class Scrollbar; +class ScrollView; + +class RenderScrollbarTheme : public ScrollbarThemeComposite { +public: + virtual ~RenderScrollbarTheme() {}; + + virtual int scrollbarThickness(ScrollbarControlSize controlSize) { return ScrollbarTheme::nativeTheme()->scrollbarThickness(controlSize); } + + virtual ScrollbarButtonsPlacement buttonsPlacement() const { return ScrollbarTheme::nativeTheme()->buttonsPlacement(); } + + virtual bool supportsControlTints() const { return true; } + + virtual void paintScrollCorner(ScrollView*, GraphicsContext* context, const IntRect& cornerRect); + + virtual bool shouldCenterOnThumb(Scrollbar* scrollbar, const PlatformMouseEvent& event) { return ScrollbarTheme::nativeTheme()->shouldCenterOnThumb(scrollbar, event); } + + virtual double initialAutoscrollTimerDelay() { return ScrollbarTheme::nativeTheme()->initialAutoscrollTimerDelay(); } + virtual double autoscrollTimerDelay() { return ScrollbarTheme::nativeTheme()->autoscrollTimerDelay(); } + + virtual void registerScrollbar(Scrollbar* scrollbar) { return ScrollbarTheme::nativeTheme()->registerScrollbar(scrollbar); } + virtual void unregisterScrollbar(Scrollbar* scrollbar) { return ScrollbarTheme::nativeTheme()->unregisterScrollbar(scrollbar); } + + virtual int minimumThumbLength(Scrollbar*); + + void buttonSizesAlongTrackAxis(Scrollbar* scrollbar, int& beforeSize, int& afterSize); + + static RenderScrollbarTheme* renderScrollbarTheme(); + +protected: + virtual bool hasButtons(Scrollbar*); + virtual bool hasThumb(Scrollbar*); + + virtual IntRect backButtonRect(Scrollbar*, ScrollbarPart, bool painting = false); + virtual IntRect forwardButtonRect(Scrollbar*, ScrollbarPart, bool painting = false); + virtual IntRect trackRect(Scrollbar*, bool painting = false); + + virtual void paintScrollbarBackground(GraphicsContext*, Scrollbar*); + virtual void paintTrackBackground(GraphicsContext*, Scrollbar*, const IntRect&); + virtual void paintTrackPiece(GraphicsContext*, Scrollbar*, const IntRect&, ScrollbarPart); + virtual void paintButton(GraphicsContext*, Scrollbar*, const IntRect&, ScrollbarPart); + virtual void paintThumb(GraphicsContext*, Scrollbar*, const IntRect&); + + virtual IntRect constrainTrackRectToTrackPieces(Scrollbar*, const IntRect&); +}; + +} // namespace WebCore + +#endif // RenderScrollbarTheme_h diff --git a/Source/WebCore/rendering/RenderSelectionInfo.h b/Source/WebCore/rendering/RenderSelectionInfo.h new file mode 100644 index 0000000..a09fc1a --- /dev/null +++ b/Source/WebCore/rendering/RenderSelectionInfo.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * (C) 2004 Allan Sandfeld Jensen (kde@carewolf.com) + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSelectionInfo_h +#define RenderSelectionInfo_h + +#include "IntRect.h" +#include "RenderBox.h" + +namespace WebCore { + +class RenderSelectionInfoBase : public Noncopyable { +public: + RenderSelectionInfoBase() + : m_object(0) + , m_repaintContainer(0) + , m_state(RenderObject::SelectionNone) + { + } + + RenderSelectionInfoBase(RenderObject* o) + : m_object(o) + , m_repaintContainer(o->containerForRepaint()) + , m_state(o->selectionState()) + { + } + + RenderObject* object() const { return m_object; } + RenderBoxModelObject* repaintContainer() const { return m_repaintContainer; } + RenderObject::SelectionState state() const { return m_state; } + +protected: + RenderObject* m_object; + RenderBoxModelObject* m_repaintContainer; + RenderObject::SelectionState m_state; +}; + +// This struct is used when the selection changes to cache the old and new state of the selection for each RenderObject. +class RenderSelectionInfo : public RenderSelectionInfoBase { +public: + RenderSelectionInfo(RenderObject* o, bool clipToVisibleContent) + : RenderSelectionInfoBase(o) + , m_rect(o->needsLayout() ? IntRect() : o->selectionRectForRepaint(m_repaintContainer, clipToVisibleContent)) + { + } + + void repaint() + { + m_object->repaintUsingContainer(m_repaintContainer, m_rect); + } + + IntRect rect() const { return m_rect; } + +private: + IntRect m_rect; // relative to repaint container +}; + + +// This struct is used when the selection changes to cache the old and new state of the selection for each RenderBlock. +class RenderBlockSelectionInfo : public RenderSelectionInfoBase { +public: + RenderBlockSelectionInfo(RenderBlock* b) + : RenderSelectionInfoBase(b) + , m_rects(b->needsLayout() ? GapRects() : block()->selectionGapRectsForRepaint(m_repaintContainer)) + { + } + + void repaint() + { + m_object->repaintUsingContainer(m_repaintContainer, m_rects); + } + + RenderBlock* block() const { return toRenderBlock(m_object); } + GapRects rects() const { return m_rects; } + +private: + GapRects m_rects; // relative to repaint container +}; + +} // namespace WebCore + + +#endif // RenderSelectionInfo_h diff --git a/Source/WebCore/rendering/RenderSlider.cpp b/Source/WebCore/rendering/RenderSlider.cpp new file mode 100644 index 0000000..b73a1ac --- /dev/null +++ b/Source/WebCore/rendering/RenderSlider.cpp @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * 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 "RenderSlider.h" + +#include "CSSPropertyNames.h" +#include "Document.h" +#include "Event.h" +#include "EventHandler.h" +#include "EventNames.h" +#include "Frame.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "HTMLParserIdioms.h" +#include "MediaControlElements.h" +#include "MouseEvent.h" +#include "RenderLayer.h" +#include "RenderTheme.h" +#include "RenderView.h" +#include "ShadowElement.h" +#include "SliderThumbElement.h" +#include "StepRange.h" +#include <wtf/MathExtras.h> + +using std::min; + +namespace WebCore { + +static const int defaultTrackLength = 129; + +// Returns a value between 0 and 1. +static double sliderPosition(HTMLInputElement* element) +{ + StepRange range(element); + return range.proportionFromValue(range.valueFromElement(element)); +} + +RenderSlider::RenderSlider(HTMLInputElement* element) + : RenderBlock(element) +{ +} + +RenderSlider::~RenderSlider() +{ + if (m_thumb) + m_thumb->detach(); +} + +int RenderSlider::baselinePosition(FontBaseline, bool /*firstLine*/, LineDirectionMode, LinePositionMode) const +{ + // FIXME: Patch this function for writing-mode. + return height() + marginTop(); +} + +void RenderSlider::computePreferredLogicalWidths() +{ + m_minPreferredLogicalWidth = 0; + m_maxPreferredLogicalWidth = 0; + + if (style()->width().isFixed() && style()->width().value() > 0) + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value()); + else + m_maxPreferredLogicalWidth = defaultTrackLength * style()->effectiveZoom(); + + if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) { + m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); + m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); + } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent())) + m_minPreferredLogicalWidth = 0; + else + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth; + + if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) { + m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); + m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); + } + + int toAdd = borderAndPaddingWidth(); + m_minPreferredLogicalWidth += toAdd; + m_maxPreferredLogicalWidth += toAdd; + + setPreferredLogicalWidthsDirty(false); +} + +void RenderSlider::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBlock::styleDidChange(diff, oldStyle); + + if (m_thumb) + m_thumb->renderer()->setStyle(createThumbStyle(style())); + + setReplaced(isInline()); +} + +PassRefPtr<RenderStyle> RenderSlider::createThumbStyle(const RenderStyle* parentStyle) +{ + RefPtr<RenderStyle> style; + RenderStyle* pseudoStyle = getCachedPseudoStyle(SLIDER_THUMB); + if (pseudoStyle) + // We may be sharing style with another slider, but we must not share the thumb style. + style = RenderStyle::clone(pseudoStyle); + else + style = RenderStyle::create(); + + if (parentStyle) + style->inheritFrom(parentStyle); + + style->setDisplay(BLOCK); + + if (parentStyle->appearance() == SliderVerticalPart) + style->setAppearance(SliderThumbVerticalPart); + else if (parentStyle->appearance() == SliderHorizontalPart) + style->setAppearance(SliderThumbHorizontalPart); + else if (parentStyle->appearance() == MediaSliderPart) + style->setAppearance(MediaSliderThumbPart); + else if (parentStyle->appearance() == MediaVolumeSliderPart) + style->setAppearance(MediaVolumeSliderThumbPart); + + return style.release(); +} + +IntRect RenderSlider::thumbRect() +{ + if (!m_thumb) + return IntRect(); + + IntRect thumbRect; + RenderBox* thumb = toRenderBox(m_thumb->renderer()); + + thumbRect.setWidth(thumb->style()->width().calcMinValue(contentWidth())); + thumbRect.setHeight(thumb->style()->height().calcMinValue(contentHeight())); + + double fraction = sliderPosition(static_cast<HTMLInputElement*>(node())); + IntRect contentRect = contentBoxRect(); + if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart) { + thumbRect.setX(contentRect.x() + (contentRect.width() - thumbRect.width()) / 2); + thumbRect.setY(contentRect.y() + static_cast<int>(nextafter((contentRect.height() - thumbRect.height()) + 1, 0) * (1 - fraction))); + } else { + thumbRect.setX(contentRect.x() + static_cast<int>(nextafter((contentRect.width() - thumbRect.width()) + 1, 0) * fraction)); + thumbRect.setY(contentRect.y() + (contentRect.height() - thumbRect.height()) / 2); + } + + return thumbRect; +} + +void RenderSlider::layout() +{ + ASSERT(needsLayout()); + + RenderBox* thumb = m_thumb ? toRenderBox(m_thumb->renderer()) : 0; + + IntSize baseSize(borderAndPaddingWidth(), borderAndPaddingHeight()); + + if (thumb) { + // Allow the theme to set the size of the thumb. + if (thumb->style()->hasAppearance()) { + // FIXME: This should pass the style, not the renderer, to the theme. + theme()->adjustSliderThumbSize(thumb); + } + + baseSize.expand(thumb->style()->width().calcMinValue(0), thumb->style()->height().calcMinValue(0)); + } + + LayoutRepainter repainter(*this, checkForRepaintDuringLayout()); + + IntSize oldSize = size(); + + setSize(baseSize); + computeLogicalWidth(); + computeLogicalHeight(); + updateLayerTransform(); + + if (thumb) { + if (oldSize != size()) + thumb->setChildNeedsLayout(true, false); + + LayoutStateMaintainer statePusher(view(), this, size(), style()->isFlippedBlocksWritingMode()); + + IntRect oldThumbRect = thumb->frameRect(); + + thumb->layoutIfNeeded(); + + IntRect rect = thumbRect(); + thumb->setFrameRect(rect); + if (thumb->checkForRepaintDuringLayout()) + thumb->repaintDuringLayoutIfMoved(oldThumbRect); + + statePusher.pop(); + addOverflowFromChild(thumb); + } + + repainter.repaintAfterLayout(); + + setNeedsLayout(false); +} + +void RenderSlider::updateFromElement() +{ + // Layout will take care of the thumb's size and position. + if (!m_thumb) { + m_thumb = SliderThumbElement::create(static_cast<HTMLElement*>(node())); + RefPtr<RenderStyle> thumbStyle = createThumbStyle(style()); + m_thumb->setRenderer(m_thumb->createRenderer(renderArena(), thumbStyle.get())); + m_thumb->renderer()->setStyle(thumbStyle.release()); + m_thumb->setAttached(); + m_thumb->setInDocument(); + addChild(m_thumb->renderer()); + } + setNeedsLayout(true); +} + +bool RenderSlider::mouseEventIsInThumb(MouseEvent* evt) +{ + if (!m_thumb || !m_thumb->renderer()) + return false; + +#if ENABLE(VIDEO) + if (style()->appearance() == MediaSliderPart || style()->appearance() == MediaVolumeSliderPart) { + MediaControlInputElement *sliderThumb = static_cast<MediaControlInputElement*>(m_thumb->renderer()->node()); + return sliderThumb->hitTest(evt->absoluteLocation()); + } +#endif + + FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true); + IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect(); + return thumbBounds.contains(roundedIntPoint(localPoint)); +} + +FloatPoint RenderSlider::mouseEventOffsetToThumb(MouseEvent* evt) +{ + ASSERT(m_thumb && m_thumb->renderer()); + FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true); + IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect(); + FloatPoint offset; + offset.setX(thumbBounds.x() + thumbBounds.width() / 2 - localPoint.x()); + offset.setY(thumbBounds.y() + thumbBounds.height() / 2 - localPoint.y()); + return offset; +} + +void RenderSlider::setValueForPosition(int position) +{ + if (!m_thumb || !m_thumb->renderer()) + return; + + HTMLInputElement* element = static_cast<HTMLInputElement*>(node()); + + // Calculate the new value based on the position, and send it to the element. + StepRange range(element); + double fraction = static_cast<double>(position) / trackSize(); + if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart) + fraction = 1 - fraction; + double value = range.clampValue(range.valueFromProportion(fraction)); + element->setValueFromRenderer(serializeForNumberType(value)); + + // Also update the position if appropriate. + if (position != currentPosition()) { + setNeedsLayout(true); + + // FIXME: It seems like this could send extra change events if the same value is set + // multiple times with no layout in between. + element->dispatchFormControlChangeEvent(); + } +} + +int RenderSlider::positionForOffset(const IntPoint& p) +{ + if (!m_thumb || !m_thumb->renderer()) + return 0; + + int position; + if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart) + position = p.y() - m_thumb->renderBox()->height() / 2; + else + position = p.x() - m_thumb->renderBox()->width() / 2; + + return max(0, min(position, trackSize())); +} + +int RenderSlider::currentPosition() +{ + ASSERT(m_thumb); + ASSERT(m_thumb->renderer()); + + if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart) + return toRenderBox(m_thumb->renderer())->y() - contentBoxRect().y(); + return toRenderBox(m_thumb->renderer())->x() - contentBoxRect().x(); +} + +int RenderSlider::trackSize() +{ + ASSERT(m_thumb); + ASSERT(m_thumb->renderer()); + + if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart) + return contentHeight() - m_thumb->renderBox()->height(); + return contentWidth() - m_thumb->renderBox()->width(); +} + +void RenderSlider::forwardEvent(Event* event) +{ + if (event->isMouseEvent()) { + MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); + if (event->type() == eventNames().mousedownEvent && mouseEvent->button() == LeftButton) { + if (!mouseEventIsInThumb(mouseEvent)) { + IntPoint eventOffset = roundedIntPoint(absoluteToLocal(mouseEvent->absoluteLocation(), false, true)); + setValueForPosition(positionForOffset(eventOffset)); + } + } + } + + m_thumb->defaultEventHandler(event); +} + +bool RenderSlider::inDragMode() const +{ + return m_thumb && m_thumb->inDragMode(); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderSlider.h b/Source/WebCore/rendering/RenderSlider.h new file mode 100644 index 0000000..03779a3 --- /dev/null +++ b/Source/WebCore/rendering/RenderSlider.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSlider_h +#define RenderSlider_h + +#include "RenderBlock.h" + +namespace WebCore { + + class HTMLInputElement; + class MouseEvent; + class SliderThumbElement; + + class RenderSlider : public RenderBlock { + public: + RenderSlider(HTMLInputElement*); + virtual ~RenderSlider(); + + void forwardEvent(Event*); + bool inDragMode() const; + IntRect thumbRect(); + + private: + virtual const char* renderName() const { return "RenderSlider"; } + virtual bool isSlider() const { return true; } + + virtual int baselinePosition(FontBaseline, bool firstLine, LineDirectionMode, LinePositionMode = PositionOnContainingLine) const; + virtual void computePreferredLogicalWidths(); + virtual void layout(); + virtual void updateFromElement(); + + bool mouseEventIsInThumb(MouseEvent*); + FloatPoint mouseEventOffsetToThumb(MouseEvent*); + + void setValueForPosition(int position); + void setPositionFromValue(); + int positionForOffset(const IntPoint&); + + int currentPosition(); + + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + virtual bool requiresForcedStyleRecalcPropagation() const { return true; } + + PassRefPtr<RenderStyle> createThumbStyle(const RenderStyle* parentStyle); + + int trackSize(); + + RefPtr<SliderThumbElement> m_thumb; + + friend class SliderThumbElement; + }; + + inline RenderSlider* toRenderSlider(RenderObject* object) + { + ASSERT(!object || object->isSlider()); + return static_cast<RenderSlider*>(object); + } + + // This will catch anyone doing an unnecessary cast. + void toRenderSlider(const RenderSlider*); + +} // namespace WebCore + +#endif // RenderSlider_h diff --git a/Source/WebCore/rendering/RenderSummary.cpp b/Source/WebCore/rendering/RenderSummary.cpp new file mode 100644 index 0000000..8fccf66 --- /dev/null +++ b/Source/WebCore/rendering/RenderSummary.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + * + * 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 "RenderSummary.h" + +namespace WebCore { + +RenderSummary::RenderSummary(Node* element) + : RenderBlock(element) +{ +} + +} diff --git a/Source/WebCore/rendering/RenderSummary.h b/Source/WebCore/rendering/RenderSummary.h new file mode 100644 index 0000000..afc18fa --- /dev/null +++ b/Source/WebCore/rendering/RenderSummary.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + * + * 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. + * + */ + +#ifndef RenderSummary_h +#define RenderSummary_h + +#include "RenderBlock.h" + +namespace WebCore { + +class RenderSummary : public RenderBlock { +public: + explicit RenderSummary(Node*); + +private: + virtual const char* renderName() const { return "RenderSummary"; } + virtual bool isSummary() const { return true; } +}; + +inline RenderSummary* toRenderSummary(RenderObject* object) +{ + ASSERT(!object || object->isSummary()); + return static_cast<RenderSummary*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderSummary(const RenderSummary*); + +} + +#endif // RenderSummary_h + diff --git a/Source/WebCore/rendering/RenderTable.cpp b/Source/WebCore/rendering/RenderTable.cpp new file mode 100644 index 0000000..f0cc264 --- /dev/null +++ b/Source/WebCore/rendering/RenderTable.cpp @@ -0,0 +1,1249 @@ +/* + * 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 "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); + + 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; + } + if (!m_caption) + m_caption = toRenderBlock(child); + 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->isTableSection() && !beforeChild->isTableCol() && beforeChild->style()->display() != TABLE_CAPTION) + 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); + 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::layout() +{ + ASSERT(needsLayout()); + + if (layoutOnlyPositionedObjects()) + 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) { + 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()); + + 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->topVisualOverflow() : section->leftVisualOverflow()); + } + section->setLogicalLocation(sectionLogicalLeft, logicalHeight()); + + setLogicalHeight(logicalHeight() + section->logicalHeight()); + section = sectionBelow(section); + } + + setLogicalHeight(logicalHeight() + borderAndPaddingAfter); + + if (m_caption && m_caption->style()->captionSide() == CAPBOTTOM) { + 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()); + } + + 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(y())); + + 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(leftVisualOverflow(), movedSectionLogicalTop, rightVisualOverflow() - leftVisualOverflow(), bottomVisualOverflow() - movedSectionLogicalTop)); + else + repaintRectangle(IntRect(movedSectionLogicalTop, topVisualOverflow(), rightVisualOverflow() - movedSectionLogicalTop, bottomVisualOverflow() - topVisualOverflow())); + } + + 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; + + int os = 2 * maximalOutlineSize(paintPhase); + if (ty + topVisualOverflow() >= paintInfo.rect.bottom() + os || ty + bottomVisualOverflow() <= paintInfo.rect.y() - os) + return; + if (tx + leftVisualOverflow() >= paintInfo.rect.right() + os || tx + rightVisualOverflow() <= paintInfo.rect.x() - os) + 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; + } +} + +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); + + 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::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 + for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { + switch (child->style()->display()) { + case TABLE_CAPTION: + if (!m_caption && child->isRenderBlock()) { + m_caption = toRenderBlock(child); + m_caption->setNeedsLayout(true); + } + 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; + + 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) +{ + IntRect rect = RenderBlock::overflowClipRect(tx, ty); + + // 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; +} + +} diff --git a/Source/WebCore/rendering/RenderTable.h b/Source/WebCore/rendering/RenderTable.h new file mode 100644 index 0000000..6afb108 --- /dev/null +++ b/Source/WebCore/rendering/RenderTable.h @@ -0,0 +1,287 @@ +/* + * 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, 2009, 2010 Apple Inc. All rights reserved. + * + * 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. + */ + +#ifndef RenderTable_h +#define RenderTable_h + +#include "CSSPropertyNames.h" +#include "RenderBlock.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class RenderTableCol; +class RenderTableCell; +class RenderTableSection; +class TableLayout; + +class RenderTable : public RenderBlock { +public: + explicit RenderTable(Node*); + virtual ~RenderTable(); + + int getColumnPos(int col) const { return m_columnPos[col]; } + + int hBorderSpacing() const { return m_hSpacing; } + int vBorderSpacing() const { return m_vSpacing; } + + bool collapseBorders() const { return style()->borderCollapse(); } + + int borderStart() const { return m_borderStart; } + int borderEnd() const { return m_borderEnd; } + int borderBefore() const; + int borderAfter() const; + + int borderLeft() const + { + if (style()->isHorizontalWritingMode()) + return style()->isLeftToRightDirection() ? borderStart() : borderEnd(); + return style()->isFlippedBlocksWritingMode() ? borderAfter() : borderBefore(); + } + + int borderRight() const + { + if (style()->isHorizontalWritingMode()) + return style()->isLeftToRightDirection() ? borderEnd() : borderStart(); + return style()->isFlippedBlocksWritingMode() ? borderBefore() : borderAfter(); + } + + int borderTop() const + { + if (style()->isHorizontalWritingMode()) + return style()->isFlippedBlocksWritingMode() ? borderAfter() : borderBefore(); + return style()->isLeftToRightDirection() ? borderStart() : borderEnd(); + } + + int borderBottom() const + { + if (style()->isHorizontalWritingMode()) + return style()->isFlippedBlocksWritingMode() ? borderBefore() : borderAfter(); + return style()->isLeftToRightDirection() ? borderEnd() : borderStart(); + } + + const Color bgColor() const { return style()->visitedDependentColor(CSSPropertyBackgroundColor); } + + int outerBorderBefore() const; + int outerBorderAfter() const; + int outerBorderStart() const; + int outerBorderEnd() const; + + int outerBorderLeft() const + { + if (style()->isHorizontalWritingMode()) + return style()->isLeftToRightDirection() ? outerBorderStart() : outerBorderEnd(); + return style()->isFlippedBlocksWritingMode() ? outerBorderAfter() : outerBorderBefore(); + } + + int outerBorderRight() const + { + if (style()->isHorizontalWritingMode()) + return style()->isLeftToRightDirection() ? outerBorderEnd() : outerBorderStart(); + return style()->isFlippedBlocksWritingMode() ? outerBorderBefore() : outerBorderAfter(); + } + + int outerBorderTop() const + { + if (style()->isHorizontalWritingMode()) + return style()->isFlippedBlocksWritingMode() ? outerBorderAfter() : outerBorderBefore(); + return style()->isLeftToRightDirection() ? outerBorderStart() : outerBorderEnd(); + } + + int outerBorderBottom() const + { + if (style()->isHorizontalWritingMode()) + return style()->isFlippedBlocksWritingMode() ? outerBorderBefore() : outerBorderAfter(); + return style()->isLeftToRightDirection() ? outerBorderEnd() : outerBorderStart(); + } + + int calcBorderStart() const; + int calcBorderEnd() const; + void recalcBordersInRowDirection(); + + virtual void addChild(RenderObject* child, RenderObject* beforeChild = 0); + + struct ColumnStruct { + enum { + WidthUndefined = 0xffff + }; + + ColumnStruct() + : span(1) + , width(WidthUndefined) + { + } + + unsigned span; + unsigned width; // the calculated position of the column + }; + + Vector<ColumnStruct>& columns() { return m_columns; } + Vector<int>& columnPositions() { return m_columnPos; } + RenderTableSection* header() const { return m_head; } + RenderTableSection* footer() const { return m_foot; } + RenderTableSection* firstBody() const { return m_firstBody; } + + void splitColumn(int pos, int firstSpan); + void appendColumn(int span); + int numEffCols() const { return m_columns.size(); } + int spanOfEffCol(int effCol) const { return m_columns[effCol].span; } + + int colToEffCol(int col) const + { + int i = 0; + int effCol = numEffCols(); + for (int c = 0; c < col && i < effCol; ++i) + c += m_columns[i].span; + return i; + } + + int effColToCol(int effCol) const + { + int c = 0; + for (int i = 0; i < effCol; i++) + c += m_columns[i].span; + return c; + } + + int bordersPaddingAndSpacingInRowDirection() const + { + return borderStart() + borderEnd() + + (collapseBorders() ? 0 : (paddingStart() + paddingEnd() + (numEffCols() + 1) * hBorderSpacing())); + } + + RenderTableCol* colElement(int col, bool* startEdge = 0, bool* endEdge = 0) const; + RenderTableCol* nextColElement(RenderTableCol* current) const; + + bool needsSectionRecalc() const { return m_needsSectionRecalc; } + void setNeedsSectionRecalc() + { + if (documentBeingDestroyed()) + return; + m_needsSectionRecalc = true; + setNeedsLayout(true); + } + + RenderTableSection* sectionAbove(const RenderTableSection*, bool skipEmptySections = false) const; + RenderTableSection* sectionBelow(const RenderTableSection*, bool skipEmptySections = false) const; + + RenderTableCell* cellAbove(const RenderTableCell*) const; + RenderTableCell* cellBelow(const RenderTableCell*) const; + RenderTableCell* cellBefore(const RenderTableCell*) const; + RenderTableCell* cellAfter(const RenderTableCell*) const; + + const CollapsedBorderValue* currentBorderStyle() const { return m_currentBorder; } + + bool hasSections() const { return m_head || m_foot || m_firstBody; } + + void recalcSectionsIfNeeded() const + { + if (m_needsSectionRecalc) + recalcSections(); + } + +#ifdef ANDROID_LAYOUT + void clearSingleColumn() { m_singleColumn = false; } + bool isSingleColumn() const { return m_singleColumn; } +#endif + +protected: + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + +private: + virtual const char* renderName() const { return "RenderTable"; } + + virtual bool isTable() const { return true; } + + virtual bool avoidsFloats() const { return true; } + + virtual void removeChild(RenderObject* oldChild); + + virtual void paint(PaintInfo&, int tx, int ty); + virtual void paintObject(PaintInfo&, int tx, int ty); + virtual void paintBoxDecorations(PaintInfo&, int tx, int ty); + virtual void paintMask(PaintInfo&, int tx, int ty); + virtual void layout(); + virtual void computePreferredLogicalWidths(); + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int xPos, int yPos, int tx, int ty, HitTestAction); + + virtual int firstLineBoxBaseline() const; + + virtual RenderBlock* firstLineBlock() const; + virtual void updateFirstLetter(); + + virtual void setCellLogicalWidths(); + + virtual void computeLogicalWidth(); + + virtual IntRect overflowClipRect(int tx, int ty); + + virtual void addOverflowFromChildren(); + + void subtractCaptionRect(IntRect&) const; + + void recalcSections() const; + + mutable Vector<int> m_columnPos; + mutable Vector<ColumnStruct> m_columns; + + mutable RenderBlock* m_caption; + mutable RenderTableSection* m_head; + mutable RenderTableSection* m_foot; + mutable RenderTableSection* m_firstBody; + + OwnPtr<TableLayout> m_tableLayout; + + const CollapsedBorderValue* m_currentBorder; + + mutable bool m_hasColElements : 1; + mutable bool m_needsSectionRecalc : 1; + +#ifdef ANDROID_LAYOUT + bool m_singleColumn; // BS(Grace): should I use compact version? +#endif + short m_hSpacing; + short m_vSpacing; + int m_borderStart; + int m_borderEnd; +}; + +inline RenderTable* toRenderTable(RenderObject* object) +{ + ASSERT(!object || object->isTable()); + return static_cast<RenderTable*>(object); +} + +inline const RenderTable* toRenderTable(const RenderObject* object) +{ + ASSERT(!object || object->isTable()); + return static_cast<const RenderTable*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderTable(const RenderTable*); + +} // namespace WebCore + +#endif // RenderTable_h diff --git a/Source/WebCore/rendering/RenderTableCell.cpp b/Source/WebCore/rendering/RenderTableCell.cpp new file mode 100644 index 0000000..0c2cf2f --- /dev/null +++ b/Source/WebCore/rendering/RenderTableCell.cpp @@ -0,0 +1,1068 @@ +/* + * 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 Apple Inc. All rights reserved. + * + * 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 "RenderTableCell.h" + +#include "FloatQuad.h" +#include "GraphicsContext.h" +#include "HTMLNames.h" +#include "HTMLTableCellElement.h" +#include "RenderTableCol.h" +#include "RenderView.h" +#include "TransformState.h" + +#ifdef ANDROID_LAYOUT +#include "Document.h" +#include "Settings.h" +#endif + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +RenderTableCell::RenderTableCell(Node* node) + : RenderBlock(node) + , m_row(-1) + , m_column(-1) + , m_rowSpan(1) + , m_columnSpan(1) + , m_intrinsicPaddingBefore(0) + , m_intrinsicPaddingAfter(0) +{ + updateFromElement(); +} + +void RenderTableCell::destroy() +{ + RenderTableSection* recalcSection = parent() ? section() : 0; + + RenderBlock::destroy(); + + if (recalcSection) + recalcSection->setNeedsCellRecalc(); +} + +void RenderTableCell::updateFromElement() +{ + Node* n = node(); + if (n && (n->hasTagName(tdTag) || n->hasTagName(thTag))) { + HTMLTableCellElement* tc = static_cast<HTMLTableCellElement*>(n); + int oldRSpan = m_rowSpan; + int oldCSpan = m_columnSpan; + + m_columnSpan = tc->colSpan(); + m_rowSpan = tc->rowSpan(); + if ((oldRSpan != m_rowSpan || oldCSpan != m_columnSpan) && style() && parent()) { + setNeedsLayoutAndPrefWidthsRecalc(); + if (section()) + section()->setNeedsCellRecalc(); + } + } +} + +Length RenderTableCell::styleOrColLogicalWidth() const +{ + Length w = style()->logicalWidth(); + if (!w.isAuto()) + return w; + + if (RenderTableCol* tableCol = table()->colElement(col())) { + int colSpanCount = colSpan(); + + Length colWidthSum = Length(0, Fixed); + for (int i = 1; i <= colSpanCount; i++) { + Length colWidth = tableCol->style()->logicalWidth(); + + // Percentage value should be returned only for colSpan == 1. + // Otherwise we return original width for the cell. + if (!colWidth.isFixed()) { + if (colSpanCount > 1) + return w; + return colWidth; + } + + colWidthSum = Length(colWidthSum.value() + colWidth.value(), Fixed); + + tableCol = table()->nextColElement(tableCol); + // If no next <col> tag found for the span we just return what we have for now. + if (!tableCol) + break; + } + + // Column widths specified on <col> apply to the border box of the cell. + // Percentages don't need to be handled since they're always treated this way (even when specified on the cells). + // See Bugzilla bug 8126 for details. + if (colWidthSum.isFixed() && colWidthSum.value() > 0) + colWidthSum = Length(max(0, colWidthSum.value() - borderAndPaddingLogicalWidth()), Fixed); + return colWidthSum; + } + + return w; +} + +void RenderTableCell::computePreferredLogicalWidths() +{ + // The child cells rely on the grids up in the sections to do their computePreferredLogicalWidths work. Normally the sections are set up early, as table + // cells are added, but relayout can cause the cells to be freed, leaving stale pointers in the sections' + // grids. We must refresh those grids before the child cells try to use them. + table()->recalcSectionsIfNeeded(); + + RenderBlock::computePreferredLogicalWidths(); + if (node() && style()->autoWrap()) { + // See if nowrap was set. + Length w = styleOrColLogicalWidth(); + String nowrap = static_cast<Element*>(node())->getAttribute(nowrapAttr); + if (!nowrap.isNull() && w.isFixed()) + // Nowrap is set, but we didn't actually use it because of the + // fixed width set on the cell. Even so, it is a WinIE/Moz trait + // to make the minwidth of the cell into the fixed width. They do this + // even in strict mode, so do not make this a quirk. Affected the top + // of hiptop.com. + m_minPreferredLogicalWidth = max(w.value(), m_minPreferredLogicalWidth); + } +} + +void RenderTableCell::computeLogicalWidth() +{ +#ifdef ANDROID_LAYOUT + if (view()->frameView()) + setVisibleWidth(view()->frameView()->textWrapWidth()); +#endif +} + +void RenderTableCell::updateLogicalWidth(int w) +{ + if (w == logicalWidth()) + return; + + setLogicalWidth(w); + setCellWidthChanged(true); +} + +void RenderTableCell::layout() +{ + layoutBlock(cellWidthChanged()); + setCellWidthChanged(false); +} + +int RenderTableCell::paddingTop(bool includeIntrinsicPadding) const +{ + int result = RenderBlock::paddingTop(); + if (!includeIntrinsicPadding || !style()->isHorizontalWritingMode()) + return result; + return result + (style()->writingMode() == TopToBottomWritingMode ? intrinsicPaddingBefore() : intrinsicPaddingAfter()); +} + +int RenderTableCell::paddingBottom(bool includeIntrinsicPadding) const +{ + int result = RenderBlock::paddingBottom(); + if (!includeIntrinsicPadding || !style()->isHorizontalWritingMode()) + return result; + return result + (style()->writingMode() == TopToBottomWritingMode ? intrinsicPaddingAfter() : intrinsicPaddingBefore()); +} + +int RenderTableCell::paddingLeft(bool includeIntrinsicPadding) const +{ + int result = RenderBlock::paddingLeft(); + if (!includeIntrinsicPadding || style()->isHorizontalWritingMode()) + return result; + return result + (style()->writingMode() == LeftToRightWritingMode ? intrinsicPaddingBefore() : intrinsicPaddingAfter()); + +} + +int RenderTableCell::paddingRight(bool includeIntrinsicPadding) const +{ + int result = RenderBlock::paddingRight(); + if (!includeIntrinsicPadding || style()->isHorizontalWritingMode()) + return result; + return result + (style()->writingMode() == LeftToRightWritingMode ? intrinsicPaddingAfter() : intrinsicPaddingBefore()); +} + +int RenderTableCell::paddingBefore(bool includeIntrinsicPadding) const +{ + int result = RenderBlock::paddingBefore(); + if (!includeIntrinsicPadding) + return result; + return result + intrinsicPaddingBefore(); +} + +int RenderTableCell::paddingAfter(bool includeIntrinsicPadding) const +{ + int result = RenderBlock::paddingAfter(); + if (!includeIntrinsicPadding) + return result; + return result + intrinsicPaddingAfter(); +} + +void RenderTableCell::setOverrideSize(int size) +{ + clearIntrinsicPadding(); + RenderBlock::setOverrideSize(size); +} + +IntSize RenderTableCell::offsetFromContainer(RenderObject* o, const IntPoint& point) const +{ + ASSERT(o == container()); + + IntSize offset = RenderBlock::offsetFromContainer(o, point); + if (parent()) + offset.expand(-parentBox()->x(), -parentBox()->y()); + + return offset; +} + +IntRect RenderTableCell::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) +{ + // If the table grid is dirty, we cannot get reliable information about adjoining cells, + // so we ignore outside borders. This should not be a problem because it means that + // the table is going to recalculate the grid, relayout and repaint its current rect, which + // includes any outside borders of this cell. + if (!table()->collapseBorders() || table()->needsSectionRecalc()) + return RenderBlock::clippedOverflowRectForRepaint(repaintContainer); + + bool rtl = !table()->style()->isLeftToRightDirection(); + int outlineSize = style()->outlineSize(); + int left = max(borderHalfLeft(true), outlineSize); + int right = max(borderHalfRight(true), outlineSize); + int top = max(borderHalfTop(true), outlineSize); + int bottom = max(borderHalfBottom(true), outlineSize); + if ((left && !rtl) || (right && rtl)) { + if (RenderTableCell* before = table()->cellBefore(this)) { + top = max(top, before->borderHalfTop(true)); + bottom = max(bottom, before->borderHalfBottom(true)); + } + } + if ((left && rtl) || (right && !rtl)) { + if (RenderTableCell* after = table()->cellAfter(this)) { + top = max(top, after->borderHalfTop(true)); + bottom = max(bottom, after->borderHalfBottom(true)); + } + } + if (top) { + if (RenderTableCell* above = table()->cellAbove(this)) { + left = max(left, above->borderHalfLeft(true)); + right = max(right, above->borderHalfRight(true)); + } + } + if (bottom) { + if (RenderTableCell* below = table()->cellBelow(this)) { + left = max(left, below->borderHalfLeft(true)); + right = max(right, below->borderHalfRight(true)); + } + } + left = max(left, -leftVisualOverflow()); + top = max(top, -topVisualOverflow()); + IntRect r(-left, - top, left + max(width() + right, rightVisualOverflow()), top + max(height() + bottom, bottomVisualOverflow())); + + if (RenderView* v = view()) { + // FIXME: layoutDelta needs to be applied in parts before/after transforms and + // repaint containers. https://bugs.webkit.org/show_bug.cgi?id=23308 + r.move(v->layoutDelta()); + } + computeRectForRepaint(repaintContainer, r); + return r; +} + +void RenderTableCell::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& r, bool fixed) +{ + if (repaintContainer == this) + return; + r.setY(r.y()); + RenderView* v = view(); + if ((!v || !v->layoutStateEnabled() || repaintContainer) && parent()) + r.move(-parentBox()->x(), -parentBox()->y()); // Rows are in the same coordinate space, so don't add their offset in. + RenderBlock::computeRectForRepaint(repaintContainer, r, fixed); +} + +int RenderTableCell::baselinePosition() const +{ + // <http://www.w3.org/TR/2007/CR-CSS21-20070719/tables.html#height-layout>: The baseline of a cell is the baseline of + // the first in-flow line box in the cell, or the first in-flow table-row in the cell, whichever comes first. If there + // is no such line box or table-row, the baseline is the bottom of content edge of the cell box. + int firstLineBaseline = firstLineBoxBaseline(); + if (firstLineBaseline != -1) + return firstLineBaseline; + return paddingBefore() + borderBefore() + contentLogicalHeight(); +} + +void RenderTableCell::styleWillChange(StyleDifference diff, const RenderStyle* newStyle) +{ + if (parent() && section() && style() && style()->height() != newStyle->height()) + section()->setNeedsCellRecalc(); + + ASSERT(newStyle->display() == TABLE_CELL); + + RenderBlock::styleWillChange(diff, newStyle); +} + +void RenderTableCell::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBlock::styleDidChange(diff, oldStyle); + setHasBoxDecorations(true); +} + +// 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 int compareBorders(const CollapsedBorderValue& border1, const CollapsedBorderValue& border2) +{ + // Sanity check the values passed in. The null border have lowest priority. + if (!border2.exists()) { + if (!border1.exists()) + return 0; + return 1; + } + if (!border1.exists()) + return -1; + + // Rule #1 above. + if (border2.style() == BHIDDEN) { + if (border1.style() == BHIDDEN) + return 0; + return -1; + } + if (border1.style() == BHIDDEN) + return 1; + + // Rule #2 above. A style of 'none' has lowest priority and always loses to any other border. + if (border2.style() == BNONE) { + if (border1.style() == BNONE) + return 0; + return 1; + } + if (border1.style() == BNONE) + return -1; + + // The first part of rule #3 above. Wider borders win. + if (border1.width() != border2.width()) + return border1.width() < border2.width() ? -1 : 1; + + // The borders have equal width. Sort by border style. + if (border1.style() != border2.style()) + return border1.style() < border2.style() ? -1 : 1; + + // The border have the same width and style. Rely on precedence (cell over row over row group, etc.) + if (border1.precedence() == border2.precedence()) + return 0; + return border1.precedence() < border2.precedence() ? -1 : 1; +} + +static CollapsedBorderValue chooseBorder(const CollapsedBorderValue& border1, const CollapsedBorderValue& border2) +{ + const CollapsedBorderValue& border = compareBorders(border1, border2) < 0 ? border2 : border1; + return border.style() == BHIDDEN ? CollapsedBorderValue() : border; +} + +CollapsedBorderValue RenderTableCell::collapsedStartBorder() const +{ + RenderTable* table = this->table(); + bool isStartColumn = col() == 0; + + // For the start border, we need to check, in order of precedence: + // (1) Our start border. + int start = CSSProperty::resolveDirectionAwareProperty(CSSPropertyWebkitBorderStartColor, table->style()->direction(), table->style()->writingMode()); + int end = CSSProperty::resolveDirectionAwareProperty(CSSPropertyWebkitBorderEndColor, table->style()->direction(), table->style()->writingMode()); + CollapsedBorderValue result(&style()->borderStart(), style()->visitedDependentColor(start), BCELL); + + // (2) The end border of the preceding cell. + if (RenderTableCell* prevCell = table->cellBefore(this)) { + CollapsedBorderValue prevCellBorder = CollapsedBorderValue(&prevCell->style()->borderEnd(), prevCell->style()->visitedDependentColor(end), BCELL); + result = chooseBorder(prevCellBorder, result); + if (!result.exists()) + return result; + } else if (isStartColumn) { + // (3) Our row's start border. + result = chooseBorder(result, CollapsedBorderValue(&parent()->style()->borderStart(), parent()->style()->visitedDependentColor(start), BROW)); + if (!result.exists()) + return result; + + // (4) Our row group's start border. + result = chooseBorder(result, CollapsedBorderValue(§ion()->style()->borderStart(), section()->style()->visitedDependentColor(start), BROWGROUP)); + if (!result.exists()) + return result; + } + + // (5) Our column and column group's start borders. + bool startColEdge; + bool endColEdge; + RenderTableCol* colElt = table->colElement(col(), &startColEdge, &endColEdge); + if (colElt && startColEdge) { + result = chooseBorder(result, CollapsedBorderValue(&colElt->style()->borderStart(), colElt->style()->visitedDependentColor(start), BCOL)); + if (!result.exists()) + return result; + if (colElt->parent()->isTableCol() && !colElt->previousSibling()) { + result = chooseBorder(result, CollapsedBorderValue(&colElt->parent()->style()->borderStart(), colElt->parent()->style()->visitedDependentColor(start), BCOLGROUP)); + if (!result.exists()) + return result; + } + } + + // (6) The end border of the preceding column. + if (!isStartColumn) { + colElt = table->colElement(col() -1, &startColEdge, &endColEdge); + if (colElt && endColEdge) { + CollapsedBorderValue endBorder = CollapsedBorderValue(&colElt->style()->borderEnd(), colElt->style()->visitedDependentColor(end), BCOL); + result = chooseBorder(endBorder, result); + if (!result.exists()) + return result; + } + } else { + // (7) The table's start border. + result = chooseBorder(result, CollapsedBorderValue(&table->style()->borderStart(), table->style()->visitedDependentColor(start), BTABLE)); + if (!result.exists()) + return result; + } + + return result; +} + +CollapsedBorderValue RenderTableCell::collapsedEndBorder() const +{ + RenderTable* table = this->table(); + bool isEndColumn = table->colToEffCol(col() + colSpan() - 1) == table->numEffCols() - 1; + + // For end border, we need to check, in order of precedence: + // (1) Our end border. + int start = CSSProperty::resolveDirectionAwareProperty(CSSPropertyWebkitBorderStartColor, table->style()->direction(), table->style()->writingMode()); + int end = CSSProperty::resolveDirectionAwareProperty(CSSPropertyWebkitBorderEndColor, table->style()->direction(), table->style()->writingMode()); + CollapsedBorderValue result = CollapsedBorderValue(&style()->borderEnd(), style()->visitedDependentColor(end), BCELL); + + // (2) The start border of the following cell. + if (!isEndColumn) { + RenderTableCell* nextCell = table->cellAfter(this); + if (nextCell && nextCell->style()) { + CollapsedBorderValue startBorder = CollapsedBorderValue(&nextCell->style()->borderStart(), nextCell->style()->visitedDependentColor(start), BCELL); + result = chooseBorder(result, startBorder); + if (!result.exists()) + return result; + } + } else { + // (3) Our row's end border. + result = chooseBorder(result, CollapsedBorderValue(&parent()->style()->borderEnd(), parent()->style()->visitedDependentColor(end), BROW)); + if (!result.exists()) + return result; + + // (4) Our row group's end border. + result = chooseBorder(result, CollapsedBorderValue(§ion()->style()->borderEnd(), section()->style()->visitedDependentColor(end), BROWGROUP)); + if (!result.exists()) + return result; + } + + // (5) Our column and column group's end borders. + bool startColEdge; + bool endColEdge; + RenderTableCol* colElt = table->colElement(col() + colSpan() - 1, &startColEdge, &endColEdge); + if (colElt && endColEdge) { + result = chooseBorder(result, CollapsedBorderValue(&colElt->style()->borderEnd(), colElt->style()->visitedDependentColor(end), BCOL)); + if (!result.exists()) + return result; + if (colElt->parent()->isTableCol() && !colElt->nextSibling()) { + result = chooseBorder(result, CollapsedBorderValue(&colElt->parent()->style()->borderEnd(), colElt->parent()->style()->visitedDependentColor(end), BCOLGROUP)); + if (!result.exists()) + return result; + } + } + + // (6) The start border of the next column. + if (!isEndColumn) { + colElt = table->colElement(col() + colSpan(), &startColEdge, &endColEdge); + if (colElt && startColEdge) { + CollapsedBorderValue startBorder = CollapsedBorderValue(&colElt->style()->borderStart(), colElt->style()->visitedDependentColor(start), BCOL); + result = chooseBorder(result, startBorder); + if (!result.exists()) + return result; + } + } else { + // (7) The table's end border. + result = chooseBorder(result, CollapsedBorderValue(&table->style()->borderEnd(), table->style()->visitedDependentColor(end), BTABLE)); + if (!result.exists()) + return result; + } + + return result; +} + +CollapsedBorderValue RenderTableCell::collapsedBeforeBorder() const +{ + RenderTable* table = this->table(); + + // For before border, we need to check, in order of precedence: + // (1) Our before border. + int before = CSSProperty::resolveDirectionAwareProperty(CSSPropertyWebkitBorderBeforeColor, table->style()->direction(), table->style()->writingMode()); + int after = CSSProperty::resolveDirectionAwareProperty(CSSPropertyWebkitBorderAfterColor, table->style()->direction(), table->style()->writingMode()); + CollapsedBorderValue result = CollapsedBorderValue(&style()->borderBefore(), style()->visitedDependentColor(before), BCELL); + + RenderTableCell* prevCell = table->cellAbove(this); + if (prevCell) { + // (2) A before cell's after border. + result = chooseBorder(CollapsedBorderValue(&prevCell->style()->borderAfter(), prevCell->style()->visitedDependentColor(after), BCELL), result); + if (!result.exists()) + return result; + } + + // (3) Our row's before border. + result = chooseBorder(result, CollapsedBorderValue(&parent()->style()->borderBefore(), parent()->style()->visitedDependentColor(before), BROW)); + if (!result.exists()) + return result; + + // (4) The previous row's after border. + if (prevCell) { + RenderObject* prevRow = 0; + if (prevCell->section() == section()) + prevRow = parent()->previousSibling(); + else + prevRow = prevCell->section()->lastChild(); + + if (prevRow) { + result = chooseBorder(CollapsedBorderValue(&prevRow->style()->borderAfter(), prevRow->style()->visitedDependentColor(after), BROW), result); + if (!result.exists()) + return result; + } + } + + // Now check row groups. + RenderTableSection* currSection = section(); + if (!row()) { + // (5) Our row group's before border. + result = chooseBorder(result, CollapsedBorderValue(&currSection->style()->borderBefore(), currSection->style()->visitedDependentColor(before), BROWGROUP)); + if (!result.exists()) + return result; + + // (6) Previous row group's after border. + currSection = table->sectionAbove(currSection); + if (currSection) { + result = chooseBorder(CollapsedBorderValue(&currSection->style()->borderAfter(), currSection->style()->visitedDependentColor(after), BROWGROUP), result); + if (!result.exists()) + return result; + } + } + + if (!currSection) { + // (8) Our column and column group's before borders. + RenderTableCol* colElt = table->colElement(col()); + if (colElt) { + result = chooseBorder(result, CollapsedBorderValue(&colElt->style()->borderBefore(), colElt->style()->visitedDependentColor(before), BCOL)); + if (!result.exists()) + return result; + if (colElt->parent()->isTableCol()) { + result = chooseBorder(result, CollapsedBorderValue(&colElt->parent()->style()->borderBefore(), colElt->parent()->style()->visitedDependentColor(before), BCOLGROUP)); + if (!result.exists()) + return result; + } + } + + // (9) The table's before border. + result = chooseBorder(result, CollapsedBorderValue(&table->style()->borderBefore(), table->style()->visitedDependentColor(before), BTABLE)); + if (!result.exists()) + return result; + } + + return result; +} + +CollapsedBorderValue RenderTableCell::collapsedAfterBorder() const +{ + RenderTable* table = this->table(); + + // For after border, we need to check, in order of precedence: + // (1) Our after border. + int before = CSSProperty::resolveDirectionAwareProperty(CSSPropertyWebkitBorderBeforeColor, table->style()->direction(), table->style()->writingMode()); + int after = CSSProperty::resolveDirectionAwareProperty(CSSPropertyWebkitBorderAfterColor, table->style()->direction(), table->style()->writingMode()); + CollapsedBorderValue result = CollapsedBorderValue(&style()->borderAfter(), style()->visitedDependentColor(after), BCELL); + + RenderTableCell* nextCell = table->cellBelow(this); + if (nextCell) { + // (2) An after cell's before border. + result = chooseBorder(result, CollapsedBorderValue(&nextCell->style()->borderBefore(), nextCell->style()->visitedDependentColor(before), BCELL)); + if (!result.exists()) + return result; + } + + // (3) Our row's after border. (FIXME: Deal with rowspan!) + result = chooseBorder(result, CollapsedBorderValue(&parent()->style()->borderAfter(), parent()->style()->visitedDependentColor(after), BROW)); + if (!result.exists()) + return result; + + // (4) The next row's before border. + if (nextCell) { + result = chooseBorder(result, CollapsedBorderValue(&nextCell->parent()->style()->borderBefore(), nextCell->parent()->style()->visitedDependentColor(before), BROW)); + if (!result.exists()) + return result; + } + + // Now check row groups. + RenderTableSection* currSection = section(); + if (row() + rowSpan() >= currSection->numRows()) { + // (5) Our row group's after border. + result = chooseBorder(result, CollapsedBorderValue(&currSection->style()->borderAfter(), currSection->style()->visitedDependentColor(after), BROWGROUP)); + if (!result.exists()) + return result; + + // (6) Following row group's before border. + currSection = table->sectionBelow(currSection); + if (currSection) { + result = chooseBorder(result, CollapsedBorderValue(&currSection->style()->borderBefore(), currSection->style()->visitedDependentColor(before), BROWGROUP)); + if (!result.exists()) + return result; + } + } + + if (!currSection) { + // (8) Our column and column group's after borders. + RenderTableCol* colElt = table->colElement(col()); + if (colElt) { + result = chooseBorder(result, CollapsedBorderValue(&colElt->style()->borderAfter(), colElt->style()->visitedDependentColor(after), BCOL)); + if (!result.exists()) return result; + if (colElt->parent()->isTableCol()) { + result = chooseBorder(result, CollapsedBorderValue(&colElt->parent()->style()->borderAfter(), colElt->parent()->style()->visitedDependentColor(after), BCOLGROUP)); + if (!result.exists()) + return result; + } + } + + // (9) The table's after border. + result = chooseBorder(result, CollapsedBorderValue(&table->style()->borderAfter(), table->style()->visitedDependentColor(after), BTABLE)); + if (!result.exists()) + return result; + } + + return result; +} + +CollapsedBorderValue RenderTableCell::collapsedLeftBorder() const +{ + RenderStyle* tableStyle = table()->style(); + if (tableStyle->isHorizontalWritingMode()) + return tableStyle->isLeftToRightDirection() ? collapsedStartBorder() : collapsedEndBorder(); + return tableStyle->isFlippedBlocksWritingMode() ? collapsedAfterBorder() : collapsedBeforeBorder(); +} + +CollapsedBorderValue RenderTableCell::collapsedRightBorder() const +{ + RenderStyle* tableStyle = table()->style(); + if (tableStyle->isHorizontalWritingMode()) + return tableStyle->isLeftToRightDirection() ? collapsedEndBorder() : collapsedStartBorder(); + return tableStyle->isFlippedBlocksWritingMode() ? collapsedBeforeBorder() : collapsedAfterBorder(); +} + +CollapsedBorderValue RenderTableCell::collapsedTopBorder() const +{ + RenderStyle* tableStyle = table()->style(); + if (tableStyle->isHorizontalWritingMode()) + return tableStyle->isFlippedBlocksWritingMode() ? collapsedAfterBorder() : collapsedBeforeBorder(); + return tableStyle->isLeftToRightDirection() ? collapsedStartBorder() : collapsedEndBorder(); +} + +CollapsedBorderValue RenderTableCell::collapsedBottomBorder() const +{ + RenderStyle* tableStyle = table()->style(); + if (tableStyle->isHorizontalWritingMode()) + return tableStyle->isFlippedBlocksWritingMode() ? collapsedBeforeBorder() : collapsedAfterBorder(); + return tableStyle->isLeftToRightDirection() ? collapsedEndBorder() : collapsedStartBorder(); +} + +int RenderTableCell::borderLeft() const +{ + return table()->collapseBorders() ? borderHalfLeft(false) : RenderBlock::borderLeft(); +} + +int RenderTableCell::borderRight() const +{ + return table()->collapseBorders() ? borderHalfRight(false) : RenderBlock::borderRight(); +} + +int RenderTableCell::borderTop() const +{ + return table()->collapseBorders() ? borderHalfTop(false) : RenderBlock::borderTop(); +} + +int RenderTableCell::borderBottom() const +{ + return table()->collapseBorders() ? borderHalfBottom(false) : RenderBlock::borderBottom(); +} + +// FIXME: https://bugs.webkit.org/show_bug.cgi?id=46191, make the collapsed border drawing +// work with different block flow values instead of being hard-coded to top-to-bottom. +int RenderTableCell::borderStart() const +{ + return table()->collapseBorders() ? borderHalfStart(false) : RenderBlock::borderStart(); +} + +int RenderTableCell::borderEnd() const +{ + return table()->collapseBorders() ? borderHalfEnd(false) : RenderBlock::borderEnd(); +} + +int RenderTableCell::borderBefore() const +{ + return table()->collapseBorders() ? borderHalfBefore(false) : RenderBlock::borderBefore(); +} + +int RenderTableCell::borderAfter() const +{ + return table()->collapseBorders() ? borderHalfAfter(false) : RenderBlock::borderAfter(); +} + +int RenderTableCell::borderHalfLeft(bool outer) const +{ + RenderStyle* tableStyle = table()->style(); + if (tableStyle->isHorizontalWritingMode()) + return tableStyle->isLeftToRightDirection() ? borderHalfStart(outer) : borderHalfEnd(outer); + return tableStyle->isFlippedBlocksWritingMode() ? borderHalfAfter(outer) : borderHalfBefore(outer); +} + +int RenderTableCell::borderHalfRight(bool outer) const +{ + RenderStyle* tableStyle = table()->style(); + if (tableStyle->isHorizontalWritingMode()) + return tableStyle->isLeftToRightDirection() ? borderHalfEnd(outer) : borderHalfStart(outer); + return tableStyle->isFlippedBlocksWritingMode() ? borderHalfBefore(outer) : borderHalfAfter(outer); +} + +int RenderTableCell::borderHalfTop(bool outer) const +{ + RenderStyle* tableStyle = table()->style(); + if (tableStyle->isHorizontalWritingMode()) + return tableStyle->isFlippedBlocksWritingMode() ? borderHalfAfter(outer) : borderHalfBefore(outer); + return tableStyle->isLeftToRightDirection() ? borderHalfStart(outer) : borderHalfEnd(outer); +} + +int RenderTableCell::borderHalfBottom(bool outer) const +{ + RenderStyle* tableStyle = table()->style(); + if (tableStyle->isHorizontalWritingMode()) + return tableStyle->isFlippedBlocksWritingMode() ? borderHalfBefore(outer) : borderHalfAfter(outer); + return tableStyle->isLeftToRightDirection() ? borderHalfEnd(outer) : borderHalfStart(outer); +} + +int RenderTableCell::borderHalfStart(bool outer) const +{ + CollapsedBorderValue border = collapsedStartBorder(); + if (border.exists()) + return (border.width() + ((table()->style()->isLeftToRightDirection() ^ outer) ? 1 : 0)) / 2; // Give the extra pixel to top and left. + return 0; +} + +int RenderTableCell::borderHalfEnd(bool outer) const +{ + CollapsedBorderValue border = collapsedEndBorder(); + if (border.exists()) + return (border.width() + ((table()->style()->isLeftToRightDirection() ^ outer) ? 0 : 1)) / 2; + return 0; +} + +int RenderTableCell::borderHalfBefore(bool outer) const +{ + CollapsedBorderValue border = collapsedBeforeBorder(); + if (border.exists()) + return (border.width() + ((table()->style()->isFlippedBlocksWritingMode() ^ outer) ? 0 : 1)) / 2; // Give the extra pixel to top and left. + return 0; +} + +int RenderTableCell::borderHalfAfter(bool outer) const +{ + CollapsedBorderValue border = collapsedAfterBorder(); + if (border.exists()) + return (border.width() + ((table()->style()->isFlippedBlocksWritingMode() ^ outer) ? 1 : 0)) / 2; + return 0; +} + +void RenderTableCell::paint(PaintInfo& paintInfo, int tx, int ty) +{ + if (paintInfo.phase == PaintPhaseCollapsedTableBorders && style()->visibility() == VISIBLE) { + if (!paintInfo.shouldPaintWithinRoot(this)) + return; + + tx += x(); + ty += y(); + int os = 2 * maximalOutlineSize(paintInfo.phase); + if (ty - table()->outerBorderTop() < paintInfo.rect.bottom() + os && + ty + height() + table()->outerBorderBottom() > paintInfo.rect.y() - os) + paintCollapsedBorder(paintInfo.context, tx, ty, width(), height()); + return; + } + + RenderBlock::paint(paintInfo, tx, ty); +} + +static EBorderStyle collapsedBorderStyle(EBorderStyle style) +{ + if (style == OUTSET) + return GROOVE; + if (style == INSET) + return RIDGE; + return style; +} + +struct CollapsedBorder { + CollapsedBorderValue borderValue; + BoxSide side; + bool shouldPaint; + int x1; + int y1; + int x2; + int y2; + EBorderStyle style; +}; + +class CollapsedBorders { +public: + CollapsedBorders() + : m_count(0) + { + } + + void addBorder(const CollapsedBorderValue& borderValue, BoxSide borderSide, bool shouldPaint, + int x1, int y1, int x2, int y2, EBorderStyle borderStyle) + { + if (borderValue.exists() && shouldPaint) { + m_borders[m_count].borderValue = borderValue; + m_borders[m_count].side = borderSide; + m_borders[m_count].shouldPaint = shouldPaint; + m_borders[m_count].x1 = x1; + m_borders[m_count].x2 = x2; + m_borders[m_count].y1 = y1; + m_borders[m_count].y2 = y2; + m_borders[m_count].style = borderStyle; + m_count++; + } + } + + CollapsedBorder* nextBorder() + { + for (int i = 0; i < m_count; i++) { + if (m_borders[i].borderValue.exists() && m_borders[i].shouldPaint) { + m_borders[i].shouldPaint = false; + return &m_borders[i]; + } + } + + return 0; + } + + CollapsedBorder m_borders[4]; + int m_count; +}; + +static void addBorderStyle(RenderTableCell::CollapsedBorderStyles& borderStyles, CollapsedBorderValue borderValue) +{ + if (!borderValue.exists()) + return; + size_t count = borderStyles.size(); + for (size_t i = 0; i < count; ++i) + if (borderStyles[i] == borderValue) + return; + borderStyles.append(borderValue); +} + +void RenderTableCell::collectBorderStyles(CollapsedBorderStyles& borderStyles) const +{ + addBorderStyle(borderStyles, collapsedStartBorder()); + addBorderStyle(borderStyles, collapsedEndBorder()); + addBorderStyle(borderStyles, collapsedBeforeBorder()); + addBorderStyle(borderStyles, collapsedAfterBorder()); +} + +static int compareBorderStylesForQSort(const void* pa, const void* pb) +{ + const CollapsedBorderValue* a = static_cast<const CollapsedBorderValue*>(pa); + const CollapsedBorderValue* b = static_cast<const CollapsedBorderValue*>(pb); + if (*a == *b) + return 0; + return compareBorders(*a, *b); +} + +void RenderTableCell::sortBorderStyles(CollapsedBorderStyles& borderStyles) +{ + qsort(borderStyles.data(), borderStyles.size(), sizeof(CollapsedBorderValue), + compareBorderStylesForQSort); +} + +void RenderTableCell::paintCollapsedBorder(GraphicsContext* graphicsContext, 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 + (rightWidth + 1) / 2; + h += topWidth / 2 + (bottomWidth + 1) / 2; + + EBorderStyle topStyle = collapsedBorderStyle(topVal.style()); + EBorderStyle bottomStyle = collapsedBorderStyle(bottomVal.style()); + EBorderStyle leftStyle = collapsedBorderStyle(leftVal.style()); + EBorderStyle rightStyle = collapsedBorderStyle(rightVal.style()); + + bool renderTop = topStyle > BHIDDEN && !topVal.isTransparent(); + bool renderBottom = bottomStyle > BHIDDEN && !bottomVal.isTransparent(); + bool renderLeft = leftStyle > BHIDDEN && !leftVal.isTransparent(); + bool renderRight = rightStyle > BHIDDEN && !rightVal.isTransparent(); + + // 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; + borders.addBorder(topVal, BSTop, renderTop, tx, ty, tx + w, ty + topWidth, topStyle); + borders.addBorder(bottomVal, BSBottom, renderBottom, tx, ty + h - bottomWidth, tx + w, ty + h, bottomStyle); + borders.addBorder(leftVal, BSLeft, renderLeft, tx, ty, tx + leftWidth, ty + h, leftStyle); + borders.addBorder(rightVal, BSRight, renderRight, tx + w - rightWidth, ty, tx + w, ty + h, rightStyle); + + for (CollapsedBorder* border = borders.nextBorder(); border; border = borders.nextBorder()) { + if (border->borderValue == *table()->currentBorderStyle()) + drawLineForBoxSide(graphicsContext, border->x1, border->y1, border->x2, border->y2, border->side, + border->borderValue.color(), border->style, 0, 0); + } +} + +void RenderTableCell::paintBackgroundsBehindCell(PaintInfo& paintInfo, int tx, int ty, RenderObject* backgroundObject) +{ + if (!paintInfo.shouldPaintWithinRoot(this)) + return; + + if (!backgroundObject) + return; + + if (style()->visibility() != VISIBLE) + return; + + RenderTable* tableElt = table(); + if (!tableElt->collapseBorders() && style()->emptyCells() == HIDE && !firstChild()) + return; + + if (backgroundObject != this) { + tx += x(); + ty += y(); + } + + int w = width(); + int h = height(); + + Color c = backgroundObject->style()->visitedDependentColor(CSSPropertyBackgroundColor); + const FillLayer* bgLayer = backgroundObject->style()->backgroundLayers(); + + if (bgLayer->hasImage() || c.isValid()) { + // We have to clip here because the background would paint + // on top of the borders otherwise. This only matters for cells and rows. + bool shouldClip = backgroundObject->hasLayer() && (backgroundObject == this || backgroundObject == parent()) && tableElt->collapseBorders(); + if (shouldClip) { + IntRect clipRect(tx + borderLeft(), ty + borderTop(), + w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); + paintInfo.context->save(); + paintInfo.context->clip(clipRect); + } + paintFillLayers(paintInfo, c, bgLayer, tx, ty, w, h, CompositeSourceOver, backgroundObject); + if (shouldClip) + paintInfo.context->restore(); + } +} + +void RenderTableCell::paintBoxDecorations(PaintInfo& paintInfo, int tx, int ty) +{ + if (!paintInfo.shouldPaintWithinRoot(this)) + return; + + RenderTable* tableElt = table(); + if (!tableElt->collapseBorders() && style()->emptyCells() == HIDE && !firstChild()) + return; + + int w = width(); + int h = height(); + + if (style()->boxShadow()) + paintBoxShadow(paintInfo.context, tx, ty, w, h, style(), Normal); + + // Paint our cell background. + paintBackgroundsBehindCell(paintInfo, tx, ty, this); + if (style()->boxShadow()) + paintBoxShadow(paintInfo.context, tx, ty, w, h, style(), Inset); + + if (!style()->hasBorder() || tableElt->collapseBorders()) + return; + + paintBorder(paintInfo.context, tx, ty, w, h, style()); +} + +void RenderTableCell::paintMask(PaintInfo& paintInfo, int tx, int ty) +{ + if (style()->visibility() != VISIBLE || paintInfo.phase != PaintPhaseMask) + return; + + RenderTable* tableElt = table(); + if (!tableElt->collapseBorders() && style()->emptyCells() == HIDE && !firstChild()) + return; + + int w = width(); + int h = height(); + + paintMaskImages(paintInfo, tx, ty, w, h); +} + +void RenderTableCell::scrollbarsChanged(bool horizontalScrollbarChanged, bool verticalScrollbarChanged) +{ + int scrollbarHeight = scrollbarLogicalHeight(); + if (!scrollbarHeight) + return; // Not sure if we should be doing something when a scrollbar goes away or not. + + // We only care if the scrollbar that affects our intrinsic padding has been added. + if ((style()->isHorizontalWritingMode() && !horizontalScrollbarChanged) || + (!style()->isHorizontalWritingMode() && !verticalScrollbarChanged)) + return; + + // Shrink our intrinsic padding as much as possible to accommodate the scrollbar. + if (style()->verticalAlign() == MIDDLE) { + int totalHeight = logicalHeight(); + int heightWithoutIntrinsicPadding = totalHeight - intrinsicPaddingBefore() - intrinsicPaddingAfter(); + totalHeight -= scrollbarHeight; + int newBeforePadding = (totalHeight - heightWithoutIntrinsicPadding) / 2; + int newAfterPadding = totalHeight - heightWithoutIntrinsicPadding - newBeforePadding; + setIntrinsicPaddingBefore(newBeforePadding); + setIntrinsicPaddingAfter(newAfterPadding); + } else + setIntrinsicPaddingAfter(intrinsicPaddingAfter() - scrollbarHeight); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderTableCell.h b/Source/WebCore/rendering/RenderTableCell.h new file mode 100644 index 0000000..5b5992a --- /dev/null +++ b/Source/WebCore/rendering/RenderTableCell.h @@ -0,0 +1,177 @@ +/* + * 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, 2009 Apple Inc. All rights reserved. + * + * 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. + */ + +#ifndef RenderTableCell_h +#define RenderTableCell_h + +#include "RenderTableSection.h" + +namespace WebCore { + +class RenderTableCell : public RenderBlock { +public: + explicit RenderTableCell(Node*); + + // FIXME: need to implement cellIndex + int cellIndex() const { return 0; } + void setCellIndex(int) { } + + int colSpan() const { return m_columnSpan; } + void setColSpan(int c) { m_columnSpan = c; } + + int rowSpan() const { return m_rowSpan; } + void setRowSpan(int r) { m_rowSpan = r; } + + int col() const { return m_column; } + void setCol(int col) { m_column = col; } + int row() const { return m_row; } + void setRow(int row) { m_row = row; } + + RenderTableSection* section() const { return toRenderTableSection(parent()->parent()); } + RenderTable* table() const { return toRenderTable(parent()->parent()->parent()); } + + Length styleOrColLogicalWidth() const; + + virtual void computePreferredLogicalWidths(); + + void updateLogicalWidth(int); + + virtual int borderLeft() const; + virtual int borderRight() const; + virtual int borderTop() const; + virtual int borderBottom() const; + virtual int borderStart() const; + virtual int borderEnd() const; + virtual int borderBefore() const; + virtual int borderAfter() const; + + int borderHalfLeft(bool outer) const; + int borderHalfRight(bool outer) const; + int borderHalfTop(bool outer) const; + int borderHalfBottom(bool outer) const; + + int borderHalfStart(bool outer) const; + int borderHalfEnd(bool outer) const; + int borderHalfBefore(bool outer) const; + int borderHalfAfter(bool outer) const; + + CollapsedBorderValue collapsedStartBorder() const; + CollapsedBorderValue collapsedEndBorder() const; + CollapsedBorderValue collapsedBeforeBorder() const; + CollapsedBorderValue collapsedAfterBorder() const; + + CollapsedBorderValue collapsedLeftBorder() const; + CollapsedBorderValue collapsedRightBorder() const; + CollapsedBorderValue collapsedTopBorder() const; + CollapsedBorderValue collapsedBottomBorder() const; + + typedef Vector<CollapsedBorderValue, 100> CollapsedBorderStyles; + void collectBorderStyles(CollapsedBorderStyles&) const; + static void sortBorderStyles(CollapsedBorderStyles&); + + virtual void updateFromElement(); + + virtual void layout(); + + virtual void paint(PaintInfo&, int tx, int ty); + + void paintBackgroundsBehindCell(PaintInfo&, int tx, int ty, RenderObject* backgroundObject); + + int baselinePosition() const; + + void setIntrinsicPaddingBefore(int p) { m_intrinsicPaddingBefore = p; } + void setIntrinsicPaddingAfter(int p) { m_intrinsicPaddingAfter = p; } + void setIntrinsicPadding(int before, int after) { setIntrinsicPaddingBefore(before); setIntrinsicPaddingAfter(after); } + void clearIntrinsicPadding() { setIntrinsicPadding(0, 0); } + + int intrinsicPaddingBefore() const { return m_intrinsicPaddingBefore; } + int intrinsicPaddingAfter() const { return m_intrinsicPaddingAfter; } + + virtual int paddingTop(bool includeIntrinsicPadding = true) const; + virtual int paddingBottom(bool includeIntrinsicPadding = true) const; + virtual int paddingLeft(bool includeIntrinsicPadding = true) const; + virtual int paddingRight(bool includeIntrinsicPadding = true) const; + + // FIXME: For now we just assume the cell has the same block flow direction as the table. It's likely we'll + // create an extra anonymous RenderBlock to handle mixing directionality anyway, in which case we can lock + // the block flow directionality of the cells to the table's directionality. + virtual int paddingBefore(bool includeIntrinsicPadding = true) const; + virtual int paddingAfter(bool includeIntrinsicPadding = true) const; + + virtual void setOverrideSize(int); + + bool hasVisualOverflow() const { return m_overflow && !borderBoxRect().contains(m_overflow->visualOverflowRect()); } + + virtual void scrollbarsChanged(bool horizontalScrollbarChanged, bool verticalScrollbarChanged); + +protected: + virtual void styleWillChange(StyleDifference, const RenderStyle* newStyle); + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + +private: + virtual const char* renderName() const { return isAnonymous() ? "RenderTableCell (anonymous)" : "RenderTableCell"; } + + virtual bool isTableCell() const { return true; } + + virtual void destroy(); + + virtual bool requiresLayer() const { return isPositioned() || isTransparent() || hasOverflowClip() || hasTransform() || hasMask() || hasReflection(); } + + virtual void computeLogicalWidth(); + + virtual void paintBoxDecorations(PaintInfo&, int tx, int ty); + virtual void paintMask(PaintInfo&, int tx, int ty); + + virtual IntSize offsetFromContainer(RenderObject*, const IntPoint&) const; + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer); + virtual void computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect&, bool fixed = false); + + void paintCollapsedBorder(GraphicsContext*, int x, int y, int w, int h); + + int m_row; + int m_column; + int m_rowSpan; + int m_columnSpan; + int m_intrinsicPaddingBefore; + int m_intrinsicPaddingAfter; +}; + +inline RenderTableCell* toRenderTableCell(RenderObject* object) +{ + ASSERT(!object || object->isTableCell()); + return static_cast<RenderTableCell*>(object); +} + +inline const RenderTableCell* toRenderTableCell(const RenderObject* object) +{ + ASSERT(!object || object->isTableCell()); + return static_cast<const RenderTableCell*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderTableCell(const RenderTableCell*); + +} // namespace WebCore + +#endif // RenderTableCell_h diff --git a/Source/WebCore/rendering/RenderTableCol.cpp b/Source/WebCore/rendering/RenderTableCol.cpp new file mode 100644 index 0000000..66d060b --- /dev/null +++ b/Source/WebCore/rendering/RenderTableCol.cpp @@ -0,0 +1,107 @@ +/* + * 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, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + * + * 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 "RenderTableCol.h" + +#include "CachedImage.h" +#include "HTMLNames.h" +#include "HTMLTableColElement.h" +#include "RenderTable.h" + +namespace WebCore { + +using namespace HTMLNames; + +RenderTableCol::RenderTableCol(Node* node) + : RenderBox(node) + , m_span(1) +{ + // init RenderObject attributes + setInline(true); // our object is not Inline + updateFromElement(); +} + +void RenderTableCol::updateFromElement() +{ + int oldSpan = m_span; + Node* n = node(); + if (n && (n->hasTagName(colTag) || n->hasTagName(colgroupTag))) { + HTMLTableColElement* tc = static_cast<HTMLTableColElement*>(n); + m_span = tc->span(); + } else + m_span = !(style() && style()->display() == TABLE_COLUMN_GROUP); + if (m_span != oldSpan && style() && parent()) + setNeedsLayoutAndPrefWidthsRecalc(); +} + +bool RenderTableCol::isChildAllowed(RenderObject* child, RenderStyle* style) const +{ + return !child->isText() && style && (style->display() == TABLE_COLUMN); +} + +bool RenderTableCol::canHaveChildren() const +{ + // Cols cannot have children. This is actually necessary to fix a bug + // with libraries.uc.edu, which makes a <p> be a table-column. + return style()->display() == TABLE_COLUMN_GROUP; +} + +IntRect RenderTableCol::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) +{ + // For now, just repaint the whole table. + // FIXME: Find a better way to do this, e.g., need to repaint all the cells that we + // might have propagated a background color or borders into. + // FIXME: check for repaintContainer each time here? + + RenderTable* parentTable = table(); + if (!parentTable) + return IntRect(); + return parentTable->clippedOverflowRectForRepaint(repaintContainer); +} + +void RenderTableCol::imageChanged(WrappedImagePtr, const IntRect*) +{ + // FIXME: Repaint only the rect the image paints in. + repaint(); +} + +void RenderTableCol::computePreferredLogicalWidths() +{ + setPreferredLogicalWidthsDirty(false); + + for (RenderObject* child = firstChild(); child; child = child->nextSibling()) + child->setPreferredLogicalWidthsDirty(false); +} + +RenderTable* RenderTableCol::table() const +{ + RenderObject* table = parent(); + if (table && !table->isTable()) + table = table->parent(); + return table && table->isTable() ? toRenderTable(table) : 0; +} + +} diff --git a/Source/WebCore/rendering/RenderTableCol.h b/Source/WebCore/rendering/RenderTableCol.h new file mode 100644 index 0000000..8255937 --- /dev/null +++ b/Source/WebCore/rendering/RenderTableCol.h @@ -0,0 +1,85 @@ +/* + * 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, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + * + * 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. + */ + +#ifndef RenderTableCol_h +#define RenderTableCol_h + +#include "RenderBox.h" + +namespace WebCore { + +class RenderTable; + +class RenderTableCol : public RenderBox { +public: + explicit RenderTableCol(Node*); + + const RenderObjectChildList* children() const { return &m_children; } + RenderObjectChildList* children() { return &m_children; } + + virtual void computePreferredLogicalWidths(); + + int span() const { return m_span; } + void setSpan(int span) { m_span = span; } + +private: + virtual RenderObjectChildList* virtualChildren() { return children(); } + virtual const RenderObjectChildList* virtualChildren() const { return children(); } + + virtual const char* renderName() const { return "RenderTableCol"; } + virtual bool isTableCol() const { return true; } + virtual void updateFromElement(); + + virtual bool isChildAllowed(RenderObject*, RenderStyle*) const; + virtual bool canHaveChildren() const; + virtual bool requiresLayer() const { return false; } + + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer); + virtual void imageChanged(WrappedImagePtr, const IntRect* = 0); + + RenderTable* table() const; + + RenderObjectChildList m_children; + int m_span; +}; + +inline RenderTableCol* toRenderTableCol(RenderObject* object) +{ + ASSERT(!object || object->isTableCol()); + return static_cast<RenderTableCol*>(object); +} + +inline const RenderTableCol* toRenderTableCol(const RenderObject* object) +{ + ASSERT(!object || object->isTableCol()); + return static_cast<const RenderTableCol*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderTableCol(const RenderTableCol*); + +} + +#endif diff --git a/Source/WebCore/rendering/RenderTableRow.cpp b/Source/WebCore/rendering/RenderTableRow.cpp new file mode 100644 index 0000000..5bb3ff4 --- /dev/null +++ b/Source/WebCore/rendering/RenderTableRow.cpp @@ -0,0 +1,217 @@ +/** + * 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. + * + * 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 "RenderTableRow.h" + +#include "CachedImage.h" +#include "Document.h" +#include "HTMLNames.h" +#include "RenderTableCell.h" +#include "RenderView.h" + +namespace WebCore { + +using namespace HTMLNames; + +RenderTableRow::RenderTableRow(Node* node) + : RenderBox(node) +{ + // init RenderObject attributes + setInline(false); // our object is not Inline +} + +void RenderTableRow::destroy() +{ + RenderTableSection* recalcSection = section(); + + RenderBox::destroy(); + + if (recalcSection) + recalcSection->setNeedsCellRecalc(); +} + +void RenderTableRow::styleWillChange(StyleDifference diff, const RenderStyle* newStyle) +{ + if (section() && style() && style()->logicalHeight() != newStyle->logicalHeight()) + section()->setNeedsCellRecalc(); + + ASSERT(newStyle->display() == TABLE_ROW); + + RenderBox::styleWillChange(diff, newStyle); +} + +void RenderTableRow::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(); + + if (!child->isTableCell()) { + RenderObject* last = beforeChild; + if (!last) + last = lastChild(); + if (last && last->isAnonymous() && last->isTableCell()) { + if (beforeChild == last) + beforeChild = last->firstChild(); + last->addChild(child, beforeChild); + return; + } + + // If beforeChild is inside an anonymous cell, insert into the cell. + if (last && !last->isTableCell() && last->parent() && last->parent()->isAnonymous()) { + last->parent()->addChild(child, beforeChild); + return; + } + + RenderTableCell* cell = new (renderArena()) RenderTableCell(document() /* anonymous object */); + RefPtr<RenderStyle> newStyle = RenderStyle::create(); + newStyle->inheritFrom(style()); + newStyle->setDisplay(TABLE_CELL); + cell->setStyle(newStyle.release()); + addChild(cell, beforeChild); + cell->addChild(child); + return; + } + + // If the next renderer is actually wrapped in an anonymous table cell, we need to go up and find that. + while (beforeChild && beforeChild->parent() != this) + beforeChild = beforeChild->parent(); + + RenderTableCell* cell = toRenderTableCell(child); + + // Generated content can result in us having a null section so make sure to null check our parent. + if (parent()) + section()->addCell(cell, this); + + ASSERT(!beforeChild || beforeChild->isTableCell()); + RenderBox::addChild(cell, beforeChild); + + if (beforeChild || nextSibling()) + section()->setNeedsCellRecalc(); +} + +void RenderTableRow::layout() +{ + ASSERT(needsLayout()); + + // Table rows do not add translation. + LayoutStateMaintainer statePusher(view(), this, IntSize(), style()->isFlippedBlocksWritingMode()); + + bool paginated = view()->layoutState()->isPaginated(); + + for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { + if (child->isTableCell()) { + RenderTableCell* cell = toRenderTableCell(child); + if (!cell->needsLayout() && paginated && view()->layoutState()->pageLogicalHeight() && view()->layoutState()->pageLogicalOffset(cell->y()) != cell->pageLogicalOffset()) + cell->setChildNeedsLayout(true, false); + + if (child->needsLayout()) { + cell->computeBlockDirectionMargins(table()); + cell->layout(); + } + } + } + + // We only ever need to repaint if our cells didn't, which menas that they didn't need + // layout, so we know that our bounds didn't change. This code is just making up for + // the fact that we did not repaint in setStyle() because we had a layout hint. + // We cannot call repaint() because our clippedOverflowRectForRepaint() is taken from the + // parent table, and being mid-layout, that is invalid. Instead, we repaint our cells. + if (selfNeedsLayout() && checkForRepaintDuringLayout()) { + for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { + if (child->isTableCell()) + child->repaint(); + } + } + + statePusher.pop(); + // RenderTableSection::layoutRows will set our logical height and width later, so it calls updateLayerTransform(). + setNeedsLayout(false); +} + +IntRect RenderTableRow::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) +{ + ASSERT(parent()); + + if (repaintContainer == this) + return RenderBox::clippedOverflowRectForRepaint(repaintContainer); + + // For now, just repaint the whole table. + // FIXME: Find a better way to do this, e.g., need to repaint all the cells that we + // might have propagated a background color into. + // FIXME: do repaintContainer checks here + if (RenderTable* parentTable = table()) + return parentTable->clippedOverflowRectForRepaint(repaintContainer); + + return IntRect(); +} + +// Hit Testing +bool RenderTableRow::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction action) +{ + // Table rows cannot ever be hit tested. Effectively they do not exist. + // Just forward to our children always. + for (RenderObject* child = lastChild(); child; child = child->previousSibling()) { + // FIXME: We have to skip over inline flows, since they can show up inside table rows + // at the moment (a demoted inline <form> for example). If we ever implement a + // table-specific hit-test method (which we should do for performance reasons anyway), + // then we can remove this check. + if (child->isTableCell() && !toRenderBox(child)->hasSelfPaintingLayer()) { + IntPoint cellPoint = flipForWritingMode(toRenderTableCell(child), IntPoint(tx, ty), ParentToChildFlippingAdjustment); + if (child->nodeAtPoint(request, result, x, y, cellPoint.x(), cellPoint.y(), action)) { + updateHitTestResult(result, IntPoint(x - cellPoint.x(), y - cellPoint.y())); + return true; + } + } + } + + return false; +} + +void RenderTableRow::paint(PaintInfo& paintInfo, int tx, int ty) +{ + ASSERT(hasSelfPaintingLayer()); + if (!layer()) + return; + for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { + if (child->isTableCell()) { + // Paint the row background behind the cell. + if (paintInfo.phase == PaintPhaseBlockBackground || paintInfo.phase == PaintPhaseChildBlockBackground) { + RenderTableCell* cell = toRenderTableCell(child); + cell->paintBackgroundsBehindCell(paintInfo, tx, ty, this); + } + if (!toRenderBox(child)->hasSelfPaintingLayer()) + child->paint(paintInfo, tx, ty); + } + } +} + +void RenderTableRow::imageChanged(WrappedImagePtr, const IntRect*) +{ + // FIXME: Examine cells and repaint only the rect the image paints in. + repaint(); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderTableRow.h b/Source/WebCore/rendering/RenderTableRow.h new file mode 100644 index 0000000..20aa424 --- /dev/null +++ b/Source/WebCore/rendering/RenderTableRow.h @@ -0,0 +1,86 @@ +/* + * 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, 2009 Apple Inc. All rights reserved. + * + * 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. + */ + +#ifndef RenderTableRow_h +#define RenderTableRow_h + +#include "RenderTableSection.h" + +namespace WebCore { + +class RenderTableRow : public RenderBox { +public: + explicit RenderTableRow(Node*); + + const RenderObjectChildList* children() const { return &m_children; } + RenderObjectChildList* children() { return &m_children; } + + RenderTableSection* section() const { return toRenderTableSection(parent()); } + RenderTable* table() const { return toRenderTable(parent()->parent()); } + +private: + virtual RenderObjectChildList* virtualChildren() { return children(); } + virtual const RenderObjectChildList* virtualChildren() const { return children(); } + + virtual const char* renderName() const { return isAnonymous() ? "RenderTableRow (anonymous)" : "RenderTableRow"; } + + virtual bool isTableRow() const { return true; } + + virtual void destroy(); + + virtual void addChild(RenderObject* child, RenderObject* beforeChild = 0); + virtual void layout(); + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer); + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + + // The only time rows get a layer is when they have transparency. + virtual bool requiresLayer() const { return isTransparent() || hasOverflowClip() || hasTransform() || hasMask(); } + + virtual void paint(PaintInfo&, int tx, int ty); + + virtual void imageChanged(WrappedImagePtr, const IntRect* = 0); + + virtual void styleWillChange(StyleDifference, const RenderStyle* newStyle); + + RenderObjectChildList m_children; +}; + +inline RenderTableRow* toRenderTableRow(RenderObject* object) +{ + ASSERT(!object || object->isTableRow()); + return static_cast<RenderTableRow*>(object); +} + +inline const RenderTableRow* toRenderTableRow(const RenderObject* object) +{ + ASSERT(!object || object->isTableRow()); + return static_cast<const RenderTableRow*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderTableRow(const RenderTableRow*); + +} // namespace WebCore + +#endif // RenderTableRow_h diff --git a/Source/WebCore/rendering/RenderTableSection.cpp b/Source/WebCore/rendering/RenderTableSection.cpp new file mode 100644 index 0000000..cb1276d --- /dev/null +++ b/Source/WebCore/rendering/RenderTableSection.cpp @@ -0,0 +1,1320 @@ +/* + * 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, 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 "RenderTableSection.h" +#include "CachedImage.h" +#include "Document.h" +#include "HitTestResult.h" +#include "HTMLNames.h" +#include "RenderTableCell.h" +#include "RenderTableCol.h" +#include "RenderTableRow.h" +#include "RenderView.h" +#include <limits> +#include <wtf/HashSet.h> +#include <wtf/Vector.h> +#ifdef ANDROID_LAYOUT +#include "Frame.h" +#include "Settings.h" +#endif + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +static inline void setRowLogicalHeightToRowStyleLogicalHeightIfNotRelative(RenderTableSection::RowStruct* row) +{ + ASSERT(row && row->rowRenderer); + row->logicalHeight = row->rowRenderer->style()->logicalHeight(); + if (row->logicalHeight.isRelative()) + row->logicalHeight = Length(); +} + +RenderTableSection::RenderTableSection(Node* node) + : RenderBox(node) + , m_gridRows(0) + , m_cCol(0) + , m_cRow(-1) + , m_outerBorderStart(0) + , m_outerBorderEnd(0) + , m_outerBorderBefore(0) + , m_outerBorderAfter(0) + , m_needsCellRecalc(false) + , m_hasOverflowingCell(false) + , m_hasMultipleCellLevels(false) +{ + // init RenderObject attributes + setInline(false); // our object is not Inline +} + +RenderTableSection::~RenderTableSection() +{ + clearGrid(); +} + +void RenderTableSection::destroy() +{ + RenderTable* recalcTable = table(); + + RenderBox::destroy(); + + // recalc cell info because RenderTable has unguarded pointers + // stored that point to this RenderTableSection. + if (recalcTable) + recalcTable->setNeedsSectionRecalc(); +} + +void RenderTableSection::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(); + + if (!child->isTableRow()) { + RenderObject* last = beforeChild; + if (!last) + last = lastChild(); + if (last && last->isAnonymous()) { + if (beforeChild == last) + beforeChild = last->firstChild(); + last->addChild(child, beforeChild); + return; + } + + // If beforeChild is inside an anonymous cell/row, insert into the cell or into + // the anonymous row containing it, if there is one. + RenderObject* lastBox = last; + while (lastBox && lastBox->parent()->isAnonymous() && !lastBox->isTableRow()) + lastBox = lastBox->parent(); + if (lastBox && lastBox->isAnonymous()) { + lastBox->addChild(child, beforeChild); + return; + } + + RenderObject* row = new (renderArena()) RenderTableRow(document() /* anonymous table row */); + RefPtr<RenderStyle> newStyle = RenderStyle::create(); + newStyle->inheritFrom(style()); + newStyle->setDisplay(TABLE_ROW); + row->setStyle(newStyle.release()); + addChild(row, beforeChild); + row->addChild(child); + return; + } + + if (beforeChild) + setNeedsCellRecalc(); + + ++m_cRow; + m_cCol = 0; + + // make sure we have enough rows + if (!ensureRows(m_cRow + 1)) + return; + + m_grid[m_cRow].rowRenderer = toRenderTableRow(child); + + if (!beforeChild) + setRowLogicalHeightToRowStyleLogicalHeightIfNotRelative(&m_grid[m_cRow]); + + // If the next renderer is actually wrapped in an anonymous table row, we need to go up and find that. + while (beforeChild && beforeChild->parent() != this) + beforeChild = beforeChild->parent(); + + ASSERT(!beforeChild || beforeChild->isTableRow()); + RenderBox::addChild(child, beforeChild); +} + +void RenderTableSection::removeChild(RenderObject* oldChild) +{ + setNeedsCellRecalc(); + RenderBox::removeChild(oldChild); +} + +bool RenderTableSection::ensureRows(int numRows) +{ + int nRows = m_gridRows; + if (numRows > nRows) { + if (numRows > static_cast<int>(m_grid.size())) { + size_t maxSize = numeric_limits<size_t>::max() / sizeof(RowStruct); + if (static_cast<size_t>(numRows) > maxSize) + return false; + m_grid.grow(numRows); + } + m_gridRows = numRows; + int nCols = max(1, table()->numEffCols()); + for (int r = nRows; r < numRows; r++) { + m_grid[r].row = new Row(nCols); + m_grid[r].rowRenderer = 0; + m_grid[r].baseline = 0; + m_grid[r].logicalHeight = Length(); + } + } + + return true; +} + +void RenderTableSection::addCell(RenderTableCell* cell, RenderTableRow* row) +{ + int rSpan = cell->rowSpan(); + int cSpan = cell->colSpan(); + Vector<RenderTable::ColumnStruct>& columns = table()->columns(); + int nCols = columns.size(); + + // ### mozilla still seems to do the old HTML way, even for strict DTD + // (see the annotation on table cell layouting in the CSS specs and the testcase below: + // <TABLE border> + // <TR><TD>1 <TD rowspan="2">2 <TD>3 <TD>4 + // <TR><TD colspan="2">5 + // </TABLE> + while (m_cCol < nCols && (cellAt(m_cRow, m_cCol).hasCells() || cellAt(m_cRow, m_cCol).inColSpan)) + m_cCol++; + + if (rSpan == 1) { + // we ignore height settings on rowspan cells + Length logicalHeight = cell->style()->logicalHeight(); + if (logicalHeight.isPositive() || (logicalHeight.isRelative() && logicalHeight.value() >= 0)) { + Length cRowLogicalHeight = m_grid[m_cRow].logicalHeight; + switch (logicalHeight.type()) { + case Percent: + if (!(cRowLogicalHeight.isPercent()) || + (cRowLogicalHeight.isPercent() && cRowLogicalHeight.rawValue() < logicalHeight.rawValue())) + m_grid[m_cRow].logicalHeight = logicalHeight; + break; + case Fixed: + if (cRowLogicalHeight.type() < Percent || + (cRowLogicalHeight.isFixed() && cRowLogicalHeight.value() < logicalHeight.value())) + m_grid[m_cRow].logicalHeight = logicalHeight; + break; + case Relative: + default: + break; + } + } + } + + // make sure we have enough rows + if (!ensureRows(m_cRow + rSpan)) + return; + + m_grid[m_cRow].rowRenderer = row; + + int col = m_cCol; + // tell the cell where it is + bool inColSpan = false; + while (cSpan) { + int currentSpan; + if (m_cCol >= nCols) { + table()->appendColumn(cSpan); + currentSpan = cSpan; + } else { + if (cSpan < (int)columns[m_cCol].span) + table()->splitColumn(m_cCol, cSpan); + currentSpan = columns[m_cCol].span; + } + for (int r = 0; r < rSpan; r++) { + CellStruct& c = cellAt(m_cRow + r, m_cCol); + ASSERT(cell); + c.cells.append(cell); + // If cells overlap then we take the slow path for painting. + if (c.cells.size() > 1) + m_hasMultipleCellLevels = true; + if (inColSpan) + c.inColSpan = true; + } + m_cCol++; + cSpan -= currentSpan; + inColSpan = true; + } + cell->setRow(m_cRow); + cell->setCol(table()->effColToCol(col)); +} + +void RenderTableSection::setCellLogicalWidths() +{ + Vector<int>& columnPos = table()->columnPositions(); + + LayoutStateMaintainer statePusher(view()); + +#ifdef ANDROID_LAYOUT + int visibleWidth = 0; + if (view()->frameView()) { + const Settings* settings = document()->settings(); + ASSERT(settings); + if (settings->layoutAlgorithm() == Settings::kLayoutFitColumnToScreen) + visibleWidth = view()->frameView()->textWrapWidth(); + } +#endif + + for (int i = 0; i < m_gridRows; i++) { + Row& row = *m_grid[i].row; + int cols = row.size(); + for (int j = 0; j < cols; j++) { + CellStruct& current = row[j]; + RenderTableCell* cell = current.primaryCell(); + if (!cell || current.inColSpan) + continue; + int endCol = j; + int cspan = cell->colSpan(); + while (cspan && endCol < cols) { + ASSERT(endCol < (int)table()->columns().size()); + cspan -= table()->columns()[endCol].span; + endCol++; + } + int w = columnPos[endCol] - columnPos[j] - table()->hBorderSpacing(); +#ifdef ANDROID_LAYOUT + if (table()->isSingleColumn()) { + int b = table()->collapseBorders() ? + 0 : table()->paddingLeft() + table()->paddingRight() + 2 * table()->hBorderSpacing(); + w = table()->width() - (table()->borderLeft() + table()->borderRight() + b); + } +#endif + int oldLogicalWidth = cell->logicalWidth(); +#ifdef ANDROID_LAYOUT + if (w != oldLogicalWidth || (visibleWidth > 0 && visibleWidth != cell->getVisibleWidth())) { +#else + if (w != oldLogicalWidth) { +#endif + cell->setNeedsLayout(true); + if (!table()->selfNeedsLayout() && cell->checkForRepaintDuringLayout()) { + if (!statePusher.didPush()) { + // Technically, we should also push state for the row, but since + // rows don't push a coordinate transform, that's not necessary. + statePusher.push(this, IntSize(x(), y())); + } + cell->repaint(); + } +#ifdef ANDROID_LAYOUT + if (w != oldLogicalWidth) +#endif + cell->updateLogicalWidth(w); + } + } + } + + statePusher.pop(); // only pops if we pushed +} + +int RenderTableSection::calcRowLogicalHeight() +{ +#ifndef NDEBUG + setNeedsLayoutIsForbidden(true); +#endif + + ASSERT(!needsLayout()); +#ifdef ANDROID_LAYOUT + if (table()->isSingleColumn()) { + int height = 0; + int spacing = table()->vBorderSpacing(); + for (int r = 0; r < m_gridRows; r++) + height += m_grid[r].logicalHeight.calcMinValue(0) + (m_grid[r].rowRenderer ? spacing : 0); + return height; + } +#endif + + RenderTableCell* cell; + + int spacing = table()->vBorderSpacing(); + + LayoutStateMaintainer statePusher(view()); + + m_rowPos.resize(m_gridRows + 1); + m_rowPos[0] = spacing; + + for (int r = 0; r < m_gridRows; r++) { + m_rowPos[r + 1] = 0; + m_grid[r].baseline = 0; + int baseline = 0; + int bdesc = 0; + int ch = m_grid[r].logicalHeight.calcMinValue(0); + int pos = m_rowPos[r] + ch + (m_grid[r].rowRenderer ? spacing : 0); + + m_rowPos[r + 1] = max(m_rowPos[r + 1], pos); + + Row* row = m_grid[r].row; + int totalCols = row->size(); + + for (int c = 0; c < totalCols; c++) { + CellStruct& current = cellAt(r, c); + cell = current.primaryCell(); + + if (!cell || current.inColSpan) + continue; + + if ((cell->row() + cell->rowSpan() - 1) > r) + continue; + + int indx = max(r - cell->rowSpan() + 1, 0); + + if (cell->overrideSize() != -1) { + if (!statePusher.didPush()) { + // Technically, we should also push state for the row, but since + // rows don't push a coordinate transform, that's not necessary. + statePusher.push(this, IntSize(x(), y())); + } + cell->setOverrideSize(-1); + cell->setChildNeedsLayout(true, false); + cell->layoutIfNeeded(); + } + + int adjustedPaddingBefore = cell->paddingBefore() - cell->intrinsicPaddingBefore(); + int adjustedPaddingAfter = cell->paddingAfter() - cell->intrinsicPaddingAfter(); + int adjustedLogicalHeight = cell->logicalHeight() - (cell->intrinsicPaddingBefore() + cell->intrinsicPaddingAfter()); + + // Explicit heights use the border box in quirks mode. In strict mode do the right + // thing and actually add in the border and padding. + ch = cell->style()->logicalHeight().calcValue(0) + + (document()->inQuirksMode() ? 0 : (adjustedPaddingBefore + adjustedPaddingAfter + + cell->borderBefore() + cell->borderAfter())); + ch = max(ch, adjustedLogicalHeight); + + pos = m_rowPos[indx] + ch + (m_grid[r].rowRenderer ? spacing : 0); + + m_rowPos[r + 1] = max(m_rowPos[r + 1], pos); + + // find out the baseline + EVerticalAlign va = cell->style()->verticalAlign(); + if (va == BASELINE || va == TEXT_BOTTOM || va == TEXT_TOP || va == SUPER || va == SUB) { + int b = cell->baselinePosition(); + if (b > cell->borderBefore() + cell->paddingBefore()) { + baseline = max(baseline, b - cell->intrinsicPaddingBefore()); + bdesc = max(bdesc, m_rowPos[indx] + ch - (b - cell->intrinsicPaddingBefore())); + } + } + } + + // do we have baseline aligned elements? + if (baseline) { + // increase rowheight if baseline requires + m_rowPos[r + 1] = max(m_rowPos[r + 1], baseline + bdesc + (m_grid[r].rowRenderer ? spacing : 0)); + m_grid[r].baseline = baseline; + } + + m_rowPos[r + 1] = max(m_rowPos[r + 1], m_rowPos[r]); + } + +#ifndef NDEBUG + setNeedsLayoutIsForbidden(false); +#endif + + ASSERT(!needsLayout()); + + statePusher.pop(); + + return m_rowPos[m_gridRows]; +} + +void RenderTableSection::layout() +{ + ASSERT(needsLayout()); + + LayoutStateMaintainer statePusher(view(), this, IntSize(x(), y()), style()->isFlippedBlocksWritingMode()); + for (RenderObject* child = children()->firstChild(); child; child = child->nextSibling()) { + if (child->isTableRow()) { + child->layoutIfNeeded(); + ASSERT(!child->needsLayout()); + } + } + statePusher.pop(); + setNeedsLayout(false); +} + +int RenderTableSection::layoutRows(int toAdd) +{ +#ifndef NDEBUG + setNeedsLayoutIsForbidden(true); +#endif + + ASSERT(!needsLayout()); +#ifdef ANDROID_LAYOUT + if (table()->isSingleColumn()) { + int totalRows = m_gridRows; + int hspacing = table()->hBorderSpacing(); + int vspacing = table()->vBorderSpacing(); + int rHeight = vspacing; + + int leftOffset = hspacing; + + int nEffCols = table()->numEffCols(); + for (int r = 0; r < totalRows; r++) { + for (int c = 0; c < nEffCols; c++) { + CellStruct current = cellAt(r, c); + RenderTableCell* cell = current.primaryCell(); + + if (!cell || current.inColSpan) + continue; + if (r > 0 && (primaryCellAt(r-1, c) == cell)) + continue; + +// cell->setCellTopExtra(0); +// cell->setCellBottomExtra(0); + + int oldCellX = cell->x(); + int oldCellY = cell->y(); + + if (style()->direction() == RTL) { + cell->setX(table()->width()); + cell->setY(rHeight); + } else { + cell->setX(leftOffset); + cell->setY(rHeight); + } + + // If the cell moved, we have to repaint it as well as any floating/positioned + // descendants. An exception is if we need a layout. In this case, we know we're going to + // repaint ourselves (and the cell) anyway. + if (!table()->selfNeedsLayout() && cell->checkForRepaintDuringLayout()) { +// IntRect cellRect(oldCellX, oldCellY - cell->borderTopExtra() , cell->width(), cell->height()); + IntRect cellRect(oldCellX, oldCellY, cell->width(), cell->height()); + cell->repaintDuringLayoutIfMoved(cellRect); + } + rHeight += cell->height() + vspacing; + } + } + + setHeight(rHeight); + return height(); + } +#endif + + int rHeight; + int rindx; + int totalRows = m_gridRows; + + // Set the width of our section now. The rows will also be this width. + setLogicalWidth(table()->contentLogicalWidth()); + m_overflow.clear(); + m_hasOverflowingCell = false; + + if (toAdd && totalRows && (m_rowPos[totalRows] || !nextSibling())) { + int totalHeight = m_rowPos[totalRows] + toAdd; + + int dh = toAdd; + int totalPercent = 0; + int numAuto = 0; + for (int r = 0; r < totalRows; r++) { + if (m_grid[r].logicalHeight.isAuto()) + numAuto++; + else if (m_grid[r].logicalHeight.isPercent()) + totalPercent += m_grid[r].logicalHeight.rawValue(); + } + if (totalPercent) { + // try to satisfy percent + int add = 0; + totalPercent = min(totalPercent, 100 * percentScaleFactor); + int rh = m_rowPos[1] - m_rowPos[0]; + for (int r = 0; r < totalRows; r++) { + if (totalPercent > 0 && m_grid[r].logicalHeight.isPercent()) { + int toAdd = min(dh, (totalHeight * m_grid[r].logicalHeight.rawValue() / (100 * percentScaleFactor)) - rh); + // If toAdd is negative, then we don't want to shrink the row (this bug + // affected Outlook Web Access). + toAdd = max(0, toAdd); + add += toAdd; + dh -= toAdd; + totalPercent -= m_grid[r].logicalHeight.rawValue(); + } + if (r < totalRows - 1) + rh = m_rowPos[r + 2] - m_rowPos[r + 1]; + m_rowPos[r + 1] += add; + } + } + if (numAuto) { + // distribute over variable cols + int add = 0; + for (int r = 0; r < totalRows; r++) { + if (numAuto > 0 && m_grid[r].logicalHeight.isAuto()) { + int toAdd = dh / numAuto; + add += toAdd; + dh -= toAdd; + numAuto--; + } + m_rowPos[r + 1] += add; + } + } + if (dh > 0 && m_rowPos[totalRows]) { + // if some left overs, distribute equally. + int tot = m_rowPos[totalRows]; + int add = 0; + int prev = m_rowPos[0]; + for (int r = 0; r < totalRows; r++) { + // weight with the original height + add += dh * (m_rowPos[r + 1] - prev) / tot; + prev = m_rowPos[r + 1]; + m_rowPos[r + 1] += add; + } + } + } + + int hspacing = table()->hBorderSpacing(); + int vspacing = table()->vBorderSpacing(); + int nEffCols = table()->numEffCols(); + + LayoutStateMaintainer statePusher(view(), this, IntSize(x(), y()), style()->isFlippedBlocksWritingMode()); + + for (int r = 0; r < totalRows; r++) { + // Set the row's x/y position and width/height. + if (RenderTableRow* rowRenderer = m_grid[r].rowRenderer) { + rowRenderer->setLocation(0, m_rowPos[r]); + rowRenderer->setLogicalWidth(logicalWidth()); + rowRenderer->setLogicalHeight(m_rowPos[r + 1] - m_rowPos[r] - vspacing); + rowRenderer->updateLayerTransform(); + } + + for (int c = 0; c < nEffCols; c++) { + CellStruct& cs = cellAt(r, c); + RenderTableCell* cell = cs.primaryCell(); + + if (!cell || cs.inColSpan) + continue; + + rindx = cell->row(); + rHeight = m_rowPos[rindx + cell->rowSpan()] - m_rowPos[rindx] - vspacing; + + // Force percent height children to lay themselves out again. + // This will cause these children to grow to fill the cell. + // FIXME: There is still more work to do here to fully match WinIE (should + // it become necessary to do so). In quirks mode, WinIE behaves like we + // do, but it will clip the cells that spill out of the table section. In + // strict mode, Mozilla and WinIE both regrow the table to accommodate the + // new height of the cell (thus letting the percentages cause growth one + // time only). We may also not be handling row-spanning cells correctly. + // + // Note also the oddity where replaced elements always flex, and yet blocks/tables do + // not necessarily flex. WinIE is crazy and inconsistent, and we can't hope to + // match the behavior perfectly, but we'll continue to refine it as we discover new + // bugs. :) + bool cellChildrenFlex = false; + bool flexAllChildren = cell->style()->logicalHeight().isFixed() + || (!table()->style()->logicalHeight().isAuto() && rHeight != cell->logicalHeight()); + + for (RenderObject* o = cell->firstChild(); o; o = o->nextSibling()) { + if (!o->isText() && o->style()->logicalHeight().isPercent() && (flexAllChildren || o->isReplaced() || (o->isBox() && toRenderBox(o)->scrollsOverflow()))) { + // Tables with no sections do not flex. + if (!o->isTable() || toRenderTable(o)->hasSections()) { + o->setNeedsLayout(true, false); + cellChildrenFlex = true; + } + } + } + + if (HashSet<RenderBox*>* percentHeightDescendants = cell->percentHeightDescendants()) { + HashSet<RenderBox*>::iterator end = percentHeightDescendants->end(); + for (HashSet<RenderBox*>::iterator it = percentHeightDescendants->begin(); it != end; ++it) { + RenderBox* box = *it; + if (!box->isReplaced() && !box->scrollsOverflow() && !flexAllChildren) + continue; + + while (box != cell) { + if (box->normalChildNeedsLayout()) + break; + box->setChildNeedsLayout(true, false); + box = box->containingBlock(); + ASSERT(box); + if (!box) + break; + } + cellChildrenFlex = true; + } + } + + if (cellChildrenFlex) { + cell->setChildNeedsLayout(true, false); + // Alignment within a cell is based off the calculated + // height, which becomes irrelevant once the cell has + // been resized based off its percentage. + cell->setOverrideSize(max(0, + rHeight - cell->borderBefore() - cell->paddingBefore() - + cell->borderAfter() - cell->paddingAfter())); + cell->layoutIfNeeded(); + + // If the baseline moved, we may have to update the data for our row. Find out the new baseline. + EVerticalAlign va = cell->style()->verticalAlign(); + if (va == BASELINE || va == TEXT_BOTTOM || va == TEXT_TOP || va == SUPER || va == SUB) { + int b = cell->baselinePosition(); + if (b > cell->borderBefore() + cell->paddingBefore()) + m_grid[r].baseline = max(m_grid[r].baseline, b); + } + } + + int oldIntrinsicPaddingBefore = cell->intrinsicPaddingBefore(); + int oldIntrinsicPaddingAfter = cell->intrinsicPaddingAfter(); + int logicalHeightWithoutIntrinsicPadding = cell->logicalHeight() - oldIntrinsicPaddingBefore - oldIntrinsicPaddingAfter; + + int intrinsicPaddingBefore = 0; + switch (cell->style()->verticalAlign()) { + case SUB: + case SUPER: + case TEXT_TOP: + case TEXT_BOTTOM: + case BASELINE: { + int b = cell->baselinePosition(); + if (b > cell->borderBefore() + cell->paddingBefore()) + intrinsicPaddingBefore = getBaseline(r) - (b - oldIntrinsicPaddingBefore); + break; + } + case TOP: + break; + case MIDDLE: + intrinsicPaddingBefore = (rHeight - logicalHeightWithoutIntrinsicPadding) / 2; + break; + case BOTTOM: + intrinsicPaddingBefore = rHeight - logicalHeightWithoutIntrinsicPadding; + break; + default: + break; + } + + int intrinsicPaddingAfter = rHeight - logicalHeightWithoutIntrinsicPadding - intrinsicPaddingBefore; + cell->setIntrinsicPaddingBefore(intrinsicPaddingBefore); + cell->setIntrinsicPaddingAfter(intrinsicPaddingAfter); + + IntRect oldCellRect(cell->x(), cell->y() , cell->width(), cell->height()); + + if (!style()->isLeftToRightDirection()) + cell->setLogicalLocation(table()->columnPositions()[nEffCols] - table()->columnPositions()[table()->colToEffCol(cell->col() + cell->colSpan())] + hspacing, m_rowPos[rindx]); + else + cell->setLogicalLocation(table()->columnPositions()[c] + hspacing, m_rowPos[rindx]); + view()->addLayoutDelta(IntSize(oldCellRect.x() - cell->x(), oldCellRect.y() - cell->y())); + + if (intrinsicPaddingBefore != oldIntrinsicPaddingBefore || intrinsicPaddingAfter != oldIntrinsicPaddingAfter) + cell->setNeedsLayout(true, false); + + if (!cell->needsLayout() && view()->layoutState()->pageLogicalHeight() && view()->layoutState()->pageLogicalOffset(cell->y()) != cell->pageLogicalOffset()) + cell->setChildNeedsLayout(true, false); + + cell->layoutIfNeeded(); + + // FIXME: Make pagination work with vertical tables. + if (style()->isHorizontalWritingMode() && view()->layoutState()->pageLogicalHeight() && cell->height() != rHeight) + cell->setHeight(rHeight); // FIXME: Pagination might have made us change size. For now just shrink or grow the cell to fit without doing a relayout. + + IntSize childOffset(cell->x() - oldCellRect.x(), cell->y() - oldCellRect.y()); + if (childOffset.width() || childOffset.height()) { + view()->addLayoutDelta(childOffset); + + // If the child moved, we have to repaint it as well as any floating/positioned + // descendants. An exception is if we need a layout. In this case, we know we're going to + // repaint ourselves (and the child) anyway. + if (!table()->selfNeedsLayout() && cell->checkForRepaintDuringLayout()) + cell->repaintDuringLayoutIfMoved(oldCellRect); + } + } + } + +#ifndef NDEBUG + setNeedsLayoutIsForbidden(false); +#endif + + ASSERT(!needsLayout()); + + setLogicalHeight(m_rowPos[totalRows]); + + // Now that our height has been determined, add in overflow from cells. + for (int r = 0; r < totalRows; r++) { + for (int c = 0; c < nEffCols; c++) { + CellStruct& cs = cellAt(r, c); + RenderTableCell* cell = cs.primaryCell(); + if (!cell || cs.inColSpan) + continue; + if (r < totalRows - 1 && cell == primaryCellAt(r + 1, c)) + continue; + addOverflowFromChild(cell); + m_hasOverflowingCell |= cell->hasVisualOverflow(); + } + } + + statePusher.pop(); + return height(); +} + +int RenderTableSection::calcOuterBorderBefore() const +{ + int totalCols = table()->numEffCols(); + if (!m_gridRows || !totalCols) + return 0; + + unsigned borderWidth = 0; + + const BorderValue& sb = style()->borderBefore(); + if (sb.style() == BHIDDEN) + return -1; + if (sb.style() > BHIDDEN) + borderWidth = sb.width(); + + const BorderValue& rb = firstChild()->style()->borderBefore(); + if (rb.style() == BHIDDEN) + return -1; + if (rb.style() > BHIDDEN && rb.width() > borderWidth) + borderWidth = rb.width(); + + bool allHidden = true; + for (int c = 0; c < totalCols; c++) { + const CellStruct& current = cellAt(0, c); + if (current.inColSpan || !current.hasCells()) + continue; + const BorderValue& cb = current.primaryCell()->style()->borderBefore(); // FIXME: Make this work with perpendicular and flipped cells. + // FIXME: Don't repeat for the same col group + RenderTableCol* colGroup = table()->colElement(c); + if (colGroup) { + const BorderValue& gb = colGroup->style()->borderBefore(); + if (gb.style() == BHIDDEN || cb.style() == BHIDDEN) + continue; + allHidden = false; + if (gb.style() > BHIDDEN && gb.width() > borderWidth) + borderWidth = gb.width(); + if (cb.style() > BHIDDEN && cb.width() > borderWidth) + borderWidth = cb.width(); + } else { + if (cb.style() == BHIDDEN) + continue; + allHidden = false; + if (cb.style() > BHIDDEN && cb.width() > borderWidth) + borderWidth = cb.width(); + } + } + if (allHidden) + return -1; + + return borderWidth / 2; +} + +int RenderTableSection::calcOuterBorderAfter() const +{ + int totalCols = table()->numEffCols(); + if (!m_gridRows || !totalCols) + return 0; + + unsigned borderWidth = 0; + + const BorderValue& sb = style()->borderAfter(); + if (sb.style() == BHIDDEN) + return -1; + if (sb.style() > BHIDDEN) + borderWidth = sb.width(); + + const BorderValue& rb = lastChild()->style()->borderAfter(); + if (rb.style() == BHIDDEN) + return -1; + if (rb.style() > BHIDDEN && rb.width() > borderWidth) + borderWidth = rb.width(); + + bool allHidden = true; + for (int c = 0; c < totalCols; c++) { + const CellStruct& current = cellAt(m_gridRows - 1, c); + if (current.inColSpan || !current.hasCells()) + continue; + const BorderValue& cb = current.primaryCell()->style()->borderAfter(); // FIXME: Make this work with perpendicular and flipped cells. + // FIXME: Don't repeat for the same col group + RenderTableCol* colGroup = table()->colElement(c); + if (colGroup) { + const BorderValue& gb = colGroup->style()->borderAfter(); + if (gb.style() == BHIDDEN || cb.style() == BHIDDEN) + continue; + allHidden = false; + if (gb.style() > BHIDDEN && gb.width() > borderWidth) + borderWidth = gb.width(); + if (cb.style() > BHIDDEN && cb.width() > borderWidth) + borderWidth = cb.width(); + } else { + if (cb.style() == BHIDDEN) + continue; + allHidden = false; + if (cb.style() > BHIDDEN && cb.width() > borderWidth) + borderWidth = cb.width(); + } + } + if (allHidden) + return -1; + + return (borderWidth + 1) / 2; +} + +int RenderTableSection::calcOuterBorderStart() const +{ + int totalCols = table()->numEffCols(); + if (!m_gridRows || !totalCols) + return 0; + + unsigned borderWidth = 0; + + const BorderValue& sb = style()->borderStart(); + if (sb.style() == BHIDDEN) + return -1; + if (sb.style() > BHIDDEN) + borderWidth = sb.width(); + + if (RenderTableCol* colGroup = table()->colElement(0)) { + const BorderValue& gb = colGroup->style()->borderStart(); + if (gb.style() == BHIDDEN) + return -1; + if (gb.style() > BHIDDEN && gb.width() > borderWidth) + borderWidth = gb.width(); + } + + bool allHidden = true; + for (int r = 0; r < m_gridRows; r++) { + const CellStruct& current = cellAt(r, 0); + if (!current.hasCells()) + continue; + // FIXME: Don't repeat for the same cell + const BorderValue& cb = current.primaryCell()->style()->borderStart(); // FIXME: Make this work with perpendicular and flipped cells. + const BorderValue& rb = current.primaryCell()->parent()->style()->borderStart(); + if (cb.style() == BHIDDEN || rb.style() == BHIDDEN) + continue; + allHidden = false; + if (cb.style() > BHIDDEN && cb.width() > borderWidth) + borderWidth = cb.width(); + if (rb.style() > BHIDDEN && rb.width() > borderWidth) + borderWidth = rb.width(); + } + if (allHidden) + return -1; + + return (borderWidth + (table()->style()->isLeftToRightDirection() ? 0 : 1)) / 2; +} + +int RenderTableSection::calcOuterBorderEnd() const +{ + int totalCols = table()->numEffCols(); + if (!m_gridRows || !totalCols) + return 0; + + unsigned borderWidth = 0; + + const BorderValue& sb = style()->borderEnd(); + if (sb.style() == BHIDDEN) + return -1; + if (sb.style() > BHIDDEN) + borderWidth = sb.width(); + + if (RenderTableCol* colGroup = table()->colElement(totalCols - 1)) { + const BorderValue& gb = colGroup->style()->borderEnd(); + if (gb.style() == BHIDDEN) + return -1; + if (gb.style() > BHIDDEN && gb.width() > borderWidth) + borderWidth = gb.width(); + } + + bool allHidden = true; + for (int r = 0; r < m_gridRows; r++) { + const CellStruct& current = cellAt(r, totalCols - 1); + if (!current.hasCells()) + continue; + // FIXME: Don't repeat for the same cell + const BorderValue& cb = current.primaryCell()->style()->borderEnd(); // FIXME: Make this work with perpendicular and flipped cells. + const BorderValue& rb = current.primaryCell()->parent()->style()->borderEnd(); + if (cb.style() == BHIDDEN || rb.style() == BHIDDEN) + continue; + allHidden = false; + if (cb.style() > BHIDDEN && cb.width() > borderWidth) + borderWidth = cb.width(); + if (rb.style() > BHIDDEN && rb.width() > borderWidth) + borderWidth = rb.width(); + } + if (allHidden) + return -1; + + return (borderWidth + (table()->style()->isLeftToRightDirection() ? 1 : 0)) / 2; +} + +void RenderTableSection::recalcOuterBorder() +{ + m_outerBorderBefore = calcOuterBorderBefore(); + m_outerBorderAfter = calcOuterBorderAfter(); + m_outerBorderStart = calcOuterBorderStart(); + m_outerBorderEnd = calcOuterBorderEnd(); +} + +int RenderTableSection::firstLineBoxBaseline() const +{ + if (!m_gridRows) + return -1; + + int firstLineBaseline = m_grid[0].baseline; + if (firstLineBaseline) + return firstLineBaseline + m_rowPos[0]; + + firstLineBaseline = -1; + Row* firstRow = m_grid[0].row; + for (size_t i = 0; i < firstRow->size(); ++i) { + CellStruct& cs = firstRow->at(i); + RenderTableCell* cell = cs.primaryCell(); + if (cell) + firstLineBaseline = max(firstLineBaseline, cell->logicalTop() + cell->paddingBefore() + cell->borderBefore() + cell->contentLogicalHeight()); + } + + return firstLineBaseline; +} + +void RenderTableSection::paint(PaintInfo& paintInfo, int tx, int ty) +{ + // put this back in when all layout tests can handle it + // ASSERT(!needsLayout()); + // avoid crashing on bugs that cause us to paint with dirty layout + if (needsLayout()) + return; + + unsigned totalRows = m_gridRows; + unsigned totalCols = table()->columns().size(); + + if (!totalRows || !totalCols) + return; + + tx += x(); + ty += y(); + + PaintPhase phase = paintInfo.phase; + bool pushedClip = pushContentsClip(paintInfo, tx, ty); + paintObject(paintInfo, tx, ty); + if (pushedClip) + popContentsClip(paintInfo, phase, tx, ty); +} + +static inline bool compareCellPositions(RenderTableCell* elem1, RenderTableCell* elem2) +{ + return elem1->row() < elem2->row(); +} + +void RenderTableSection::paintCell(RenderTableCell* cell, PaintInfo& paintInfo, int tx, int ty) +{ + IntPoint cellPoint = flipForWritingMode(cell, IntPoint(tx, ty), ParentToChildFlippingAdjustment); + PaintPhase paintPhase = paintInfo.phase; + RenderTableRow* row = toRenderTableRow(cell->parent()); + + if (paintPhase == PaintPhaseBlockBackground || paintPhase == PaintPhaseChildBlockBackground) { + // We need to handle painting a stack of backgrounds. This stack (from bottom to top) consists of + // the column group, column, row group, row, and then the cell. + RenderObject* col = table()->colElement(cell->col()); + RenderObject* colGroup = 0; + if (col && col->parent()->style()->display() == TABLE_COLUMN_GROUP) + colGroup = col->parent(); + + // Column groups and columns first. + // FIXME: Columns and column groups do not currently support opacity, and they are being painted "too late" in + // the stack, since we have already opened a transparency layer (potentially) for the table row group. + // Note that we deliberately ignore whether or not the cell has a layer, since these backgrounds paint "behind" the + // cell. + cell->paintBackgroundsBehindCell(paintInfo, cellPoint.x(), cellPoint.y(), colGroup); + cell->paintBackgroundsBehindCell(paintInfo, cellPoint.x(), cellPoint.y(), col); + + // Paint the row group next. + cell->paintBackgroundsBehindCell(paintInfo, cellPoint.x(), cellPoint.y(), this); + + // Paint the row next, but only if it doesn't have a layer. If a row has a layer, it will be responsible for + // painting the row background for the cell. + if (!row->hasSelfPaintingLayer()) + cell->paintBackgroundsBehindCell(paintInfo, cellPoint.x(), cellPoint.y(), row); + } + if ((!cell->hasSelfPaintingLayer() && !row->hasSelfPaintingLayer()) || paintInfo.phase == PaintPhaseCollapsedTableBorders) + cell->paint(paintInfo, cellPoint.x(), cellPoint.y()); +} + +void RenderTableSection::paintObject(PaintInfo& paintInfo, int tx, int ty) +{ + // Check which rows and cols are visible and only paint these. + // FIXME: Could use a binary search here. + unsigned totalRows = m_gridRows; + unsigned totalCols = table()->columns().size(); + + PaintPhase paintPhase = paintInfo.phase; + +#ifdef ANDROID_LAYOUT + unsigned int startrow = 0; + unsigned int endrow = totalRows; + unsigned int startcol = 0; + unsigned int endcol = totalCols; + if (table()->isSingleColumn()) { + // FIXME: should we be smarter too? + } else { + // FIXME: possible to rollback to the common tree. + // rowPos size is set in calcRowHeight(), which is called from table layout(). + // BUT RenderTableSection is init through parsing. On a slow device, paint() as + // the result of layout() can come after the next parse() as everything is triggered + // by timer. So we have to check rowPos before using it. + if (m_rowPos.size() != (totalRows + 1)) + return; +#endif + + int os = 2 * maximalOutlineSize(paintPhase); + unsigned startrow = 0; + unsigned endrow = totalRows; + + IntRect localRepaintRect = paintInfo.rect; + localRepaintRect.move(-tx, -ty); + if (style()->isFlippedBlocksWritingMode()) { + if (style()->isHorizontalWritingMode()) + localRepaintRect.setY(height() - localRepaintRect.bottom()); + else + localRepaintRect.setX(width() - localRepaintRect.right()); + } + + // If some cell overflows, just paint all of them. + if (!m_hasOverflowingCell) { + int before = (style()->isHorizontalWritingMode() ? localRepaintRect.y() : localRepaintRect.x()) - os; + // binary search to find a row + startrow = std::lower_bound(m_rowPos.begin(), m_rowPos.end(), before) - m_rowPos.begin(); + + // The binary search above gives us the first row with + // a y position >= the top of the paint rect. Thus, the previous + // may need to be repainted as well. + if (startrow == m_rowPos.size() || (startrow > 0 && (m_rowPos[startrow] > before))) + --startrow; + + int after = (style()->isHorizontalWritingMode() ? localRepaintRect.bottom() : localRepaintRect.right()) + os; + endrow = std::lower_bound(m_rowPos.begin(), m_rowPos.end(), after) - m_rowPos.begin(); + if (endrow == m_rowPos.size()) + --endrow; + + if (!endrow && m_rowPos[0] - table()->outerBorderBefore() <= after) + ++endrow; + } + + unsigned startcol = 0; + unsigned endcol = totalCols; + // FIXME: Implement RTL. + if (!m_hasOverflowingCell && style()->isLeftToRightDirection()) { + int start = (style()->isHorizontalWritingMode() ? localRepaintRect.x() : localRepaintRect.y()) - os; + Vector<int>& columnPos = table()->columnPositions(); + startcol = std::lower_bound(columnPos.begin(), columnPos.end(), start) - columnPos.begin(); + if ((startcol == columnPos.size()) || (startcol > 0 && (columnPos[startcol] > start))) + --startcol; + + int end = (style()->isHorizontalWritingMode() ? localRepaintRect.right() : localRepaintRect.bottom()) + os; + endcol = std::lower_bound(columnPos.begin(), columnPos.end(), end) - columnPos.begin(); + if (endcol == columnPos.size()) + --endcol; + + if (!endcol && columnPos[0] - table()->outerBorderStart() <= end) + ++endcol; + } + +#ifdef ANDROID_LAYOUT + } +#endif + + if (startcol < endcol) { + if (!m_hasMultipleCellLevels) { + // Draw the dirty cells in the order that they appear. + for (unsigned r = startrow; r < endrow; r++) { + for (unsigned c = startcol; c < endcol; c++) { + CellStruct& current = cellAt(r, c); + RenderTableCell* cell = current.primaryCell(); + if (!cell || (r > startrow && primaryCellAt(r - 1, c) == cell) || (c > startcol && primaryCellAt(r, c - 1) == cell)) + continue; + paintCell(cell, paintInfo, tx, ty); + } + } + } else { + // Draw the cells in the correct paint order. + Vector<RenderTableCell*> cells; + HashSet<RenderTableCell*> spanningCells; + for (unsigned r = startrow; r < endrow; r++) { + for (unsigned c = startcol; c < endcol; c++) { + CellStruct& current = cellAt(r, c); + if (!current.hasCells()) + continue; + for (unsigned i = 0; i < current.cells.size(); ++i) { + if (current.cells[i]->rowSpan() > 1 || current.cells[i]->colSpan() > 1) { + if (spanningCells.contains(current.cells[i])) + continue; + spanningCells.add(current.cells[i]); + } + cells.append(current.cells[i]); + } + } + } + // Sort the dirty cells by paint order. + std::stable_sort(cells.begin(), cells.end(), compareCellPositions); + int size = cells.size(); + // Paint the cells. + for (int i = 0; i < size; ++i) + paintCell(cells[i], paintInfo, tx, ty); + } + } +} + +void RenderTableSection::imageChanged(WrappedImagePtr, const IntRect*) +{ + // FIXME: Examine cells and repaint only the rect the image paints in. + repaint(); +} + +void RenderTableSection::recalcCells() +{ + m_cCol = 0; + m_cRow = -1; + clearGrid(); + m_gridRows = 0; + + for (RenderObject* row = firstChild(); row; row = row->nextSibling()) { + if (row->isTableRow()) { + m_cRow++; + m_cCol = 0; + if (!ensureRows(m_cRow + 1)) + break; + + RenderTableRow* tableRow = toRenderTableRow(row); + m_grid[m_cRow].rowRenderer = tableRow; + setRowLogicalHeightToRowStyleLogicalHeightIfNotRelative(&m_grid[m_cRow]); + + for (RenderObject* cell = row->firstChild(); cell; cell = cell->nextSibling()) { + if (cell->isTableCell()) + addCell(toRenderTableCell(cell), tableRow); + } + } + } + m_needsCellRecalc = false; + setNeedsLayout(true); +} + +void RenderTableSection::clearGrid() +{ + int rows = m_gridRows; + while (rows--) + delete m_grid[rows].row; +} + +int RenderTableSection::numColumns() const +{ + int result = 0; + + for (int r = 0; r < m_gridRows; ++r) { + for (int c = result; c < table()->numEffCols(); ++c) { + const CellStruct& cell = cellAt(r, c); + if (cell.hasCells() || cell.inColSpan) + result = c; + } + } + + return result + 1; +} + +void RenderTableSection::appendColumn(int pos) +{ + for (int row = 0; row < m_gridRows; ++row) + m_grid[row].row->resize(pos + 1); +} + +void RenderTableSection::splitColumn(int pos, int first) +{ + if (m_cCol > pos) + m_cCol++; + for (int row = 0; row < m_gridRows; ++row) { + Row& r = *m_grid[row].row; + r.insert(pos + 1, CellStruct()); + if (r[pos].hasCells()) { + r[pos + 1].cells.append(r[pos].cells); + RenderTableCell* cell = r[pos].primaryCell(); + ASSERT(cell); + int colleft = cell->colSpan() - r[pos].inColSpan; + if (first > colleft) + r[pos + 1].inColSpan = 0; + else + r[pos + 1].inColSpan = first + r[pos].inColSpan; + } else { + r[pos + 1].inColSpan = 0; + } + } +} + +// Hit Testing +bool RenderTableSection::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int xPos, int yPos, int tx, int ty, HitTestAction action) +{ + // If we have no children then we have nothing to do. + if (!firstChild()) + return false; + + // Table sections cannot ever be hit tested. Effectively they do not exist. + // Just forward to our children always. + tx += x(); + ty += y(); + + if (hasOverflowClip() && !overflowClipRect(tx, ty).intersects(result.rectForPoint(xPos, yPos))) + return false; + + if (m_hasOverflowingCell) { + for (RenderObject* child = lastChild(); child; child = child->previousSibling()) { + // FIXME: We have to skip over inline flows, since they can show up inside table rows + // at the moment (a demoted inline <form> for example). If we ever implement a + // table-specific hit-test method (which we should do for performance reasons anyway), + // then we can remove this check. + if (child->isBox() && !toRenderBox(child)->hasSelfPaintingLayer()) { + 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; + } + } + } + return false; + } + + IntPoint location = IntPoint(xPos - tx, yPos - ty); + if (style()->isFlippedBlocksWritingMode()) { + if (style()->isHorizontalWritingMode()) + location.setY(height() - location.y()); + else + location.setX(width() - location.x()); + } + + int offsetInColumnDirection = style()->isHorizontalWritingMode() ? location.y() : location.x(); + // Find the first row that starts after offsetInColumnDirection. + unsigned nextRow = std::upper_bound(m_rowPos.begin(), m_rowPos.end(), offsetInColumnDirection) - m_rowPos.begin(); + if (nextRow == m_rowPos.size()) + return false; + // Now set hitRow to the index of the hit row, or 0. + unsigned hitRow = nextRow > 0 ? nextRow - 1 : 0; + + Vector<int>& columnPos = table()->columnPositions(); + int offsetInRowDirection = style()->isHorizontalWritingMode() ? location.x() : location.y(); + if (!style()->isLeftToRightDirection()) + offsetInRowDirection = columnPos[columnPos.size() - 1] - offsetInRowDirection; + + unsigned nextColumn = std::lower_bound(columnPos.begin(), columnPos.end(), offsetInRowDirection) - columnPos.begin(); + if (nextColumn == columnPos.size()) + return false; + unsigned hitColumn = nextColumn > 0 ? nextColumn - 1 : 0; + + CellStruct& current = cellAt(hitRow, hitColumn); + + // If the cell is empty, there's nothing to do + if (!current.hasCells()) + return false; + + for (int i = current.cells.size() - 1; i >= 0; --i) { + RenderTableCell* cell = current.cells[i]; + IntPoint cellPoint = flipForWritingMode(cell, IntPoint(tx, ty), ParentToChildFlippingAdjustment); + if (static_cast<RenderObject*>(cell)->nodeAtPoint(request, result, xPos, yPos, cellPoint.x(), cellPoint.y(), action)) { + updateHitTestResult(result, IntPoint(xPos - cellPoint.x(), yPos - cellPoint.y())); + return true; + } + } + return false; + +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderTableSection.h b/Source/WebCore/rendering/RenderTableSection.h new file mode 100644 index 0000000..fac6a84 --- /dev/null +++ b/Source/WebCore/rendering/RenderTableSection.h @@ -0,0 +1,189 @@ +/* + * 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, 2009 Apple Inc. All rights reserved. + * + * 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. + */ + +#ifndef RenderTableSection_h +#define RenderTableSection_h + +#include "RenderTable.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class RenderTableCell; +class RenderTableRow; + +class RenderTableSection : public RenderBox { +public: + RenderTableSection(Node*); + virtual ~RenderTableSection(); + + const RenderObjectChildList* children() const { return &m_children; } + RenderObjectChildList* children() { return &m_children; } + + virtual void addChild(RenderObject* child, RenderObject* beforeChild = 0); + + virtual int firstLineBoxBaseline() const; + + void addCell(RenderTableCell*, RenderTableRow* row); + + void setCellLogicalWidths(); + int calcRowLogicalHeight(); + int layoutRows(int logicalHeight); + + RenderTable* table() const { return toRenderTable(parent()); } + + struct CellStruct { + Vector<RenderTableCell*, 1> cells; + bool inColSpan; // true for columns after the first in a colspan + + CellStruct(): + inColSpan(false) {} + + RenderTableCell* primaryCell() + { + return hasCells() ? cells[cells.size() - 1] : 0; + } + + const RenderTableCell* primaryCell() const + { + return hasCells() ? cells[cells.size() - 1] : 0; + } + + bool hasCells() const { return cells.size() > 0; } + }; + + typedef Vector<CellStruct> Row; + + struct RowStruct { + Row* row; + RenderTableRow* rowRenderer; + int baseline; + Length logicalHeight; + }; + + CellStruct& cellAt(int row, int col) { return (*m_grid[row].row)[col]; } + const CellStruct& cellAt(int row, int col) const { return (*m_grid[row].row)[col]; } + RenderTableCell* primaryCellAt(int row, int col) + { + CellStruct& c = (*m_grid[row].row)[col]; + return c.primaryCell(); + } + + void appendColumn(int pos); + void splitColumn(int pos, int first); + + int calcOuterBorderBefore() const; + int calcOuterBorderAfter() const; + int calcOuterBorderStart() const; + int calcOuterBorderEnd() const; + void recalcOuterBorder(); + + int outerBorderBefore() const { return m_outerBorderBefore; } + int outerBorderAfter() const { return m_outerBorderAfter; } + int outerBorderStart() const { return m_outerBorderStart; } + int outerBorderEnd() const { return m_outerBorderEnd; } + + int numRows() const { return m_gridRows; } + int numColumns() const; + void recalcCells(); + void recalcCellsIfNeeded() + { + if (m_needsCellRecalc) + recalcCells(); + } + + bool needsCellRecalc() const { return m_needsCellRecalc; } + void setNeedsCellRecalc() + { + m_needsCellRecalc = true; + table()->setNeedsSectionRecalc(); + } + + int getBaseline(int row) { return m_grid[row].baseline; } + +private: + virtual RenderObjectChildList* virtualChildren() { return children(); } + virtual const RenderObjectChildList* virtualChildren() const { return children(); } + + virtual const char* renderName() const { return isAnonymous() ? "RenderTableSection (anonymous)" : "RenderTableSection"; } + + virtual bool isTableSection() const { return true; } + + virtual void destroy(); + + virtual void layout(); + + virtual void removeChild(RenderObject* oldChild); + + virtual void paint(PaintInfo&, int tx, int ty); + virtual void paintCell(RenderTableCell*, PaintInfo&, int tx, int ty); + virtual void paintObject(PaintInfo&, int tx, int ty); + + virtual void imageChanged(WrappedImagePtr, const IntRect* = 0); + + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + + bool ensureRows(int); + void clearGrid(); + + RenderObjectChildList m_children; + + Vector<RowStruct> m_grid; + Vector<int> m_rowPos; + + int m_gridRows; + + // the current insertion position + int m_cCol; + int m_cRow; + + int m_outerBorderStart; + int m_outerBorderEnd; + int m_outerBorderBefore; + int m_outerBorderAfter; + + bool m_needsCellRecalc; + bool m_hasOverflowingCell; + + bool m_hasMultipleCellLevels; +}; + +inline RenderTableSection* toRenderTableSection(RenderObject* object) +{ + ASSERT(!object || object->isTableSection()); + return static_cast<RenderTableSection*>(object); +} + +inline const RenderTableSection* toRenderTableSection(const RenderObject* object) +{ + ASSERT(!object || object->isTableSection()); + return static_cast<const RenderTableSection*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderTableSection(const RenderTableSection*); + +} // namespace WebCore + +#endif // RenderTableSection_h diff --git a/Source/WebCore/rendering/RenderText.cpp b/Source/WebCore/rendering/RenderText.cpp new file mode 100644 index 0000000..78c5684 --- /dev/null +++ b/Source/WebCore/rendering/RenderText.cpp @@ -0,0 +1,1539 @@ +/* + * (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net) + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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 "RenderText.h" + +#include "AXObjectCache.h" +#include "CharacterNames.h" +#include "EllipsisBox.h" +#include "FloatQuad.h" +#include "FontTranscoder.h" +#include "FrameView.h" +#include "InlineTextBox.h" +#include "Range.h" +#include "RenderArena.h" +#include "RenderBlock.h" +#include "RenderLayer.h" +#include "RenderView.h" +#include "Text.h" +#include "TextBreakIterator.h" +#include "TextResourceDecoder.h" +#include "VisiblePosition.h" +#include "break_lines.h" +#include <wtf/AlwaysInline.h> +#include <wtf/text/StringBuffer.h> + +using namespace std; +using namespace WTF; +using namespace Unicode; + +namespace WebCore { + +static void makeCapitalized(String* string, UChar previous) +{ + if (string->isNull()) + return; + + unsigned length = string->length(); + const UChar* characters = string->characters(); + + if (length >= numeric_limits<unsigned>::max()) + CRASH(); + + StringBuffer stringWithPrevious(length + 1); + stringWithPrevious[0] = previous == noBreakSpace ? ' ' : previous; + for (unsigned i = 1; i < length + 1; i++) { + // Replace   with a real space since ICU no longer treats   as a word separator. + if (characters[i - 1] == noBreakSpace) + stringWithPrevious[i] = ' '; + else + stringWithPrevious[i] = characters[i - 1]; + } + + TextBreakIterator* boundary = wordBreakIterator(stringWithPrevious.characters(), length + 1); + if (!boundary) + return; + + StringBuffer data(length); + + int32_t endOfWord; + int32_t startOfWord = textBreakFirst(boundary); + for (endOfWord = textBreakNext(boundary); endOfWord != TextBreakDone; startOfWord = endOfWord, endOfWord = textBreakNext(boundary)) { + if (startOfWord != 0) // Ignore first char of previous string + data[startOfWord - 1] = characters[startOfWord - 1] == noBreakSpace ? noBreakSpace : toTitleCase(stringWithPrevious[startOfWord]); + for (int i = startOfWord + 1; i < endOfWord; i++) + data[i - 1] = characters[i - 1]; + } + + *string = String::adopt(data); +} + +RenderText::RenderText(Node* node, PassRefPtr<StringImpl> str) + : RenderObject(node) + , m_minWidth(-1) + , m_text(str) + , m_firstTextBox(0) + , m_lastTextBox(0) + , m_maxWidth(-1) + , m_beginMinWidth(0) + , m_endMinWidth(0) + , m_hasTab(false) + , m_linesDirty(false) + , m_containsReversedText(false) + , m_isAllASCII(m_text.containsOnlyASCII()) + , m_knownToHaveNoOverflowAndNoFallbackFonts(false) + , m_needsTranscoding(false) +{ + ASSERT(m_text); + + setIsText(); + + // FIXME: It would be better to call this only if !m_text->containsOnlyWhitespace(). + // But that might slow things down, and maybe should only be done if visuallyNonEmpty + // is still false. Not making any change for now, but should consider in the future. + view()->frameView()->setIsVisuallyNonEmpty(); +} + +#ifndef NDEBUG + +RenderText::~RenderText() +{ + ASSERT(!m_firstTextBox); + ASSERT(!m_lastTextBox); +} + +#endif + +const char* RenderText::renderName() const +{ + return "RenderText"; +} + +bool RenderText::isTextFragment() const +{ + return false; +} + +bool RenderText::isWordBreak() const +{ + return false; +} + +void RenderText::updateNeedsTranscoding() +{ + const TextEncoding* encoding = document()->decoder() ? &document()->decoder()->encoding() : 0; + m_needsTranscoding = fontTranscoder().needsTranscoding(style()->font().fontDescription(), encoding); +} + +void RenderText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + // There is no need to ever schedule repaints from a style change of a text run, since + // we already did this for the parent of the text run. + // We do have to schedule layouts, though, since a style change can force us to + // need to relayout. + if (diff == StyleDifferenceLayout) { + setNeedsLayoutAndPrefWidthsRecalc(); + m_knownToHaveNoOverflowAndNoFallbackFonts = false; + } + + bool needsResetText = false; + if (!oldStyle) { + updateNeedsTranscoding(); + needsResetText = m_needsTranscoding; + } else if (oldStyle->font().needsTranscoding() != style()->font().needsTranscoding() || (style()->font().needsTranscoding() && oldStyle->font().family().family() != style()->font().family().family())) { + updateNeedsTranscoding(); + needsResetText = true; + } + + ETextTransform oldTransform = oldStyle ? oldStyle->textTransform() : TTNONE; + ETextSecurity oldSecurity = oldStyle ? oldStyle->textSecurity() : TSNONE; + if (needsResetText || oldTransform != style()->textTransform() || oldSecurity != style()->textSecurity()) { + if (RefPtr<StringImpl> textToTransform = originalText()) + setText(textToTransform.release(), true); + } +} + +void RenderText::destroy() +{ + if (!documentBeingDestroyed()) { + if (firstTextBox()) { + if (isBR()) { + RootInlineBox* next = firstTextBox()->root()->nextRootBox(); + if (next) + next->markDirty(); + } + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) + box->remove(); + } else if (parent()) + parent()->dirtyLinesFromChangedChild(this); + } + deleteTextBoxes(); + RenderObject::destroy(); +} + +void RenderText::extractTextBox(InlineTextBox* box) +{ + checkConsistency(); + + m_lastTextBox = box->prevTextBox(); + if (box == m_firstTextBox) + m_firstTextBox = 0; + if (box->prevTextBox()) + box->prevTextBox()->setNextTextBox(0); + box->setPreviousTextBox(0); + for (InlineTextBox* curr = box; curr; curr = curr->nextTextBox()) + curr->setExtracted(); + + checkConsistency(); +} + +void RenderText::attachTextBox(InlineTextBox* box) +{ + checkConsistency(); + + if (m_lastTextBox) { + m_lastTextBox->setNextTextBox(box); + box->setPreviousTextBox(m_lastTextBox); + } else + m_firstTextBox = box; + InlineTextBox* last = box; + for (InlineTextBox* curr = box; curr; curr = curr->nextTextBox()) { + curr->setExtracted(false); + last = curr; + } + m_lastTextBox = last; + + checkConsistency(); +} + +void RenderText::removeTextBox(InlineTextBox* box) +{ + checkConsistency(); + + if (box == m_firstTextBox) + m_firstTextBox = box->nextTextBox(); + if (box == m_lastTextBox) + m_lastTextBox = box->prevTextBox(); + if (box->nextTextBox()) + box->nextTextBox()->setPreviousTextBox(box->prevTextBox()); + if (box->prevTextBox()) + box->prevTextBox()->setNextTextBox(box->nextTextBox()); + + checkConsistency(); +} + +void RenderText::deleteTextBoxes() +{ + if (firstTextBox()) { + RenderArena* arena = renderArena(); + InlineTextBox* next; + for (InlineTextBox* curr = firstTextBox(); curr; curr = next) { + next = curr->nextTextBox(); + curr->destroy(arena); + } + m_firstTextBox = m_lastTextBox = 0; + } +} + +PassRefPtr<StringImpl> RenderText::originalText() const +{ + Node* e = node(); + return (e && e->isTextNode()) ? static_cast<Text*>(e)->dataImpl() : 0; +} + +void RenderText::absoluteRects(Vector<IntRect>& rects, int tx, int ty) +{ + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) + rects.append(IntRect(tx + box->x(), ty + box->y(), box->logicalWidth(), box->logicalHeight())); +} + +void RenderText::absoluteRectsForRange(Vector<IntRect>& rects, unsigned start, unsigned end, bool useSelectionHeight) +{ + // Work around signed/unsigned issues. This function takes unsigneds, and is often passed UINT_MAX + // to mean "all the way to the end". InlineTextBox coordinates are unsigneds, so changing this + // function to take ints causes various internal mismatches. But selectionRect takes ints, and + // passing UINT_MAX to it causes trouble. Ideally we'd change selectionRect to take unsigneds, but + // that would cause many ripple effects, so for now we'll just clamp our unsigned parameters to INT_MAX. + ASSERT(end == UINT_MAX || end <= INT_MAX); + ASSERT(start <= INT_MAX); + start = min(start, static_cast<unsigned>(INT_MAX)); + end = min(end, static_cast<unsigned>(INT_MAX)); + + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { + // Note: box->end() returns the index of the last character, not the index past it + if (start <= box->start() && box->end() < end) { + IntRect r = IntRect(box->x(), box->y(), box->logicalWidth(), box->logicalHeight()); + if (useSelectionHeight) { + IntRect selectionRect = box->selectionRect(0, 0, start, end); + r.setHeight(selectionRect.height()); + r.setY(selectionRect.y()); + } + FloatPoint origin = localToAbsolute(r.location()); + r.setX(origin.x()); + r.setY(origin.y()); + rects.append(r); + } else { + unsigned realEnd = min(box->end() + 1, end); + IntRect r = box->selectionRect(0, 0, start, realEnd); + if (!r.isEmpty()) { + if (!useSelectionHeight) { + // change the height and y position because selectionRect uses selection-specific values + r.setHeight(box->logicalHeight()); + r.setY(box->y()); + } + FloatPoint origin = localToAbsolute(r.location()); + localToAbsolute(origin); + r.setX(origin.x()); + r.setY(origin.y()); + rects.append(r); + } + } + } +} + +static IntRect ellipsisRectForBox(InlineTextBox* box, unsigned startPos, unsigned endPos) +{ + if (!box) + return IntRect(); + + unsigned short truncation = box->truncation(); + if (truncation == cNoTruncation) + return IntRect(); + + IntRect rect; + if (EllipsisBox* ellipsis = box->root()->ellipsisBox()) { + int ellipsisStartPosition = max<int>(startPos - box->start(), 0); + int ellipsisEndPosition = min<int>(endPos - box->start(), box->len()); + + // The ellipsis should be considered to be selected if the end of + // the selection is past the beginning of the truncation and the + // beginning of the selection is before or at the beginning of the truncation. + if (ellipsisEndPosition >= truncation && ellipsisStartPosition <= truncation) + return ellipsis->selectionRect(0, 0); + } + + return IntRect(); +} + +void RenderText::absoluteQuads(Vector<FloatQuad>& quads, ClippingOption option) +{ + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { + IntRect boundaries = box->calculateBoundaries(); + + // Shorten the width of this text box if it ends in an ellipsis. + IntRect ellipsisRect = (option == ClipToEllipsis) ? ellipsisRectForBox(box, 0, textLength()) : IntRect(); + if (!ellipsisRect.isEmpty()) + boundaries.setWidth(ellipsisRect.right() - boundaries.x()); + quads.append(localToAbsoluteQuad(FloatRect(boundaries))); + } +} + +void RenderText::absoluteQuads(Vector<FloatQuad>& quads) +{ + absoluteQuads(quads, NoClipping); +} + +void RenderText::absoluteQuadsForRange(Vector<FloatQuad>& quads, unsigned start, unsigned end, bool useSelectionHeight) +{ + // Work around signed/unsigned issues. This function takes unsigneds, and is often passed UINT_MAX + // to mean "all the way to the end". InlineTextBox coordinates are unsigneds, so changing this + // function to take ints causes various internal mismatches. But selectionRect takes ints, and + // passing UINT_MAX to it causes trouble. Ideally we'd change selectionRect to take unsigneds, but + // that would cause many ripple effects, so for now we'll just clamp our unsigned parameters to INT_MAX. + ASSERT(end == UINT_MAX || end <= INT_MAX); + ASSERT(start <= INT_MAX); + start = min(start, static_cast<unsigned>(INT_MAX)); + end = min(end, static_cast<unsigned>(INT_MAX)); + + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { + // Note: box->end() returns the index of the last character, not the index past it + if (start <= box->start() && box->end() < end) { + IntRect r(box->calculateBoundaries()); + if (useSelectionHeight) { + IntRect selectionRect = box->selectionRect(0, 0, start, end); + r.setHeight(selectionRect.height()); + r.setY(selectionRect.y()); + } + quads.append(localToAbsoluteQuad(FloatRect(r))); + } else { + unsigned realEnd = min(box->end() + 1, end); + IntRect r = box->selectionRect(0, 0, start, realEnd); + if (r.height()) { + if (!useSelectionHeight) { + // change the height and y position because selectionRect uses selection-specific values + r.setHeight(box->logicalHeight()); + r.setY(box->y()); + } + quads.append(localToAbsoluteQuad(FloatRect(r))); + } + } + } +} + +InlineTextBox* RenderText::findNextInlineTextBox(int offset, int& pos) const +{ + // The text runs point to parts of the RenderText's m_text + // (they don't include '\n') + // Find the text run that includes the character at offset + // and return pos, which is the position of the char in the run. + + if (!m_firstTextBox) + return 0; + + InlineTextBox* s = m_firstTextBox; + int off = s->len(); + while (offset > off && s->nextTextBox()) { + s = s->nextTextBox(); + off = s->start() + s->len(); + } + // we are now in the correct text run + pos = (offset > off ? s->len() : s->len() - (off - offset) ); + return s; +} + +VisiblePosition RenderText::positionForPoint(const IntPoint& point) +{ + if (!firstTextBox() || textLength() == 0) + return createVisiblePosition(0, DOWNSTREAM); + + // Get the offset for the position, since this will take rtl text into account. + int offset; + + int pointLineDirection = firstTextBox()->isHorizontal() ? point.x() : point.y(); + int pointBlockDirection = firstTextBox()->isHorizontal() ? point.y() : point.x(); + + // FIXME: We should be able to roll these special cases into the general cases in the loop below. + if (firstTextBox() && pointBlockDirection < firstTextBox()->root()->selectionBottom() && pointLineDirection < firstTextBox()->logicalLeft()) { + // at the y coordinate of the first line or above + // and the x coordinate is to the left of the first text box left edge + offset = firstTextBox()->offsetForPosition(pointLineDirection); + return createVisiblePosition(offset + firstTextBox()->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM); + } + if (lastTextBox() && pointBlockDirection >= lastTextBox()->root()->selectionTop() && pointLineDirection >= lastTextBox()->logicalRight()) { + // at the y coordinate of the last line or below + // and the x coordinate is to the right of the last text box right edge + offset = lastTextBox()->offsetForPosition(pointLineDirection); + return createVisiblePosition(offset + lastTextBox()->start(), VP_UPSTREAM_IF_POSSIBLE); + } + + InlineTextBox* lastBoxAbove = 0; + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { + RootInlineBox* rootBox = box->root(); + if (pointBlockDirection >= rootBox->selectionTop()) { + int bottom = rootBox->selectionBottom(); + if (rootBox->nextRootBox()) + bottom = min(bottom, rootBox->nextRootBox()->lineTop()); + if (pointBlockDirection < bottom) { + offset = box->offsetForPosition(pointLineDirection); + + if (pointLineDirection == box->logicalLeft()) + // the x coordinate is equal to the left edge of this box + // the affinity must be downstream so the position doesn't jump back to the previous line + return createVisiblePosition(offset + box->start(), DOWNSTREAM); + + if (pointLineDirection < box->logicalRight()) + // and the x coordinate is to the left of the right edge of this box + // check to see if position goes in this box + return createVisiblePosition(offset + box->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM); + + if (!box->prevOnLine() && pointLineDirection < box->logicalLeft()) + // box is first on line + // and the x coordinate is to the left of the first text box left edge + return createVisiblePosition(offset + box->start(), DOWNSTREAM); + + if (!box->nextOnLine()) + // box is last on line + // and the x coordinate is to the right of the last text box right edge + // generate VisiblePosition, use UPSTREAM affinity if possible + return createVisiblePosition(offset + box->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM); + } + lastBoxAbove = box; + } + } + + return createVisiblePosition(lastBoxAbove ? lastBoxAbove->start() + lastBoxAbove->len() : 0, DOWNSTREAM); +} + +IntRect RenderText::localCaretRect(InlineBox* inlineBox, int caretOffset, int* extraWidthToEndOfLine) +{ + if (!inlineBox) + return IntRect(); + + ASSERT(inlineBox->isInlineTextBox()); + if (!inlineBox->isInlineTextBox()) + return IntRect(); + + InlineTextBox* box = static_cast<InlineTextBox*>(inlineBox); + + int height = box->root()->selectionHeight(); + int top = box->root()->selectionTop(); + + int left = box->positionForOffset(caretOffset); + + // Distribute the caret's width to either side of the offset. + int caretWidthLeftOfOffset = caretWidth / 2; + left -= caretWidthLeftOfOffset; + int caretWidthRightOfOffset = caretWidth - caretWidthLeftOfOffset; + + int rootLeft = box->root()->logicalLeft(); + int rootRight = rootLeft + box->root()->logicalWidth(); + // FIXME: should we use the width of the root inline box or the + // width of the containing block for this? + if (extraWidthToEndOfLine) + *extraWidthToEndOfLine = (box->root()->logicalWidth() + rootLeft) - (left + 1); + + RenderBlock* cb = containingBlock(); + RenderStyle* cbStyle = cb->style(); + int leftEdge; + int rightEdge; + if (style()->autoWrap()) { + leftEdge = cb->logicalLeft(); + rightEdge = cb->logicalRight(); + } else { + leftEdge = min(cb->logicalLeft(), rootLeft); + rightEdge = max(cb->logicalRight(), rootRight); + } + + bool rightAligned = false; + switch (cbStyle->textAlign()) { + case TAAUTO: + case JUSTIFY: + rightAligned = !cbStyle->isLeftToRightDirection(); + break; + case RIGHT: + case WEBKIT_RIGHT: + rightAligned = true; + break; + case LEFT: + case WEBKIT_LEFT: + case CENTER: + case WEBKIT_CENTER: + break; + } + + if (rightAligned) { + left = max(left, leftEdge); + left = min(left, rootRight - caretWidth); + } else { + left = min(left, rightEdge - caretWidthRightOfOffset); + left = max(left, rootLeft); + } + + return style()->isHorizontalWritingMode() ? IntRect(left, top, caretWidth, height) : IntRect(top, left, height, caretWidth); +} + +ALWAYS_INLINE int RenderText::widthFromCache(const Font& f, int start, int len, int xPos, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* glyphOverflow) const +{ + if (f.isFixedPitch() && !f.isSmallCaps() && m_isAllASCII) { + int monospaceCharacterWidth = f.spaceWidth(); + int tabWidth = allowTabs() ? monospaceCharacterWidth * 8 : 0; + int w = 0; + bool isSpace; + bool previousCharWasSpace = true; // FIXME: Preserves historical behavior, but seems wrong for start > 0. + ASSERT(m_text); + StringImpl& text = *m_text.impl(); + for (int i = start; i < start + len; i++) { + char c = text[i]; + if (c <= ' ') { + if (c == ' ' || c == '\n') { + w += monospaceCharacterWidth; + isSpace = true; + } else if (c == '\t') { + w += tabWidth ? tabWidth - ((xPos + w) % tabWidth) : monospaceCharacterWidth; + isSpace = true; + } else + isSpace = false; + } else { + w += monospaceCharacterWidth; + isSpace = false; + } + if (isSpace && !previousCharWasSpace) + w += f.wordSpacing(); + previousCharWasSpace = isSpace; + } + return w; + } + + return f.width(TextRun(text()->characters() + start, len, allowTabs(), xPos), fallbackFonts, glyphOverflow); +} + +void RenderText::trimmedPrefWidths(int leadWidth, + int& beginMinW, bool& beginWS, + int& endMinW, bool& endWS, + bool& hasBreakableChar, bool& hasBreak, + int& beginMaxW, int& endMaxW, + int& minW, int& maxW, bool& stripFrontSpaces) +{ + bool collapseWhiteSpace = style()->collapseWhiteSpace(); + if (!collapseWhiteSpace) + stripFrontSpaces = false; + + if (m_hasTab || preferredLogicalWidthsDirty()) + computePreferredLogicalWidths(leadWidth); + + beginWS = !stripFrontSpaces && m_hasBeginWS; + endWS = m_hasEndWS; + + int len = textLength(); + + if (!len || (stripFrontSpaces && text()->containsOnlyWhitespace())) { + beginMinW = 0; + endMinW = 0; + beginMaxW = 0; + endMaxW = 0; + minW = 0; + maxW = 0; + hasBreak = false; + return; + } + + minW = m_minWidth; + maxW = m_maxWidth; + + beginMinW = m_beginMinWidth; + endMinW = m_endMinWidth; + + hasBreakableChar = m_hasBreakableChar; + hasBreak = m_hasBreak; + + ASSERT(m_text); + StringImpl& text = *m_text.impl(); + if (text[0] == ' ' || (text[0] == '\n' && !style()->preserveNewline()) || text[0] == '\t') { + const Font& f = style()->font(); // FIXME: This ignores first-line. + if (stripFrontSpaces) { + const UChar space = ' '; + int spaceWidth = f.width(TextRun(&space, 1)); + maxW -= spaceWidth; + } else + maxW += f.wordSpacing(); + } + + stripFrontSpaces = collapseWhiteSpace && m_hasEndWS; + + if (!style()->autoWrap() || minW > maxW) + minW = maxW; + + // Compute our max widths by scanning the string for newlines. + if (hasBreak) { + const Font& f = style()->font(); // FIXME: This ignores first-line. + bool firstLine = true; + beginMaxW = maxW; + endMaxW = maxW; + for (int i = 0; i < len; i++) { + int linelen = 0; + while (i + linelen < len && text[i + linelen] != '\n') + linelen++; + + if (linelen) { + endMaxW = widthFromCache(f, i, linelen, leadWidth + endMaxW, 0, 0); + if (firstLine) { + firstLine = false; + leadWidth = 0; + beginMaxW = endMaxW; + } + i += linelen; + } else if (firstLine) { + beginMaxW = 0; + firstLine = false; + leadWidth = 0; + } + + if (i == len - 1) + // A <pre> run that ends with a newline, as in, e.g., + // <pre>Some text\n\n<span>More text</pre> + endMaxW = 0; + } + } +} + +static inline bool isSpaceAccordingToStyle(UChar c, RenderStyle* style) +{ + return c == ' ' || (c == noBreakSpace && style->nbspMode() == SPACE); +} + +int RenderText::minPreferredLogicalWidth() const +{ + if (preferredLogicalWidthsDirty()) + const_cast<RenderText*>(this)->computePreferredLogicalWidths(0); + + return m_minWidth; +} + +int RenderText::maxPreferredLogicalWidth() const +{ + if (preferredLogicalWidthsDirty()) + const_cast<RenderText*>(this)->computePreferredLogicalWidths(0); + + return m_maxWidth; +} + +void RenderText::computePreferredLogicalWidths(int leadWidth) +{ + HashSet<const SimpleFontData*> fallbackFonts; + GlyphOverflow glyphOverflow; + computePreferredLogicalWidths(leadWidth, fallbackFonts, glyphOverflow); + if (fallbackFonts.isEmpty() && !glyphOverflow.left && !glyphOverflow.right && !glyphOverflow.top && !glyphOverflow.bottom) + m_knownToHaveNoOverflowAndNoFallbackFonts = true; +} + +void RenderText::computePreferredLogicalWidths(int leadWidth, HashSet<const SimpleFontData*>& fallbackFonts, GlyphOverflow& glyphOverflow) +{ + ASSERT(m_hasTab || preferredLogicalWidthsDirty() || !m_knownToHaveNoOverflowAndNoFallbackFonts); + + m_minWidth = 0; + m_beginMinWidth = 0; + m_endMinWidth = 0; + m_maxWidth = 0; + + if (isBR()) + return; + + int currMinWidth = 0; + int currMaxWidth = 0; + m_hasBreakableChar = false; + m_hasBreak = false; + m_hasTab = false; + m_hasBeginWS = false; + m_hasEndWS = false; + + const Font& f = style()->font(); // FIXME: This ignores first-line. + int wordSpacing = style()->wordSpacing(); + int len = textLength(); + const UChar* txt = characters(); + bool needsWordSpacing = false; + bool ignoringSpaces = false; + bool isSpace = false; + bool firstWord = true; + bool firstLine = true; + int nextBreakable = -1; + int lastWordBoundary = 0; + + int firstGlyphLeftOverflow = -1; + + bool breakNBSP = style()->autoWrap() && style()->nbspMode() == SPACE; + bool breakAll = (style()->wordBreak() == BreakAllWordBreak || style()->wordBreak() == BreakWordBreak) && style()->autoWrap(); + + for (int i = 0; i < len; i++) { + UChar c = txt[i]; + + bool previousCharacterIsSpace = isSpace; + + bool isNewline = false; + if (c == '\n') { + if (style()->preserveNewline()) { + m_hasBreak = true; + isNewline = true; + isSpace = false; + } else + isSpace = true; + } else if (c == '\t') { + if (!style()->collapseWhiteSpace()) { + m_hasTab = true; + isSpace = false; + } else + isSpace = true; + } else + isSpace = c == ' '; + + if ((isSpace || isNewline) && !i) + m_hasBeginWS = true; + if ((isSpace || isNewline) && i == len - 1) + m_hasEndWS = true; + + if (!ignoringSpaces && style()->collapseWhiteSpace() && previousCharacterIsSpace && isSpace) + ignoringSpaces = true; + + if (ignoringSpaces && !isSpace) + ignoringSpaces = false; + + // Ignore spaces and soft hyphens + if (ignoringSpaces) { + ASSERT(lastWordBoundary == i); + lastWordBoundary++; + continue; + } else if (c == softHyphen) { + currMaxWidth += widthFromCache(f, lastWordBoundary, i - lastWordBoundary, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow); + if (firstGlyphLeftOverflow < 0) + firstGlyphLeftOverflow = glyphOverflow.left; + lastWordBoundary = i + 1; + continue; + } + + bool hasBreak = breakAll || isBreakable(txt, i, len, nextBreakable, breakNBSP); + bool betweenWords = true; + int j = i; + while (c != '\n' && !isSpaceAccordingToStyle(c, style()) && c != '\t' && c != softHyphen) { + j++; + if (j == len) + break; + c = txt[j]; + if (isBreakable(txt, j, len, nextBreakable, breakNBSP)) + break; + if (breakAll) { + betweenWords = false; + break; + } + } + + int wordLen = j - i; + if (wordLen) { + int w = widthFromCache(f, i, wordLen, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow); + if (firstGlyphLeftOverflow < 0) + firstGlyphLeftOverflow = glyphOverflow.left; + currMinWidth += w; + if (betweenWords) { + if (lastWordBoundary == i) + currMaxWidth += w; + else + currMaxWidth += widthFromCache(f, lastWordBoundary, j - lastWordBoundary, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow); + lastWordBoundary = j; + } + + bool isSpace = (j < len) && isSpaceAccordingToStyle(c, style()); + bool isCollapsibleWhiteSpace = (j < len) && style()->isCollapsibleWhiteSpace(c); + if (j < len && style()->autoWrap()) + m_hasBreakableChar = true; + + // Add in wordSpacing to our currMaxWidth, but not if this is the last word on a line or the + // last word in the run. + if (wordSpacing && (isSpace || isCollapsibleWhiteSpace) && !containsOnlyWhitespace(j, len-j)) + currMaxWidth += wordSpacing; + + if (firstWord) { + firstWord = false; + // If the first character in the run is breakable, then we consider ourselves to have a beginning + // minimum width of 0, since a break could occur right before our run starts, preventing us from ever + // being appended to a previous text run when considering the total minimum width of the containing block. + if (hasBreak) + m_hasBreakableChar = true; + m_beginMinWidth = hasBreak ? 0 : w; + } + m_endMinWidth = w; + + if (currMinWidth > m_minWidth) + m_minWidth = currMinWidth; + currMinWidth = 0; + + i += wordLen - 1; + } else { + // Nowrap can never be broken, so don't bother setting the + // breakable character boolean. Pre can only be broken if we encounter a newline. + if (style()->autoWrap() || isNewline) + m_hasBreakableChar = true; + + if (currMinWidth > m_minWidth) + m_minWidth = currMinWidth; + currMinWidth = 0; + + if (isNewline) { // Only set if preserveNewline was true and we saw a newline. + if (firstLine) { + firstLine = false; + leadWidth = 0; + if (!style()->autoWrap()) + m_beginMinWidth = currMaxWidth; + } + + if (currMaxWidth > m_maxWidth) + m_maxWidth = currMaxWidth; + currMaxWidth = 0; + } else { + currMaxWidth += f.width(TextRun(txt + i, 1, allowTabs(), leadWidth + currMaxWidth)); + glyphOverflow.right = 0; + needsWordSpacing = isSpace && !previousCharacterIsSpace && i == len - 1; + } + ASSERT(lastWordBoundary == i); + lastWordBoundary++; + } + } + + if (firstGlyphLeftOverflow > 0) + glyphOverflow.left = firstGlyphLeftOverflow; + + if ((needsWordSpacing && len > 1) || (ignoringSpaces && !firstWord)) + currMaxWidth += wordSpacing; + + m_minWidth = max(currMinWidth, m_minWidth); + m_maxWidth = max(currMaxWidth, m_maxWidth); + + if (!style()->autoWrap()) + m_minWidth = m_maxWidth; + + if (style()->whiteSpace() == PRE) { + if (firstLine) + m_beginMinWidth = m_maxWidth; + m_endMinWidth = currMaxWidth; + } + + setPreferredLogicalWidthsDirty(false); +} + +bool RenderText::isAllCollapsibleWhitespace() +{ + int length = textLength(); + const UChar* text = characters(); + for (int i = 0; i < length; i++) { + if (!style()->isCollapsibleWhiteSpace(text[i])) + return false; + } + return true; +} + +bool RenderText::containsOnlyWhitespace(unsigned from, unsigned len) const +{ + ASSERT(m_text); + StringImpl& text = *m_text.impl(); + unsigned currPos; + for (currPos = from; + currPos < from + len && (text[currPos] == '\n' || text[currPos] == ' ' || text[currPos] == '\t'); + currPos++) { } + return currPos >= (from + len); +} + +IntPoint RenderText::firstRunOrigin() const +{ + return IntPoint(firstRunX(), firstRunY()); +} + +int RenderText::firstRunX() const +{ + return m_firstTextBox ? m_firstTextBox->m_x : 0; +} + +int RenderText::firstRunY() const +{ + return m_firstTextBox ? m_firstTextBox->m_y : 0; +} + +void RenderText::setSelectionState(SelectionState state) +{ + InlineTextBox* box; + + RenderObject::setSelectionState(state); + if (state == SelectionStart || state == SelectionEnd || state == SelectionBoth) { + int startPos, endPos; + selectionStartEnd(startPos, endPos); + if (selectionState() == SelectionStart) { + endPos = textLength(); + + // to handle selection from end of text to end of line + if (startPos != 0 && startPos == endPos) + startPos = endPos - 1; + } else if (selectionState() == SelectionEnd) + startPos = 0; + + for (box = firstTextBox(); box; box = box->nextTextBox()) { + if (box->isSelected(startPos, endPos)) { + RootInlineBox* line = box->root(); + if (line) + line->setHasSelectedChildren(true); + } + } + } else { + for (box = firstTextBox(); box; box = box->nextTextBox()) { + RootInlineBox* line = box->root(); + if (line) + line->setHasSelectedChildren(state == SelectionInside); + } + } + + // The returned value can be null in case of an orphaned tree. + if (RenderBlock* cb = containingBlock()) + cb->setSelectionState(state); +} + +void RenderText::setTextWithOffset(PassRefPtr<StringImpl> text, unsigned offset, unsigned len, bool force) +{ + unsigned oldLen = textLength(); + unsigned newLen = text->length(); + int delta = newLen - oldLen; + unsigned end = len ? offset + len - 1 : offset; + + RootInlineBox* firstRootBox = 0; + RootInlineBox* lastRootBox = 0; + + bool dirtiedLines = false; + + // Dirty all text boxes that include characters in between offset and offset+len. + for (InlineTextBox* curr = firstTextBox(); curr; curr = curr->nextTextBox()) { + // Text run is entirely before the affected range. + if (curr->end() < offset) + continue; + + // Text run is entirely after the affected range. + if (curr->start() > end) { + curr->offsetRun(delta); + RootInlineBox* root = curr->root(); + if (!firstRootBox) { + firstRootBox = root; + if (!dirtiedLines) { + // The affected area was in between two runs. Go ahead and mark the root box of + // the run after the affected area as dirty. + firstRootBox->markDirty(); + dirtiedLines = true; + } + } + lastRootBox = root; + } else if (curr->end() >= offset && curr->end() <= end) { + // Text run overlaps with the left end of the affected range. + curr->dirtyLineBoxes(); + dirtiedLines = true; + } else if (curr->start() <= offset && curr->end() >= end) { + // Text run subsumes the affected range. + curr->dirtyLineBoxes(); + dirtiedLines = true; + } else if (curr->start() <= end && curr->end() >= end) { + // Text run overlaps with right end of the affected range. + curr->dirtyLineBoxes(); + dirtiedLines = true; + } + } + + // Now we have to walk all of the clean lines and adjust their cached line break information + // to reflect our updated offsets. + if (lastRootBox) + lastRootBox = lastRootBox->nextRootBox(); + if (firstRootBox) { + RootInlineBox* prev = firstRootBox->prevRootBox(); + if (prev) + firstRootBox = prev; + } else if (lastTextBox()) { + ASSERT(!lastRootBox); + firstRootBox = lastTextBox()->root(); + firstRootBox->markDirty(); + dirtiedLines = true; + } + for (RootInlineBox* curr = firstRootBox; curr && curr != lastRootBox; curr = curr->nextRootBox()) { + if (curr->lineBreakObj() == this && curr->lineBreakPos() > end) + curr->setLineBreakPos(curr->lineBreakPos() + delta); + } + + // If the text node is empty, dirty the line where new text will be inserted. + if (!firstTextBox() && parent()) { + parent()->dirtyLinesFromChangedChild(this); + dirtiedLines = true; + } + + m_linesDirty = dirtiedLines; + setText(text, force); +} + +static inline bool isInlineFlowOrEmptyText(const RenderObject* o) +{ + if (o->isRenderInline()) + return true; + if (!o->isText()) + return false; + StringImpl* text = toRenderText(o)->text(); + if (!text) + return true; + return !text->length(); +} + +UChar RenderText::previousCharacter() const +{ + // find previous text renderer if one exists + const RenderObject* previousText = this; + while ((previousText = previousText->previousInPreOrder())) + if (!isInlineFlowOrEmptyText(previousText)) + break; + UChar prev = ' '; + if (previousText && previousText->isText()) + if (StringImpl* previousString = toRenderText(previousText)->text()) + prev = (*previousString)[previousString->length() - 1]; + return prev; +} + +void RenderText::transformText(String& text) const +{ + ASSERT(style()); + switch (style()->textTransform()) { + case TTNONE: + break; + case CAPITALIZE: + makeCapitalized(&text, previousCharacter()); + break; + case UPPERCASE: + text.makeUpper(); + break; + case LOWERCASE: + text.makeLower(); + break; + } +} + +void RenderText::setTextInternal(PassRefPtr<StringImpl> text) +{ + ASSERT(text); + m_text = text; + if (m_needsTranscoding) { + const TextEncoding* encoding = document()->decoder() ? &document()->decoder()->encoding() : 0; + fontTranscoder().convert(m_text, style()->font().fontDescription(), encoding); + } + ASSERT(m_text); + + if (style()) { + transformText(m_text); + + // We use the same characters here as for list markers. + // See the listMarkerText function in RenderListMarker.cpp. + switch (style()->textSecurity()) { + case TSNONE: + break; + case TSCIRCLE: + m_text.makeSecure(whiteBullet); + break; + case TSDISC: + m_text.makeSecure(bullet); + break; + case TSSQUARE: + m_text.makeSecure(blackSquare); + } + } + + ASSERT(m_text); + ASSERT(!isBR() || (textLength() == 1 && m_text[0] == '\n')); + + m_isAllASCII = m_text.containsOnlyASCII(); +} + +void RenderText::setText(PassRefPtr<StringImpl> text, bool force) +{ + ASSERT(text); + + if (!force && equal(m_text.impl(), text.get())) + return; + + setTextInternal(text); + setNeedsLayoutAndPrefWidthsRecalc(); + m_knownToHaveNoOverflowAndNoFallbackFonts = false; + + AXObjectCache* axObjectCache = document()->axObjectCache(); + if (axObjectCache->accessibilityEnabled()) + axObjectCache->contentChanged(this); +} + +String RenderText::textWithoutTranscoding() const +{ + // If m_text isn't transcoded or is secure, we can just return the modified text. + if (!m_needsTranscoding || style()->textSecurity() != TSNONE) + return text(); + + // Otherwise, we should use original text. If text-transform is + // specified, we should transform the text on the fly. + String text = originalText(); + if (style()) + transformText(text); + return text; +} + +void RenderText::dirtyLineBoxes(bool fullLayout) +{ + if (fullLayout) + deleteTextBoxes(); + else if (!m_linesDirty) { + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) + box->dirtyLineBoxes(); + } + m_linesDirty = false; +} +#ifdef ANDROID_DISABLE_ROUNDING_HACKS +static bool disableRoundingHacks(RenderText* renderText) +{ + RenderObject* renderer = renderText; + while (renderer) { + if (renderer->isTextControl()) { + return true; + } + renderer = renderer->parent(); + } + return false; +} +#endif + +InlineTextBox* RenderText::createTextBox() +{ +#ifdef ANDROID_DISABLE_ROUNDING_HACKS + return new (renderArena()) InlineTextBox(this, disableRoundingHacks(this)); +#else + return new (renderArena()) InlineTextBox(this); +#endif +} + +InlineTextBox* RenderText::createInlineTextBox() +{ + InlineTextBox* textBox = createTextBox(); + if (!m_firstTextBox) + m_firstTextBox = m_lastTextBox = textBox; + else { + m_lastTextBox->setNextTextBox(textBox); + textBox->setPreviousTextBox(m_lastTextBox); + m_lastTextBox = textBox; + } + textBox->setIsText(true); + return textBox; +} + +void RenderText::positionLineBox(InlineBox* box) +{ + InlineTextBox* s = static_cast<InlineTextBox*>(box); + + // FIXME: should not be needed!!! + if (!s->len()) { + // We want the box to be destroyed. + s->remove(); + if (m_firstTextBox == s) + m_firstTextBox = s->nextTextBox(); + else + s->prevTextBox()->setNextTextBox(s->nextTextBox()); + if (m_lastTextBox == s) + m_lastTextBox = s->prevTextBox(); + else + s->nextTextBox()->setPreviousTextBox(s->prevTextBox()); + s->destroy(renderArena()); + return; + } + + m_containsReversedText |= !s->isLeftToRightDirection(); +} + +unsigned RenderText::width(unsigned from, unsigned len, int xPos, bool firstLine, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* glyphOverflow) const +{ + if (from >= textLength()) + return 0; + + if (from + len > textLength()) + len = textLength() - from; + + return width(from, len, style(firstLine)->font(), xPos, fallbackFonts, glyphOverflow); +} + +unsigned RenderText::width(unsigned from, unsigned len, const Font& f, int xPos, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* glyphOverflow) const +{ + ASSERT(from + len <= textLength()); + if (!characters()) + return 0; + + int w; + if (&f == &style()->font()) { + if (!style()->preserveNewline() && !from && len == textLength()) { + if (fallbackFonts) { + ASSERT(glyphOverflow); + if (preferredLogicalWidthsDirty() || !m_knownToHaveNoOverflowAndNoFallbackFonts) { + const_cast<RenderText*>(this)->computePreferredLogicalWidths(0, *fallbackFonts, *glyphOverflow); + if (fallbackFonts->isEmpty() && !glyphOverflow->left && !glyphOverflow->right && !glyphOverflow->top && !glyphOverflow->bottom) + m_knownToHaveNoOverflowAndNoFallbackFonts = true; + } + w = m_maxWidth; + } else + w = maxPreferredLogicalWidth(); + } else + w = widthFromCache(f, from, len, xPos, fallbackFonts, glyphOverflow); + } else + w = f.width(TextRun(text()->characters() + from, len, allowTabs(), xPos), fallbackFonts, glyphOverflow); + + return w; +} + +IntRect RenderText::linesBoundingBox() const +{ + IntRect result; + + ASSERT(!firstTextBox() == !lastTextBox()); // Either both are null or both exist. + if (firstTextBox() && lastTextBox()) { + // Return the width of the minimal left side and the maximal right side. + int logicalLeftSide = 0; + int logicalRightSide = 0; + for (InlineTextBox* curr = firstTextBox(); curr; curr = curr->nextTextBox()) { + if (curr == firstTextBox() || curr->logicalLeft() < logicalLeftSide) + logicalLeftSide = curr->logicalLeft(); + if (curr == firstTextBox() || curr->logicalRight() > logicalRightSide) + logicalRightSide = curr->logicalRight(); + } + + bool isHorizontal = style()->isHorizontalWritingMode(); + + int x = isHorizontal ? logicalLeftSide : firstTextBox()->x(); + int y = isHorizontal ? firstTextBox()->y() : logicalLeftSide; + int width = isHorizontal ? logicalRightSide - logicalLeftSide : lastTextBox()->logicalBottom() - x; + int height = isHorizontal ? lastTextBox()->logicalBottom() - y : logicalRightSide - logicalLeftSide; + result = IntRect(x, y, width, height); + } + + return result; +} + +IntRect RenderText::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) +{ + RenderObject* cb = containingBlock(); + // The containing block may be an ancestor of repaintContainer, but we need to do a repaintContainer-relative repaint. + if (repaintContainer && repaintContainer != cb) { + if (!cb->isDescendantOf(repaintContainer)) + return repaintContainer->clippedOverflowRectForRepaint(repaintContainer); + } + return cb->clippedOverflowRectForRepaint(repaintContainer); +} + +IntRect RenderText::selectionRectForRepaint(RenderBoxModelObject* repaintContainer, bool clipToVisibleContent) +{ + ASSERT(!needsLayout()); + + if (selectionState() == SelectionNone) + return IntRect(); + RenderBlock* cb = containingBlock(); + if (!cb) + return IntRect(); + + // Now calculate startPos and endPos for painting selection. + // We include a selection while endPos > 0 + int startPos, endPos; + if (selectionState() == SelectionInside) { + // We are fully selected. + startPos = 0; + endPos = textLength(); + } else { + selectionStartEnd(startPos, endPos); + if (selectionState() == SelectionStart) + endPos = textLength(); + else if (selectionState() == SelectionEnd) + startPos = 0; + } + + if (startPos == endPos) + return IntRect(); + + IntRect rect; + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { + rect.unite(box->selectionRect(0, 0, startPos, endPos)); + rect.unite(ellipsisRectForBox(box, startPos, endPos)); + } + + if (clipToVisibleContent) + computeRectForRepaint(repaintContainer, rect); + else { + if (cb->hasColumns()) + cb->adjustRectForColumns(rect); + + rect = localToContainerQuad(FloatRect(rect), repaintContainer).enclosingBoundingBox(); + } + + return rect; +} + +int RenderText::caretMinOffset() const +{ + InlineTextBox* box = firstTextBox(); + if (!box) + return 0; + int minOffset = box->start(); + for (box = box->nextTextBox(); box; box = box->nextTextBox()) + minOffset = min<int>(minOffset, box->start()); + return minOffset; +} + +int RenderText::caretMaxOffset() const +{ + InlineTextBox* box = lastTextBox(); + if (!box) + return textLength(); + int maxOffset = box->start() + box->len(); + for (box = box->prevTextBox(); box; box = box->prevTextBox()) + maxOffset = max<int>(maxOffset, box->start() + box->len()); + return maxOffset; +} + +unsigned RenderText::caretMaxRenderedOffset() const +{ + int l = 0; + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) + l += box->len(); + return l; +} + +int RenderText::previousOffset(int current) const +{ + StringImpl* si = m_text.impl(); + TextBreakIterator* iterator = cursorMovementIterator(si->characters(), si->length()); + if (!iterator) + return current - 1; + + long result = textBreakPreceding(iterator, current); + if (result == TextBreakDone) + result = current - 1; + +#ifdef BUILDING_ON_TIGER + // ICU 3.2 allows character breaks before a half-width Katakana voiced mark. + if (static_cast<unsigned>(result) < si->length()) { + UChar character = (*si)[result]; + if (character == 0xFF9E || character == 0xFF9F) + --result; + } +#endif + + return result; +} + +#define HANGUL_CHOSEONG_START (0x1100) +#define HANGUL_CHOSEONG_END (0x115F) +#define HANGUL_JUNGSEONG_START (0x1160) +#define HANGUL_JUNGSEONG_END (0x11A2) +#define HANGUL_JONGSEONG_START (0x11A8) +#define HANGUL_JONGSEONG_END (0x11F9) +#define HANGUL_SYLLABLE_START (0xAC00) +#define HANGUL_SYLLABLE_END (0xD7AF) +#define HANGUL_JONGSEONG_COUNT (28) + +enum HangulState { + HangulStateL, + HangulStateV, + HangulStateT, + HangulStateLV, + HangulStateLVT, + HangulStateBreak +}; + +inline bool isHangulLVT(UChar32 character) +{ + return (character - HANGUL_SYLLABLE_START) % HANGUL_JONGSEONG_COUNT; +} + +int RenderText::previousOffsetForBackwardDeletion(int current) const +{ +#if PLATFORM(MAC) + ASSERT(m_text); + StringImpl& text = *m_text.impl(); + UChar32 character; + while (current > 0) { + if (U16_IS_TRAIL(text[--current])) + --current; + if (current < 0) + break; + + UChar32 character = text.characterStartingAt(current); + + // We don't combine characters in Armenian ... Limbu range for backward deletion. + if ((character >= 0x0530) && (character < 0x1950)) + break; + + if (u_isbase(character) && (character != 0xFF9E) && (character != 0xFF9F)) + break; + } + + if (current <= 0) + return current; + + // Hangul + character = text.characterStartingAt(current); + if (((character >= HANGUL_CHOSEONG_START) && (character <= HANGUL_JONGSEONG_END)) || ((character >= HANGUL_SYLLABLE_START) && (character <= HANGUL_SYLLABLE_END))) { + HangulState state; + HangulState initialState; + + if (character < HANGUL_JUNGSEONG_START) + state = HangulStateL; + else if (character < HANGUL_JONGSEONG_START) + state = HangulStateV; + else if (character < HANGUL_SYLLABLE_START) + state = HangulStateT; + else + state = isHangulLVT(character) ? HangulStateLVT : HangulStateLV; + + initialState = state; + + while (current > 0 && ((character = text.characterStartingAt(current - 1)) >= HANGUL_CHOSEONG_START) && (character <= HANGUL_SYLLABLE_END) && ((character <= HANGUL_JONGSEONG_END) || (character >= HANGUL_SYLLABLE_START))) { + switch (state) { + case HangulStateV: + if (character <= HANGUL_CHOSEONG_END) + state = HangulStateL; + else if ((character >= HANGUL_SYLLABLE_START) && (character <= HANGUL_SYLLABLE_END) && !isHangulLVT(character)) + state = HangulStateLV; + else if (character > HANGUL_JUNGSEONG_END) + state = HangulStateBreak; + break; + case HangulStateT: + if ((character >= HANGUL_JUNGSEONG_START) && (character <= HANGUL_JUNGSEONG_END)) + state = HangulStateV; + else if ((character >= HANGUL_SYLLABLE_START) && (character <= HANGUL_SYLLABLE_END)) + state = (isHangulLVT(character) ? HangulStateLVT : HangulStateLV); + else if (character < HANGUL_JUNGSEONG_START) + state = HangulStateBreak; + break; + default: + state = (character < HANGUL_JUNGSEONG_START) ? HangulStateL : HangulStateBreak; + break; + } + if (state == HangulStateBreak) + break; + + --current; + } + } + + return current; +#else + // Platforms other than Mac delete by one code point. + return current - 1; +#endif +} + +int RenderText::nextOffset(int current) const +{ + StringImpl* si = m_text.impl(); + TextBreakIterator* iterator = cursorMovementIterator(si->characters(), si->length()); + if (!iterator) + return current + 1; + + long result = textBreakFollowing(iterator, current); + if (result == TextBreakDone) + result = current + 1; + +#ifdef BUILDING_ON_TIGER + // ICU 3.2 allows character breaks before a half-width Katakana voiced mark. + if (static_cast<unsigned>(result) < si->length()) { + UChar character = (*si)[result]; + if (character == 0xFF9E || character == 0xFF9F) + ++result; + } +#endif + + return result; +} + +#ifndef NDEBUG + +void RenderText::checkConsistency() const +{ +#ifdef CHECK_CONSISTENCY + const InlineTextBox* prev = 0; + for (const InlineTextBox* child = m_firstTextBox; child != 0; child = child->nextTextBox()) { + ASSERT(child->renderer() == this); + ASSERT(child->prevTextBox() == prev); + prev = child; + } + ASSERT(prev == m_lastTextBox); +#endif +} + +#endif + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderText.h b/Source/WebCore/rendering/RenderText.h new file mode 100644 index 0000000..964a1d3 --- /dev/null +++ b/Source/WebCore/rendering/RenderText.h @@ -0,0 +1,206 @@ +/* + * (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderText_h +#define RenderText_h + +#include "RenderObject.h" +#include <wtf/Forward.h> + +namespace WebCore { + +class InlineTextBox; + +class RenderText : public RenderObject { +public: + RenderText(Node*, PassRefPtr<StringImpl>); +#ifndef NDEBUG + virtual ~RenderText(); +#endif + + virtual const char* renderName() const; + + virtual bool isTextFragment() const; + virtual bool isWordBreak() const; + + virtual PassRefPtr<StringImpl> originalText() const; + + void extractTextBox(InlineTextBox*); + void attachTextBox(InlineTextBox*); + void removeTextBox(InlineTextBox*); + + virtual void destroy(); + + StringImpl* text() const { return m_text.impl(); } + String textWithoutTranscoding() const; + + InlineTextBox* createInlineTextBox(); + void dirtyLineBoxes(bool fullLayout); + + virtual void absoluteRects(Vector<IntRect>&, int tx, int ty); + void absoluteRectsForRange(Vector<IntRect>&, unsigned startOffset = 0, unsigned endOffset = UINT_MAX, bool useSelectionHeight = false); + + virtual void absoluteQuads(Vector<FloatQuad>&); + void absoluteQuadsForRange(Vector<FloatQuad>&, unsigned startOffset = 0, unsigned endOffset = UINT_MAX, bool useSelectionHeight = false); + + enum ClippingOption { NoClipping, ClipToEllipsis }; + void absoluteQuads(Vector<FloatQuad>&, ClippingOption option = NoClipping); + + virtual VisiblePosition positionForPoint(const IntPoint&); + + const UChar* characters() const { return m_text.characters(); } + unsigned textLength() const { return m_text.length(); } // non virtual implementation of length() + void positionLineBox(InlineBox*); + + virtual unsigned width(unsigned from, unsigned len, const Font&, int xPos, HashSet<const SimpleFontData*>* fallbackFonts = 0, GlyphOverflow* = 0) const; + virtual unsigned width(unsigned from, unsigned len, int xPos, bool firstLine = false, HashSet<const SimpleFontData*>* fallbackFonts = 0, GlyphOverflow* = 0) const; + + virtual int minPreferredLogicalWidth() const; + virtual int maxPreferredLogicalWidth() const; + + void trimmedPrefWidths(int leadWidth, + int& beginMinW, bool& beginWS, + int& endMinW, bool& endWS, + bool& hasBreakableChar, bool& hasBreak, + int& beginMaxW, int& endMaxW, + int& minW, int& maxW, bool& stripFrontSpaces); + + virtual IntRect linesBoundingBox() const; + + IntPoint firstRunOrigin() const; + int firstRunX() const; + int firstRunY() const; + + void setText(PassRefPtr<StringImpl>, bool force = false); + void setTextWithOffset(PassRefPtr<StringImpl>, unsigned offset, unsigned len, bool force = false); + + virtual bool canBeSelectionLeaf() const { return true; } + virtual void setSelectionState(SelectionState s); + virtual IntRect selectionRectForRepaint(RenderBoxModelObject* repaintContainer, bool clipToVisibleContent = true); + virtual IntRect localCaretRect(InlineBox*, int caretOffset, int* extraWidthToEndOfLine = 0); + + virtual int marginLeft() const { return style()->marginLeft().calcMinValue(0); } + virtual int marginRight() const { return style()->marginRight().calcMinValue(0); } + + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer); + + InlineTextBox* firstTextBox() const { return m_firstTextBox; } + InlineTextBox* lastTextBox() const { return m_lastTextBox; } + + virtual int caretMinOffset() const; + virtual int caretMaxOffset() const; + virtual unsigned caretMaxRenderedOffset() const; + + virtual int previousOffset(int current) const; + virtual int previousOffsetForBackwardDeletion(int current) const; + virtual int nextOffset(int current) const; + + bool containsReversedText() const { return m_containsReversedText; } + + InlineTextBox* findNextInlineTextBox(int offset, int& pos) const; + + bool allowTabs() const { return !style()->collapseWhiteSpace(); } + + void checkConsistency() const; + + virtual void computePreferredLogicalWidths(int leadWidth); + bool isAllCollapsibleWhitespace(); + +protected: + virtual void styleWillChange(StyleDifference, const RenderStyle*) { } + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + virtual void setTextInternal(PassRefPtr<StringImpl>); + virtual UChar previousCharacter() const; + + virtual InlineTextBox* createTextBox(); // Subclassed by SVG. + +private: + void computePreferredLogicalWidths(int leadWidth, HashSet<const SimpleFontData*>& fallbackFonts, GlyphOverflow&); + + // Make length() private so that callers that have a RenderText* + // will use the more efficient textLength() instead, while + // callers with a RenderObject* can continue to use length(). + virtual unsigned length() const { return textLength(); } + + virtual void paint(PaintInfo&, int, int) { ASSERT_NOT_REACHED(); } + virtual void layout() { ASSERT_NOT_REACHED(); } + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int, int, int, int, HitTestAction) { ASSERT_NOT_REACHED(); return false; } + + void deleteTextBoxes(); + bool containsOnlyWhitespace(unsigned from, unsigned len) const; + int widthFromCache(const Font&, int start, int len, int xPos, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow*) const; + bool isAllASCII() const { return m_isAllASCII; } + void updateNeedsTranscoding(); + + inline void transformText(String&) const; + + int m_minWidth; // here to minimize padding in 64-bit. + + String m_text; + + InlineTextBox* m_firstTextBox; + InlineTextBox* m_lastTextBox; + + int m_maxWidth; + int m_beginMinWidth; + int m_endMinWidth; + + bool m_hasBreakableChar : 1; // Whether or not we can be broken into multiple lines. + bool m_hasBreak : 1; // Whether or not we have a hard break (e.g., <pre> with '\n'). + bool m_hasTab : 1; // Whether or not we have a variable width tab character (e.g., <pre> with '\t'). + bool m_hasBeginWS : 1; // Whether or not we begin with WS (only true if we aren't pre) + bool m_hasEndWS : 1; // Whether or not we end with WS (only true if we aren't pre) + bool m_linesDirty : 1; // This bit indicates that the text run has already dirtied specific + // line boxes, and this hint will enable layoutInlineChildren to avoid + // just dirtying everything when character data is modified (e.g., appended/inserted + // or removed). + bool m_containsReversedText : 1; + bool m_isAllASCII : 1; + mutable bool m_knownToHaveNoOverflowAndNoFallbackFonts : 1; + bool m_needsTranscoding : 1; +}; + +inline RenderText* toRenderText(RenderObject* object) +{ + ASSERT(!object || object->isText()); + return static_cast<RenderText*>(object); +} + +inline const RenderText* toRenderText(const RenderObject* object) +{ + ASSERT(!object || object->isText()); + return static_cast<const RenderText*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderText(const RenderText*); + +#ifdef NDEBUG +inline void RenderText::checkConsistency() const +{ +} +#endif + +} // namespace WebCore + +#endif // RenderText_h diff --git a/Source/WebCore/rendering/RenderTextControl.cpp b/Source/WebCore/rendering/RenderTextControl.cpp new file mode 100644 index 0000000..0192ee4 --- /dev/null +++ b/Source/WebCore/rendering/RenderTextControl.cpp @@ -0,0 +1,629 @@ +/** + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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 "RenderTextControl.h" + +#include "AXObjectCache.h" +#include "CharacterNames.h" +#include "Editor.h" +#include "Event.h" +#include "EventNames.h" +#include "Frame.h" +#include "HTMLBRElement.h" +#include "HTMLFormControlElement.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "HitTestResult.h" +#include "RenderLayer.h" +#include "RenderText.h" +#include "ScrollbarTheme.h" +#include "SelectionController.h" +#include "Text.h" +#include "TextControlInnerElements.h" +#include "TextIterator.h" + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +// Value chosen by observation. This can be tweaked. +static const int minColorContrastValue = 1300; + +static Color disabledTextColor(const Color& textColor, const Color& backgroundColor) +{ + // The explicit check for black is an optimization for the 99% case (black on white). + // This also means that black on black will turn into grey on black when disabled. + Color disabledColor; + if (textColor.rgb() == Color::black || differenceSquared(textColor, Color::white) > differenceSquared(backgroundColor, Color::white)) + disabledColor = textColor.light(); + else + disabledColor = textColor.dark(); + + // If there's not very much contrast between the disabled color and the background color, + // just leave the text color alone. We don't want to change a good contrast color scheme so that it has really bad contrast. + // If the the contrast was already poor, then it doesn't do any good to change it to a different poor contrast color scheme. + if (differenceSquared(disabledColor, backgroundColor) < minColorContrastValue) + return textColor; + + return disabledColor; +} + +RenderTextControl::RenderTextControl(Node* node, bool placeholderVisible) + : RenderBlock(node) + , m_placeholderVisible(placeholderVisible) + , m_wasChangedSinceLastChangeEvent(false) + , m_lastChangeWasUserEdit(false) +{ +} + +RenderTextControl::~RenderTextControl() +{ + // The children renderers have already been destroyed by destroyLeftoverChildren + if (m_innerText) + m_innerText->detach(); +} + +void RenderTextControl::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBlock::styleDidChange(diff, oldStyle); + + if (m_innerText) { + RenderBlock* textBlockRenderer = toRenderBlock(m_innerText->renderer()); + RefPtr<RenderStyle> textBlockStyle = createInnerTextStyle(style()); + // We may have set the width and the height in the old style in layout(). + // Reset them now to avoid getting a spurious layout hint. + textBlockRenderer->style()->setHeight(Length()); + textBlockRenderer->style()->setWidth(Length()); + setInnerTextStyle(textBlockStyle); + } + + setReplaced(isInline()); +} + +void RenderTextControl::setInnerTextStyle(PassRefPtr<RenderStyle> style) +{ + if (m_innerText) { + RefPtr<RenderStyle> textStyle = style; + m_innerText->renderer()->setStyle(textStyle); + for (Node* n = m_innerText->firstChild(); n; n = n->traverseNextNode(m_innerText.get())) { + if (n->renderer()) + n->renderer()->setStyle(textStyle); + } + } +} + +static inline bool updateUserModifyProperty(Node* node, RenderStyle* style) +{ + bool isEnabled = true; + bool isReadOnlyControl = false; + + if (node->isElementNode()) { + Element* element = static_cast<Element*>(node); + isEnabled = element->isEnabledFormControl(); + isReadOnlyControl = element->isReadOnlyFormControl(); + } + + style->setUserModify((isReadOnlyControl || !isEnabled) ? READ_ONLY : READ_WRITE_PLAINTEXT_ONLY); + return !isEnabled; +} + +void RenderTextControl::adjustInnerTextStyle(const RenderStyle* startStyle, RenderStyle* textBlockStyle) const +{ + // The inner block, if present, always has its direction set to LTR, + // so we need to inherit the direction from the element. + textBlockStyle->setDirection(style()->direction()); + + bool disabled = updateUserModifyProperty(node(), textBlockStyle); + if (disabled) + textBlockStyle->setColor(disabledTextColor(textBlockStyle->visitedDependentColor(CSSPropertyColor), startStyle->visitedDependentColor(CSSPropertyBackgroundColor))); +} + +void RenderTextControl::createSubtreeIfNeeded(TextControlInnerElement* innerBlock) +{ + if (!m_innerText) { + // Create the text block element + // For non-search fields, there is no intermediate innerBlock as the shadow node. + // m_innerText will be the shadow node in that case. + RenderStyle* parentStyle = innerBlock ? innerBlock->renderer()->style() : style(); + m_innerText = TextControlInnerTextElement::create(document(), innerBlock ? 0 : static_cast<HTMLElement*>(node())); + m_innerText->attachInnerElement(innerBlock ? innerBlock : node(), createInnerTextStyle(parentStyle), renderArena()); + } +} + +int RenderTextControl::textBlockHeight() const +{ + return height() - borderAndPaddingHeight(); +} + +int RenderTextControl::textBlockWidth() const +{ + return width() - borderAndPaddingWidth() - m_innerText->renderBox()->paddingLeft() - m_innerText->renderBox()->paddingRight(); +} + +void RenderTextControl::updateFromElement() +{ + updateUserModifyProperty(node(), m_innerText->renderer()->style()); +} + +void RenderTextControl::setInnerTextValue(const String& innerTextValue) +{ + String value = innerTextValue; + if (value != text() || !m_innerText->hasChildNodes()) { + if (value != text()) { + if (Frame* frame = this->frame()) { + frame->editor()->clearUndoRedoOperations(); + + if (AXObjectCache::accessibilityEnabled()) + document()->axObjectCache()->postNotification(this, AXObjectCache::AXValueChanged, false); + } + } + + ExceptionCode ec = 0; + m_innerText->setInnerText(value, ec); + ASSERT(!ec); + + if (value.endsWith("\n") || value.endsWith("\r")) { + m_innerText->appendChild(HTMLBRElement::create(document()), ec); + ASSERT(!ec); + } + + // We set m_lastChangeWasUserEdit to false since this change was not explicitly made by the user (say, via typing on the keyboard), see <rdar://problem/5359921>. + m_lastChangeWasUserEdit = false; + } + + static_cast<Element*>(node())->setFormControlValueMatchesRenderer(true); +} + +void RenderTextControl::setLastChangeWasUserEdit(bool lastChangeWasUserEdit) +{ + m_lastChangeWasUserEdit = lastChangeWasUserEdit; + document()->setIgnoreAutofocus(lastChangeWasUserEdit); +} + +int RenderTextControl::selectionStart() const +{ + Frame* frame = this->frame(); + if (!frame) + return 0; + return indexForVisiblePosition(frame->selection()->start()); +} + +int RenderTextControl::selectionEnd() const +{ + Frame* frame = this->frame(); + if (!frame) + return 0; + return indexForVisiblePosition(frame->selection()->end()); +} + +bool RenderTextControl::hasVisibleTextArea() const +{ + return style()->visibility() == HIDDEN || !m_innerText || !m_innerText->renderer() || !m_innerText->renderBox()->height(); +} + +void setSelectionRange(Node* node, int start, int end) +{ + ASSERT(node); + node->document()->updateLayoutIgnorePendingStylesheets(); + + if (!node->renderer() || !node->renderer()->isTextControl()) + return; + + end = max(end, 0); + start = min(max(start, 0), end); + + RenderTextControl* control = toRenderTextControl(node->renderer()); + + if (control->hasVisibleTextArea()) { + control->cacheSelection(start, end); + return; + } + VisiblePosition startPosition = control->visiblePositionForIndex(start); + VisiblePosition endPosition; + if (start == end) + endPosition = startPosition; + else + endPosition = control->visiblePositionForIndex(end); + + // startPosition and endPosition can be null position for example when + // "-webkit-user-select: none" style attribute is specified. + if (startPosition.isNotNull() && endPosition.isNotNull()) { + ASSERT(startPosition.deepEquivalent().node()->shadowAncestorNode() == node && endPosition.deepEquivalent().node()->shadowAncestorNode() == node); + } + VisibleSelection newSelection = VisibleSelection(startPosition, endPosition); + + if (Frame* frame = node->document()->frame()) + frame->selection()->setSelection(newSelection); +} + +bool RenderTextControl::isSelectableElement(Node* node) const +{ + if (!node || !m_innerText) + return false; + + if (node->rootEditableElement() == m_innerText) + return true; + + if (!m_innerText->contains(node)) + return false; + + Node* shadowAncestor = node->shadowAncestorNode(); + return shadowAncestor && (shadowAncestor->hasTagName(textareaTag) + || (shadowAncestor->hasTagName(inputTag) && static_cast<HTMLInputElement*>(shadowAncestor)->isTextField())); +} + +PassRefPtr<Range> RenderTextControl::selection(int start, int end) const +{ + if (!m_innerText) + return 0; + + return Range::create(document(), m_innerText, start, m_innerText, end); +} + +VisiblePosition RenderTextControl::visiblePositionForIndex(int index) const +{ + if (index <= 0) + return VisiblePosition(m_innerText.get(), 0, DOWNSTREAM); + ExceptionCode ec = 0; + RefPtr<Range> range = Range::create(document()); + range->selectNodeContents(m_innerText.get(), ec); + ASSERT(!ec); + CharacterIterator it(range.get()); + it.advance(index - 1); + Node* endContainer = it.range()->endContainer(ec); + ASSERT(!ec); + int endOffset = it.range()->endOffset(ec); + ASSERT(!ec); + return VisiblePosition(endContainer, endOffset, UPSTREAM); +} + +int RenderTextControl::indexForVisiblePosition(const VisiblePosition& pos) const +{ + Position indexPosition = pos.deepEquivalent(); + if (!isSelectableElement(indexPosition.node())) + return 0; + ExceptionCode ec = 0; + RefPtr<Range> range = Range::create(document()); + range->setStart(m_innerText.get(), 0, ec); + ASSERT(!ec); + range->setEnd(indexPosition.node(), indexPosition.deprecatedEditingOffset(), ec); + ASSERT(!ec); + return TextIterator::rangeLength(range.get()); +} + +void RenderTextControl::subtreeHasChanged() +{ + m_wasChangedSinceLastChangeEvent = true; + m_lastChangeWasUserEdit = true; +} + +String RenderTextControl::finishText(Vector<UChar>& result) const +{ + // Remove one trailing newline; there's always one that's collapsed out by rendering. + size_t size = result.size(); + if (size && result[size - 1] == '\n') + result.shrink(--size); + + return String::adopt(result); +} + +String RenderTextControl::text() +{ + if (!m_innerText) + return ""; + + Vector<UChar> result; + + for (Node* n = m_innerText.get(); n; n = n->traverseNextNode(m_innerText.get())) { + if (n->hasTagName(brTag)) + result.append(&newlineCharacter, 1); + else if (n->isTextNode()) { + String data = static_cast<Text*>(n)->data(); + result.append(data.characters(), data.length()); + } + } + + return finishText(result); +} + +static void getNextSoftBreak(RootInlineBox*& line, Node*& breakNode, unsigned& breakOffset) +{ + RootInlineBox* next; + for (; line; line = next) { + next = line->nextRootBox(); + if (next && !line->endsWithBreak()) { + ASSERT(line->lineBreakObj()); + breakNode = line->lineBreakObj()->node(); + breakOffset = line->lineBreakPos(); + line = next; + return; + } + } + breakNode = 0; + breakOffset = 0; +} + +String RenderTextControl::textWithHardLineBreaks() +{ + if (!m_innerText) + return ""; + + RenderBlock* renderer = toRenderBlock(m_innerText->renderer()); + if (!renderer) + return ""; + + Node* breakNode; + unsigned breakOffset; + RootInlineBox* line = renderer->firstRootBox(); + if (!line) + return ""; + + getNextSoftBreak(line, breakNode, breakOffset); + + Vector<UChar> result; + + for (Node* n = m_innerText->firstChild(); n; n = n->traverseNextNode(m_innerText.get())) { + if (n->hasTagName(brTag)) + result.append(&newlineCharacter, 1); + else if (n->isTextNode()) { + Text* text = static_cast<Text*>(n); + String data = text->data(); + unsigned length = data.length(); + unsigned position = 0; + while (breakNode == n && breakOffset <= length) { + if (breakOffset > position) { + result.append(data.characters() + position, breakOffset - position); + position = breakOffset; + result.append(&newlineCharacter, 1); + } + getNextSoftBreak(line, breakNode, breakOffset); + } + result.append(data.characters() + position, length - position); + } + while (breakNode == n) + getNextSoftBreak(line, breakNode, breakOffset); + } + + return finishText(result); +} + +int RenderTextControl::scrollbarThickness() const +{ + // FIXME: We should get the size of the scrollbar from the RenderTheme instead. + return ScrollbarTheme::nativeTheme()->scrollbarThickness(); +} + +void RenderTextControl::computeLogicalHeight() +{ + setHeight(m_innerText->renderBox()->borderTop() + m_innerText->renderBox()->borderBottom() + + m_innerText->renderBox()->paddingTop() + m_innerText->renderBox()->paddingBottom() + + m_innerText->renderBox()->marginTop() + m_innerText->renderBox()->marginBottom()); + + adjustControlHeightBasedOnLineHeight(m_innerText->renderBox()->lineHeight(true, HorizontalLine, PositionOfInteriorLineBoxes)); + setHeight(height() + borderAndPaddingHeight()); + + // We are able to have a horizontal scrollbar if the overflow style is scroll, or if its auto and there's no word wrap. + if (style()->overflowX() == OSCROLL || (style()->overflowX() == OAUTO && m_innerText->renderer()->style()->wordWrap() == NormalWordWrap)) + setHeight(height() + scrollbarThickness()); + + RenderBlock::computeLogicalHeight(); +} + +void RenderTextControl::hitInnerTextElement(HitTestResult& result, int xPos, int yPos, int tx, int ty) +{ + result.setInnerNode(m_innerText.get()); + result.setInnerNonSharedNode(m_innerText.get()); + result.setLocalPoint(IntPoint(xPos - tx - x() - m_innerText->renderBox()->x(), + yPos - ty - y() - m_innerText->renderBox()->y())); +} + +void RenderTextControl::forwardEvent(Event* event) +{ + if (event->type() == eventNames().blurEvent || event->type() == eventNames().focusEvent) + return; + m_innerText->defaultEventHandler(event); +} + +static const char* fontFamiliesWithInvalidCharWidth[] = { + "American Typewriter", + "Arial Hebrew", + "Chalkboard", + "Cochin", + "Corsiva Hebrew", + "Courier", + "Euphemia UCAS", + "Geneva", + "Gill Sans", + "Hei", + "Helvetica", + "Hoefler Text", + "InaiMathi", + "Kai", + "Lucida Grande", + "Marker Felt", + "Monaco", + "Mshtakan", + "New Peninim MT", + "Osaka", + "Raanana", + "STHeiti", + "Symbol", + "Times", + "Apple Braille", + "Apple LiGothic", + "Apple LiSung", + "Apple Symbols", + "AppleGothic", + "AppleMyungjo", + "#GungSeo", + "#HeadLineA", + "#PCMyungjo", + "#PilGi", +}; + +// For font families where any of the fonts don't have a valid entry in the OS/2 table +// for avgCharWidth, fallback to the legacy webkit behavior of getting the avgCharWidth +// from the width of a '0'. This only seems to apply to a fixed number of Mac fonts, +// but, in order to get similar rendering across platforms, we do this check for +// all platforms. +bool RenderTextControl::hasValidAvgCharWidth(AtomicString family) +{ + static HashSet<AtomicString>* fontFamiliesWithInvalidCharWidthMap = 0; + + if (!fontFamiliesWithInvalidCharWidthMap) { + fontFamiliesWithInvalidCharWidthMap = new HashSet<AtomicString>; + + for (size_t i = 0; i < WTF_ARRAY_LENGTH(fontFamiliesWithInvalidCharWidth); ++i) + fontFamiliesWithInvalidCharWidthMap->add(AtomicString(fontFamiliesWithInvalidCharWidth[i])); + } + + return !fontFamiliesWithInvalidCharWidthMap->contains(family); +} + +float RenderTextControl::getAvgCharWidth(AtomicString family) +{ + if (hasValidAvgCharWidth(family)) + return roundf(style()->font().primaryFont()->avgCharWidth()); + + const UChar ch = '0'; + return style()->font().floatWidth(TextRun(&ch, 1, false, 0, 0, false, false, false)); +} + +float RenderTextControl::scaleEmToUnits(int x) const +{ + // This matches the unitsPerEm value for MS Shell Dlg and Courier New from the "head" font table. + float unitsPerEm = 2048.0f; + return roundf(style()->font().size() * x / unitsPerEm); +} + +void RenderTextControl::computePreferredLogicalWidths() +{ + ASSERT(preferredLogicalWidthsDirty()); + + m_minPreferredLogicalWidth = 0; + m_maxPreferredLogicalWidth = 0; + + if (style()->width().isFixed() && style()->width().value() > 0) + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value()); + else { + // Use average character width. Matches IE. + AtomicString family = style()->font().family().family(); + m_maxPreferredLogicalWidth = preferredContentWidth(getAvgCharWidth(family)) + m_innerText->renderBox()->paddingLeft() + m_innerText->renderBox()->paddingRight(); + } + + if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) { + m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); + m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); + } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent())) + m_minPreferredLogicalWidth = 0; + else + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth; + + if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) { + m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); + m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); + } + + int toAdd = borderAndPaddingWidth(); + + m_minPreferredLogicalWidth += toAdd; + m_maxPreferredLogicalWidth += toAdd; + + setPreferredLogicalWidthsDirty(false); +} + +void RenderTextControl::selectionChanged(bool userTriggered) +{ + cacheSelection(selectionStart(), selectionEnd()); + + if (Frame* frame = this->frame()) { + if (frame->selection()->isRange() && userTriggered) + node()->dispatchEvent(Event::create(eventNames().selectEvent, true, false)); + } +} + +void RenderTextControl::addFocusRingRects(Vector<IntRect>& rects, int tx, int ty) +{ + if (width() && height()) + rects.append(IntRect(tx, ty, width(), height())); +} + +HTMLElement* RenderTextControl::innerTextElement() const +{ + return m_innerText.get(); +} + +void RenderTextControl::updatePlaceholderVisibility(bool placeholderShouldBeVisible, bool placeholderValueChanged) +{ + bool oldPlaceholderVisible = m_placeholderVisible; + m_placeholderVisible = placeholderShouldBeVisible; + if (oldPlaceholderVisible != m_placeholderVisible || placeholderValueChanged) + repaint(); +} + +void RenderTextControl::paintPlaceholder(PaintInfo& paintInfo, int tx, int ty) +{ + if (style()->visibility() != VISIBLE) + return; + + IntRect clipRect(tx + borderLeft(), ty + borderTop(), width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop()); + if (clipRect.isEmpty()) + return; + + paintInfo.context->save(); + + paintInfo.context->clip(clipRect); + + RefPtr<RenderStyle> placeholderStyle = getCachedPseudoStyle(INPUT_PLACEHOLDER); + if (!placeholderStyle) + placeholderStyle = style(); + + paintInfo.context->setFillColor(placeholderStyle->visitedDependentColor(CSSPropertyColor), placeholderStyle->colorSpace()); + + String placeholderText = static_cast<HTMLTextFormControlElement*>(node())->strippedPlaceholder(); + TextRun textRun(placeholderText.characters(), placeholderText.length(), 0, 0, 0, !placeholderStyle->isLeftToRightDirection(), placeholderStyle->unicodeBidi() == Override, false, false); + + RenderBox* textRenderer = innerTextElement() ? innerTextElement()->renderBox() : 0; + if (textRenderer) { + IntPoint textPoint; + textPoint.setY(ty + textBlockInsetTop() + placeholderStyle->font().ascent()); + if (placeholderStyle->isLeftToRightDirection()) + textPoint.setX(tx + textBlockInsetLeft()); + else + textPoint.setX(tx + width() - textBlockInsetRight() - style()->font().width(textRun)); + + paintInfo.context->drawBidiText(placeholderStyle->font(), textRun, textPoint); + } + paintInfo.context->restore(); +} + +void RenderTextControl::paintObject(PaintInfo& paintInfo, int tx, int ty) +{ + if (m_placeholderVisible && paintInfo.phase == PaintPhaseForeground) + paintPlaceholder(paintInfo, tx, ty); + + RenderBlock::paintObject(paintInfo, tx, ty); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderTextControl.h b/Source/WebCore/rendering/RenderTextControl.h new file mode 100644 index 0000000..fdb7fdc --- /dev/null +++ b/Source/WebCore/rendering/RenderTextControl.h @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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. + * + */ + +#ifndef RenderTextControl_h +#define RenderTextControl_h + +#include "RenderBlock.h" + +namespace WebCore { + +class VisibleSelection; +class TextControlInnerElement; +class TextControlInnerTextElement; + +class RenderTextControl : public RenderBlock { +public: + virtual ~RenderTextControl(); + + bool wasChangedSinceLastChangeEvent() const { return m_wasChangedSinceLastChangeEvent; } + void setChangedSinceLastChangeEvent(bool wasChangedSinceLastChangeEvent) { m_wasChangedSinceLastChangeEvent = wasChangedSinceLastChangeEvent; } + + bool lastChangeWasUserEdit() const { return m_lastChangeWasUserEdit; } + void setLastChangeWasUserEdit(bool lastChangeWasUserEdit); + + int selectionStart() const; + int selectionEnd() const; + PassRefPtr<Range> selection(int start, int end) const; + + virtual void subtreeHasChanged(); + String text(); + String textWithHardLineBreaks(); + void selectionChanged(bool userTriggered); + + VisiblePosition visiblePositionForIndex(int index) const; + int indexForVisiblePosition(const VisiblePosition&) const; + + void updatePlaceholderVisibility(bool, bool); + +protected: + RenderTextControl(Node*, bool); + + int scrollbarThickness() const; + void adjustInnerTextStyle(const RenderStyle* startStyle, RenderStyle* textBlockStyle) const; + void setInnerTextValue(const String&); + + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + void createSubtreeIfNeeded(TextControlInnerElement* innerBlock); + void hitInnerTextElement(HitTestResult&, int x, int y, int tx, int ty); + void forwardEvent(Event*); + + int textBlockWidth() const; + int textBlockHeight() const; + + float scaleEmToUnits(int x) const; + + static bool hasValidAvgCharWidth(AtomicString family); + virtual float getAvgCharWidth(AtomicString family); + virtual int preferredContentWidth(float charWidth) const = 0; + virtual void adjustControlHeightBasedOnLineHeight(int lineHeight) = 0; + virtual void cacheSelection(int start, int end) = 0; + virtual PassRefPtr<RenderStyle> createInnerTextStyle(const RenderStyle* startStyle) const = 0; + virtual RenderStyle* textBaseStyle() const = 0; + + virtual void updateFromElement(); + virtual void computeLogicalHeight(); + + friend class TextIterator; + HTMLElement* innerTextElement() const; + + bool m_placeholderVisible; + +private: + virtual const char* renderName() const { return "RenderTextControl"; } + virtual bool isTextControl() const { return true; } + virtual void computePreferredLogicalWidths(); + virtual void removeLeftoverAnonymousBlock(RenderBlock*) { } + virtual bool canHaveChildren() const { return false; } + virtual bool avoidsFloats() const { return true; } + void setInnerTextStyle(PassRefPtr<RenderStyle>); + virtual void paintObject(PaintInfo&, int tx, int ty); + + virtual void addFocusRingRects(Vector<IntRect>&, int tx, int ty); + + virtual bool canBeProgramaticallyScrolled(bool) const { return true; } + + virtual bool requiresForcedStyleRecalcPropagation() const { return true; } + + String finishText(Vector<UChar>&) const; + + bool hasVisibleTextArea() const; + friend void setSelectionRange(Node*, int start, int end); + bool isSelectableElement(Node*) const; + + virtual int textBlockInsetLeft() const = 0; + virtual int textBlockInsetRight() const = 0; + virtual int textBlockInsetTop() const = 0; + + void paintPlaceholder(PaintInfo&, int tx, int ty); + + bool m_wasChangedSinceLastChangeEvent; + bool m_lastChangeWasUserEdit; + RefPtr<TextControlInnerTextElement> m_innerText; +}; + +void setSelectionRange(Node*, int start, int end); + +inline RenderTextControl* toRenderTextControl(RenderObject* object) +{ + ASSERT(!object || object->isTextControl()); + return static_cast<RenderTextControl*>(object); +} + +inline const RenderTextControl* toRenderTextControl(const RenderObject* object) +{ + ASSERT(!object || object->isTextControl()); + return static_cast<const RenderTextControl*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderTextControl(const RenderTextControl*); + +} // namespace WebCore + +#endif // RenderTextControl_h diff --git a/Source/WebCore/rendering/RenderTextControlMultiLine.cpp b/Source/WebCore/rendering/RenderTextControlMultiLine.cpp new file mode 100644 index 0000000..d0b0cbc --- /dev/null +++ b/Source/WebCore/rendering/RenderTextControlMultiLine.cpp @@ -0,0 +1,163 @@ +/** + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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 "RenderTextControlMultiLine.h" + +#include "Event.h" +#include "EventNames.h" +#include "Frame.h" +#include "HTMLNames.h" +#include "HTMLTextAreaElement.h" +#include "HitTestResult.h" +#ifdef ANDROID_LAYOUT +#include "Settings.h" +#endif + +namespace WebCore { + +RenderTextControlMultiLine::RenderTextControlMultiLine(Node* node, bool placeholderVisible) + : RenderTextControl(node, placeholderVisible) +{ +} + +RenderTextControlMultiLine::~RenderTextControlMultiLine() +{ + if (node()) + static_cast<HTMLTextAreaElement*>(node())->rendererWillBeDestroyed(); +} + +void RenderTextControlMultiLine::subtreeHasChanged() +{ + RenderTextControl::subtreeHasChanged(); + HTMLTextAreaElement* textArea = static_cast<HTMLTextAreaElement*>(node()); + textArea->setFormControlValueMatchesRenderer(false); + textArea->setNeedsValidityCheck(); + + if (!node()->focused()) + return; + + if (Frame* frame = this->frame()) + frame->editor()->textDidChangeInTextArea(textArea); +} + +bool RenderTextControlMultiLine::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction hitTestAction) +{ + if (!RenderTextControl::nodeAtPoint(request, result, x, y, tx, ty, hitTestAction)) + return false; + + if (result.innerNode() == node() || result.innerNode() == innerTextElement()) + hitInnerTextElement(result, x, y, tx, ty); + + return true; +} + +void RenderTextControlMultiLine::forwardEvent(Event* event) +{ + RenderTextControl::forwardEvent(event); +} + +float RenderTextControlMultiLine::getAvgCharWidth(AtomicString family) +{ + // Since Lucida Grande is the default font, we want this to match the width + // of Courier New, the default font for textareas in IE, Firefox and Safari Win. + // 1229 is the avgCharWidth value in the OS/2 table for Courier New. + if (family == AtomicString("Lucida Grande")) + return scaleEmToUnits(1229); + + return RenderTextControl::getAvgCharWidth(family); +} + +int RenderTextControlMultiLine::preferredContentWidth(float charWidth) const +{ + int factor = static_cast<HTMLTextAreaElement*>(node())->cols(); + return static_cast<int>(ceilf(charWidth * factor)) + scrollbarThickness(); +} + +void RenderTextControlMultiLine::adjustControlHeightBasedOnLineHeight(int lineHeight) +{ + setHeight(height() + lineHeight * static_cast<HTMLTextAreaElement*>(node())->rows()); +} + +int RenderTextControlMultiLine::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const +{ + return RenderBox::baselinePosition(baselineType, firstLine, direction, linePositionMode); +} + +void RenderTextControlMultiLine::updateFromElement() +{ + createSubtreeIfNeeded(0); + RenderTextControl::updateFromElement(); + + setInnerTextValue(static_cast<HTMLTextAreaElement*>(node())->value()); +} + +void RenderTextControlMultiLine::cacheSelection(int start, int end) +{ + static_cast<HTMLTextAreaElement*>(node())->cacheSelection(start, end); +} + +PassRefPtr<RenderStyle> RenderTextControlMultiLine::createInnerTextStyle(const RenderStyle* startStyle) const +{ + RefPtr<RenderStyle> textBlockStyle = RenderStyle::create(); + textBlockStyle->inheritFrom(startStyle); + adjustInnerTextStyle(startStyle, textBlockStyle.get()); + textBlockStyle->setDisplay(BLOCK); + + return textBlockStyle.release(); +} + +RenderStyle* RenderTextControlMultiLine::textBaseStyle() const +{ + return style(); +} + +int RenderTextControlMultiLine::textBlockInsetLeft() const +{ + int inset = borderLeft() + paddingLeft(); + if (HTMLElement* innerText = innerTextElement()) { + if (RenderBox* innerTextRenderer = innerText->renderBox()) + inset += innerTextRenderer->paddingLeft(); + } + return inset; +} + +int RenderTextControlMultiLine::textBlockInsetRight() const +{ + int inset = borderRight() + paddingRight(); + if (HTMLElement* innerText = innerTextElement()) { + if (RenderBox* innerTextRenderer = innerText->renderBox()) + inset += innerTextRenderer->paddingRight(); + } + return inset; +} + +int RenderTextControlMultiLine::textBlockInsetTop() const +{ + int inset = borderTop() + paddingTop(); + if (HTMLElement* innerText = innerTextElement()) { + if (RenderBox* innerTextRenderer = innerText->renderBox()) + inset += innerTextRenderer->paddingTop(); + } + return inset; +} + +} diff --git a/Source/WebCore/rendering/RenderTextControlMultiLine.h b/Source/WebCore/rendering/RenderTextControlMultiLine.h new file mode 100644 index 0000000..0bb8c8f --- /dev/null +++ b/Source/WebCore/rendering/RenderTextControlMultiLine.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderTextControlMultiLine_h +#define RenderTextControlMultiLine_h + +#include "RenderTextControl.h" + +namespace WebCore { + +class RenderTextControlMultiLine : public RenderTextControl { +public: + RenderTextControlMultiLine(Node*, bool); + virtual ~RenderTextControlMultiLine(); + + void forwardEvent(Event*); + +private: + virtual bool isTextArea() const { return true; } + + virtual void subtreeHasChanged(); + + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + + virtual float getAvgCharWidth(AtomicString family); + virtual int preferredContentWidth(float charWidth) const; + virtual void adjustControlHeightBasedOnLineHeight(int lineHeight); + virtual int baselinePosition(FontBaseline, bool firstLine, LineDirectionMode, LinePositionMode = PositionOnContainingLine) const; + + virtual void updateFromElement(); + virtual void cacheSelection(int start, int end); + + virtual RenderStyle* textBaseStyle() const; + virtual PassRefPtr<RenderStyle> createInnerTextStyle(const RenderStyle* startStyle) const; + virtual int textBlockInsetLeft() const; + virtual int textBlockInsetRight() const; + virtual int textBlockInsetTop() const; +}; + +inline RenderTextControlMultiLine* toRenderTextControlMultiLine(RenderObject* object) +{ + ASSERT(!object || object->isTextArea()); + return static_cast<RenderTextControlMultiLine*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderTextControlMultiLine(const RenderTextControlMultiLine*); + +} + +#endif diff --git a/Source/WebCore/rendering/RenderTextControlSingleLine.cpp b/Source/WebCore/rendering/RenderTextControlSingleLine.cpp new file mode 100644 index 0000000..29538ce --- /dev/null +++ b/Source/WebCore/rendering/RenderTextControlSingleLine.cpp @@ -0,0 +1,1151 @@ +/** + * Copyright (C) 2006, 2007, 2010 Apple Inc. All rights reserved. + * (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * + * 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 "RenderTextControlSingleLine.h" + +#include "Chrome.h" +#include "CSSStyleSelector.h" +#include "Event.h" +#include "EventNames.h" +#include "Frame.h" +#include "FrameView.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "HitTestResult.h" +#include "InputElement.h" +#include "LocalizedStrings.h" +#include "MouseEvent.h" +#include "PlatformKeyboardEvent.h" +#include "RenderLayer.h" +#include "RenderScrollbar.h" +#include "RenderTheme.h" +#include "SelectionController.h" +#include "Settings.h" +#include "SimpleFontData.h" +#include "TextControlInnerElements.h" + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +RenderTextControlSingleLine::RenderTextControlSingleLine(Node* node, bool placeholderVisible) + : RenderTextControl(node, placeholderVisible) + , m_searchPopupIsVisible(false) + , m_shouldDrawCapsLockIndicator(false) + , m_searchEventTimer(this, &RenderTextControlSingleLine::searchEventTimerFired) + , m_searchPopup(0) +{ +} + +RenderTextControlSingleLine::~RenderTextControlSingleLine() +{ + if (m_searchPopup) { + m_searchPopup->popupMenu()->disconnectClient(); + m_searchPopup = 0; + } + + if (m_innerBlock) { + m_innerBlock->detach(); + m_innerBlock = 0; + } + + if (m_innerSpinButton) + m_innerSpinButton->detach(); + if (m_outerSpinButton) + m_outerSpinButton->detach(); +#if ENABLE(INPUT_SPEECH) + if (m_speechButton) + m_speechButton->detach(); +#endif +} + +RenderStyle* RenderTextControlSingleLine::textBaseStyle() const +{ + return m_innerBlock ? m_innerBlock->renderer()->style() : style(); +} + +void RenderTextControlSingleLine::addSearchResult() +{ + ASSERT(node()->isHTMLElement()); + HTMLInputElement* input = static_cast<HTMLInputElement*>(node()); + if (input->maxResults() <= 0) + return; + + String value = input->value(); + if (value.isEmpty()) + return; + + Settings* settings = document()->settings(); + if (!settings || settings->privateBrowsingEnabled()) + return; + + int size = static_cast<int>(m_recentSearches.size()); + for (int i = size - 1; i >= 0; --i) { + if (m_recentSearches[i] == value) + m_recentSearches.remove(i); + } + + m_recentSearches.insert(0, value); + while (static_cast<int>(m_recentSearches.size()) > input->maxResults()) + m_recentSearches.removeLast(); + + const AtomicString& name = autosaveName(); + if (!m_searchPopup) + m_searchPopup = document()->page()->chrome()->createSearchPopupMenu(this); + + m_searchPopup->saveRecentSearches(name, m_recentSearches); +} + +void RenderTextControlSingleLine::stopSearchEventTimer() +{ + ASSERT(node()->isHTMLElement()); + m_searchEventTimer.stop(); +} + +void RenderTextControlSingleLine::showPopup() +{ + ASSERT(node()->isHTMLElement()); + if (m_searchPopupIsVisible) + return; + + if (!m_searchPopup) + m_searchPopup = document()->page()->chrome()->createSearchPopupMenu(this); + + if (!m_searchPopup->enabled()) + return; + + m_searchPopupIsVisible = true; + + const AtomicString& name = autosaveName(); + m_searchPopup->loadRecentSearches(name, m_recentSearches); + + // Trim the recent searches list if the maximum size has changed since we last saved. + HTMLInputElement* input = static_cast<HTMLInputElement*>(node()); + if (static_cast<int>(m_recentSearches.size()) > input->maxResults()) { + do { + m_recentSearches.removeLast(); + } while (static_cast<int>(m_recentSearches.size()) > input->maxResults()); + + m_searchPopup->saveRecentSearches(name, m_recentSearches); + } + + m_searchPopup->popupMenu()->show(absoluteBoundingBoxRect(true), document()->view(), -1); +} + +void RenderTextControlSingleLine::hidePopup() +{ + ASSERT(node()->isHTMLElement()); + if (m_searchPopup) + m_searchPopup->popupMenu()->hide(); +} + +void RenderTextControlSingleLine::subtreeHasChanged() +{ + bool wasChanged = wasChangedSinceLastChangeEvent(); + RenderTextControl::subtreeHasChanged(); + + InputElement* input = inputElement(); + // We don't need to call sanitizeUserInputValue() function here because + // InputElement::handleBeforeTextInsertedEvent() has already called + // sanitizeUserInputValue(). + // sanitizeValue() is needed because IME input doesn't dispatch BeforeTextInsertedEvent. + String value = text(); + if (input->isAcceptableValue(value)) + input->setValueFromRenderer(input->sanitizeValue(value)); + if (node()->isHTMLElement()) { + // Recalc for :invalid and hasUnacceptableValue() change. + static_cast<HTMLInputElement*>(input)->setNeedsStyleRecalc(); + } + + if (m_cancelButton) + updateCancelButtonVisibility(); + + // If the incremental attribute is set, then dispatch the search event + if (input->searchEventsShouldBeDispatched()) + startSearchEventTimer(); + + if (!wasChanged && node()->focused()) { + if (Frame* frame = this->frame()) + frame->editor()->textFieldDidBeginEditing(static_cast<Element*>(node())); + } + + if (node()->focused()) { + if (Frame* frame = document()->frame()) + frame->editor()->textDidChangeInTextField(static_cast<Element*>(node())); + } +} + +void RenderTextControlSingleLine::paint(PaintInfo& paintInfo, int tx, int ty) +{ + RenderTextControl::paint(paintInfo, tx, ty); + + if (paintInfo.phase == PaintPhaseBlockBackground && m_shouldDrawCapsLockIndicator) { + IntRect contentsRect = contentBoxRect(); + + // Center vertically like the text. + contentsRect.setY((height() - contentsRect.height()) / 2); + + // Convert the rect into the coords used for painting the content + contentsRect.move(tx + x(), ty + y()); + theme()->paintCapsLockIndicator(this, paintInfo, contentsRect); + } +} + +void RenderTextControlSingleLine::paintBoxDecorations(PaintInfo& paintInfo, int tx, int ty) +{ + paintBoxDecorationsWithSize(paintInfo, tx, ty, width() - decorationWidthRight(), height()); +} + +void RenderTextControlSingleLine::addFocusRingRects(Vector<IntRect>& rects, int tx, int ty) +{ + int w = width() - decorationWidthRight(); + if (w && height()) + rects.append(IntRect(tx, ty, w, height())); +} + +void RenderTextControlSingleLine::layout() +{ + int oldHeight = height(); + computeLogicalHeight(); + + int oldWidth = width(); + computeLogicalWidth(); + + bool relayoutChildren = oldHeight != height() || oldWidth != width(); + +#ifdef ANDROID_LAYOUT + checkAndSetRelayoutChildren(&relayoutChildren); +#endif + + RenderBox* innerTextRenderer = innerTextElement()->renderBox(); + RenderBox* innerBlockRenderer = m_innerBlock ? m_innerBlock->renderBox() : 0; + + // Set the text block height + int desiredHeight = textBlockHeight(); + int currentHeight = innerTextRenderer->height(); + + if (currentHeight > height()) { + if (desiredHeight != currentHeight) + relayoutChildren = true; + innerTextRenderer->style()->setHeight(Length(desiredHeight, Fixed)); + if (m_innerBlock) + innerBlockRenderer->style()->setHeight(Length(desiredHeight, Fixed)); + } + + // Set the text block width + int desiredWidth = textBlockWidth(); + if (desiredWidth != innerTextRenderer->width()) + relayoutChildren = true; + innerTextRenderer->style()->setWidth(Length(desiredWidth, Fixed)); + + if (m_innerBlock) { + int innerBlockWidth = width() - borderAndPaddingWidth(); + if (innerBlockWidth != innerBlockRenderer->width()) + relayoutChildren = true; + innerBlockRenderer->style()->setWidth(Length(innerBlockWidth, Fixed)); + } + + RenderBlock::layoutBlock(relayoutChildren); + + // Center the child block vertically + RenderBox* childBlock = innerBlockRenderer ? innerBlockRenderer : innerTextRenderer; + currentHeight = childBlock->height(); + if (currentHeight < height()) + childBlock->setY((height() - currentHeight) / 2); + + // Ignores the paddings for the inner spin button. + if (RenderBox* spinBox = m_innerSpinButton ? m_innerSpinButton->renderBox() : 0) { + spinBox->setLocation(spinBox->x() + paddingRight(), borderTop()); + spinBox->setHeight(height() - borderTop() - borderBottom()); + } + +#if ENABLE(INPUT_SPEECH) + if (RenderBox* button = m_speechButton ? m_speechButton->renderBox() : 0) { + if (m_innerBlock) { + // This is mostly the case where this is a search field. The speech button is a sibling + // of the inner block and laid out at the far right. + int x = width() - borderAndPaddingWidth() - button->width() - button->borderAndPaddingWidth(); + int y = (height() - button->height()) / 2; + button->setLocation(x, y); + } else { + // For non-search fields which are simpler and we let the defaut layout handle things + // except for small tweaking below. + button->setLocation(button->x() + paddingRight(), (height() - button->height()) / 2); + } + } +#endif + + // Center the spin button vertically, and move it to the right by + // padding + border of the text fields. + if (RenderBox* spinBox = m_outerSpinButton ? m_outerSpinButton->renderBox() : 0) { + int diff = height() - spinBox->height(); + // If the diff is odd, the top area over the spin button takes the + // remaining one pixel. It's good for Mac NSStepper because it has + // shadow at the bottom. + int y = (diff / 2) + (diff % 2); + int x = width() - borderRight() - paddingRight() - spinBox->width(); + spinBox->setLocation(x, y); + } +} + +bool RenderTextControlSingleLine::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int xPos, int yPos, int tx, int ty, HitTestAction hitTestAction) +{ + // If we're within the text control, we want to act as if we've hit the inner text block element, in case the point + // was on the control but not on the inner element (see Radar 4617841). + + // In a search field, we want to act as if we've hit the results block if we're to the left of the inner text block, + // and act as if we've hit the close block if we're to the right of the inner text block. + + if (!RenderTextControl::nodeAtPoint(request, result, xPos, yPos, tx, ty, hitTestAction)) + return false; + + // If we hit a node inside the inner text element, say that we hit that element, + // and if we hit our node (e.g. we're over the border or padding), also say that we hit the + // inner text element so that it gains focus. + if (result.innerNode()->isDescendantOf(innerTextElement()) || result.innerNode() == node()) + hitInnerTextElement(result, xPos, yPos, tx, ty); + + // If we found a spin button, we're done. + if (m_innerSpinButton && result.innerNode() == m_innerSpinButton) + return true; + if (m_outerSpinButton && result.innerNode() == m_outerSpinButton) + return true; +#if ENABLE(INPUT_SPEECH) + if (m_speechButton && result.innerNode() == m_speechButton) + return true; +#endif + // If we're not a search field, or we already found the speech, results or cancel buttons, we're done. + if (!m_innerBlock || result.innerNode() == m_resultsButton || result.innerNode() == m_cancelButton) + return true; + + Node* innerNode = 0; + RenderBox* innerBlockRenderer = m_innerBlock->renderBox(); + RenderBox* innerTextRenderer = innerTextElement()->renderBox(); + + IntPoint localPoint = result.localPoint(); + localPoint.move(-innerBlockRenderer->x(), -innerBlockRenderer->y()); + + int textLeft = tx + x() + innerBlockRenderer->x() + innerTextRenderer->x(); + if (m_resultsButton && m_resultsButton->renderer() && xPos < textLeft) + innerNode = m_resultsButton.get(); + + if (!innerNode) { + int textRight = textLeft + innerTextRenderer->width(); + if (m_cancelButton && m_cancelButton->renderer() && xPos > textRight) + innerNode = m_cancelButton.get(); + } + + if (innerNode) { + result.setInnerNode(innerNode); + localPoint.move(-innerNode->renderBox()->x(), -innerNode->renderBox()->y()); + } + + result.setLocalPoint(localPoint); + return true; +} + +void RenderTextControlSingleLine::forwardEvent(Event* event) +{ + RenderBox* innerTextRenderer = innerTextElement()->renderBox(); + + if (event->type() == eventNames().blurEvent) { + if (innerTextRenderer) { + if (RenderLayer* innerLayer = innerTextRenderer->layer()) + innerLayer->scrollToOffset(!style()->isLeftToRightDirection() ? innerLayer->scrollWidth() : 0, 0); + } + + capsLockStateMayHaveChanged(); + } else if (event->type() == eventNames().focusEvent) + capsLockStateMayHaveChanged(); + + if (!event->isMouseEvent()) { + RenderTextControl::forwardEvent(event); + return; + } + +#if ENABLE(INPUT_SPEECH) + if (RenderBox* speechBox = m_speechButton ? m_speechButton->renderBox() : 0) { + FloatPoint pointInTextControlCoords = absoluteToLocal(static_cast<MouseEvent*>(event)->absoluteLocation(), false, true); + if (speechBox->frameRect().contains(roundedIntPoint(pointInTextControlCoords))) { + m_speechButton->defaultEventHandler(event); + return; + } + } +#endif + + FloatPoint localPoint = innerTextRenderer->absoluteToLocal(static_cast<MouseEvent*>(event)->absoluteLocation(), false, true); + int textRight = innerTextRenderer->borderBoxRect().right(); + + if (m_resultsButton && localPoint.x() < innerTextRenderer->borderBoxRect().x()) + m_resultsButton->defaultEventHandler(event); + else if (m_cancelButton && localPoint.x() > textRight) + m_cancelButton->defaultEventHandler(event); + else if (m_innerSpinButton && localPoint.x() > textRight && m_innerSpinButton->renderBox() && localPoint.x() < textRight + m_innerSpinButton->renderBox()->width()) + m_innerSpinButton->defaultEventHandler(event); + else if (m_outerSpinButton && localPoint.x() > textRight) + m_outerSpinButton->defaultEventHandler(event); + else + RenderTextControl::forwardEvent(event); +} + +void RenderTextControlSingleLine::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderTextControl::styleDidChange(diff, oldStyle); + + if (RenderObject* innerBlockRenderer = m_innerBlock ? m_innerBlock->renderer() : 0) { + // We may have set the width and the height in the old style in layout(). + // Reset them now to avoid getting a spurious layout hint. + innerBlockRenderer->style()->setHeight(Length()); + innerBlockRenderer->style()->setWidth(Length()); + innerBlockRenderer->setStyle(createInnerBlockStyle(style())); + } + + if (RenderObject* resultsRenderer = m_resultsButton ? m_resultsButton->renderer() : 0) + resultsRenderer->setStyle(createResultsButtonStyle(style())); + + if (RenderObject* cancelRenderer = m_cancelButton ? m_cancelButton->renderer() : 0) + cancelRenderer->setStyle(createCancelButtonStyle(style())); + + if (RenderObject* spinRenderer = m_outerSpinButton ? m_outerSpinButton->renderer() : 0) + spinRenderer->setStyle(createOuterSpinButtonStyle()); + +#if ENABLE(INPUT_SPEECH) + if (RenderObject* speechRenderer = m_speechButton ? m_speechButton->renderer() : 0) + speechRenderer->setStyle(createSpeechButtonStyle()); +#endif + + setHasOverflowClip(false); +} + +void RenderTextControlSingleLine::capsLockStateMayHaveChanged() +{ + if (!node() || !document()) + return; + + // Only draw the caps lock indicator if these things are true: + // 1) The field is a password field + // 2) The frame is active + // 3) The element is focused + // 4) The caps lock is on + bool shouldDrawCapsLockIndicator = false; + + if (Frame* frame = document()->frame()) + shouldDrawCapsLockIndicator = inputElement()->isPasswordField() + && frame->selection()->isFocusedAndActive() + && document()->focusedNode() == node() + && PlatformKeyboardEvent::currentCapsLockState(); + + if (shouldDrawCapsLockIndicator != m_shouldDrawCapsLockIndicator) { + m_shouldDrawCapsLockIndicator = shouldDrawCapsLockIndicator; + repaint(); + } +} + +bool RenderTextControlSingleLine::hasControlClip() const +{ + bool clip = m_cancelButton; + return clip; +} + +IntRect RenderTextControlSingleLine::controlClipRect(int tx, int ty) const +{ + // This should only get called for search & speech inputs. + ASSERT(hasControlClip()); + + IntRect clipRect = IntRect(m_innerBlock->renderBox()->frameRect()); + clipRect.move(tx, ty); + return clipRect; +} + +int RenderTextControlSingleLine::textBlockWidth() const +{ + int width = RenderTextControl::textBlockWidth(); + + if (RenderBox* resultsRenderer = m_resultsButton ? m_resultsButton->renderBox() : 0) { + resultsRenderer->computeLogicalWidth(); + width -= resultsRenderer->width() + resultsRenderer->marginLeft() + resultsRenderer->marginRight(); + } + + if (RenderBox* cancelRenderer = m_cancelButton ? m_cancelButton->renderBox() : 0) { + cancelRenderer->computeLogicalWidth(); + width -= cancelRenderer->width() + cancelRenderer->marginLeft() + cancelRenderer->marginRight(); + } + + if (RenderBox* spinRenderer = m_innerSpinButton ? m_innerSpinButton->renderBox() : 0) { + spinRenderer->computeLogicalWidth(); + width -= spinRenderer->width() + spinRenderer->marginLeft() + spinRenderer->marginRight(); + } + +#if ENABLE(INPUT_SPEECH) + if (RenderBox* speechRenderer = m_speechButton ? m_speechButton->renderBox() : 0) { + speechRenderer->computeLogicalWidth(); + width -= speechRenderer->width() + speechRenderer->marginLeft() + speechRenderer->marginRight(); + } +#endif + + return width - decorationWidthRight(); +} + +int RenderTextControlSingleLine::decorationWidthRight() const +{ + int width = 0; + if (RenderBox* spinRenderer = m_outerSpinButton ? m_outerSpinButton->renderBox() : 0) { + spinRenderer->computeLogicalWidth(); + width += spinRenderer->width() + spinRenderer->marginLeft() + spinRenderer->marginRight(); + } + if (width > 0) + width += paddingRight() + borderRight(); + return width; +} + +float RenderTextControlSingleLine::getAvgCharWidth(AtomicString family) +{ + // Since Lucida Grande is the default font, we want this to match the width + // of MS Shell Dlg, the default font for textareas in Firefox, Safari Win and + // IE for some encodings (in IE, the default font is encoding specific). + // 901 is the avgCharWidth value in the OS/2 table for MS Shell Dlg. + if (family == AtomicString("Lucida Grande")) + return scaleEmToUnits(901); + + return RenderTextControl::getAvgCharWidth(family); +} + +int RenderTextControlSingleLine::preferredContentWidth(float charWidth) const +{ + int factor = inputElement()->size(); + if (factor <= 0) + factor = 20; + + int result = static_cast<int>(ceilf(charWidth * factor)); + + float maxCharWidth = 0.f; + AtomicString family = style()->font().family().family(); + // Since Lucida Grande is the default font, we want this to match the width + // of MS Shell Dlg, the default font for textareas in Firefox, Safari Win and + // IE for some encodings (in IE, the default font is encoding specific). + // 4027 is the (xMax - xMin) value in the "head" font table for MS Shell Dlg. + if (family == AtomicString("Lucida Grande")) + maxCharWidth = scaleEmToUnits(4027); + else if (hasValidAvgCharWidth(family)) + maxCharWidth = roundf(style()->font().primaryFont()->maxCharWidth()); + + // For text inputs, IE adds some extra width. + if (maxCharWidth > 0.f) + result += maxCharWidth - charWidth; + + if (RenderBox* resultsRenderer = m_resultsButton ? m_resultsButton->renderBox() : 0) + result += resultsRenderer->borderLeft() + resultsRenderer->borderRight() + + resultsRenderer->paddingLeft() + resultsRenderer->paddingRight(); + + if (RenderBox* cancelRenderer = m_cancelButton ? m_cancelButton->renderBox() : 0) + result += cancelRenderer->borderLeft() + cancelRenderer->borderRight() + + cancelRenderer->paddingLeft() + cancelRenderer->paddingRight(); + + if (RenderBox* spinRenderer = m_innerSpinButton ? m_innerSpinButton->renderBox() : 0) + result += spinRenderer->minPreferredLogicalWidth(); + +#if ENABLE(INPUT_SPEECH) + if (RenderBox* speechRenderer = m_speechButton ? m_speechButton->renderBox() : 0) { + result += speechRenderer->borderLeft() + speechRenderer->borderRight() + + speechRenderer->paddingLeft() + speechRenderer->paddingRight(); + } +#endif + return result; +} + +int RenderTextControlSingleLine::preferredDecorationWidthRight() const +{ + int width = 0; + if (RenderBox* spinRenderer = m_outerSpinButton ? m_outerSpinButton->renderBox() : 0) { + spinRenderer->computeLogicalWidth(); + width += spinRenderer->minPreferredLogicalWidth() + spinRenderer->marginLeft() + spinRenderer->marginRight(); + } + if (width > 0) + width += paddingRight() + borderRight(); + return width; +} + +void RenderTextControlSingleLine::adjustControlHeightBasedOnLineHeight(int lineHeight) +{ + if (RenderBox* resultsRenderer = m_resultsButton ? m_resultsButton->renderBox() : 0) { + resultsRenderer->computeLogicalHeight(); + setHeight(max(height(), + resultsRenderer->borderTop() + resultsRenderer->borderBottom() + + resultsRenderer->paddingTop() + resultsRenderer->paddingBottom() + + resultsRenderer->marginTop() + resultsRenderer->marginBottom())); + lineHeight = max(lineHeight, resultsRenderer->height()); + } + if (RenderBox* cancelRenderer = m_cancelButton ? m_cancelButton->renderBox() : 0) { + cancelRenderer->computeLogicalHeight(); + setHeight(max(height(), + cancelRenderer->borderTop() + cancelRenderer->borderBottom() + + cancelRenderer->paddingTop() + cancelRenderer->paddingBottom() + + cancelRenderer->marginTop() + cancelRenderer->marginBottom())); + lineHeight = max(lineHeight, cancelRenderer->height()); + } + + setHeight(height() + lineHeight); +} + +void RenderTextControlSingleLine::createSubtreeIfNeeded() +{ + bool createSubtree = inputElement()->isSearchField(); + if (!createSubtree) { + RenderTextControl::createSubtreeIfNeeded(m_innerBlock.get()); +#if ENABLE(INPUT_SPEECH) + if (inputElement()->isSpeechEnabled() && !m_speechButton) { + // Create the speech button element. + m_speechButton = InputFieldSpeechButtonElement::create(static_cast<HTMLElement*>(node())); + m_speechButton->attachInnerElement(node(), createSpeechButtonStyle(), renderArena()); + } +#endif + bool hasSpinButton = inputElement()->hasSpinButton(); + if (hasSpinButton && !m_innerSpinButton) { + m_innerSpinButton = SpinButtonElement::create(static_cast<HTMLElement*>(node())); + m_innerSpinButton->attachInnerElement(node(), createInnerSpinButtonStyle(), renderArena()); + } + if (hasSpinButton && !m_outerSpinButton) { + m_outerSpinButton = SpinButtonElement::create(static_cast<HTMLElement*>(node())); + m_outerSpinButton->attachInnerElement(node(), createOuterSpinButtonStyle(), renderArena()); + } + return; + } + + if (!m_innerBlock) { + // Create the inner block element + m_innerBlock = TextControlInnerElement::create(static_cast<HTMLElement*>(node())); + m_innerBlock->attachInnerElement(node(), createInnerBlockStyle(style()), renderArena()); + } +#if ENABLE(INPUT_SPEECH) + if (inputElement()->isSpeechEnabled() && !m_speechButton) { + // Create the speech button element. + m_speechButton = InputFieldSpeechButtonElement::create(static_cast<HTMLElement*>(node())); + m_speechButton->attachInnerElement(node(), createSpeechButtonStyle(), renderArena()); + } +#endif + if (inputElement()->hasSpinButton() && !m_outerSpinButton) { + m_outerSpinButton = SpinButtonElement::create(static_cast<HTMLElement*>(node())); + m_outerSpinButton->attachInnerElement(node(), createOuterSpinButtonStyle(), renderArena()); + } + + if (inputElement()->isSearchField()) { + if (!m_resultsButton) { + // Create the search results button element. + m_resultsButton = SearchFieldResultsButtonElement::create(document()); + m_resultsButton->attachInnerElement(m_innerBlock.get(), createResultsButtonStyle(m_innerBlock->renderer()->style()), renderArena()); + } + } + + // Create innerText element before adding the other buttons. + RenderTextControl::createSubtreeIfNeeded(m_innerBlock.get()); + + if (inputElement()->isSearchField()) { + if (!m_cancelButton) { + // Create the cancel button element. + m_cancelButton = SearchFieldCancelButtonElement::create(document()); + m_cancelButton->attachInnerElement(m_innerBlock.get(), createCancelButtonStyle(m_innerBlock->renderer()->style()), renderArena()); + } + } +} + +#if ENABLE(INPUT_SPEECH) +void RenderTextControlSingleLine::speechAttributeChanged() +{ + // The inner text element of this renderer has different styles depending on whether the + // speech button is visible or not. So when the speech attribute changes, we reset the + // whole thing and recreate to get the right styles and layout. + if (m_speechButton) + m_speechButton->detach(); + setChildrenInline(true); + RenderStyle* parentStyle = m_innerBlock ? m_innerBlock->renderer()->style() : style(); + setStyle(createInnerTextStyle(parentStyle)); + updateFromElement(); +} +#endif + +void RenderTextControlSingleLine::updateFromElement() +{ + createSubtreeIfNeeded(); + RenderTextControl::updateFromElement(); + + if (m_cancelButton) + updateCancelButtonVisibility(); + + if (!inputElement()->suggestedValue().isNull()) + setInnerTextValue(inputElement()->suggestedValue()); + else { + if (node()->hasTagName(inputTag)) { + // For HTMLInputElement, update the renderer value if the formControlValueMatchesRenderer() + // flag is false. It protects an unacceptable renderer value from + // being overwritten with the DOM value. + if (!static_cast<HTMLInputElement*>(node())->formControlValueMatchesRenderer()) + setInnerTextValue(inputElement()->value()); + } + } + + if (m_searchPopupIsVisible) + m_searchPopup->popupMenu()->updateFromElement(); +} + +void RenderTextControlSingleLine::cacheSelection(int start, int end) +{ + inputElement()->cacheSelection(start, end); +} + +PassRefPtr<RenderStyle> RenderTextControlSingleLine::createInnerTextStyle(const RenderStyle* startStyle) const +{ + RefPtr<RenderStyle> textBlockStyle = RenderStyle::create(); + textBlockStyle->inheritFrom(startStyle); + adjustInnerTextStyle(startStyle, textBlockStyle.get()); + + textBlockStyle->setWhiteSpace(PRE); + textBlockStyle->setWordWrap(NormalWordWrap); + textBlockStyle->setOverflowX(OHIDDEN); + textBlockStyle->setOverflowY(OHIDDEN); + + // Do not allow line-height to be smaller than our default. + if (textBlockStyle->font().lineSpacing() > lineHeight(true, HorizontalLine, PositionOfInteriorLineBoxes)) + textBlockStyle->setLineHeight(Length(-100.0f, Percent)); + + WebCore::EDisplay display = (m_innerBlock || inputElement()->hasSpinButton() ? INLINE_BLOCK : BLOCK); +#if ENABLE(INPUT_SPEECH) + if (inputElement()->isSpeechEnabled()) + display = INLINE_BLOCK; +#endif + textBlockStyle->setDisplay(display); + + // We're adding one extra pixel of padding to match WinIE. + textBlockStyle->setPaddingLeft(Length(1, Fixed)); + textBlockStyle->setPaddingRight(Length(1, Fixed)); + + return textBlockStyle.release(); +} + +PassRefPtr<RenderStyle> RenderTextControlSingleLine::createInnerBlockStyle(const RenderStyle* startStyle) const +{ + ASSERT(node()->isHTMLElement()); + + RefPtr<RenderStyle> innerBlockStyle = RenderStyle::create(); + innerBlockStyle->inheritFrom(startStyle); + + innerBlockStyle->setDisplay(inputElement()->hasSpinButton() ? INLINE_BLOCK : BLOCK); + innerBlockStyle->setDirection(LTR); + + // We don't want the shadow dom to be editable, so we set this block to read-only in case the input itself is editable. + innerBlockStyle->setUserModify(READ_ONLY); + + return innerBlockStyle.release(); +} + +PassRefPtr<RenderStyle> RenderTextControlSingleLine::createResultsButtonStyle(const RenderStyle* startStyle) const +{ + ASSERT(node()->isHTMLElement()); + HTMLInputElement* input = static_cast<HTMLInputElement*>(node()); + + RefPtr<RenderStyle> resultsBlockStyle; + if (input->maxResults() < 0) + resultsBlockStyle = getCachedPseudoStyle(SEARCH_DECORATION); + else if (!input->maxResults()) + resultsBlockStyle = getCachedPseudoStyle(SEARCH_RESULTS_DECORATION); + else + resultsBlockStyle = getCachedPseudoStyle(SEARCH_RESULTS_BUTTON); + + if (!resultsBlockStyle) + resultsBlockStyle = RenderStyle::create(); + + if (startStyle) + resultsBlockStyle->inheritFrom(startStyle); + + return resultsBlockStyle.release(); +} + +PassRefPtr<RenderStyle> RenderTextControlSingleLine::createCancelButtonStyle(const RenderStyle* startStyle) const +{ + ASSERT(node()->isHTMLElement()); + RefPtr<RenderStyle> cancelBlockStyle; + + if (RefPtr<RenderStyle> pseudoStyle = getCachedPseudoStyle(SEARCH_CANCEL_BUTTON)) + // We may be sharing style with another search field, but we must not share the cancel button style. + cancelBlockStyle = RenderStyle::clone(pseudoStyle.get()); + else + cancelBlockStyle = RenderStyle::create(); + + if (startStyle) + cancelBlockStyle->inheritFrom(startStyle); + + cancelBlockStyle->setVisibility(visibilityForCancelButton()); + return cancelBlockStyle.release(); +} + +PassRefPtr<RenderStyle> RenderTextControlSingleLine::createInnerSpinButtonStyle() const +{ + ASSERT(node()->isHTMLElement()); + RefPtr<RenderStyle> buttonStyle = getCachedPseudoStyle(INNER_SPIN_BUTTON); + if (!buttonStyle) + buttonStyle = RenderStyle::create(); + buttonStyle->inheritFrom(style()); + return buttonStyle.release(); +} + +PassRefPtr<RenderStyle> RenderTextControlSingleLine::createOuterSpinButtonStyle() const +{ + ASSERT(node()->isHTMLElement()); + RefPtr<RenderStyle> buttonStyle = getCachedPseudoStyle(OUTER_SPIN_BUTTON); + if (!buttonStyle) + buttonStyle = RenderStyle::create(); + buttonStyle->inheritFrom(style()); + return buttonStyle.release(); +} + +#if ENABLE(INPUT_SPEECH) +PassRefPtr<RenderStyle> RenderTextControlSingleLine::createSpeechButtonStyle() const +{ + ASSERT(node()->isHTMLElement()); + RefPtr<RenderStyle> buttonStyle = getCachedPseudoStyle(INPUT_SPEECH_BUTTON); + if (!buttonStyle) + buttonStyle = RenderStyle::create(); + buttonStyle->inheritFrom(style()); + return buttonStyle.release(); +} +#endif + +void RenderTextControlSingleLine::updateCancelButtonVisibility() const +{ + if (!m_cancelButton->renderer()) + return; + + const RenderStyle* curStyle = m_cancelButton->renderer()->style(); + EVisibility buttonVisibility = visibilityForCancelButton(); + if (curStyle->visibility() == buttonVisibility) + return; + + RefPtr<RenderStyle> cancelButtonStyle = RenderStyle::clone(curStyle); + cancelButtonStyle->setVisibility(buttonVisibility); + m_cancelButton->renderer()->setStyle(cancelButtonStyle); +} + +EVisibility RenderTextControlSingleLine::visibilityForCancelButton() const +{ + ASSERT(node()->isHTMLElement()); + HTMLInputElement* input = static_cast<HTMLInputElement*>(node()); + return input->value().isEmpty() ? HIDDEN : VISIBLE; +} + +const AtomicString& RenderTextControlSingleLine::autosaveName() const +{ + return static_cast<Element*>(node())->getAttribute(autosaveAttr); +} + +void RenderTextControlSingleLine::startSearchEventTimer() +{ + ASSERT(node()->isHTMLElement()); + unsigned length = text().length(); + + // If there's no text, fire the event right away. + if (!length) { + stopSearchEventTimer(); + static_cast<HTMLInputElement*>(node())->onSearch(); + return; + } + + // After typing the first key, we wait 0.5 seconds. + // After the second key, 0.4 seconds, then 0.3, then 0.2 from then on. + m_searchEventTimer.startOneShot(max(0.2, 0.6 - 0.1 * length)); +} + +void RenderTextControlSingleLine::searchEventTimerFired(Timer<RenderTextControlSingleLine>*) +{ + ASSERT(node()->isHTMLElement()); + static_cast<HTMLInputElement*>(node())->onSearch(); +} + +// PopupMenuClient methods +void RenderTextControlSingleLine::valueChanged(unsigned listIndex, bool fireEvents) +{ + ASSERT(node()->isHTMLElement()); + ASSERT(static_cast<int>(listIndex) < listSize()); + HTMLInputElement* input = static_cast<HTMLInputElement*>(node()); + if (static_cast<int>(listIndex) == (listSize() - 1)) { + if (fireEvents) { + m_recentSearches.clear(); + const AtomicString& name = autosaveName(); + if (!name.isEmpty()) { + if (!m_searchPopup) + m_searchPopup = document()->page()->chrome()->createSearchPopupMenu(this); + m_searchPopup->saveRecentSearches(name, m_recentSearches); + } + } + } else { + input->setValue(itemText(listIndex)); + if (fireEvents) + input->onSearch(); + input->select(); + } +} + +String RenderTextControlSingleLine::itemText(unsigned listIndex) const +{ + int size = listSize(); + if (size == 1) { + ASSERT(!listIndex); + return searchMenuNoRecentSearchesText(); + } + if (!listIndex) + return searchMenuRecentSearchesText(); + if (itemIsSeparator(listIndex)) + return String(); + if (static_cast<int>(listIndex) == (size - 1)) + return searchMenuClearRecentSearchesText(); + return m_recentSearches[listIndex - 1]; +} + +String RenderTextControlSingleLine::itemLabel(unsigned) const +{ + return String(); +} + +String RenderTextControlSingleLine::itemIcon(unsigned) const +{ + return String(); +} + +bool RenderTextControlSingleLine::itemIsEnabled(unsigned listIndex) const +{ + if (!listIndex || itemIsSeparator(listIndex)) + return false; + return true; +} + +PopupMenuStyle RenderTextControlSingleLine::itemStyle(unsigned) const +{ + return menuStyle(); +} + +PopupMenuStyle RenderTextControlSingleLine::menuStyle() const +{ + return PopupMenuStyle(style()->visitedDependentColor(CSSPropertyColor), style()->visitedDependentColor(CSSPropertyBackgroundColor), style()->font(), style()->visibility() == VISIBLE, style()->display() == NONE, style()->textIndent(), style()->direction()); +} + +int RenderTextControlSingleLine::clientInsetLeft() const +{ + // Inset the menu by the radius of the cap on the left so that + // it only runs along the straight part of the bezel. + return height() / 2; +} + +int RenderTextControlSingleLine::clientInsetRight() const +{ + // Inset the menu by the radius of the cap on the right so that + // it only runs along the straight part of the bezel (unless it needs + // to be wider). + return height() / 2; +} + +int RenderTextControlSingleLine::clientPaddingLeft() const +{ + int padding = paddingLeft(); + + if (RenderBox* resultsRenderer = m_resultsButton ? m_resultsButton->renderBox() : 0) + padding += resultsRenderer->width(); + + return padding; +} + +int RenderTextControlSingleLine::clientPaddingRight() const +{ + int padding = paddingRight(); + + if (RenderBox* cancelRenderer = m_cancelButton ? m_cancelButton->renderBox() : 0) + padding += cancelRenderer->width(); + + return padding; +} + +int RenderTextControlSingleLine::listSize() const +{ + // If there are no recent searches, then our menu will have 1 "No recent searches" item. + if (!m_recentSearches.size()) + return 1; + // Otherwise, leave room in the menu for a header, a separator, and the "Clear recent searches" item. + return m_recentSearches.size() + 3; +} + +int RenderTextControlSingleLine::selectedIndex() const +{ + return -1; +} + +void RenderTextControlSingleLine::popupDidHide() +{ + m_searchPopupIsVisible = false; +} + +bool RenderTextControlSingleLine::itemIsSeparator(unsigned listIndex) const +{ + // The separator will be the second to last item in our list. + return static_cast<int>(listIndex) == (listSize() - 2); +} + +bool RenderTextControlSingleLine::itemIsLabel(unsigned listIndex) const +{ + return listIndex == 0; +} + +bool RenderTextControlSingleLine::itemIsSelected(unsigned) const +{ + return false; +} + +void RenderTextControlSingleLine::setTextFromItem(unsigned listIndex) +{ + ASSERT(node()->isHTMLElement()); + static_cast<HTMLInputElement*>(node())->setValue(itemText(listIndex)); +} + +FontSelector* RenderTextControlSingleLine::fontSelector() const +{ + return document()->styleSelector()->fontSelector(); +} + +HostWindow* RenderTextControlSingleLine::hostWindow() const +{ + return document()->view()->hostWindow(); +} + +void RenderTextControlSingleLine::autoscroll() +{ + RenderLayer* layer = innerTextElement()->renderBox()->layer(); + if (layer) + layer->autoscroll(); +} + +int RenderTextControlSingleLine::scrollWidth() const +{ + if (innerTextElement()) + return innerTextElement()->scrollWidth(); + return RenderBlock::scrollWidth(); +} + +int RenderTextControlSingleLine::scrollHeight() const +{ + if (innerTextElement()) + return innerTextElement()->scrollHeight(); + return RenderBlock::scrollHeight(); +} + +int RenderTextControlSingleLine::scrollLeft() const +{ + if (innerTextElement()) + return innerTextElement()->scrollLeft(); + return RenderBlock::scrollLeft(); +} + +int RenderTextControlSingleLine::scrollTop() const +{ + if (innerTextElement()) + return innerTextElement()->scrollTop(); + return RenderBlock::scrollTop(); +} + +void RenderTextControlSingleLine::setScrollLeft(int newLeft) +{ + if (innerTextElement()) + innerTextElement()->setScrollLeft(newLeft); +} + +void RenderTextControlSingleLine::setScrollTop(int newTop) +{ + if (innerTextElement()) + innerTextElement()->setScrollTop(newTop); +} + +bool RenderTextControlSingleLine::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier, Node** stopNode) +{ + RenderLayer* layer = innerTextElement()->renderBox()->layer(); + if (layer && layer->scroll(direction, granularity, multiplier)) + return true; + return RenderBlock::scroll(direction, granularity, multiplier, stopNode); +} + +bool RenderTextControlSingleLine::logicalScroll(ScrollLogicalDirection direction, ScrollGranularity granularity, float multiplier, Node** stopNode) +{ + RenderLayer* layer = innerTextElement()->renderBox()->layer(); + if (layer && layer->scroll(logicalToPhysical(direction, style()->isHorizontalWritingMode(), style()->isFlippedBlocksWritingMode()), granularity, multiplier)) + return true; + return RenderBlock::logicalScroll(direction, granularity, multiplier, stopNode); +} + +PassRefPtr<Scrollbar> RenderTextControlSingleLine::createScrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, ScrollbarControlSize controlSize) +{ + RefPtr<Scrollbar> widget; + bool hasCustomScrollbarStyle = style()->hasPseudoStyle(SCROLLBAR); + if (hasCustomScrollbarStyle) + widget = RenderScrollbar::createCustomScrollbar(client, orientation, this); + else + widget = Scrollbar::createNativeScrollbar(client, orientation, controlSize); + return widget.release(); +} + +InputElement* RenderTextControlSingleLine::inputElement() const +{ + return toInputElement(static_cast<Element*>(node())); +} + +int RenderTextControlSingleLine::textBlockInsetLeft() const +{ + int inset = borderLeft() + clientPaddingLeft(); + if (HTMLElement* innerText = innerTextElement()) { + if (RenderBox* innerTextRenderer = innerText->renderBox()) + inset += innerTextRenderer->paddingLeft(); + } + return inset; +} + +int RenderTextControlSingleLine::textBlockInsetRight() const +{ + int inset = borderRight() + clientPaddingRight(); + if (HTMLElement* innerText = innerTextElement()) { + if (RenderBox* innerTextRenderer = innerText->renderBox()) + inset += innerTextRenderer->paddingRight(); + } + return inset; +} + +int RenderTextControlSingleLine::textBlockInsetTop() const +{ + RenderBox* innerRenderer = 0; + if (m_innerBlock) + innerRenderer = m_innerBlock->renderBox(); + else if (HTMLElement* innerText = innerTextElement()) + innerRenderer = innerText->renderBox(); + + if (innerRenderer) + return innerRenderer->y(); + + return borderTop() + paddingTop(); +} + +} diff --git a/Source/WebCore/rendering/RenderTextControlSingleLine.h b/Source/WebCore/rendering/RenderTextControlSingleLine.h new file mode 100644 index 0000000..51509c0 --- /dev/null +++ b/Source/WebCore/rendering/RenderTextControlSingleLine.h @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2006, 2007, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * + * 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. + * + */ + +#ifndef RenderTextControlSingleLine_h +#define RenderTextControlSingleLine_h + +#include "PopupMenuClient.h" +#include "RenderTextControl.h" +#include "SearchPopupMenu.h" +#include "Timer.h" + +namespace WebCore { + +class InputElement; +class InputFieldSpeechButtonElement; +class SearchFieldCancelButtonElement; +class SearchFieldResultsButtonElement; +class SpinButtonElement; +class TextControlInnerElement; + +class RenderTextControlSingleLine : public RenderTextControl, private PopupMenuClient { +public: + RenderTextControlSingleLine(Node*, bool); + virtual ~RenderTextControlSingleLine(); + + bool placeholderIsVisible() const { return m_placeholderVisible; } + bool placeholderShouldBeVisible() const; + + void addSearchResult(); + void stopSearchEventTimer(); + + bool popupIsVisible() const { return m_searchPopupIsVisible; } + void showPopup(); + void hidePopup(); + + void forwardEvent(Event*); + + void capsLockStateMayHaveChanged(); + + // Decoration width outside of the text field. + int decorationWidthRight() const; + +#if ENABLE(INPUT_SPEECH) + void speechAttributeChanged(); +#endif + +private: + int preferredDecorationWidthRight() const; + virtual bool hasControlClip() const; + virtual IntRect controlClipRect(int tx, int ty) const; + virtual bool isTextField() const { return true; } + + virtual void subtreeHasChanged(); + virtual void paint(PaintInfo&, int tx, int ty); + virtual void paintBoxDecorations(PaintInfo&, int tx, int ty); + virtual void addFocusRingRects(Vector<IntRect>&, int tx, int ty); + virtual void layout(); + + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + + virtual void autoscroll(); + + // Subclassed to forward to our inner div. + virtual int scrollLeft() const; + virtual int scrollTop() const; + virtual int scrollWidth() const; + virtual int scrollHeight() const; + virtual void setScrollLeft(int); + virtual void setScrollTop(int); + virtual bool scroll(ScrollDirection, ScrollGranularity, float multiplier = 1, Node** stopNode = 0); + virtual bool logicalScroll(ScrollLogicalDirection, ScrollGranularity, float multiplier = 1, Node** stopNode = 0); + + int textBlockWidth() const; + virtual float getAvgCharWidth(AtomicString family); + virtual int preferredContentWidth(float charWidth) const; + virtual void adjustControlHeightBasedOnLineHeight(int lineHeight); + + void createSubtreeIfNeeded(); + virtual void updateFromElement(); + virtual void cacheSelection(int start, int end); + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + + virtual RenderStyle* textBaseStyle() const; + virtual PassRefPtr<RenderStyle> createInnerTextStyle(const RenderStyle* startStyle) const; + PassRefPtr<RenderStyle> createInnerBlockStyle(const RenderStyle* startStyle) const; + PassRefPtr<RenderStyle> createResultsButtonStyle(const RenderStyle* startStyle) const; + PassRefPtr<RenderStyle> createCancelButtonStyle(const RenderStyle* startStyle) const; + PassRefPtr<RenderStyle> createInnerSpinButtonStyle() const; + PassRefPtr<RenderStyle> createOuterSpinButtonStyle() const; +#if ENABLE(INPUT_SPEECH) + PassRefPtr<RenderStyle> createSpeechButtonStyle() const; +#endif + + void updateCancelButtonVisibility() const; + EVisibility visibilityForCancelButton() const; + const AtomicString& autosaveName() const; + + void startSearchEventTimer(); + void searchEventTimerFired(Timer<RenderTextControlSingleLine>*); + + // PopupMenuClient methods + virtual void valueChanged(unsigned listIndex, bool fireEvents = true); + virtual void selectionChanged(unsigned, bool) {} + virtual void selectionCleared() {} + virtual String itemText(unsigned listIndex) const; + virtual String itemLabel(unsigned listIndex) const; + virtual String itemIcon(unsigned listIndex) const; + virtual String itemToolTip(unsigned) const { return String(); } + virtual String itemAccessibilityText(unsigned) const { return String(); } + virtual bool itemIsEnabled(unsigned listIndex) const; + virtual PopupMenuStyle itemStyle(unsigned listIndex) const; + virtual PopupMenuStyle menuStyle() const; + virtual int clientInsetLeft() const; + virtual int clientInsetRight() const; + virtual int clientPaddingLeft() const; + virtual int clientPaddingRight() const; + virtual int listSize() const; + virtual int selectedIndex() const; + virtual void popupDidHide(); + virtual bool itemIsSeparator(unsigned listIndex) const; + virtual bool itemIsLabel(unsigned listIndex) const; + virtual bool itemIsSelected(unsigned listIndex) const; + virtual bool shouldPopOver() const { return false; } + virtual bool valueShouldChangeOnHotTrack() const { return false; } + virtual void setTextFromItem(unsigned listIndex); + virtual FontSelector* fontSelector() const; + virtual HostWindow* hostWindow() const; + virtual PassRefPtr<Scrollbar> createScrollbar(ScrollbarClient*, ScrollbarOrientation, ScrollbarControlSize); + + InputElement* inputElement() const; + + virtual int textBlockInsetLeft() const; + virtual int textBlockInsetRight() const; + virtual int textBlockInsetTop() const; + + bool m_searchPopupIsVisible; + bool m_shouldDrawCapsLockIndicator; + + RefPtr<TextControlInnerElement> m_innerBlock; + RefPtr<SearchFieldResultsButtonElement> m_resultsButton; + RefPtr<SearchFieldCancelButtonElement> m_cancelButton; + RefPtr<TextControlInnerElement> m_innerSpinButton; + RefPtr<TextControlInnerElement> m_outerSpinButton; +#if ENABLE(INPUT_SPEECH) + RefPtr<InputFieldSpeechButtonElement> m_speechButton; +#endif + + Timer<RenderTextControlSingleLine> m_searchEventTimer; + RefPtr<SearchPopupMenu> m_searchPopup; + Vector<String> m_recentSearches; +}; + +inline RenderTextControlSingleLine* toRenderTextControlSingleLine(RenderObject* object) +{ + ASSERT(!object || object->isTextField()); + return static_cast<RenderTextControlSingleLine*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderTextControlSingleLine(const RenderTextControlSingleLine*); + +} + +#endif diff --git a/Source/WebCore/rendering/RenderTextFragment.cpp b/Source/WebCore/rendering/RenderTextFragment.cpp new file mode 100644 index 0000000..ec62d85 --- /dev/null +++ b/Source/WebCore/rendering/RenderTextFragment.cpp @@ -0,0 +1,117 @@ +/* + * (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * + * 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 "RenderTextFragment.h" + +#include "RenderBlock.h" +#include "Text.h" + +namespace WebCore { + +RenderTextFragment::RenderTextFragment(Node* node, StringImpl* str, int startOffset, int length) + : RenderText(node, str ? str->substring(startOffset, length) : 0) + , m_start(startOffset) + , m_end(length) + , m_firstLetter(0) +{ +} + +RenderTextFragment::RenderTextFragment(Node* node, StringImpl* str) + : RenderText(node, str) + , m_start(0) + , m_end(str ? str->length() : 0) + , m_contentString(str) + , m_firstLetter(0) +{ +} + +RenderTextFragment::~RenderTextFragment() +{ +} + +PassRefPtr<StringImpl> RenderTextFragment::originalText() const +{ + Node* e = node(); + RefPtr<StringImpl> result = ((e && e->isTextNode()) ? static_cast<Text*>(e)->dataImpl() : contentString()); + if (!result) + return 0; + return result->substring(start(), end()); +} + +void RenderTextFragment::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderText::styleDidChange(diff, oldStyle); + + if (RenderBlock* block = blockForAccompanyingFirstLetter()) { + block->style()->removeCachedPseudoStyle(FIRST_LETTER); + block->updateFirstLetter(); + } +} + +void RenderTextFragment::destroy() +{ + if (m_firstLetter) + m_firstLetter->destroy(); + RenderText::destroy(); +} + +void RenderTextFragment::setTextInternal(PassRefPtr<StringImpl> text) +{ + RenderText::setTextInternal(text); + if (m_firstLetter) { + ASSERT(!m_contentString); + m_firstLetter->destroy(); + m_firstLetter = 0; + m_start = 0; + m_end = textLength(); + if (Node* t = node()) { + ASSERT(!t->renderer()); + t->setRenderer(this); + } + } +} + +UChar RenderTextFragment::previousCharacter() const +{ + if (start()) { + Node* e = node(); + StringImpl* original = ((e && e->isTextNode()) ? static_cast<Text*>(e)->dataImpl() : contentString()); + if (original && start() <= original->length()) + return (*original)[start() - 1]; + } + + return RenderText::previousCharacter(); +} + +RenderBlock* RenderTextFragment::blockForAccompanyingFirstLetter() const +{ + if (!m_firstLetter) + return 0; + for (RenderObject* block = m_firstLetter->parent(); block; block = block->parent()) { + if (block->style()->hasPseudoStyle(FIRST_LETTER) && block->canHaveChildren() && block->isRenderBlock()) + return toRenderBlock(block); + } + return 0; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderTextFragment.h b/Source/WebCore/rendering/RenderTextFragment.h new file mode 100644 index 0000000..c251f81 --- /dev/null +++ b/Source/WebCore/rendering/RenderTextFragment.h @@ -0,0 +1,84 @@ +/* + * (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderTextFragment_h +#define RenderTextFragment_h + +#include "RenderText.h" + +namespace WebCore { + +// Used to represent a text substring of an element, e.g., for text runs that are split because of +// first letter and that must therefore have different styles (and positions in the render tree). +// We cache offsets so that text transformations can be applied in such a way that we can recover +// the original unaltered string from our corresponding DOM node. +class RenderTextFragment : public RenderText { +public: + RenderTextFragment(Node*, StringImpl*, int startOffset, int length); + RenderTextFragment(Node*, StringImpl*); + virtual ~RenderTextFragment(); + + virtual bool isTextFragment() const { return true; } + + virtual void destroy(); + + unsigned start() const { return m_start; } + unsigned end() const { return m_end; } + + RenderObject* firstLetter() const { return m_firstLetter; } + void setFirstLetter(RenderObject* firstLetter) { m_firstLetter = firstLetter; } + + StringImpl* contentString() const { return m_contentString.get(); } + virtual PassRefPtr<StringImpl> originalText() const; + +protected: + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + +private: + virtual void setTextInternal(PassRefPtr<StringImpl>); + virtual UChar previousCharacter() const; + RenderBlock* blockForAccompanyingFirstLetter() const; + + unsigned m_start; + unsigned m_end; + RefPtr<StringImpl> m_contentString; + RenderObject* m_firstLetter; +}; + +inline RenderTextFragment* toRenderTextFragment(RenderObject* object) +{ + ASSERT(!object || toRenderText(object)->isTextFragment()); + return static_cast<RenderTextFragment*>(object); +} + +inline const RenderTextFragment* toRenderTextFragment(const RenderObject* object) +{ + ASSERT(!object || toRenderText(object)->isTextFragment()); + return static_cast<const RenderTextFragment*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderTextFragment(const RenderTextFragment*); + +} // namespace WebCore + +#endif // RenderTextFragment_h diff --git a/Source/WebCore/rendering/RenderTheme.cpp b/Source/WebCore/rendering/RenderTheme.cpp new file mode 100644 index 0000000..538b6c6 --- /dev/null +++ b/Source/WebCore/rendering/RenderTheme.cpp @@ -0,0 +1,1134 @@ +/** + * This file is part of the theme implementation for form controls in WebCore. + * + * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Apple Computer, Inc. + * + * 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 "RenderTheme.h" + +#include "CSSValueKeywords.h" +#include "Document.h" +#include "FloatConversion.h" +#include "FocusController.h" +#include "FontSelector.h" +#include "Frame.h" +#include "GraphicsContext.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "MediaControlElements.h" +#include "Page.h" +#include "RenderStyle.h" +#include "RenderView.h" +#include "SelectionController.h" +#include "Settings.h" +#include "TextControlInnerElements.h" + +#if ENABLE(METER_TAG) +#include "HTMLMeterElement.h" +#include "RenderMeter.h" +#endif + +#if ENABLE(INPUT_SPEECH) +#include "RenderInputSpeech.h" +#endif + +// The methods in this file are shared by all themes on every platform. + +namespace WebCore { + +using namespace HTMLNames; + +static Color& customFocusRingColor() +{ + DEFINE_STATIC_LOCAL(Color, color, ()); + return color; +} + +RenderTheme::RenderTheme() +#if USE(NEW_THEME) + : m_theme(platformTheme()) +#endif +{ +} + +void RenderTheme::adjustStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e, + bool UAHasAppearance, const BorderData& border, const FillLayer& background, const Color& backgroundColor) +{ + // Force inline and table display styles to be inline-block (except for table- which is block) + ControlPart part = style->appearance(); + if (style->display() == INLINE || style->display() == INLINE_TABLE || style->display() == TABLE_ROW_GROUP || + style->display() == TABLE_HEADER_GROUP || style->display() == TABLE_FOOTER_GROUP || + style->display() == TABLE_ROW || style->display() == TABLE_COLUMN_GROUP || style->display() == TABLE_COLUMN || + style->display() == TABLE_CELL || style->display() == TABLE_CAPTION) + style->setDisplay(INLINE_BLOCK); + else if (style->display() == COMPACT || style->display() == RUN_IN || style->display() == LIST_ITEM || style->display() == TABLE) + style->setDisplay(BLOCK); + + if (UAHasAppearance && isControlStyled(style, border, background, backgroundColor)) { + if (part == MenulistPart) { + style->setAppearance(MenulistButtonPart); + part = MenulistButtonPart; + } else + style->setAppearance(NoControlPart); + } + + if (!style->hasAppearance()) + return; + + // Never support box-shadow on native controls. + style->setBoxShadow(0); + +#if USE(NEW_THEME) + switch (part) { + case ListButtonPart: + case CheckboxPart: + case InnerSpinButtonPart: + case OuterSpinButtonPart: + case RadioPart: + case PushButtonPart: + case SquareButtonPart: + case DefaultButtonPart: + case ButtonPart: { + // Border + LengthBox borderBox(style->borderTopWidth(), style->borderRightWidth(), style->borderBottomWidth(), style->borderLeftWidth()); + borderBox = m_theme->controlBorder(part, style->font(), borderBox, style->effectiveZoom()); + if (borderBox.top().value() != style->borderTopWidth()) { + if (borderBox.top().value()) + style->setBorderTopWidth(borderBox.top().value()); + else + style->resetBorderTop(); + } + if (borderBox.right().value() != style->borderRightWidth()) { + if (borderBox.right().value()) + style->setBorderRightWidth(borderBox.right().value()); + else + style->resetBorderRight(); + } + if (borderBox.bottom().value() != style->borderBottomWidth()) { + style->setBorderBottomWidth(borderBox.bottom().value()); + if (borderBox.bottom().value()) + style->setBorderBottomWidth(borderBox.bottom().value()); + else + style->resetBorderBottom(); + } + if (borderBox.left().value() != style->borderLeftWidth()) { + style->setBorderLeftWidth(borderBox.left().value()); + if (borderBox.left().value()) + style->setBorderLeftWidth(borderBox.left().value()); + else + style->resetBorderLeft(); + } + + // Padding + LengthBox paddingBox = m_theme->controlPadding(part, style->font(), style->paddingBox(), style->effectiveZoom()); + if (paddingBox != style->paddingBox()) + style->setPaddingBox(paddingBox); + + // Whitespace + if (m_theme->controlRequiresPreWhiteSpace(part)) + style->setWhiteSpace(PRE); + + // Width / Height + // The width and height here are affected by the zoom. + // FIXME: Check is flawed, since it doesn't take min-width/max-width into account. + LengthSize controlSize = m_theme->controlSize(part, style->font(), LengthSize(style->width(), style->height()), style->effectiveZoom()); + if (controlSize.width() != style->width()) + style->setWidth(controlSize.width()); + if (controlSize.height() != style->height()) + style->setHeight(controlSize.height()); + + // Min-Width / Min-Height + LengthSize minControlSize = m_theme->minimumControlSize(part, style->font(), style->effectiveZoom()); + if (minControlSize.width() != style->minWidth()) + style->setMinWidth(minControlSize.width()); + if (minControlSize.height() != style->minHeight()) + style->setMinHeight(minControlSize.height()); + + // Font + FontDescription controlFont = m_theme->controlFont(part, style->font(), style->effectiveZoom()); + if (controlFont != style->font().fontDescription()) { + // Reset our line-height + style->setLineHeight(RenderStyle::initialLineHeight()); + + // Now update our font. + if (style->setFontDescription(controlFont)) + style->font().update(0); + } + } + default: + break; + } +#endif + + // Call the appropriate style adjustment method based off the appearance value. + switch (style->appearance()) { +#if !USE(NEW_THEME) + case CheckboxPart: + return adjustCheckboxStyle(selector, style, e); + case RadioPart: + return adjustRadioStyle(selector, style, e); + case PushButtonPart: + case SquareButtonPart: + case ListButtonPart: + case DefaultButtonPart: + case ButtonPart: + return adjustButtonStyle(selector, style, e); + case InnerSpinButtonPart: + return adjustInnerSpinButtonStyle(selector, style, e); + case OuterSpinButtonPart: + return adjustOuterSpinButtonStyle(selector, style, e); +#endif + case TextFieldPart: + return adjustTextFieldStyle(selector, style, e); + case TextAreaPart: + return adjustTextAreaStyle(selector, style, e); +#if ENABLE(NO_LISTBOX_RENDERING) + case ListboxPart: + return adjustListboxStyle(selector, style, e); +#endif + case MenulistPart: + return adjustMenuListStyle(selector, style, e); + case MenulistButtonPart: + return adjustMenuListButtonStyle(selector, style, e); + case MediaSliderPart: + case MediaVolumeSliderPart: + case SliderHorizontalPart: + case SliderVerticalPart: + return adjustSliderTrackStyle(selector, style, e); + case SliderThumbHorizontalPart: + case SliderThumbVerticalPart: + return adjustSliderThumbStyle(selector, style, e); + case SearchFieldPart: + return adjustSearchFieldStyle(selector, style, e); + case SearchFieldCancelButtonPart: + return adjustSearchFieldCancelButtonStyle(selector, style, e); + case SearchFieldDecorationPart: + return adjustSearchFieldDecorationStyle(selector, style, e); + case SearchFieldResultsDecorationPart: + return adjustSearchFieldResultsDecorationStyle(selector, style, e); + case SearchFieldResultsButtonPart: + return adjustSearchFieldResultsButtonStyle(selector, style, e); +#if ENABLE(PROGRESS_TAG) + case ProgressBarPart: + return adjustProgressBarStyle(selector, style, e); +#endif +#if ENABLE(METER_TAG) + case MeterPart: + case RelevancyLevelIndicatorPart: + case ContinuousCapacityLevelIndicatorPart: + case DiscreteCapacityLevelIndicatorPart: + case RatingLevelIndicatorPart: + return adjustMeterStyle(selector, style, e); +#endif +#if ENABLE(INPUT_SPEECH) + case InputSpeechButtonPart: + return adjustInputFieldSpeechButtonStyle(selector, style, e); +#endif + default: + break; + } +} + +bool RenderTheme::paint(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + // If painting is disabled, but we aren't updating control tints, then just bail. + // If we are updating control tints, just schedule a repaint if the theme supports tinting + // for that control. + if (paintInfo.context->updatingControlTints()) { + if (controlSupportsTints(o)) + o->repaint(); + return false; + } + if (paintInfo.context->paintingDisabled()) + return false; + + ControlPart part = o->style()->appearance(); + +#if USE(NEW_THEME) + switch (part) { + case CheckboxPart: + case RadioPart: + case PushButtonPart: + case SquareButtonPart: + case ListButtonPart: + case DefaultButtonPart: + case ButtonPart: + case InnerSpinButtonPart: + case OuterSpinButtonPart: + m_theme->paint(part, controlStatesForRenderer(o), const_cast<GraphicsContext*>(paintInfo.context), r, o->style()->effectiveZoom(), o->view()->frameView()); + return false; + default: + break; + } +#endif + + // Call the appropriate paint method based off the appearance value. + switch (part) { +#if !USE(NEW_THEME) + case CheckboxPart: + return paintCheckbox(o, paintInfo, r); + case RadioPart: + return paintRadio(o, paintInfo, r); + case PushButtonPart: + case SquareButtonPart: + case ListButtonPart: + case DefaultButtonPart: + case ButtonPart: + return paintButton(o, paintInfo, r); + case InnerSpinButtonPart: + return paintInnerSpinButton(o, paintInfo, r); + case OuterSpinButtonPart: + return paintOuterSpinButton(o, paintInfo, r); +#endif + case MenulistPart: + return paintMenuList(o, paintInfo, r); +#if ENABLE(METER_TAG) + case MeterPart: + case RelevancyLevelIndicatorPart: + case ContinuousCapacityLevelIndicatorPart: + case DiscreteCapacityLevelIndicatorPart: + case RatingLevelIndicatorPart: + return paintMeter(o, paintInfo, r); +#endif +#if ENABLE(PROGRESS_TAG) + case ProgressBarPart: + return paintProgressBar(o, paintInfo, r); +#endif + case SliderHorizontalPart: + case SliderVerticalPart: + return paintSliderTrack(o, paintInfo, r); + case SliderThumbHorizontalPart: + case SliderThumbVerticalPart: + if (o->parent()->isSlider()) + return paintSliderThumb(o, paintInfo, r); + // We don't support drawing a slider thumb without a parent slider + break; + case MediaFullscreenButtonPart: + return paintMediaFullscreenButton(o, paintInfo, r); + case MediaPlayButtonPart: + return paintMediaPlayButton(o, paintInfo, r); + case MediaMuteButtonPart: + return paintMediaMuteButton(o, paintInfo, r); + case MediaSeekBackButtonPart: + return paintMediaSeekBackButton(o, paintInfo, r); + case MediaSeekForwardButtonPart: + return paintMediaSeekForwardButton(o, paintInfo, r); + case MediaRewindButtonPart: + return paintMediaRewindButton(o, paintInfo, r); + case MediaReturnToRealtimeButtonPart: + return paintMediaReturnToRealtimeButton(o, paintInfo, r); + case MediaToggleClosedCaptionsButtonPart: + return paintMediaToggleClosedCaptionsButton(o, paintInfo, r); + case MediaSliderPart: + return paintMediaSliderTrack(o, paintInfo, r); + case MediaSliderThumbPart: + if (o->parent()->isSlider()) + return paintMediaSliderThumb(o, paintInfo, r); + break; + case MediaVolumeSliderMuteButtonPart: + return paintMediaMuteButton(o, paintInfo, r); + case MediaVolumeSliderContainerPart: + return paintMediaVolumeSliderContainer(o, paintInfo, r); + case MediaVolumeSliderPart: + return paintMediaVolumeSliderTrack(o, paintInfo, r); + case MediaVolumeSliderThumbPart: + if (o->parent()->isSlider()) + return paintMediaVolumeSliderThumb(o, paintInfo, r); + break; + case MediaTimeRemainingPart: + return paintMediaTimeRemaining(o, paintInfo, r); + case MediaCurrentTimePart: + return paintMediaCurrentTime(o, paintInfo, r); + case MediaControlsBackgroundPart: + return paintMediaControlsBackground(o, paintInfo, r); + case MenulistButtonPart: + case TextFieldPart: + case TextAreaPart: + case ListboxPart: + return true; + case SearchFieldPart: + return paintSearchField(o, paintInfo, r); + case SearchFieldCancelButtonPart: + return paintSearchFieldCancelButton(o, paintInfo, r); + case SearchFieldDecorationPart: + return paintSearchFieldDecoration(o, paintInfo, r); + case SearchFieldResultsDecorationPart: + return paintSearchFieldResultsDecoration(o, paintInfo, r); + case SearchFieldResultsButtonPart: + return paintSearchFieldResultsButton(o, paintInfo, r); +#if ENABLE(INPUT_SPEECH) + case InputSpeechButtonPart: + return paintInputFieldSpeechButton(o, paintInfo, r); +#endif + default: + break; + } + + return true; // We don't support the appearance, so let the normal background/border paint. +} + +bool RenderTheme::paintBorderOnly(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + if (paintInfo.context->paintingDisabled()) + return false; + + // Call the appropriate paint method based off the appearance value. + switch (o->style()->appearance()) { + case TextFieldPart: + return paintTextField(o, paintInfo, r); + case ListboxPart: + case TextAreaPart: + return paintTextArea(o, paintInfo, r); + case MenulistButtonPart: + case SearchFieldPart: + return true; + case CheckboxPart: + case RadioPart: + case PushButtonPart: + case SquareButtonPart: + case ListButtonPart: + case DefaultButtonPart: + case ButtonPart: + case MenulistPart: +#if ENABLE(METER_TAG) + case MeterPart: + case RelevancyLevelIndicatorPart: + case ContinuousCapacityLevelIndicatorPart: + case DiscreteCapacityLevelIndicatorPart: + case RatingLevelIndicatorPart: +#endif +#if ENABLE(PROGRESS_TAG) + case ProgressBarPart: +#endif + case SliderHorizontalPart: + case SliderVerticalPart: + case SliderThumbHorizontalPart: + case SliderThumbVerticalPart: + case SearchFieldCancelButtonPart: + case SearchFieldDecorationPart: + case SearchFieldResultsDecorationPart: + case SearchFieldResultsButtonPart: +#if ENABLE(INPUT_SPEECH) + case InputSpeechButtonPart: +#endif + default: + break; + } + + return false; +} + +bool RenderTheme::paintDecorations(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + if (paintInfo.context->paintingDisabled()) + return false; + + // Call the appropriate paint method based off the appearance value. + switch (o->style()->appearance()) { + case MenulistButtonPart: + return paintMenuListButton(o, paintInfo, r); + case TextFieldPart: + case TextAreaPart: + case ListboxPart: + case CheckboxPart: + case RadioPart: + case PushButtonPart: + case SquareButtonPart: + case ListButtonPart: + case DefaultButtonPart: + case ButtonPart: + case MenulistPart: +#if ENABLE(METER_TAG) + case MeterPart: + case RelevancyLevelIndicatorPart: + case ContinuousCapacityLevelIndicatorPart: + case DiscreteCapacityLevelIndicatorPart: + case RatingLevelIndicatorPart: +#endif +#if ENABLE(PROGRESS_TAG) + case ProgressBarPart: +#endif + case SliderHorizontalPart: + case SliderVerticalPart: + case SliderThumbHorizontalPart: + case SliderThumbVerticalPart: + case SearchFieldPart: + case SearchFieldCancelButtonPart: + case SearchFieldDecorationPart: + case SearchFieldResultsDecorationPart: + case SearchFieldResultsButtonPart: +#if ENABLE(INPUT_SPEECH) + case InputSpeechButtonPart: +#endif + default: + break; + } + + return false; +} + +#if ENABLE(VIDEO) +bool RenderTheme::hitTestMediaControlPart(RenderObject* o, const IntPoint& absPoint) +{ + if (!o->isBox()) + return false; + + FloatPoint localPoint = o->absoluteToLocal(absPoint, false, true); // respect transforms + return toRenderBox(o)->borderBoxRect().contains(roundedIntPoint(localPoint)); +} + +bool RenderTheme::shouldRenderMediaControlPart(ControlPart part, Element* e) +{ + HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(e); + switch (part) { + case MediaMuteButtonPart: + return mediaElement->hasAudio(); + case MediaRewindButtonPart: + return mediaElement->movieLoadType() != MediaPlayer::LiveStream; + case MediaReturnToRealtimeButtonPart: + return mediaElement->movieLoadType() == MediaPlayer::LiveStream; + case MediaFullscreenButtonPart: + return mediaElement->supportsFullscreen(); + case MediaToggleClosedCaptionsButtonPart: + return mediaElement->hasClosedCaptions(); + default: + return true; + } +} + +String RenderTheme::formatMediaControlsTime(float time) const +{ + if (!isfinite(time)) + time = 0; + int seconds = (int)fabsf(time); + int hours = seconds / (60 * 60); + int minutes = (seconds / 60) % 60; + seconds %= 60; + if (hours) { + if (hours > 9) + return String::format("%s%02d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds); + + return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds); + } + + return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds); +} + +String RenderTheme::formatMediaControlsCurrentTime(float currentTime, float /*duration*/) const +{ + return formatMediaControlsTime(currentTime); +} + +String RenderTheme::formatMediaControlsRemainingTime(float currentTime, float duration) const +{ + return formatMediaControlsTime(currentTime - duration); +} + +IntPoint RenderTheme::volumeSliderOffsetFromMuteButton(Node* muteButton, const IntSize& size) const +{ + int y = -size.height(); + FloatPoint absPoint = muteButton->renderer()->localToAbsolute(FloatPoint(muteButton->renderBox()->offsetLeft(), y), true, true); + if (absPoint.y() < 0) + y = muteButton->renderBox()->height(); + return IntPoint(0, y); +} + +#endif + +Color RenderTheme::activeSelectionBackgroundColor() const +{ + if (!m_activeSelectionBackgroundColor.isValid()) + m_activeSelectionBackgroundColor = platformActiveSelectionBackgroundColor().blendWithWhite(); + return m_activeSelectionBackgroundColor; +} + +Color RenderTheme::inactiveSelectionBackgroundColor() const +{ + if (!m_inactiveSelectionBackgroundColor.isValid()) + m_inactiveSelectionBackgroundColor = platformInactiveSelectionBackgroundColor().blendWithWhite(); + return m_inactiveSelectionBackgroundColor; +} + +Color RenderTheme::activeSelectionForegroundColor() const +{ + if (!m_activeSelectionForegroundColor.isValid() && supportsSelectionForegroundColors()) + m_activeSelectionForegroundColor = platformActiveSelectionForegroundColor(); + return m_activeSelectionForegroundColor; +} + +Color RenderTheme::inactiveSelectionForegroundColor() const +{ + if (!m_inactiveSelectionForegroundColor.isValid() && supportsSelectionForegroundColors()) + m_inactiveSelectionForegroundColor = platformInactiveSelectionForegroundColor(); + return m_inactiveSelectionForegroundColor; +} + +Color RenderTheme::activeListBoxSelectionBackgroundColor() const +{ + if (!m_activeListBoxSelectionBackgroundColor.isValid()) + m_activeListBoxSelectionBackgroundColor = platformActiveListBoxSelectionBackgroundColor(); + return m_activeListBoxSelectionBackgroundColor; +} + +Color RenderTheme::inactiveListBoxSelectionBackgroundColor() const +{ + if (!m_inactiveListBoxSelectionBackgroundColor.isValid()) + m_inactiveListBoxSelectionBackgroundColor = platformInactiveListBoxSelectionBackgroundColor(); + return m_inactiveListBoxSelectionBackgroundColor; +} + +Color RenderTheme::activeListBoxSelectionForegroundColor() const +{ + if (!m_activeListBoxSelectionForegroundColor.isValid() && supportsListBoxSelectionForegroundColors()) + m_activeListBoxSelectionForegroundColor = platformActiveListBoxSelectionForegroundColor(); + return m_activeListBoxSelectionForegroundColor; +} + +Color RenderTheme::inactiveListBoxSelectionForegroundColor() const +{ + if (!m_inactiveListBoxSelectionForegroundColor.isValid() && supportsListBoxSelectionForegroundColors()) + m_inactiveListBoxSelectionForegroundColor = platformInactiveListBoxSelectionForegroundColor(); + return m_inactiveListBoxSelectionForegroundColor; +} + +Color RenderTheme::platformActiveSelectionBackgroundColor() const +{ + // Use a blue color by default if the platform theme doesn't define anything. + return Color(0, 0, 255); +} + +Color RenderTheme::platformActiveSelectionForegroundColor() const +{ + // Use a white color by default if the platform theme doesn't define anything. + return Color::white; +} + +Color RenderTheme::platformInactiveSelectionBackgroundColor() const +{ + // Use a grey color by default if the platform theme doesn't define anything. + // This color matches Firefox's inactive color. + return Color(176, 176, 176); +} + +Color RenderTheme::platformInactiveSelectionForegroundColor() const +{ + // Use a black color by default. + return Color::black; +} + +Color RenderTheme::platformActiveListBoxSelectionBackgroundColor() const +{ + return platformActiveSelectionBackgroundColor(); +} + +Color RenderTheme::platformActiveListBoxSelectionForegroundColor() const +{ + return platformActiveSelectionForegroundColor(); +} + +Color RenderTheme::platformInactiveListBoxSelectionBackgroundColor() const +{ + return platformInactiveSelectionBackgroundColor(); +} + +Color RenderTheme::platformInactiveListBoxSelectionForegroundColor() const +{ + return platformInactiveSelectionForegroundColor(); +} + +int RenderTheme::baselinePosition(const RenderObject* o) const +{ + if (!o->isBox()) + return 0; + + const RenderBox* box = toRenderBox(o); + +#if USE(NEW_THEME) + return box->height() + box->marginTop() + m_theme->baselinePositionAdjustment(o->style()->appearance()) * o->style()->effectiveZoom(); +#else + return box->height() + box->marginTop(); +#endif +} + +bool RenderTheme::isControlContainer(ControlPart appearance) const +{ + // There are more leaves than this, but we'll patch this function as we add support for + // more controls. + return appearance != CheckboxPart && appearance != RadioPart; +} + +bool RenderTheme::isControlStyled(const RenderStyle* style, const BorderData& border, const FillLayer& background, + const Color& backgroundColor) const +{ + switch (style->appearance()) { + case PushButtonPart: + case SquareButtonPart: + case DefaultButtonPart: + case ButtonPart: + case ListboxPart: + case MenulistPart: + case ProgressBarPart: + case MeterPart: + case RelevancyLevelIndicatorPart: + case ContinuousCapacityLevelIndicatorPart: + case DiscreteCapacityLevelIndicatorPart: + case RatingLevelIndicatorPart: + // FIXME: Uncomment this when making search fields style-able. + // case SearchFieldPart: + case TextFieldPart: + case TextAreaPart: + // Test the style to see if the UA border and background match. + return (style->border() != border || + *style->backgroundLayers() != background || + style->visitedDependentColor(CSSPropertyBackgroundColor) != backgroundColor); + default: + return false; + } +} + +void RenderTheme::adjustRepaintRect(const RenderObject* o, IntRect& r) +{ +#if USE(NEW_THEME) + m_theme->inflateControlPaintRect(o->style()->appearance(), controlStatesForRenderer(o), r, o->style()->effectiveZoom()); +#endif +} + +bool RenderTheme::supportsFocusRing(const RenderStyle* style) const +{ + return (style->hasAppearance() && style->appearance() != TextFieldPart && style->appearance() != TextAreaPart && style->appearance() != MenulistButtonPart && style->appearance() != ListboxPart); +} + +bool RenderTheme::stateChanged(RenderObject* o, ControlState state) const +{ + // Default implementation assumes the controls don't respond to changes in :hover state + if (state == HoverState && !supportsHover(o->style())) + return false; + + // Assume pressed state is only responded to if the control is enabled. + if (state == PressedState && !isEnabled(o)) + return false; + + // Repaint the control. + o->repaint(); + return true; +} + +ControlStates RenderTheme::controlStatesForRenderer(const RenderObject* o) const +{ + ControlStates result = 0; + if (isHovered(o)) { + result |= HoverState; + if (isSpinUpButtonPartHovered(o)) + result |= SpinUpState; + } + if (isPressed(o)) { + result |= PressedState; + if (isSpinUpButtonPartPressed(o)) + result |= SpinUpState; + } + if (isFocused(o) && o->style()->outlineStyleIsAuto()) + result |= FocusState; + if (isEnabled(o)) + result |= EnabledState; + if (isChecked(o)) + result |= CheckedState; + if (isReadOnlyControl(o)) + result |= ReadOnlyState; + if (isDefault(o)) + result |= DefaultState; + if (!isActive(o)) + result |= WindowInactiveState; + if (isIndeterminate(o)) + result |= IndeterminateState; + return result; +} + +bool RenderTheme::isActive(const RenderObject* o) const +{ + Node* node = o->node(); + if (!node) + return false; + + Frame* frame = node->document()->frame(); + if (!frame) + return false; + + Page* page = frame->page(); + if (!page) + return false; + + return page->focusController()->isActive(); +} + +bool RenderTheme::isChecked(const RenderObject* o) const +{ + if (!o->node() || !o->node()->isElementNode()) + return false; + + InputElement* inputElement = toInputElement(static_cast<Element*>(o->node())); + if (!inputElement) + return false; + + return inputElement->isChecked(); +} + +bool RenderTheme::isIndeterminate(const RenderObject* o) const +{ + if (!o->node() || !o->node()->isElementNode()) + return false; + + InputElement* inputElement = toInputElement(static_cast<Element*>(o->node())); + if (!inputElement) + return false; + + return inputElement->isIndeterminate(); +} + +bool RenderTheme::isEnabled(const RenderObject* o) const +{ + Node* node = o->node(); + if (!node || !node->isElementNode()) + return true; + return static_cast<Element*>(node)->isEnabledFormControl(); +} + +bool RenderTheme::isFocused(const RenderObject* o) const +{ + Node* node = o->node(); + if (!node) + return false; + Document* document = node->document(); + Frame* frame = document->frame(); + return node == document->focusedNode() && frame && frame->selection()->isFocusedAndActive(); +} + +bool RenderTheme::isPressed(const RenderObject* o) const +{ + if (!o->node()) + return false; + return o->node()->active(); +} + +bool RenderTheme::isSpinUpButtonPartPressed(const RenderObject* o) const +{ + Node* node = o->node(); + if (!node || !node->active() || !node->isElementNode() + || !static_cast<Element*>(node)->isSpinButtonElement()) + return false; + SpinButtonElement* element = static_cast<SpinButtonElement*>(node); + return element->upDownState() == SpinButtonElement::Up; +} + +bool RenderTheme::isReadOnlyControl(const RenderObject* o) const +{ + Node* node = o->node(); + if (!node || !node->isElementNode()) + return false; + return static_cast<Element*>(node)->isReadOnlyFormControl(); +} + +bool RenderTheme::isHovered(const RenderObject* o) const +{ + Node* node = o->node(); + if (!node) + return false; + if (!node->isElementNode() || !static_cast<Element*>(node)->isSpinButtonElement()) + return node->hovered(); + SpinButtonElement* element = static_cast<SpinButtonElement*>(node); + return element->hovered() && element->upDownState() != SpinButtonElement::Indeterminate; +} + +bool RenderTheme::isSpinUpButtonPartHovered(const RenderObject* o) const +{ + Node* node = o->node(); + if (!node || !node->isElementNode() || !static_cast<Element*>(node)->isSpinButtonElement()) + return false; + SpinButtonElement* element = static_cast<SpinButtonElement*>(node); + return element->upDownState() == SpinButtonElement::Up; +} + +bool RenderTheme::isDefault(const RenderObject* o) const +{ + // A button should only have the default appearance if the page is active + if (!isActive(o)) + return false; + + if (!o->document()) + return false; + + Settings* settings = o->document()->settings(); + if (!settings || !settings->inApplicationChromeMode()) + return false; + + return o->style()->appearance() == DefaultButtonPart; +} + +#if !USE(NEW_THEME) + +void RenderTheme::adjustCheckboxStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + // A summary of the rules for checkbox designed to match WinIE: + // width/height - honored (WinIE actually scales its control for small widths, but lets it overflow for small heights.) + // font-size - not honored (control has no text), but we use it to decide which control size to use. + setCheckboxSize(style); + + // padding - not honored by WinIE, needs to be removed. + style->resetPadding(); + + // border - honored by WinIE, but looks terrible (just paints in the control box and turns off the Windows XP theme) + // for now, we will not honor it. + style->resetBorder(); + + style->setBoxShadow(0); +} + +void RenderTheme::adjustRadioStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + // A summary of the rules for checkbox designed to match WinIE: + // width/height - honored (WinIE actually scales its control for small widths, but lets it overflow for small heights.) + // font-size - not honored (control has no text), but we use it to decide which control size to use. + setRadioSize(style); + + // padding - not honored by WinIE, needs to be removed. + style->resetPadding(); + + // border - honored by WinIE, but looks terrible (just paints in the control box and turns off the Windows XP theme) + // for now, we will not honor it. + style->resetBorder(); + + style->setBoxShadow(0); +} + +void RenderTheme::adjustButtonStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + // Most platforms will completely honor all CSS, and so we have no need to adjust the style + // at all by default. We will still allow the theme a crack at setting up a desired vertical size. + setButtonSize(style); +} + +void RenderTheme::adjustInnerSpinButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +void RenderTheme::adjustOuterSpinButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +#endif + +void RenderTheme::adjustTextFieldStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +void RenderTheme::adjustTextAreaStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +void RenderTheme::adjustMenuListStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +#if ENABLE(INPUT_SPEECH) +void RenderTheme::adjustInputFieldSpeechButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* element) const +{ + RenderInputSpeech::adjustInputFieldSpeechButtonStyle(selector, style, element); +} + +bool RenderTheme::paintInputFieldSpeechButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + return RenderInputSpeech::paintInputFieldSpeechButton(object, paintInfo, rect); +} +#endif + +#if ENABLE(METER_TAG) +void RenderTheme::adjustMeterStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + style->setBoxShadow(0); +} + +IntSize RenderTheme::meterSizeForBounds(const RenderMeter*, const IntRect& bounds) const +{ + return bounds.size(); +} + +bool RenderTheme::supportsMeter(ControlPart, bool) const +{ + return false; +} + +bool RenderTheme::paintMeter(RenderObject*, const PaintInfo&, const IntRect&) +{ + return true; +} + +#endif + +#if ENABLE(PROGRESS_TAG) +double RenderTheme::animationRepeatIntervalForProgressBar(RenderProgress*) const +{ + return 0; +} + +double RenderTheme::animationDurationForProgressBar(RenderProgress*) const +{ + return 0; +} + +void RenderTheme::adjustProgressBarStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} +#endif + +void RenderTheme::adjustMenuListButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +void RenderTheme::adjustSliderTrackStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +void RenderTheme::adjustSliderThumbStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +void RenderTheme::adjustSliderThumbSize(RenderObject*) const +{ +} + +void RenderTheme::adjustSearchFieldStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +void RenderTheme::adjustSearchFieldCancelButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +void RenderTheme::adjustSearchFieldDecorationStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +void RenderTheme::adjustSearchFieldResultsDecorationStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +void RenderTheme::adjustSearchFieldResultsButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +void RenderTheme::platformColorsDidChange() +{ + m_activeSelectionForegroundColor = Color(); + m_inactiveSelectionForegroundColor = Color(); + m_activeSelectionBackgroundColor = Color(); + m_inactiveSelectionBackgroundColor = Color(); + + m_activeListBoxSelectionForegroundColor = Color(); + m_inactiveListBoxSelectionForegroundColor = Color(); + m_activeListBoxSelectionBackgroundColor = Color(); + m_inactiveListBoxSelectionForegroundColor = Color(); + + Page::scheduleForcedStyleRecalcForAllPages(); +} + +Color RenderTheme::systemColor(int cssValueId) const +{ + switch (cssValueId) { + case CSSValueActiveborder: + return 0xFFFFFFFF; + case CSSValueActivecaption: + return 0xFFCCCCCC; + case CSSValueAppworkspace: + return 0xFFFFFFFF; + case CSSValueBackground: + return 0xFF6363CE; + case CSSValueButtonface: + return 0xFFC0C0C0; + case CSSValueButtonhighlight: + return 0xFFDDDDDD; + case CSSValueButtonshadow: + return 0xFF888888; + case CSSValueButtontext: + return 0xFF000000; + case CSSValueCaptiontext: + return 0xFF000000; + case CSSValueGraytext: + return 0xFF808080; + case CSSValueHighlight: + return 0xFFB5D5FF; + case CSSValueHighlighttext: + return 0xFF000000; + case CSSValueInactiveborder: + return 0xFFFFFFFF; + case CSSValueInactivecaption: + return 0xFFFFFFFF; + case CSSValueInactivecaptiontext: + return 0xFF7F7F7F; + case CSSValueInfobackground: + return 0xFFFBFCC5; + case CSSValueInfotext: + return 0xFF000000; + case CSSValueMenu: + return 0xFFC0C0C0; + case CSSValueMenutext: + return 0xFF000000; + case CSSValueScrollbar: + return 0xFFFFFFFF; + case CSSValueText: + return 0xFF000000; + case CSSValueThreeddarkshadow: + return 0xFF666666; + case CSSValueThreedface: + return 0xFFC0C0C0; + case CSSValueThreedhighlight: + return 0xFFDDDDDD; + case CSSValueThreedlightshadow: + return 0xFFC0C0C0; + case CSSValueThreedshadow: + return 0xFF888888; + case CSSValueWindow: + return 0xFFFFFFFF; + case CSSValueWindowframe: + return 0xFFCCCCCC; + case CSSValueWindowtext: + return 0xFF000000; + } + return Color(); +} + +Color RenderTheme::platformActiveTextSearchHighlightColor() const +{ + return Color(255, 150, 50); // Orange. +} + +Color RenderTheme::platformInactiveTextSearchHighlightColor() const +{ + return Color(255, 255, 0); // Yellow. +} + +void RenderTheme::setCustomFocusRingColor(const Color& c) +{ + customFocusRingColor() = c; +} + +Color RenderTheme::focusRingColor() +{ + return customFocusRingColor().isValid() ? customFocusRingColor() : defaultTheme()->platformFocusRingColor(); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderTheme.h b/Source/WebCore/rendering/RenderTheme.h new file mode 100644 index 0000000..13c69e6 --- /dev/null +++ b/Source/WebCore/rendering/RenderTheme.h @@ -0,0 +1,334 @@ +/* + * This file is part of the theme implementation for form controls in WebCore. + * + * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Apple Computer, Inc. + * + * 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. + * + */ + +#ifndef RenderTheme_h +#define RenderTheme_h + +#if USE(NEW_THEME) +#include "Theme.h" +#else +#include "ThemeTypes.h" +#endif +#include "RenderObject.h" +#include "ScrollTypes.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> + +namespace WebCore { + +class Element; +class PopupMenu; +class RenderMenuList; +#if ENABLE(METER_TAG) +class RenderMeter; +#endif +#if ENABLE(PROGRESS_TAG) +class RenderProgress; +#endif +class CSSStyleSheet; + +class RenderTheme : public RefCounted<RenderTheme> { +protected: + RenderTheme(); + +public: + virtual ~RenderTheme() { } + + // This function is to be implemented in your platform-specific theme implementation to hand back the + // appropriate platform theme. When the theme is needed in non-page dependent code, a default theme is + // used as fallback, which is returned for a nulled page, so the platform code needs to account for this. + static PassRefPtr<RenderTheme> themeForPage(Page* page); + + // When the theme is needed in non-page dependent code, the defaultTheme() is used as fallback. + static inline PassRefPtr<RenderTheme> defaultTheme() + { + return themeForPage(0); + }; + + // This method is called whenever style has been computed for an element and the appearance + // property has been set to a value other than "none". The theme should map in all of the appropriate + // metrics and defaults given the contents of the style. This includes sophisticated operations like + // selection of control size based off the font, the disabling of appearance when certain other properties like + // "border" are set, or if the appearance is not supported by the theme. + void adjustStyle(CSSStyleSelector*, RenderStyle*, Element*, bool UAHasAppearance, + const BorderData&, const FillLayer&, const Color& backgroundColor); + + // This method is called to paint the widget as a background of the RenderObject. A widget's foreground, e.g., the + // text of a button, is always rendered by the engine itself. The boolean return value indicates + // whether the CSS border/background should also be painted. + bool paint(RenderObject*, const PaintInfo&, const IntRect&); + bool paintBorderOnly(RenderObject*, const PaintInfo&, const IntRect&); + bool paintDecorations(RenderObject*, const PaintInfo&, const IntRect&); + + // The remaining methods should be implemented by the platform-specific portion of the theme, e.g., + // RenderThemeMac.cpp for Mac OS X. + + // These methods return the theme's extra style sheets rules, to let each platform + // adjust the default CSS rules in html.css, quirks.css, or mediaControls.css + virtual String extraDefaultStyleSheet() { return String(); } + virtual String extraQuirksStyleSheet() { return String(); } +#if ENABLE(VIDEO) + virtual String extraMediaControlsStyleSheet() { return String(); }; +#endif + + // A method to obtain the baseline position for a "leaf" control. This will only be used if a baseline + // position cannot be determined by examining child content. Checkboxes and radio buttons are examples of + // controls that need to do this. + virtual int baselinePosition(const RenderObject*) const; + + // A method for asking if a control is a container or not. Leaf controls have to have some special behavior (like + // the baseline position API above). + bool isControlContainer(ControlPart) const; + + // A method asking if the control changes its tint when the window has focus or not. + virtual bool controlSupportsTints(const RenderObject*) const { return false; } + + // Whether or not the control has been styled enough by the author to disable the native appearance. + virtual bool isControlStyled(const RenderStyle*, const BorderData&, const FillLayer&, const Color& backgroundColor) const; + + // A general method asking if any control tinting is supported at all. + virtual bool supportsControlTints() const { return false; } + + // Some controls may spill out of their containers (e.g., the check on an OS X checkbox). When these controls repaint, + // the theme needs to communicate this inflated rect to the engine so that it can invalidate the whole control. + virtual void adjustRepaintRect(const RenderObject*, IntRect&); + + // This method is called whenever a relevant state changes on a particular themed object, e.g., the mouse becomes pressed + // or a control becomes disabled. + virtual bool stateChanged(RenderObject*, ControlState) const; + + // This method is called whenever the theme changes on the system in order to flush cached resources from the + // old theme. + virtual void themeChanged() { } + + // A method asking if the theme is able to draw the focus ring. + virtual bool supportsFocusRing(const RenderStyle*) const; + + // A method asking if the theme's controls actually care about redrawing when hovered. + virtual bool supportsHover(const RenderStyle*) const { return false; } + + // Text selection colors. + Color activeSelectionBackgroundColor() const; + Color inactiveSelectionBackgroundColor() const; + Color activeSelectionForegroundColor() const; + Color inactiveSelectionForegroundColor() const; + + // List box selection colors + Color activeListBoxSelectionBackgroundColor() const; + Color activeListBoxSelectionForegroundColor() const; + Color inactiveListBoxSelectionBackgroundColor() const; + Color inactiveListBoxSelectionForegroundColor() const; + + // Highlighting colors for TextMatches. + virtual Color platformActiveTextSearchHighlightColor() const; + virtual Color platformInactiveTextSearchHighlightColor() const; + + static Color focusRingColor(); + virtual Color platformFocusRingColor() const { return Color(0, 0, 0); } + static void setCustomFocusRingColor(const Color&); + + virtual void platformColorsDidChange(); + + virtual double caretBlinkInterval() const { return 0.5; } + + // System fonts and colors for CSS. + virtual void systemFont(int cssValueId, FontDescription&) const = 0; + virtual Color systemColor(int cssValueId) const; + + virtual int minimumMenuListSize(RenderStyle*) const { return 0; } + + virtual void adjustSliderThumbSize(RenderObject*) const; + + virtual int popupInternalPaddingLeft(RenderStyle*) const { return 0; } + virtual int popupInternalPaddingRight(RenderStyle*) const { return 0; } + virtual int popupInternalPaddingTop(RenderStyle*) const { return 0; } + virtual int popupInternalPaddingBottom(RenderStyle*) const { return 0; } + virtual bool popupOptionSupportsTextIndent() const { return false; } + + virtual ScrollbarControlSize scrollbarControlSizeForPart(ControlPart) { return RegularScrollbar; } + + // Method for painting the caps lock indicator + virtual bool paintCapsLockIndicator(RenderObject*, const PaintInfo&, const IntRect&) { return 0; }; + +#if ENABLE(PROGRESS_TAG) + // Returns the repeat interval of the animation for the progress bar. + virtual double animationRepeatIntervalForProgressBar(RenderProgress*) const; + // Returns the duration of the animation for the progress bar. + virtual double animationDurationForProgressBar(RenderProgress*) const; +#endif + +#if ENABLE(VIDEO) + // Media controls + virtual bool hitTestMediaControlPart(RenderObject*, const IntPoint& absPoint); + virtual bool shouldRenderMediaControlPart(ControlPart, Element*); + virtual double mediaControlsFadeInDuration() { return 0.1; } + virtual double mediaControlsFadeOutDuration() { return 0.3; } + virtual String formatMediaControlsTime(float time) const; + virtual String formatMediaControlsCurrentTime(float currentTime, float duration) const; + virtual String formatMediaControlsRemainingTime(float currentTime, float duration) const; + + // Returns the media volume slider container's offset from the mute button. + virtual IntPoint volumeSliderOffsetFromMuteButton(Node*, const IntSize&) const; +#endif + +#if ENABLE(METER_TAG) + virtual IntSize meterSizeForBounds(const RenderMeter*, const IntRect&) const; + virtual bool supportsMeter(ControlPart, bool isHorizontal) const; +#endif + +protected: + // The platform selection color. + virtual Color platformActiveSelectionBackgroundColor() const; + virtual Color platformInactiveSelectionBackgroundColor() const; + virtual Color platformActiveSelectionForegroundColor() const; + virtual Color platformInactiveSelectionForegroundColor() const; + + virtual Color platformActiveListBoxSelectionBackgroundColor() const; + virtual Color platformInactiveListBoxSelectionBackgroundColor() const; + virtual Color platformActiveListBoxSelectionForegroundColor() const; + virtual Color platformInactiveListBoxSelectionForegroundColor() const; + + virtual bool supportsSelectionForegroundColors() const { return true; } + virtual bool supportsListBoxSelectionForegroundColors() const { return true; } + +#if !USE(NEW_THEME) + // Methods for each appearance value. + virtual void adjustCheckboxStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintCheckbox(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual void setCheckboxSize(RenderStyle*) const { } + + virtual void adjustRadioStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintRadio(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual void setRadioSize(RenderStyle*) const { } + + virtual void adjustButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintButton(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual void setButtonSize(RenderStyle*) const { } + + virtual void adjustInnerSpinButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintInnerSpinButton(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual void adjustOuterSpinButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintOuterSpinButton(RenderObject*, const PaintInfo&, const IntRect&) { return true; } +#endif + + virtual void adjustTextFieldStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintTextField(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + + virtual void adjustTextAreaStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintTextArea(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + +#if ENABLE(NO_LISTBOX_RENDERING) + virtual void adjustListboxStyle(CSSStyleSelector*, RenderStyle*, Element*) const {} +#endif + virtual void adjustMenuListStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintMenuList(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + + virtual void adjustMenuListButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintMenuListButton(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + +#if ENABLE(METER_TAG) + virtual void adjustMeterStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintMeter(RenderObject*, const PaintInfo&, const IntRect&); +#endif + +#if ENABLE(PROGRESS_TAG) + virtual void adjustProgressBarStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintProgressBar(RenderObject*, const PaintInfo&, const IntRect&) { return true; } +#endif + +#if ENABLE(INPUT_SPEECH) + virtual void adjustInputFieldSpeechButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintInputFieldSpeechButton(RenderObject*, const PaintInfo&, const IntRect&); +#endif + + virtual void adjustSliderTrackStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSliderTrack(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + + virtual void adjustSliderThumbStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSliderThumb(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + + virtual void adjustSearchFieldStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchField(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + + virtual void adjustSearchFieldCancelButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldCancelButton(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + + virtual void adjustSearchFieldDecorationStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldDecoration(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + + virtual void adjustSearchFieldResultsDecorationStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldResultsDecoration(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + + virtual void adjustSearchFieldResultsButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldResultsButton(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + + virtual bool paintMediaFullscreenButton(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual bool paintMediaPlayButton(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual bool paintMediaMuteButton(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual bool paintMediaSeekBackButton(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual bool paintMediaSeekForwardButton(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual bool paintMediaSliderTrack(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual bool paintMediaSliderThumb(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual bool paintMediaVolumeSliderContainer(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual bool paintMediaVolumeSliderTrack(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual bool paintMediaVolumeSliderThumb(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual bool paintMediaRewindButton(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual bool paintMediaReturnToRealtimeButton(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual bool paintMediaToggleClosedCaptionsButton(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual bool paintMediaControlsBackground(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual bool paintMediaCurrentTime(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + virtual bool paintMediaTimeRemaining(RenderObject*, const PaintInfo&, const IntRect&) { return true; } + +public: + // Methods for state querying + ControlStates controlStatesForRenderer(const RenderObject* o) const; + bool isActive(const RenderObject*) const; + bool isChecked(const RenderObject*) const; + bool isIndeterminate(const RenderObject*) const; + bool isEnabled(const RenderObject*) const; + bool isFocused(const RenderObject*) const; + bool isPressed(const RenderObject*) const; + bool isSpinUpButtonPartPressed(const RenderObject*) const; + bool isHovered(const RenderObject*) const; + bool isSpinUpButtonPartHovered(const RenderObject*) const; + bool isReadOnlyControl(const RenderObject*) const; + bool isDefault(const RenderObject*) const; + +private: + mutable Color m_activeSelectionBackgroundColor; + mutable Color m_inactiveSelectionBackgroundColor; + mutable Color m_activeSelectionForegroundColor; + mutable Color m_inactiveSelectionForegroundColor; + + mutable Color m_activeListBoxSelectionBackgroundColor; + mutable Color m_inactiveListBoxSelectionBackgroundColor; + mutable Color m_activeListBoxSelectionForegroundColor; + mutable Color m_inactiveListBoxSelectionForegroundColor; + +#if USE(NEW_THEME) + Theme* m_theme; // The platform-specific theme. +#endif +}; + +} // namespace WebCore + +#endif // RenderTheme_h diff --git a/Source/WebCore/rendering/RenderThemeChromiumLinux.cpp b/Source/WebCore/rendering/RenderThemeChromiumLinux.cpp new file mode 100644 index 0000000..de83ae9 --- /dev/null +++ b/Source/WebCore/rendering/RenderThemeChromiumLinux.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2007 Apple Inc. + * Copyright (C) 2007 Alp Toker <alp@atoker.com> + * Copyright (C) 2008 Collabora Ltd. + * Copyright (C) 2008, 2009 Google Inc. + * Copyright (C) 2009 Kenneth Rohde Christiansen + * + * 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 "RenderThemeChromiumLinux.h" + +#include "CSSValueKeywords.h" +#include "Color.h" +#include "PlatformThemeChromiumGtk.h" +#include "RenderObject.h" +#include "ScrollbarTheme.h" +#include "UserAgentStyleSheets.h" + +namespace WebCore { + +unsigned RenderThemeChromiumLinux::m_activeSelectionBackgroundColor = + 0xff1e90ff; +unsigned RenderThemeChromiumLinux::m_activeSelectionForegroundColor = + Color::black; +unsigned RenderThemeChromiumLinux::m_inactiveSelectionBackgroundColor = + 0xffc8c8c8; +unsigned RenderThemeChromiumLinux::m_inactiveSelectionForegroundColor = + 0xff323232; + +double RenderThemeChromiumLinux::m_caretBlinkInterval; + +PassRefPtr<RenderTheme> RenderThemeChromiumLinux::create() +{ + return adoptRef(new RenderThemeChromiumLinux()); +} + +PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page* page) +{ + static RenderTheme* rt = RenderThemeChromiumLinux::create().releaseRef(); + return rt; +} + +RenderThemeChromiumLinux::RenderThemeChromiumLinux() +{ + m_caretBlinkInterval = RenderTheme::caretBlinkInterval(); +} + +RenderThemeChromiumLinux::~RenderThemeChromiumLinux() +{ +} + +Color RenderThemeChromiumLinux::systemColor(int cssValueId) const +{ + static const Color linuxButtonGrayColor(0xffdddddd); + + if (cssValueId == CSSValueButtonface) + return linuxButtonGrayColor; + return RenderTheme::systemColor(cssValueId); +} + +String RenderThemeChromiumLinux::extraDefaultStyleSheet() +{ + return RenderThemeChromiumSkia::extraDefaultStyleSheet() + + String(themeChromiumLinuxUserAgentStyleSheet, sizeof(themeChromiumLinuxUserAgentStyleSheet)); +} + +bool RenderThemeChromiumLinux::controlSupportsTints(const RenderObject* o) const +{ + return isEnabled(o); +} + +Color RenderThemeChromiumLinux::activeListBoxSelectionBackgroundColor() const +{ + return Color(0x28, 0x28, 0x28); +} + +Color RenderThemeChromiumLinux::activeListBoxSelectionForegroundColor() const +{ + return Color::black; +} + +Color RenderThemeChromiumLinux::inactiveListBoxSelectionBackgroundColor() const +{ + return Color(0xc8, 0xc8, 0xc8); +} + +Color RenderThemeChromiumLinux::inactiveListBoxSelectionForegroundColor() const +{ + return Color(0x32, 0x32, 0x32); +} + +Color RenderThemeChromiumLinux::platformActiveSelectionBackgroundColor() const +{ + return m_activeSelectionBackgroundColor; +} + +Color RenderThemeChromiumLinux::platformInactiveSelectionBackgroundColor() const +{ + return m_inactiveSelectionBackgroundColor; +} + +Color RenderThemeChromiumLinux::platformActiveSelectionForegroundColor() const +{ + return m_activeSelectionForegroundColor; +} + +Color RenderThemeChromiumLinux::platformInactiveSelectionForegroundColor() const +{ + return m_inactiveSelectionForegroundColor; +} + +void RenderThemeChromiumLinux::adjustSliderThumbSize(RenderObject* o) const +{ + // These sizes match the sizes in Chromium Win. + const int sliderThumbAlongAxis = 11; + const int sliderThumbAcrossAxis = 21; + if (o->style()->appearance() == SliderThumbHorizontalPart) { + o->style()->setWidth(Length(sliderThumbAlongAxis, Fixed)); + o->style()->setHeight(Length(sliderThumbAcrossAxis, Fixed)); + } else if (o->style()->appearance() == SliderThumbVerticalPart) { + o->style()->setWidth(Length(sliderThumbAcrossAxis, Fixed)); + o->style()->setHeight(Length(sliderThumbAlongAxis, Fixed)); + } else + RenderThemeChromiumSkia::adjustSliderThumbSize(o); +} + +bool RenderThemeChromiumLinux::supportsControlTints() const +{ + return true; +} + +void RenderThemeChromiumLinux::setCaretBlinkInterval(double interval) +{ + m_caretBlinkInterval = interval; +} + +double RenderThemeChromiumLinux::caretBlinkIntervalInternal() const +{ + return m_caretBlinkInterval; +} + +void RenderThemeChromiumLinux::setSelectionColors( + unsigned activeBackgroundColor, + unsigned activeForegroundColor, + unsigned inactiveBackgroundColor, + unsigned inactiveForegroundColor) +{ + m_activeSelectionBackgroundColor = activeBackgroundColor; + m_activeSelectionForegroundColor = activeForegroundColor; + m_inactiveSelectionBackgroundColor = inactiveBackgroundColor; + m_inactiveSelectionForegroundColor = inactiveForegroundColor; +} + +void RenderThemeChromiumLinux::adjustInnerSpinButtonStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + int width = ScrollbarTheme::nativeTheme()->scrollbarThickness(); + style->setWidth(Length(width, Fixed)); + style->setMinWidth(Length(width, Fixed)); +} + +bool RenderThemeChromiumLinux::paintInnerSpinButton(RenderObject* object, const PaintInfo& info, const IntRect& rect) +{ + ControlStates northStates = controlStatesForRenderer(object); + ControlStates southStates = northStates; + if (northStates & SpinUpState) + southStates &= ~(HoverState | PressedState); + else + northStates &= ~(HoverState | PressedState); + + IntRect half = rect; + half.setHeight(rect.height() / 2); + PlatformThemeChromiumGtk::paintArrowButton(info.context, half, PlatformThemeChromiumGtk::North, northStates); + + half.setY(rect.y() + rect.height() / 2); + PlatformThemeChromiumGtk::paintArrowButton(info.context, half, PlatformThemeChromiumGtk::South, southStates); + return false; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderThemeChromiumLinux.h b/Source/WebCore/rendering/RenderThemeChromiumLinux.h new file mode 100644 index 0000000..9eeca97 --- /dev/null +++ b/Source/WebCore/rendering/RenderThemeChromiumLinux.h @@ -0,0 +1,87 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2006 Apple Computer, Inc. + * Copyright (C) 2006 Michael Emmel mike.emmel@gmail.com + * Copyright (C) 2007 Holger Hans Peter Freyther + * Copyright (C) 2007 Alp Toker <alp@atoker.com> + * Copyright (C) 2008, 2009 Google, Inc. + * All rights reserved. + * Copyright (C) 2009 Kenneth Rohde Christiansen + * + * 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. + * + */ + +#ifndef RenderThemeChromiumLinux_h +#define RenderThemeChromiumLinux_h + +#include "RenderThemeChromiumSkia.h" + +namespace WebCore { + + class RenderThemeChromiumLinux : public RenderThemeChromiumSkia { + public: + static PassRefPtr<RenderTheme> create(); + virtual String extraDefaultStyleSheet(); + + virtual Color systemColor(int cssValidId) const; + + // A method asking if the control changes its tint when the window has focus or not. + virtual bool controlSupportsTints(const RenderObject*) const; + + // List Box selection color + virtual Color activeListBoxSelectionBackgroundColor() const; + virtual Color activeListBoxSelectionForegroundColor() const; + virtual Color inactiveListBoxSelectionBackgroundColor() const; + virtual Color inactiveListBoxSelectionForegroundColor() const; + + virtual Color platformActiveSelectionBackgroundColor() const; + virtual Color platformInactiveSelectionBackgroundColor() const; + virtual Color platformActiveSelectionForegroundColor() const; + virtual Color platformInactiveSelectionForegroundColor() const; + + virtual void adjustSliderThumbSize(RenderObject*) const; + + static void setCaretBlinkInterval(double interval); + virtual double caretBlinkIntervalInternal() const; + + virtual void adjustInnerSpinButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintInnerSpinButton(RenderObject*, const PaintInfo&, const IntRect&); + + static void setSelectionColors(unsigned activeBackgroundColor, + unsigned activeForegroundColor, + unsigned inactiveBackgroundColor, + unsigned inactiveForegroundColor); + + private: + RenderThemeChromiumLinux(); + virtual ~RenderThemeChromiumLinux(); + + // A general method asking if any control tinting is supported at all. + virtual bool supportsControlTints() const; + + static double m_caretBlinkInterval; + + static unsigned m_activeSelectionBackgroundColor; + static unsigned m_activeSelectionForegroundColor; + static unsigned m_inactiveSelectionBackgroundColor; + static unsigned m_inactiveSelectionForegroundColor; + }; + +} // namespace WebCore + +#endif // RenderThemeChromiumLinux_h diff --git a/Source/WebCore/rendering/RenderThemeChromiumMac.h b/Source/WebCore/rendering/RenderThemeChromiumMac.h new file mode 100644 index 0000000..d1875fc --- /dev/null +++ b/Source/WebCore/rendering/RenderThemeChromiumMac.h @@ -0,0 +1,60 @@ +/* + * This file is part of the theme implementation for form controls in WebCore. + * + * Copyright (C) 2005 Apple Computer, Inc. + * Copyright (C) 2008, 2009 Google, Inc. + * + * 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. + * + */ + +#ifndef RenderThemeChromiumMac_h +#define RenderThemeChromiumMac_h + +#import "RenderThemeMac.h" + +namespace WebCore { + +class RenderThemeChromiumMac : public RenderThemeMac { +public: + static PassRefPtr<RenderTheme> create(); +protected: +#if ENABLE(VIDEO) + virtual void adjustMediaSliderThumbSize(RenderObject*) const; + virtual bool paintMediaPlayButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaMuteButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSliderTrack(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaControlsBackground(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool shouldRenderMediaControlPart(ControlPart, Element*); + virtual String extraMediaControlsStyleSheet(); + + virtual bool paintMediaSliderThumb(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaVolumeSliderContainer(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaVolumeSliderTrack(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaVolumeSliderThumb(RenderObject*, const PaintInfo&, const IntRect&); + virtual IntPoint volumeSliderOffsetFromMuteButton(Node*, const IntSize&) const; + +#endif + + virtual bool usesTestModeFocusRingColor() const; + virtual NSView* documentViewFor(RenderObject*) const; +private: + virtual void updateActiveState(NSCell*, const RenderObject*); +}; + +} // namespace WebCore + +#endif // RenderThemeChromiumMac_h diff --git a/Source/WebCore/rendering/RenderThemeChromiumMac.mm b/Source/WebCore/rendering/RenderThemeChromiumMac.mm new file mode 100644 index 0000000..e8ffe6c --- /dev/null +++ b/Source/WebCore/rendering/RenderThemeChromiumMac.mm @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009 Google, Inc. + * + * 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. + */ + +#import "config.h" +#import "RenderThemeChromiumMac.h" +#import "ChromiumBridge.h" +#import "RenderMediaControlsChromium.h" +#import "UserAgentStyleSheets.h" +#import <Carbon/Carbon.h> +#import <Cocoa/Cocoa.h> +#import <wtf/RetainPtr.h> +#import <wtf/StdLibExtras.h> +#import <math.h> + +@interface RTCMFlippedView : NSView +{} + +- (BOOL)isFlipped; +- (NSText *)currentEditor; + +@end + +@implementation RTCMFlippedView + +- (BOOL)isFlipped { + return [[NSGraphicsContext currentContext] isFlipped]; +} + +- (NSText *)currentEditor { + return nil; +} + +@end + +namespace WebCore { + +NSView* FlippedView() +{ + static NSView* view = [[RTCMFlippedView alloc] init]; + return view; +} + +PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page*) +{ + static RenderTheme* rt = RenderThemeChromiumMac::create().releaseRef(); + return rt; +} + +PassRefPtr<RenderTheme> RenderThemeChromiumMac::create() +{ + return adoptRef(new RenderThemeChromiumMac); +} + +bool RenderThemeChromiumMac::usesTestModeFocusRingColor() const +{ + return ChromiumBridge::layoutTestMode(); +} + +NSView* RenderThemeChromiumMac::documentViewFor(RenderObject*) const +{ + return FlippedView(); +} + +// Updates the control tint (a.k.a. active state) of |cell| (from |o|). +// In the Chromium port, the renderer runs as a background process and controls' +// NSCell(s) lack a parent NSView. Therefore controls don't have their tint +// color updated correctly when the application is activated/deactivated. +// FocusController's setActive() is called when the application is +// activated/deactivated, which causes a repaint at which time this code is +// called. +// This function should be called before drawing any NSCell-derived controls, +// unless you're sure it isn't needed. +void RenderThemeChromiumMac::updateActiveState(NSCell* cell, const RenderObject* o) +{ + NSControlTint oldTint = [cell controlTint]; + NSControlTint tint = isActive(o) ? [NSColor currentControlTint] : + static_cast<NSControlTint>(NSClearControlTint); + + if (tint != oldTint) + [cell setControlTint:tint]; +} + +#if ENABLE(VIDEO) + +void RenderThemeChromiumMac::adjustMediaSliderThumbSize(RenderObject* o) const +{ + RenderMediaControlsChromium::adjustMediaSliderThumbSize(o); +} + +bool RenderThemeChromiumMac::shouldRenderMediaControlPart(ControlPart part, Element* e) +{ + return RenderMediaControlsChromium::shouldRenderMediaControlPart(part, e); +} + +bool RenderThemeChromiumMac::paintMediaPlayButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + return RenderMediaControlsChromium::paintMediaControlsPart(MediaPlayButton, object, paintInfo, rect); +} + +bool RenderThemeChromiumMac::paintMediaMuteButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + return RenderMediaControlsChromium::paintMediaControlsPart(MediaMuteButton, object, paintInfo, rect); +} + +bool RenderThemeChromiumMac::paintMediaSliderTrack(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + return RenderMediaControlsChromium::paintMediaControlsPart(MediaSlider, object, paintInfo, rect); +} + +bool RenderThemeChromiumMac::paintMediaControlsBackground(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + return RenderMediaControlsChromium::paintMediaControlsPart(MediaTimelineContainer, object, paintInfo, rect); +} + +String RenderThemeChromiumMac::extraMediaControlsStyleSheet() +{ + return String(mediaControlsChromiumUserAgentStyleSheet, sizeof(mediaControlsChromiumUserAgentStyleSheet)); +} + +bool RenderThemeChromiumMac::paintMediaVolumeSliderContainer(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + return true; +} + +bool RenderThemeChromiumMac::paintMediaVolumeSliderTrack(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + return RenderMediaControlsChromium::paintMediaControlsPart(MediaVolumeSlider, object, paintInfo, rect); +} + +bool RenderThemeChromiumMac::paintMediaVolumeSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + return RenderMediaControlsChromium::paintMediaControlsPart(MediaVolumeSliderThumb, object, paintInfo, rect); +} + +bool RenderThemeChromiumMac::paintMediaSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ + return RenderMediaControlsChromium::paintMediaControlsPart(MediaSliderThumb, object, paintInfo, rect); +} + +IntPoint RenderThemeChromiumMac::volumeSliderOffsetFromMuteButton(Node* muteButton, const IntSize& size) const +{ + return RenderTheme::volumeSliderOffsetFromMuteButton(muteButton, size); +} +#endif + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderThemeChromiumSkia.cpp b/Source/WebCore/rendering/RenderThemeChromiumSkia.cpp new file mode 100644 index 0000000..9824851 --- /dev/null +++ b/Source/WebCore/rendering/RenderThemeChromiumSkia.cpp @@ -0,0 +1,888 @@ +/* + * Copyright (C) 2007 Apple Inc. + * Copyright (C) 2007 Alp Toker <alp@atoker.com> + * Copyright (C) 2008 Collabora Ltd. + * Copyright (C) 2008, 2009 Google Inc. + * + * 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 "RenderThemeChromiumSkia.h" + +#include "ChromiumBridge.h" +#include "CSSValueKeywords.h" +#include "CurrentTime.h" +#include "GraphicsContext.h" +#include "HTMLMediaElement.h" +#include "HTMLNames.h" +#include "Image.h" +#include "MediaControlElements.h" +#include "PlatformContextSkia.h" +#include "RenderBox.h" +#include "RenderMediaControlsChromium.h" +#include "RenderObject.h" +#include "RenderProgress.h" +#include "RenderSlider.h" +#include "ScrollbarTheme.h" +#include "TimeRanges.h" +#include "TransformationMatrix.h" +#include "UserAgentStyleSheets.h" + +#include "SkShader.h" +#include "SkGradientShader.h" + +namespace WebCore { + +enum PaddingType { + TopPadding, + RightPadding, + BottomPadding, + LeftPadding +}; + +static const int styledMenuListInternalPadding[4] = { 1, 4, 1, 4 }; + +// These values all match Safari/Win. +static const float defaultControlFontPixelSize = 13; +static const float defaultCancelButtonSize = 9; +static const float minCancelButtonSize = 5; +static const float maxCancelButtonSize = 21; +static const float defaultSearchFieldResultsDecorationSize = 13; +static const float minSearchFieldResultsDecorationSize = 9; +static const float maxSearchFieldResultsDecorationSize = 30; +static const float defaultSearchFieldResultsButtonWidth = 18; + +static void setSizeIfAuto(RenderStyle* style, const IntSize& size) +{ + if (style->width().isIntrinsicOrAuto()) + style->setWidth(Length(size.width(), Fixed)); + if (style->height().isAuto()) + style->setHeight(Length(size.height(), Fixed)); +} + +static void drawVertLine(SkCanvas* canvas, int x, int y1, int y2, const SkPaint& paint) +{ + SkIRect skrect; + skrect.set(x, y1, x + 1, y2 + 1); + canvas->drawIRect(skrect, paint); +} + +static void drawHorizLine(SkCanvas* canvas, int x1, int x2, int y, const SkPaint& paint) +{ + SkIRect skrect; + skrect.set(x1, y, x2 + 1, y + 1); + canvas->drawIRect(skrect, paint); +} + +static void drawBox(SkCanvas* canvas, const IntRect& rect, const SkPaint& paint) +{ + const int right = rect.x() + rect.width() - 1; + const int bottom = rect.y() + rect.height() - 1; + drawHorizLine(canvas, rect.x(), right, rect.y(), paint); + drawVertLine(canvas, right, rect.y(), bottom, paint); + drawHorizLine(canvas, rect.x(), right, bottom, paint); + drawVertLine(canvas, rect.x(), rect.y(), bottom, paint); +} + +// We aim to match IE here. +// -IE uses a font based on the encoding as the default font for form controls. +// -Gecko uses MS Shell Dlg (actually calls GetStockObject(DEFAULT_GUI_FONT), +// which returns MS Shell Dlg) +// -Safari uses Lucida Grande. +// +// FIXME: The only case where we know we don't match IE is for ANSI encodings. +// IE uses MS Shell Dlg there, which we render incorrectly at certain pixel +// sizes (e.g. 15px). So, for now we just use Arial. +const String& RenderThemeChromiumSkia::defaultGUIFont() +{ + DEFINE_STATIC_LOCAL(String, fontFace, ("Arial")); + return fontFace; +} + +float RenderThemeChromiumSkia::defaultFontSize = 16.0; + +RenderThemeChromiumSkia::RenderThemeChromiumSkia() +{ +} + +RenderThemeChromiumSkia::~RenderThemeChromiumSkia() +{ +} + +// Use the Windows style sheets to match their metrics. +String RenderThemeChromiumSkia::extraDefaultStyleSheet() +{ + return String(themeWinUserAgentStyleSheet, sizeof(themeWinUserAgentStyleSheet)) + + String(themeChromiumSkiaUserAgentStyleSheet, sizeof(themeChromiumSkiaUserAgentStyleSheet)); +} + +String RenderThemeChromiumSkia::extraQuirksStyleSheet() +{ + return String(themeWinQuirksUserAgentStyleSheet, sizeof(themeWinQuirksUserAgentStyleSheet)); +} + +#if ENABLE(VIDEO) +String RenderThemeChromiumSkia::extraMediaControlsStyleSheet() +{ + return String(mediaControlsChromiumUserAgentStyleSheet, sizeof(mediaControlsChromiumUserAgentStyleSheet)); +} +#endif + +bool RenderThemeChromiumSkia::supportsHover(const RenderStyle* style) const +{ + return true; +} + +bool RenderThemeChromiumSkia::supportsFocusRing(const RenderStyle* style) const +{ + // This causes WebKit to draw the focus rings for us. + return false; +} + +Color RenderThemeChromiumSkia::platformActiveSelectionBackgroundColor() const +{ + return Color(0x1e, 0x90, 0xff); +} + +Color RenderThemeChromiumSkia::platformInactiveSelectionBackgroundColor() const +{ + return Color(0xc8, 0xc8, 0xc8); +} + +Color RenderThemeChromiumSkia::platformActiveSelectionForegroundColor() const +{ + return Color::black; +} + +Color RenderThemeChromiumSkia::platformInactiveSelectionForegroundColor() const +{ + return Color(0x32, 0x32, 0x32); +} + +Color RenderThemeChromiumSkia::platformFocusRingColor() const +{ + static Color focusRingColor(229, 151, 0, 255); + return focusRingColor; +} + +double RenderThemeChromiumSkia::caretBlinkInterval() const +{ + // Disable the blinking caret in layout test mode, as it introduces + // a race condition for the pixel tests. http://b/1198440 + if (ChromiumBridge::layoutTestMode()) + return 0; + + return caretBlinkIntervalInternal(); +} + +void RenderThemeChromiumSkia::systemFont(int propId, FontDescription& fontDescription) const +{ + float fontSize = defaultFontSize; + + switch (propId) { + case CSSValueWebkitMiniControl: + case CSSValueWebkitSmallControl: + case CSSValueWebkitControl: + // Why 2 points smaller? Because that's what Gecko does. Note that we + // are assuming a 96dpi screen, which is the default that we use on + // Windows. + static const float pointsPerInch = 72.0f; + static const float pixelsPerInch = 96.0f; + fontSize -= (2.0f / pointsPerInch) * pixelsPerInch; + break; + } + + fontDescription.firstFamily().setFamily(defaultGUIFont()); + fontDescription.setSpecifiedSize(fontSize); + fontDescription.setIsAbsoluteSize(true); + fontDescription.setGenericFamily(FontDescription::NoFamily); + fontDescription.setWeight(FontWeightNormal); + fontDescription.setItalic(false); +} + +int RenderThemeChromiumSkia::minimumMenuListSize(RenderStyle* style) const +{ + return 0; +} + +// These are the default dimensions of radio buttons and checkboxes. +static const int widgetStandardWidth = 13; +static const int widgetStandardHeight = 13; + +// Return a rectangle that has the same center point as |original|, but with a +// size capped at |width| by |height|. +IntRect center(const IntRect& original, int width, int height) +{ + width = std::min(original.width(), width); + height = std::min(original.height(), height); + int x = original.x() + (original.width() - width) / 2; + int y = original.y() + (original.height() - height) / 2; + + return IntRect(x, y, width, height); +} + +bool RenderThemeChromiumSkia::paintCheckbox(RenderObject* o, const PaintInfo& i, const IntRect& rect) +{ + static Image* const checkedImage = Image::loadPlatformResource("linuxCheckboxOn").releaseRef(); + static Image* const uncheckedImage = Image::loadPlatformResource("linuxCheckboxOff").releaseRef(); + static Image* const indeterminateImage = Image::loadPlatformResource("linuxCheckboxIndeterminate").releaseRef(); + static Image* const disabledCheckedImage = Image::loadPlatformResource("linuxCheckboxDisabledOn").releaseRef(); + static Image* const disabledUncheckedImage = Image::loadPlatformResource("linuxCheckboxDisabledOff").releaseRef(); + static Image* const disabledIndeterminateImage = Image::loadPlatformResource("linuxCheckboxDisabledIndeterminate").releaseRef(); + + Image* image; + + if (isIndeterminate(o)) + image = isEnabled(o) ? indeterminateImage : disabledIndeterminateImage; + else if (isChecked(o)) + image = isEnabled(o) ? checkedImage : disabledCheckedImage; + else + image = isEnabled(o) ? uncheckedImage : disabledUncheckedImage; + + i.context->drawImage(image, o->style()->colorSpace(), center(rect, widgetStandardHeight, widgetStandardWidth)); + return false; +} + +void RenderThemeChromiumSkia::setCheckboxSize(RenderStyle* style) const +{ + // If the width and height are both specified, then we have nothing to do. + if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto()) + return; + + // FIXME: A hard-coded size of 13 is used. This is wrong but necessary + // for now. It matches Firefox. At different DPI settings on Windows, + // querying the theme gives you a larger size that accounts for the higher + // DPI. Until our entire engine honors a DPI setting other than 96, we + // can't rely on the theme's metrics. + const IntSize size(widgetStandardHeight, widgetStandardWidth); + setSizeIfAuto(style, size); +} + +bool RenderThemeChromiumSkia::paintRadio(RenderObject* o, const PaintInfo& i, const IntRect& rect) +{ + static Image* const checkedImage = Image::loadPlatformResource("linuxRadioOn").releaseRef(); + static Image* const uncheckedImage = Image::loadPlatformResource("linuxRadioOff").releaseRef(); + static Image* const disabledCheckedImage = Image::loadPlatformResource("linuxRadioDisabledOn").releaseRef(); + static Image* const disabledUncheckedImage = Image::loadPlatformResource("linuxRadioDisabledOff").releaseRef(); + + Image* image; + if (this->isEnabled(o)) + image = this->isChecked(o) ? checkedImage : uncheckedImage; + else + image = this->isChecked(o) ? disabledCheckedImage : disabledUncheckedImage; + + i.context->drawImage(image, o->style()->colorSpace(), center(rect, widgetStandardHeight, widgetStandardWidth)); + return false; +} + +void RenderThemeChromiumSkia::setRadioSize(RenderStyle* style) const +{ + // Use same sizing for radio box as checkbox. + setCheckboxSize(style); +} + +static SkColor brightenColor(double h, double s, double l, float brightenAmount) +{ + l += brightenAmount; + if (l > 1.0) + l = 1.0; + if (l < 0.0) + l = 0.0; + + return makeRGBAFromHSLA(h, s, l, 1.0); +} + +static void paintButtonLike(RenderTheme* theme, RenderObject* o, const PaintInfo& i, const IntRect& rect) +{ + SkCanvas* const canvas = i.context->platformContext()->canvas(); + SkPaint paint; + SkRect skrect; + const int right = rect.x() + rect.width(); + const int bottom = rect.y() + rect.height(); + SkColor baseColor = SkColorSetARGB(0xff, 0xdd, 0xdd, 0xdd); + if (o->hasBackground()) + baseColor = o->style()->visitedDependentColor(CSSPropertyBackgroundColor).rgb(); + double h, s, l; + Color(baseColor).getHSL(h, s, l); + // Our standard gradient is from 0xdd to 0xf8. This is the amount of + // increased luminance between those values. + SkColor lightColor(brightenColor(h, s, l, 0.105)); + + // If the button is too small, fallback to drawing a single, solid color + if (rect.width() < 5 || rect.height() < 5) { + paint.setColor(baseColor); + skrect.set(rect.x(), rect.y(), right, bottom); + canvas->drawRect(skrect, paint); + return; + } + + const int borderAlpha = theme->isHovered(o) ? 0x80 : 0x55; + paint.setARGB(borderAlpha, 0, 0, 0); + canvas->drawLine(rect.x() + 1, rect.y(), right - 1, rect.y(), paint); + canvas->drawLine(right - 1, rect.y() + 1, right - 1, bottom - 1, paint); + canvas->drawLine(rect.x() + 1, bottom - 1, right - 1, bottom - 1, paint); + canvas->drawLine(rect.x(), rect.y() + 1, rect.x(), bottom - 1, paint); + + paint.setColor(SK_ColorBLACK); + SkPoint p[2]; + const int lightEnd = theme->isPressed(o) ? 1 : 0; + const int darkEnd = !lightEnd; + p[lightEnd].set(SkIntToScalar(rect.x()), SkIntToScalar(rect.y())); + p[darkEnd].set(SkIntToScalar(rect.x()), SkIntToScalar(bottom - 1)); + SkColor colors[2]; + colors[0] = lightColor; + colors[1] = baseColor; + + SkShader* shader = SkGradientShader::CreateLinear( + p, colors, NULL, 2, SkShader::kClamp_TileMode, NULL); + paint.setStyle(SkPaint::kFill_Style); + paint.setShader(shader); + shader->unref(); + + skrect.set(rect.x() + 1, rect.y() + 1, right - 1, bottom - 1); + canvas->drawRect(skrect, paint); + + paint.setShader(NULL); + paint.setColor(brightenColor(h, s, l, -0.0588)); + canvas->drawPoint(rect.x() + 1, rect.y() + 1, paint); + canvas->drawPoint(right - 2, rect.y() + 1, paint); + canvas->drawPoint(rect.x() + 1, bottom - 2, paint); + canvas->drawPoint(right - 2, bottom - 2, paint); +} + +bool RenderThemeChromiumSkia::paintButton(RenderObject* o, const PaintInfo& i, const IntRect& rect) +{ + paintButtonLike(this, o, i, rect); + return false; +} + +void RenderThemeChromiumSkia::adjustButtonStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + if (style->appearance() == PushButtonPart) { + // Ignore line-height. + style->setLineHeight(RenderStyle::initialLineHeight()); + } +} + + +bool RenderThemeChromiumSkia::paintTextField(RenderObject* o, const PaintInfo& i, const IntRect& rect) +{ + return true; +} + +bool RenderThemeChromiumSkia::paintTextArea(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + return paintTextField(o, i, r); +} + +void RenderThemeChromiumSkia::adjustSearchFieldStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + // Ignore line-height. + style->setLineHeight(RenderStyle::initialLineHeight()); +} + +bool RenderThemeChromiumSkia::paintSearchField(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + return paintTextField(o, i, r); +} + +void RenderThemeChromiumSkia::adjustSearchFieldCancelButtonStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + // Scale the button size based on the font size + float fontScale = style->fontSize() / defaultControlFontPixelSize; + int cancelButtonSize = lroundf(std::min(std::max(minCancelButtonSize, defaultCancelButtonSize * fontScale), maxCancelButtonSize)); + style->setWidth(Length(cancelButtonSize, Fixed)); + style->setHeight(Length(cancelButtonSize, Fixed)); +} + +IntRect RenderThemeChromiumSkia::convertToPaintingRect(RenderObject* inputRenderer, const RenderObject* partRenderer, IntRect partRect, const IntRect& localOffset) const +{ + // Compute an offset between the part renderer and the input renderer. + IntSize offsetFromInputRenderer = -(partRenderer->offsetFromAncestorContainer(inputRenderer)); + // Move the rect into partRenderer's coords. + partRect.move(offsetFromInputRenderer); + // Account for the local drawing offset. + partRect.move(localOffset.x(), localOffset.y()); + + return partRect; +} + +bool RenderThemeChromiumSkia::paintSearchFieldCancelButton(RenderObject* cancelButtonObject, const PaintInfo& paintInfo, const IntRect& r) +{ + // Get the renderer of <input> element. + Node* input = cancelButtonObject->node()->shadowAncestorNode(); + if (!input->renderer()->isBox()) + return false; + RenderBox* inputRenderBox = toRenderBox(input->renderer()); + IntRect inputContentBox = inputRenderBox->contentBoxRect(); + + // Make sure the scaled button stays square and will fit in its parent's box. + int cancelButtonSize = std::min(inputContentBox.width(), std::min(inputContentBox.height(), r.height())); + // Calculate cancel button's coordinates relative to the input element. + // Center the button vertically. Round up though, so if it has to be one pixel off-center, it will + // be one pixel closer to the bottom of the field. This tends to look better with the text. + IntRect cancelButtonRect(cancelButtonObject->offsetFromAncestorContainer(inputRenderBox).width(), + inputContentBox.y() + (inputContentBox.height() - cancelButtonSize + 1) / 2, + cancelButtonSize, cancelButtonSize); + IntRect paintingRect = convertToPaintingRect(inputRenderBox, cancelButtonObject, cancelButtonRect, r); + + static Image* cancelImage = Image::loadPlatformResource("searchCancel").releaseRef(); + static Image* cancelPressedImage = Image::loadPlatformResource("searchCancelPressed").releaseRef(); + paintInfo.context->drawImage(isPressed(cancelButtonObject) ? cancelPressedImage : cancelImage, + cancelButtonObject->style()->colorSpace(), paintingRect); + return false; +} + +void RenderThemeChromiumSkia::adjustSearchFieldDecorationStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + IntSize emptySize(1, 11); + style->setWidth(Length(emptySize.width(), Fixed)); + style->setHeight(Length(emptySize.height(), Fixed)); +} + +void RenderThemeChromiumSkia::adjustSearchFieldResultsDecorationStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + // Scale the decoration size based on the font size + float fontScale = style->fontSize() / defaultControlFontPixelSize; + int magnifierSize = lroundf(std::min(std::max(minSearchFieldResultsDecorationSize, defaultSearchFieldResultsDecorationSize * fontScale), + maxSearchFieldResultsDecorationSize)); + style->setWidth(Length(magnifierSize, Fixed)); + style->setHeight(Length(magnifierSize, Fixed)); +} + +bool RenderThemeChromiumSkia::paintSearchFieldResultsDecoration(RenderObject* magnifierObject, const PaintInfo& paintInfo, const IntRect& r) +{ + // Get the renderer of <input> element. + Node* input = magnifierObject->node()->shadowAncestorNode(); + if (!input->renderer()->isBox()) + return false; + RenderBox* inputRenderBox = toRenderBox(input->renderer()); + IntRect inputContentBox = inputRenderBox->contentBoxRect(); + + // Make sure the scaled decoration stays square and will fit in its parent's box. + int magnifierSize = std::min(inputContentBox.width(), std::min(inputContentBox.height(), r.height())); + // Calculate decoration's coordinates relative to the input element. + // Center the decoration vertically. Round up though, so if it has to be one pixel off-center, it will + // be one pixel closer to the bottom of the field. This tends to look better with the text. + IntRect magnifierRect(magnifierObject->offsetFromAncestorContainer(inputRenderBox).width(), + inputContentBox.y() + (inputContentBox.height() - magnifierSize + 1) / 2, + magnifierSize, magnifierSize); + IntRect paintingRect = convertToPaintingRect(inputRenderBox, magnifierObject, magnifierRect, r); + + static Image* magnifierImage = Image::loadPlatformResource("searchMagnifier").releaseRef(); + paintInfo.context->drawImage(magnifierImage, magnifierObject->style()->colorSpace(), paintingRect); + return false; +} + +void RenderThemeChromiumSkia::adjustSearchFieldResultsButtonStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + // Scale the button size based on the font size + float fontScale = style->fontSize() / defaultControlFontPixelSize; + int magnifierHeight = lroundf(std::min(std::max(minSearchFieldResultsDecorationSize, defaultSearchFieldResultsDecorationSize * fontScale), + maxSearchFieldResultsDecorationSize)); + int magnifierWidth = lroundf(magnifierHeight * defaultSearchFieldResultsButtonWidth / defaultSearchFieldResultsDecorationSize); + style->setWidth(Length(magnifierWidth, Fixed)); + style->setHeight(Length(magnifierHeight, Fixed)); +} + +bool RenderThemeChromiumSkia::paintSearchFieldResultsButton(RenderObject* magnifierObject, const PaintInfo& paintInfo, const IntRect& r) +{ + // Get the renderer of <input> element. + Node* input = magnifierObject->node()->shadowAncestorNode(); + if (!input->renderer()->isBox()) + return false; + RenderBox* inputRenderBox = toRenderBox(input->renderer()); + IntRect inputContentBox = inputRenderBox->contentBoxRect(); + + // Make sure the scaled decoration will fit in its parent's box. + int magnifierHeight = std::min(inputContentBox.height(), r.height()); + int magnifierWidth = std::min(inputContentBox.width(), static_cast<int>(magnifierHeight * defaultSearchFieldResultsButtonWidth / defaultSearchFieldResultsDecorationSize)); + IntRect magnifierRect(magnifierObject->offsetFromAncestorContainer(inputRenderBox).width(), + inputContentBox.y() + (inputContentBox.height() - magnifierHeight + 1) / 2, + magnifierWidth, magnifierHeight); + IntRect paintingRect = convertToPaintingRect(inputRenderBox, magnifierObject, magnifierRect, r); + + static Image* magnifierImage = Image::loadPlatformResource("searchMagnifierResults").releaseRef(); + paintInfo.context->drawImage(magnifierImage, magnifierObject->style()->colorSpace(), paintingRect); + return false; +} + +bool RenderThemeChromiumSkia::paintMediaControlsBackground(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ +#if ENABLE(VIDEO) + return RenderMediaControlsChromium::paintMediaControlsPart(MediaTimelineContainer, object, paintInfo, rect); +#else + UNUSED_PARAM(object); + UNUSED_PARAM(paintInfo); + UNUSED_PARAM(rect); + return false; +#endif +} + +bool RenderThemeChromiumSkia::paintMediaSliderTrack(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ +#if ENABLE(VIDEO) + return RenderMediaControlsChromium::paintMediaControlsPart(MediaSlider, object, paintInfo, rect); +#else + UNUSED_PARAM(object); + UNUSED_PARAM(paintInfo); + UNUSED_PARAM(rect); + return false; +#endif +} + +bool RenderThemeChromiumSkia::paintMediaVolumeSliderTrack(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ +#if ENABLE(VIDEO) + return RenderMediaControlsChromium::paintMediaControlsPart(MediaVolumeSlider, object, paintInfo, rect); +#else + UNUSED_PARAM(object); + UNUSED_PARAM(paintInfo); + UNUSED_PARAM(rect); + return false; +#endif +} + +void RenderThemeChromiumSkia::adjustSliderThumbSize(RenderObject* object) const +{ +#if ENABLE(VIDEO) + RenderMediaControlsChromium::adjustMediaSliderThumbSize(object); +#else + UNUSED_PARAM(object); +#endif +} + +bool RenderThemeChromiumSkia::paintMediaSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ +#if ENABLE(VIDEO) + return RenderMediaControlsChromium::paintMediaControlsPart(MediaSliderThumb, object, paintInfo, rect); +#else + UNUSED_PARAM(object); + UNUSED_PARAM(paintInfo); + UNUSED_PARAM(rect); + return false; +#endif +} + +bool RenderThemeChromiumSkia::paintMediaVolumeSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ +#if ENABLE(VIDEO) + return RenderMediaControlsChromium::paintMediaControlsPart(MediaVolumeSliderThumb, object, paintInfo, rect); +#else + UNUSED_PARAM(object); + UNUSED_PARAM(paintInfo); + UNUSED_PARAM(rect); + return false; +#endif +} + +bool RenderThemeChromiumSkia::paintMediaPlayButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ +#if ENABLE(VIDEO) + return RenderMediaControlsChromium::paintMediaControlsPart(MediaPlayButton, object, paintInfo, rect); +#else + UNUSED_PARAM(object); + UNUSED_PARAM(paintInfo); + UNUSED_PARAM(rect); + return false; +#endif +} + +bool RenderThemeChromiumSkia::paintMediaMuteButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) +{ +#if ENABLE(VIDEO) + return RenderMediaControlsChromium::paintMediaControlsPart(MediaMuteButton, object, paintInfo, rect); +#else + UNUSED_PARAM(object); + UNUSED_PARAM(paintInfo); + UNUSED_PARAM(rect); + return false; +#endif +} + +void RenderThemeChromiumSkia::adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, WebCore::Element* e) const +{ + // Height is locked to auto on all browsers. + style->setLineHeight(RenderStyle::initialLineHeight()); +} + +bool RenderThemeChromiumSkia::paintMenuList(RenderObject* o, const PaintInfo& i, const IntRect& rect) +{ + SkCanvas* const canvas = i.context->platformContext()->canvas(); + const int right = rect.x() + rect.width(); + const int middle = rect.y() + rect.height() / 2; + + paintButtonLike(this, o, i, rect); + + SkPaint paint; + paint.setColor(SK_ColorBLACK); + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kFill_Style); + + int arrowXPosition = (o->style()->direction() == RTL) ? rect.x() + 7 : right - 13; + SkPath path; + path.moveTo(arrowXPosition, middle - 3); + path.rLineTo(6, 0); + path.rLineTo(-3, 6); + path.close(); + canvas->drawPath(path, paint); + + return false; +} + +void RenderThemeChromiumSkia::adjustMenuListButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + adjustMenuListStyle(selector, style, e); +} + +// Used to paint styled menulists (i.e. with a non-default border) +bool RenderThemeChromiumSkia::paintMenuListButton(RenderObject* o, const PaintInfo& i, const IntRect& rect) +{ + return paintMenuList(o, i, rect); +} + +bool RenderThemeChromiumSkia::paintSliderTrack(RenderObject*, const PaintInfo& i, const IntRect& rect) +{ + // Just paint a grey box for now (matches the color of a scrollbar background. + SkCanvas* const canvas = i.context->platformContext()->canvas(); + int verticalCenter = rect.y() + rect.height() / 2; + int top = std::max(rect.y(), verticalCenter - 2); + int bottom = std::min(rect.y() + rect.height(), verticalCenter + 2); + + SkPaint paint; + const SkColor grey = SkColorSetARGB(0xff, 0xe3, 0xdd, 0xd8); + paint.setColor(grey); + + SkRect skrect; + skrect.set(rect.x(), top, rect.x() + rect.width(), bottom); + canvas->drawRect(skrect, paint); + + return false; +} + +bool RenderThemeChromiumSkia::paintSliderThumb(RenderObject* o, const PaintInfo& i, const IntRect& rect) +{ + // Make a thumb similar to the scrollbar thumb. + const bool hovered = isHovered(o) || toRenderSlider(o->parent())->inDragMode(); + const int midx = rect.x() + rect.width() / 2; + const int midy = rect.y() + rect.height() / 2; + const bool vertical = (o->style()->appearance() == SliderThumbVerticalPart); + SkCanvas* const canvas = i.context->platformContext()->canvas(); + + const SkColor thumbLightGrey = SkColorSetARGB(0xff, 0xf4, 0xf2, 0xef); + const SkColor thumbDarkGrey = SkColorSetARGB(0xff, 0xea, 0xe5, 0xe0); + SkPaint paint; + paint.setColor(hovered ? SK_ColorWHITE : thumbLightGrey); + + SkIRect skrect; + if (vertical) + skrect.set(rect.x(), rect.y(), midx + 1, rect.bottom()); + else + skrect.set(rect.x(), rect.y(), rect.right(), midy + 1); + + canvas->drawIRect(skrect, paint); + + paint.setColor(hovered ? thumbLightGrey : thumbDarkGrey); + + if (vertical) + skrect.set(midx + 1, rect.y(), rect.right(), rect.bottom()); + else + skrect.set(rect.x(), midy + 1, rect.right(), rect.bottom()); + + canvas->drawIRect(skrect, paint); + + const SkColor borderDarkGrey = SkColorSetARGB(0xff, 0x9d, 0x96, 0x8e); + paint.setColor(borderDarkGrey); + drawBox(canvas, rect, paint); + + if (rect.height() > 10 && rect.width() > 10) { + drawHorizLine(canvas, midx - 2, midx + 2, midy, paint); + drawHorizLine(canvas, midx - 2, midx + 2, midy - 3, paint); + drawHorizLine(canvas, midx - 2, midx + 2, midy + 3, paint); + } + + return false; +} + +int RenderThemeChromiumSkia::popupInternalPaddingLeft(RenderStyle* style) const +{ + return menuListInternalPadding(style, LeftPadding); +} + +int RenderThemeChromiumSkia::popupInternalPaddingRight(RenderStyle* style) const +{ + return menuListInternalPadding(style, RightPadding); +} + +int RenderThemeChromiumSkia::popupInternalPaddingTop(RenderStyle* style) const +{ + return menuListInternalPadding(style, TopPadding); +} + +int RenderThemeChromiumSkia::popupInternalPaddingBottom(RenderStyle* style) const +{ + return menuListInternalPadding(style, BottomPadding); +} + +#if ENABLE(VIDEO) +bool RenderThemeChromiumSkia::shouldRenderMediaControlPart(ControlPart part, Element* e) +{ + return RenderMediaControlsChromium::shouldRenderMediaControlPart(part, e); +} +#endif + +// static +void RenderThemeChromiumSkia::setDefaultFontSize(int fontSize) +{ + defaultFontSize = static_cast<float>(fontSize); +} + +double RenderThemeChromiumSkia::caretBlinkIntervalInternal() const +{ + return RenderTheme::caretBlinkInterval(); +} + +int RenderThemeChromiumSkia::menuListInternalPadding(RenderStyle* style, int paddingType) const +{ + // This internal padding is in addition to the user-supplied padding. + // Matches the FF behavior. + int padding = styledMenuListInternalPadding[paddingType]; + + // Reserve the space for right arrow here. The rest of the padding is + // set by adjustMenuListStyle, since PopMenuWin.cpp uses the padding from + // RenderMenuList to lay out the individual items in the popup. + // If the MenuList actually has appearance "NoAppearance", then that means + // we don't draw a button, so don't reserve space for it. + const int barType = style->direction() == LTR ? RightPadding : LeftPadding; + if (paddingType == barType && style->appearance() != NoControlPart) + padding += ScrollbarTheme::nativeTheme()->scrollbarThickness(); + + return padding; +} + +#if ENABLE(PROGRESS_TAG) + +// +// Following values are come from default of GTK+ +// +static const int progressDeltaPixelsPerSecond = 100; +static const int progressActivityBlocks = 5; +static const int progressAnimationFrmaes = 10; +static const double progressAnimationInterval = 0.125; + +IntRect RenderThemeChromiumSkia::determinateProgressValueRectFor(RenderProgress* renderProgress, const IntRect& rect) const +{ + int dx = rect.width() * renderProgress->position(); + if (renderProgress->style()->direction() == RTL) + return IntRect(rect.x() + rect.width() - dx, rect.y(), dx, rect.height()); + return IntRect(rect.x(), rect.y(), dx, rect.height()); +} + +IntRect RenderThemeChromiumSkia::indeterminateProgressValueRectFor(RenderProgress* renderProgress, const IntRect& rect) const +{ + + int valueWidth = rect.width() / progressActivityBlocks; + int movableWidth = rect.width() - valueWidth; + if (movableWidth <= 0) + return IntRect(); + + double progress = renderProgress->animationProgress(); + if (progress < 0.5) + return IntRect(rect.x() + progress * 2 * movableWidth, rect.y(), valueWidth, rect.height()); + return IntRect(rect.x() + (1.0 - progress) * 2 * movableWidth, rect.y(), valueWidth, rect.height()); +} + +double RenderThemeChromiumSkia::animationRepeatIntervalForProgressBar(RenderProgress*) const +{ + return progressAnimationInterval; +} + +double RenderThemeChromiumSkia::animationDurationForProgressBar(RenderProgress* renderProgress) const +{ + return progressAnimationInterval * progressAnimationFrmaes * 2; // "2" for back and forth +} + +bool RenderThemeChromiumSkia::paintProgressBar(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect) +{ + static Image* barImage = Image::loadPlatformResource("linuxProgressBar").releaseRef(); + static Image* valueImage = Image::loadPlatformResource("linuxProgressValue").releaseRef(); + static Image* leftBorderImage = Image::loadPlatformResource("linuxProgressBorderLeft").releaseRef(); + static Image* rightBorderImage = Image::loadPlatformResource("linuxProgressBorderRight").releaseRef(); + ASSERT(barImage->height() == valueImage->height()); + + if (!renderObject->isProgress()) + return true; + + paintInfo.context->platformContext()->setImageResamplingHint(barImage->size(), rect.size()); + + RenderProgress* renderProgress = toRenderProgress(renderObject); + double tileScale = static_cast<double>(rect.height()) / barImage->height(); + IntSize barTileSize(static_cast<int>(barImage->width() * tileScale), rect.height()); + ColorSpace colorSpace = renderObject->style()->colorSpace(); + + paintInfo.context->drawTiledImage(barImage, colorSpace, rect, IntPoint(0, 0), barTileSize); + + IntRect valueRect = progressValueRectFor(renderProgress, rect); + if (valueRect.width()) { + + IntSize valueTileSize(std::max(1, static_cast<int>(valueImage->width() * tileScale)), valueRect.height()); + + int leftOffset = valueRect.x() - rect.x(); + int roundedLeftOffset= (leftOffset / valueTileSize.width()) * valueTileSize.width(); + int dstLeftValueWidth = roundedLeftOffset - leftOffset + (leftOffset % valueImage->width()) ? valueTileSize.width() : 0; + + IntRect dstLeftValueRect(valueRect.x(), valueRect.y(), dstLeftValueWidth, valueRect.height()); + int srcLeftValueWidth = dstLeftValueWidth / tileScale; + IntRect srcLeftValueRect(valueImage->width() - srcLeftValueWidth, 0, srcLeftValueWidth, valueImage->height()); + paintInfo.context->drawImage(valueImage, colorSpace, dstLeftValueRect, srcLeftValueRect); + + int rightOffset = valueRect.right() - rect.x(); + int roundedRightOffset = (rightOffset / valueTileSize.width()) * valueTileSize.width(); + int dstRightValueWidth = rightOffset - roundedRightOffset; + IntRect dstRightValueRect(rect.x() + roundedRightOffset, valueRect.y(), dstRightValueWidth, valueTileSize.height()); + int srcRightValueWidth = dstRightValueWidth / tileScale; + IntRect srcRightValueRect(0, 0, srcRightValueWidth, valueImage->height()); + paintInfo.context->drawImage(valueImage, colorSpace, dstRightValueRect, srcRightValueRect); + + IntRect alignedValueRect(dstLeftValueRect.right(), dstLeftValueRect.y(), + dstRightValueRect.x() - dstLeftValueRect.right(), dstLeftValueRect.height()); + paintInfo.context->drawTiledImage(valueImage, colorSpace, alignedValueRect, IntPoint(0, 0), valueTileSize); + } + + int dstLeftBorderWidth = leftBorderImage->width() * tileScale; + IntRect dstLeftBorderRect(rect.x(), rect.y(), dstLeftBorderWidth, rect.height()); + paintInfo.context->drawImage(leftBorderImage, colorSpace, dstLeftBorderRect, leftBorderImage->rect()); + + int dstRightBorderWidth = rightBorderImage->width() * tileScale; + IntRect dstRightBorderRect(rect.right() - dstRightBorderWidth, rect.y(), dstRightBorderWidth, rect.height()); + paintInfo.context->drawImage(rightBorderImage, colorSpace, dstRightBorderRect, rightBorderImage->rect()); + + paintInfo.context->platformContext()->clearImageResamplingHint(); + + return false; +} + + +IntRect RenderThemeChromiumSkia::progressValueRectFor(RenderProgress* renderProgress, const IntRect& rect) const +{ + return renderProgress->isDeterminate() ? determinateProgressValueRectFor(renderProgress, rect) : indeterminateProgressValueRectFor(renderProgress, rect); +} + +#endif + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderThemeChromiumSkia.h b/Source/WebCore/rendering/RenderThemeChromiumSkia.h new file mode 100644 index 0000000..a11046d --- /dev/null +++ b/Source/WebCore/rendering/RenderThemeChromiumSkia.h @@ -0,0 +1,170 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2006 Apple Computer, Inc. + * Copyright (C) 2006 Michael Emmel mike.emmel@gmail.com + * Copyright (C) 2007 Holger Hans Peter Freyther + * Copyright (C) 2007 Alp Toker <alp@atoker.com> + * Copyright (C) 2008, 2009 Google, Inc. + * All rights reserved. + * + * 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. + * + */ + +#ifndef RenderThemeChromiumSkia_h +#define RenderThemeChromiumSkia_h + +#include "RenderTheme.h" + +namespace WebCore { + +class RenderProgress; + + class RenderThemeChromiumSkia : public RenderTheme { + public: + RenderThemeChromiumSkia(); + virtual ~RenderThemeChromiumSkia(); + + virtual String extraDefaultStyleSheet(); + virtual String extraQuirksStyleSheet(); +#if ENABLE(VIDEO) + virtual String extraMediaControlsStyleSheet(); +#endif + + // A method asking if the theme's controls actually care about redrawing when hovered. + virtual bool supportsHover(const RenderStyle*) const; + + // A method asking if the theme is able to draw the focus ring. + virtual bool supportsFocusRing(const RenderStyle*) const; + + // The platform selection color. + virtual Color platformActiveSelectionBackgroundColor() const; + virtual Color platformInactiveSelectionBackgroundColor() const; + virtual Color platformActiveSelectionForegroundColor() const; + virtual Color platformInactiveSelectionForegroundColor() const; + virtual Color platformFocusRingColor() const; + + // To change the blink interval, override caretBlinkIntervalInternal instead of this one so that we may share layout test code an intercepts. + virtual double caretBlinkInterval() const; + + // System fonts. + virtual void systemFont(int propId, FontDescription&) const; + + virtual int minimumMenuListSize(RenderStyle*) const; + + virtual bool paintCheckbox(RenderObject*, const PaintInfo&, const IntRect&); + virtual void setCheckboxSize(RenderStyle*) const; + + virtual bool paintRadio(RenderObject*, const PaintInfo&, const IntRect&); + virtual void setRadioSize(RenderStyle*) const; + + virtual bool paintButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + + virtual bool paintTextField(RenderObject*, const PaintInfo&, const IntRect&); + + virtual bool paintTextArea(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustSearchFieldStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchField(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustSearchFieldCancelButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldCancelButton(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustSearchFieldDecorationStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + + virtual void adjustSearchFieldResultsDecorationStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldResultsDecoration(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustSearchFieldResultsButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldResultsButton(RenderObject*, const PaintInfo&, const IntRect&); + + virtual bool paintMediaControlsBackground(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSliderTrack(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaVolumeSliderTrack(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustSliderThumbSize(RenderObject*) const; + virtual bool paintMediaSliderThumb(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaVolumeSliderThumb(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaPlayButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaMuteButton(RenderObject*, const PaintInfo&, const IntRect&); + + // MenuList refers to an unstyled menulist (meaning a menulist without + // background-color or border set) and MenuListButton refers to a styled + // menulist (a menulist with background-color or border set). They have + // this distinction to support showing aqua style themes whenever they + // possibly can, which is something we don't want to replicate. + // + // In short, we either go down the MenuList code path or the MenuListButton + // codepath. We never go down both. And in both cases, they render the + // entire menulist. + virtual void adjustMenuListStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintMenuList(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustMenuListButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintMenuListButton(RenderObject*, const PaintInfo&, const IntRect&); + + virtual bool paintSliderTrack(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintSliderThumb(RenderObject*, const PaintInfo&, const IntRect&); + +#if ENABLE(PROGRESS_TAG) + virtual double animationRepeatIntervalForProgressBar(RenderProgress*) const; + virtual double animationDurationForProgressBar(RenderProgress*) const; + virtual bool paintProgressBar(RenderObject*, const PaintInfo&, const IntRect&); +#endif + + // These methods define the padding for the MenuList's inner block. + virtual int popupInternalPaddingLeft(RenderStyle*) const; + virtual int popupInternalPaddingRight(RenderStyle*) const; + virtual int popupInternalPaddingTop(RenderStyle*) const; + virtual int popupInternalPaddingBottom(RenderStyle*) const; + +#if ENABLE(VIDEO) + // Media controls + virtual bool shouldRenderMediaControlPart(ControlPart, Element*); +#endif + + // Provide a way to pass the default font size from the Settings object + // to the render theme. FIXME: http://b/1129186 A cleaner way would be + // to remove the default font size from this object and have callers + // that need the value to get it directly from the appropriate Settings + // object. + static void setDefaultFontSize(int); + + protected: + static const String& defaultGUIFont(); + + // The default variable-width font size. We use this as the default font + // size for the "system font", and as a base size (which we then shrink) for + // form control fonts. + static float defaultFontSize; + + virtual double caretBlinkIntervalInternal() const; + +#if ENABLE(PROGRESS_TAG) + IntRect determinateProgressValueRectFor(RenderProgress*, const IntRect&) const; + IntRect indeterminateProgressValueRectFor(RenderProgress*, const IntRect&) const; + IntRect progressValueRectFor(RenderProgress*, const IntRect&) const; +#endif + + private: + int menuListInternalPadding(RenderStyle*, int paddingType) const; + bool paintMediaButtonInternal(GraphicsContext*, const IntRect&, Image*); + IntRect convertToPaintingRect(RenderObject* inputRenderer, const RenderObject* partRenderer, IntRect partRect, const IntRect& localOffset) const; + }; + +} // namespace WebCore + +#endif // RenderThemeChromiumSkia_h diff --git a/Source/WebCore/rendering/RenderThemeChromiumWin.cpp b/Source/WebCore/rendering/RenderThemeChromiumWin.cpp new file mode 100644 index 0000000..be670ff --- /dev/null +++ b/Source/WebCore/rendering/RenderThemeChromiumWin.cpp @@ -0,0 +1,770 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2006 Apple Computer, Inc. + * Copyright (C) 2008, 2009 Google, Inc. + * Copyright (C) 2009 Kenneth Rohde Christiansen + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" +#include "RenderThemeChromiumWin.h" + +#include <windows.h> +#include <uxtheme.h> +#include <vssym32.h> + +#include "CSSValueKeywords.h" +#include "ChromiumBridge.h" +#include "CurrentTime.h" +#include "FontSelector.h" +#include "FontUtilsChromiumWin.h" +#include "GraphicsContext.h" +#include "HTMLMediaElement.h" +#include "HTMLNames.h" +#include "MediaControlElements.h" +#include "RenderBox.h" +#include "RenderProgress.h" +#include "RenderSlider.h" +#include "ScrollbarTheme.h" +#include "TransparencyWin.h" +#include "WindowsVersion.h" + +// FIXME: This dependency should eventually be removed. +#include <skia/ext/skia_utils_win.h> + +#define SIZEOF_STRUCT_WITH_SPECIFIED_LAST_MEMBER(structName, member) \ + offsetof(structName, member) + \ + (sizeof static_cast<structName*>(0)->member) +#define NONCLIENTMETRICS_SIZE_PRE_VISTA \ + SIZEOF_STRUCT_WITH_SPECIFIED_LAST_MEMBER(NONCLIENTMETRICS, lfMessageFont) + +namespace WebCore { + +// The standard width for the menu list drop-down button when run under +// layout test mode. Use the value that's currently captured in most baselines. +static const int kStandardMenuListButtonWidth = 17; + +namespace { +class ThemePainter { +public: + ThemePainter(GraphicsContext* context, const IntRect& r) + { + TransparencyWin::TransformMode transformMode = getTransformMode(context->getCTM()); + m_helper.init(context, getLayerMode(context, transformMode), transformMode, r); + + if (!m_helper.context()) { + // TransparencyWin doesn't have well-defined copy-ctor nor op=() + // so we re-initialize it instead of assigning a fresh istance. + // On the reinitialization, we fallback to use NoLayer mode. + // Note that the original initialization failure can be caused by + // a failure of an internal buffer allocation and NoLayer mode + // does not have such buffer allocations. + m_helper.~TransparencyWin(); + new (&m_helper) TransparencyWin(); + m_helper.init(context, TransparencyWin::NoLayer, transformMode, r); + } + } + + ~ThemePainter() + { + m_helper.composite(); + } + + GraphicsContext* context() { return m_helper.context(); } + const IntRect& drawRect() { return m_helper.drawRect(); } + +private: + + static bool canvasHasMultipleLayers(const SkCanvas* canvas) + { + SkCanvas::LayerIter iter(const_cast<SkCanvas*>(canvas), false); + iter.next(); // There is always at least one layer. + return !iter.done(); // There is > 1 layer if the the iterator can stil advance. + } + + static TransparencyWin::LayerMode getLayerMode(GraphicsContext* context, TransparencyWin::TransformMode transformMode) + { + if (context->platformContext()->isDrawingToImageBuffer()) // Might have transparent background. + return TransparencyWin::WhiteLayer; + if (canvasHasMultipleLayers(context->platformContext()->canvas())) // Needs antialiasing help. + return TransparencyWin::OpaqueCompositeLayer; + // Nothing interesting. + return transformMode == TransparencyWin::KeepTransform ? TransparencyWin::NoLayer : TransparencyWin::OpaqueCompositeLayer; + } + + static TransparencyWin::TransformMode getTransformMode(const AffineTransform& matrix) + { + if (matrix.b() || matrix.c()) // Skew. + return TransparencyWin::Untransform; + if (matrix.a() != 1.0 || matrix.d() != 1.0) // Scale. + return TransparencyWin::ScaleTransform; + // Nothing interesting. + return TransparencyWin::KeepTransform; + } + + TransparencyWin m_helper; +}; + +} // namespace + +static void getNonClientMetrics(NONCLIENTMETRICS* metrics) +{ + static UINT size = isVistaOrNewer() ? + sizeof(NONCLIENTMETRICS) : NONCLIENTMETRICS_SIZE_PRE_VISTA; + metrics->cbSize = size; + bool success = !!SystemParametersInfo(SPI_GETNONCLIENTMETRICS, size, metrics, 0); + ASSERT(success); +} + +static FontDescription smallSystemFont; +static FontDescription menuFont; +static FontDescription labelFont; + +// Internal static helper functions. We don't put them in an anonymous +// namespace so they have easier access to the WebCore namespace. + +static bool supportsFocus(ControlPart appearance) +{ + switch (appearance) { + case PushButtonPart: + case ButtonPart: + case DefaultButtonPart: + case SearchFieldPart: + case TextFieldPart: + case TextAreaPart: + return true; + } + return false; +} + +// Return the height of system font |font| in pixels. We use this size by +// default for some non-form-control elements. +static float systemFontSize(const LOGFONT& font) +{ + float size = -font.lfHeight; + if (size < 0) { + HFONT hFont = CreateFontIndirect(&font); + if (hFont) { + HDC hdc = GetDC(0); // What about printing? Is this the right DC? + if (hdc) { + HGDIOBJ hObject = SelectObject(hdc, hFont); + TEXTMETRIC tm; + GetTextMetrics(hdc, &tm); + SelectObject(hdc, hObject); + ReleaseDC(0, hdc); + size = tm.tmAscent; + } + DeleteObject(hFont); + } + } + + // The "codepage 936" bit here is from Gecko; apparently this helps make + // fonts more legible in Simplified Chinese where the default font size is + // too small. + // + // FIXME: http://b/1119883 Since this is only used for "small caption", + // "menu", and "status bar" objects, I'm not sure how much this even + // matters. Plus the Gecko patch went in back in 2002, and maybe this + // isn't even relevant anymore. We should investigate whether this should + // be removed, or perhaps broadened to be "any CJK locale". + // + return ((size < 12.0f) && (GetACP() == 936)) ? 12.0f : size; +} + +// Converts |points| to pixels. One point is 1/72 of an inch. +static float pointsToPixels(float points) +{ + static float pixelsPerInch = 0.0f; + if (!pixelsPerInch) { + HDC hdc = GetDC(0); // What about printing? Is this the right DC? + if (hdc) { // Can this ever actually be NULL? + pixelsPerInch = GetDeviceCaps(hdc, LOGPIXELSY); + ReleaseDC(0, hdc); + } else { + pixelsPerInch = 96.0f; + } + } + + static const float pointsPerInch = 72.0f; + return points / pointsPerInch * pixelsPerInch; +} + +static double querySystemBlinkInterval(double defaultInterval) +{ + UINT blinkTime = GetCaretBlinkTime(); + if (!blinkTime) + return defaultInterval; + if (blinkTime == INFINITE) + return 0; + return blinkTime / 1000.0; +} + +PassRefPtr<RenderTheme> RenderThemeChromiumWin::create() +{ + return adoptRef(new RenderThemeChromiumWin); +} + +PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page* page) +{ + static RenderTheme* rt = RenderThemeChromiumWin::create().releaseRef(); + return rt; +} + +bool RenderThemeChromiumWin::supportsFocusRing(const RenderStyle* style) const +{ + // Let webkit draw one of its halo rings around any focused element, + // except push buttons. For buttons we use the windows PBS_DEFAULTED + // styling to give it a blue border. + return style->appearance() == ButtonPart + || style->appearance() == PushButtonPart; +} + +Color RenderThemeChromiumWin::platformActiveSelectionBackgroundColor() const +{ + if (ChromiumBridge::layoutTestMode()) + return Color(0x00, 0x00, 0xff); // Royal blue. + COLORREF color = GetSysColor(COLOR_HIGHLIGHT); + return Color(GetRValue(color), GetGValue(color), GetBValue(color), 0xff); +} + +Color RenderThemeChromiumWin::platformInactiveSelectionBackgroundColor() const +{ + if (ChromiumBridge::layoutTestMode()) + return Color(0x99, 0x99, 0x99); // Medium gray. + COLORREF color = GetSysColor(COLOR_GRAYTEXT); + return Color(GetRValue(color), GetGValue(color), GetBValue(color), 0xff); +} + +Color RenderThemeChromiumWin::platformActiveSelectionForegroundColor() const +{ + if (ChromiumBridge::layoutTestMode()) + return Color(0xff, 0xff, 0xcc); // Pale yellow. + COLORREF color = GetSysColor(COLOR_HIGHLIGHTTEXT); + return Color(GetRValue(color), GetGValue(color), GetBValue(color), 0xff); +} + +Color RenderThemeChromiumWin::platformInactiveSelectionForegroundColor() const +{ + return Color::white; +} + +Color RenderThemeChromiumWin::platformActiveTextSearchHighlightColor() const +{ + return Color(0xff, 0x96, 0x32); // Orange. +} + +Color RenderThemeChromiumWin::platformInactiveTextSearchHighlightColor() const +{ + return Color(0xff, 0xff, 0x96); // Yellow. +} + +void RenderThemeChromiumWin::systemFont(int propId, FontDescription& fontDescription) const +{ + // This logic owes much to RenderThemeSafari.cpp. + FontDescription* cachedDesc = 0; + AtomicString faceName; + float fontSize = 0; + switch (propId) { + case CSSValueSmallCaption: + cachedDesc = &smallSystemFont; + if (!smallSystemFont.isAbsoluteSize()) { + NONCLIENTMETRICS metrics; + getNonClientMetrics(&metrics); + faceName = AtomicString(metrics.lfSmCaptionFont.lfFaceName, wcslen(metrics.lfSmCaptionFont.lfFaceName)); + fontSize = systemFontSize(metrics.lfSmCaptionFont); + } + break; + case CSSValueMenu: + cachedDesc = &menuFont; + if (!menuFont.isAbsoluteSize()) { + NONCLIENTMETRICS metrics; + getNonClientMetrics(&metrics); + faceName = AtomicString(metrics.lfMenuFont.lfFaceName, wcslen(metrics.lfMenuFont.lfFaceName)); + fontSize = systemFontSize(metrics.lfMenuFont); + } + break; + case CSSValueStatusBar: + cachedDesc = &labelFont; + if (!labelFont.isAbsoluteSize()) { + NONCLIENTMETRICS metrics; + getNonClientMetrics(&metrics); + faceName = metrics.lfStatusFont.lfFaceName; + fontSize = systemFontSize(metrics.lfStatusFont); + } + break; + case CSSValueWebkitMiniControl: + case CSSValueWebkitSmallControl: + case CSSValueWebkitControl: + faceName = defaultGUIFont(); + // Why 2 points smaller? Because that's what Gecko does. + fontSize = defaultFontSize - pointsToPixels(2); + break; + default: + faceName = defaultGUIFont(); + fontSize = defaultFontSize; + break; + } + + if (!cachedDesc) + cachedDesc = &fontDescription; + + if (fontSize) { + cachedDesc->firstFamily().setFamily(faceName); + cachedDesc->setIsAbsoluteSize(true); + cachedDesc->setGenericFamily(FontDescription::NoFamily); + cachedDesc->setSpecifiedSize(fontSize); + cachedDesc->setWeight(FontWeightNormal); + cachedDesc->setItalic(false); + } + fontDescription = *cachedDesc; +} + +// Map a CSSValue* system color to an index understood by GetSysColor(). +static int cssValueIdToSysColorIndex(int cssValueId) +{ + switch (cssValueId) { + case CSSValueActiveborder: return COLOR_ACTIVEBORDER; + case CSSValueActivecaption: return COLOR_ACTIVECAPTION; + case CSSValueAppworkspace: return COLOR_APPWORKSPACE; + case CSSValueBackground: return COLOR_BACKGROUND; + case CSSValueButtonface: return COLOR_BTNFACE; + case CSSValueButtonhighlight: return COLOR_BTNHIGHLIGHT; + case CSSValueButtonshadow: return COLOR_BTNSHADOW; + case CSSValueButtontext: return COLOR_BTNTEXT; + case CSSValueCaptiontext: return COLOR_CAPTIONTEXT; + case CSSValueGraytext: return COLOR_GRAYTEXT; + case CSSValueHighlight: return COLOR_HIGHLIGHT; + case CSSValueHighlighttext: return COLOR_HIGHLIGHTTEXT; + case CSSValueInactiveborder: return COLOR_INACTIVEBORDER; + case CSSValueInactivecaption: return COLOR_INACTIVECAPTION; + case CSSValueInactivecaptiontext: return COLOR_INACTIVECAPTIONTEXT; + case CSSValueInfobackground: return COLOR_INFOBK; + case CSSValueInfotext: return COLOR_INFOTEXT; + case CSSValueMenu: return COLOR_MENU; + case CSSValueMenutext: return COLOR_MENUTEXT; + case CSSValueScrollbar: return COLOR_SCROLLBAR; + case CSSValueThreeddarkshadow: return COLOR_3DDKSHADOW; + case CSSValueThreedface: return COLOR_3DFACE; + case CSSValueThreedhighlight: return COLOR_3DHIGHLIGHT; + case CSSValueThreedlightshadow: return COLOR_3DLIGHT; + case CSSValueThreedshadow: return COLOR_3DSHADOW; + case CSSValueWindow: return COLOR_WINDOW; + case CSSValueWindowframe: return COLOR_WINDOWFRAME; + case CSSValueWindowtext: return COLOR_WINDOWTEXT; + default: return -1; // Unsupported CSSValue + } +} + +Color RenderThemeChromiumWin::systemColor(int cssValueId) const +{ + int sysColorIndex = cssValueIdToSysColorIndex(cssValueId); + if (ChromiumBridge::layoutTestMode() || (sysColorIndex == -1)) + return RenderTheme::systemColor(cssValueId); + + COLORREF color = GetSysColor(sysColorIndex); + return Color(GetRValue(color), GetGValue(color), GetBValue(color)); +} + +void RenderThemeChromiumWin::adjustSliderThumbSize(RenderObject* o) const +{ + // These sizes match what WinXP draws for various menus. + const int sliderThumbAlongAxis = 11; + const int sliderThumbAcrossAxis = 21; + if (o->style()->appearance() == SliderThumbHorizontalPart) { + o->style()->setWidth(Length(sliderThumbAlongAxis, Fixed)); + o->style()->setHeight(Length(sliderThumbAcrossAxis, Fixed)); + } else if (o->style()->appearance() == SliderThumbVerticalPart) { + o->style()->setWidth(Length(sliderThumbAcrossAxis, Fixed)); + o->style()->setHeight(Length(sliderThumbAlongAxis, Fixed)); + } else + RenderThemeChromiumSkia::adjustSliderThumbSize(o); +} + +bool RenderThemeChromiumWin::paintCheckbox(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + return paintButton(o, i, r); +} +bool RenderThemeChromiumWin::paintRadio(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + return paintButton(o, i, r); +} + +bool RenderThemeChromiumWin::paintButton(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + const ThemeData& themeData = getThemeData(o); + + ThemePainter painter(i.context, r); + ChromiumBridge::paintButton(painter.context(), + themeData.m_part, + themeData.m_state, + themeData.m_classicState, + painter.drawRect()); + return false; +} + +bool RenderThemeChromiumWin::paintTextField(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + return paintTextFieldInternal(o, i, r, true); +} + +bool RenderThemeChromiumWin::paintSliderTrack(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + const ThemeData& themeData = getThemeData(o); + + ThemePainter painter(i.context, r); + ChromiumBridge::paintTrackbar(painter.context(), + themeData.m_part, + themeData.m_state, + themeData.m_classicState, + painter.drawRect()); + return false; +} + +bool RenderThemeChromiumWin::paintSliderThumb(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + return paintSliderTrack(o, i, r); +} + +static int menuListButtonWidth() +{ + static int width = ChromiumBridge::layoutTestMode() ? kStandardMenuListButtonWidth : GetSystemMetrics(SM_CXVSCROLL); + return width; +} + +// Used to paint unstyled menulists (i.e. with the default border) +bool RenderThemeChromiumWin::paintMenuList(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + if (!o->isBox()) + return false; + + const RenderBox* box = toRenderBox(o); + int borderRight = box->borderRight(); + int borderLeft = box->borderLeft(); + int borderTop = box->borderTop(); + int borderBottom = box->borderBottom(); + + // If all the borders are 0, then tell skia not to paint the border on the + // textfield. FIXME: http://b/1210017 Figure out how to get Windows to not + // draw individual borders and then pass that to skia so we can avoid + // drawing any borders that are set to 0. For non-zero borders, we draw the + // border, but webkit just draws over it. + bool drawEdges = !(!borderRight && !borderLeft && !borderTop && !borderBottom); + + paintTextFieldInternal(o, i, r, drawEdges); + + // Take padding and border into account. If the MenuList is smaller than + // the size of a button, make sure to shrink it appropriately and not put + // its x position to the left of the menulist. + const int buttonWidth = menuListButtonWidth(); + int spacingLeft = borderLeft + box->paddingLeft(); + int spacingRight = borderRight + box->paddingRight(); + int spacingTop = borderTop + box->paddingTop(); + int spacingBottom = borderBottom + box->paddingBottom(); + + int buttonX; + if (r.right() - r.x() < buttonWidth) + buttonX = r.x(); + else + buttonX = o->style()->direction() == LTR ? r.right() - spacingRight - buttonWidth : r.x() + spacingLeft; + + // Compute the rectangle of the button in the destination image. + IntRect rect(buttonX, + r.y() + spacingTop, + std::min(buttonWidth, r.right() - r.x()), + r.height() - (spacingTop + spacingBottom)); + + // Get the correct theme data for a textfield and paint the menu. + ThemePainter painter(i.context, rect); + ChromiumBridge::paintMenuList(painter.context(), + CP_DROPDOWNBUTTON, + determineState(o), + determineClassicState(o), + painter.drawRect()); + return false; +} + +// static +void RenderThemeChromiumWin::setDefaultFontSize(int fontSize) +{ + RenderThemeChromiumSkia::setDefaultFontSize(fontSize); + + // Reset cached fonts. + smallSystemFont = menuFont = labelFont = FontDescription(); +} + +double RenderThemeChromiumWin::caretBlinkIntervalInternal() const +{ + // This involves a system call, so we cache the result. + static double blinkInterval = querySystemBlinkInterval(RenderTheme::caretBlinkInterval()); + return blinkInterval; +} + +unsigned RenderThemeChromiumWin::determineState(RenderObject* o, ControlSubPart subPart) +{ + unsigned result = TS_NORMAL; + ControlPart appearance = o->style()->appearance(); + if (!isEnabled(o)) + result = TS_DISABLED; + else if (isReadOnlyControl(o)) + result = (appearance == TextFieldPart || appearance == TextAreaPart || appearance == SearchFieldPart) ? ETS_READONLY : TS_DISABLED; + // Active overrides hover and focused. + else if (isPressed(o) && (subPart == SpinButtonUp) == isSpinUpButtonPartPressed(o)) + result = TS_PRESSED; + else if (supportsFocus(appearance) && isFocused(o)) + result = ETS_FOCUSED; + else if (isHovered(o) && (subPart == SpinButtonUp) == isSpinUpButtonPartHovered(o)) + result = TS_HOT; + + // CBS_UNCHECKED*: 1-4 + // CBS_CHECKED*: 5-8 + // CBS_MIXED*: 9-12 + if (isIndeterminate(o)) + result += 8; + else if (isChecked(o)) + result += 4; + return result; +} + +unsigned RenderThemeChromiumWin::determineSliderThumbState(RenderObject* o) +{ + unsigned result = TUS_NORMAL; + if (!isEnabled(o->parent())) + result = TUS_DISABLED; + else if (supportsFocus(o->style()->appearance()) && isFocused(o->parent())) + result = TUS_FOCUSED; + else if (toRenderSlider(o->parent())->inDragMode()) + result = TUS_PRESSED; + else if (isHovered(o)) + result = TUS_HOT; + return result; +} + +unsigned RenderThemeChromiumWin::determineClassicState(RenderObject* o, ControlSubPart subPart) +{ + unsigned result = 0; + + ControlPart part = o->style()->appearance(); + + // Sliders are always in the normal state. + if (part == SliderHorizontalPart || part == SliderVerticalPart) + return result; + + // So are readonly text fields. + if (isReadOnlyControl(o) && (part == TextFieldPart || part == TextAreaPart || part == SearchFieldPart)) + return result; + + if (part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart) { + if (!isEnabled(o->parent())) + result = DFCS_INACTIVE; + else if (toRenderSlider(o->parent())->inDragMode()) // Active supersedes hover + result = DFCS_PUSHED; + else if (isHovered(o)) + result = DFCS_HOT; + } else { + if (!isEnabled(o) || isReadOnlyControl(o)) + result = DFCS_INACTIVE; + // Active supersedes hover + else if (isPressed(o) && (subPart == SpinButtonUp) == isSpinUpButtonPartPressed(o)) + result = DFCS_PUSHED; + else if (supportsFocus(part) && isFocused(o)) // So does focused + result = 0; + else if (isHovered(o) && (subPart == SpinButtonUp) == isSpinUpButtonPartHovered(o)) + result = DFCS_HOT; + // Classic theme can't represent indeterminate states. Use unchecked appearance. + if (isChecked(o) && !isIndeterminate(o)) + result |= DFCS_CHECKED; + } + return result; +} + +ThemeData RenderThemeChromiumWin::getThemeData(RenderObject* o, ControlSubPart subPart) +{ + ThemeData result; + switch (o->style()->appearance()) { + case CheckboxPart: + result.m_part = BP_CHECKBOX; + result.m_state = determineState(o); + result.m_classicState = DFCS_BUTTONCHECK; + break; + case RadioPart: + result.m_part = BP_RADIOBUTTON; + result.m_state = determineState(o); + result.m_classicState = DFCS_BUTTONRADIO; + break; + case PushButtonPart: + case ButtonPart: + result.m_part = BP_PUSHBUTTON; + result.m_state = determineState(o); + result.m_classicState = DFCS_BUTTONPUSH; + break; + case SliderHorizontalPart: + result.m_part = TKP_TRACK; + result.m_state = TRS_NORMAL; + break; + case SliderVerticalPart: + result.m_part = TKP_TRACKVERT; + result.m_state = TRVS_NORMAL; + break; + case SliderThumbHorizontalPart: + result.m_part = TKP_THUMBBOTTOM; + result.m_state = determineSliderThumbState(o); + break; + case SliderThumbVerticalPart: + result.m_part = TKP_THUMBVERT; + result.m_state = determineSliderThumbState(o); + break; + case ListboxPart: + case MenulistPart: + case MenulistButtonPart: + case SearchFieldPart: + case TextFieldPart: + case TextAreaPart: + result.m_part = EP_EDITTEXT; + result.m_state = determineState(o); + break; + case InnerSpinButtonPart: + result.m_part = subPart == SpinButtonUp ? SPNP_UP : SPNP_DOWN; + result.m_state = determineState(o, subPart); + result.m_classicState = subPart == SpinButtonUp ? DFCS_SCROLLUP : DFCS_SCROLLDOWN; + break; + } + + result.m_classicState |= determineClassicState(o, subPart); + + return result; +} + +bool RenderThemeChromiumWin::paintTextFieldInternal(RenderObject* o, + const PaintInfo& i, + const IntRect& r, + bool drawEdges) +{ + // Fallback to white if the specified color object is invalid. + // (Note ChromiumBridge::paintTextField duplicates this check). + Color backgroundColor(Color::white); + if (o->style()->visitedDependentColor(CSSPropertyBackgroundColor).isValid()) + backgroundColor = o->style()->visitedDependentColor(CSSPropertyBackgroundColor); + + // If we have background-image, don't fill the content area to expose the + // parent's background. Also, we shouldn't fill the content area if the + // alpha of the color is 0. The API of Windows GDI ignores the alpha. + // + // Note that we should paint the content area white if we have neither the + // background color nor background image explicitly specified to keep the + // appearance of select element consistent with other browsers. + bool fillContentArea = !o->style()->hasBackgroundImage() && backgroundColor.alpha(); + + if (o->style()->hasBorderRadius()) { + // If the style has rounded borders, setup the context to clip the + // background (themed or filled) appropriately. + // FIXME: make sure we do the right thing if css background-clip is set. + i.context->save(); + IntSize topLeft, topRight, bottomLeft, bottomRight; + o->style()->getBorderRadiiForRect(r, topLeft, topRight, bottomLeft, bottomRight); + i.context->addRoundedRectClip(r, topLeft, topRight, bottomLeft, bottomRight); + } + { + const ThemeData& themeData = getThemeData(o); + ThemePainter painter(i.context, r); + ChromiumBridge::paintTextField(painter.context(), + themeData.m_part, + themeData.m_state, + themeData.m_classicState, + painter.drawRect(), + backgroundColor, + fillContentArea, + drawEdges); + // End of block commits the painter before restoring context. + } + if (o->style()->hasBorderRadius()) + i.context->restore(); + return false; +} + +void RenderThemeChromiumWin::adjustInnerSpinButtonStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + int width = ScrollbarTheme::nativeTheme()->scrollbarThickness(); + style->setWidth(Length(width, Fixed)); + style->setMinWidth(Length(width, Fixed)); +} + +bool RenderThemeChromiumWin::paintInnerSpinButton(RenderObject* object, const PaintInfo& info, const IntRect& rect) +{ + IntRect half = rect; + + half.setHeight(rect.height() / 2); + const ThemeData& upThemeData = getThemeData(object, SpinButtonUp); + ThemePainter upPainter(info.context, half); + ChromiumBridge::paintSpinButton(upPainter.context(), + upThemeData.m_part, + upThemeData.m_state, + upThemeData.m_classicState, + upPainter.drawRect()); + + half.setY(rect.y() + rect.height() / 2); + const ThemeData& downThemeData = getThemeData(object, SpinButtonDown); + ThemePainter downPainter(info.context, half); + ChromiumBridge::paintSpinButton(downPainter.context(), + downThemeData.m_part, + downThemeData.m_state, + downThemeData.m_classicState, + downPainter.drawRect()); + return false; +} + +#if ENABLE(PROGRESS_TAG) + +// MSDN says that update intervals for the bar is 30ms. +// http://msdn.microsoft.com/en-us/library/bb760842(v=VS.85).aspx +static const double progressAnimationFrameRate = 0.033; + +double RenderThemeChromiumWin::animationRepeatIntervalForProgressBar(RenderProgress*) const +{ + return progressAnimationFrameRate; +} + +double RenderThemeChromiumWin::animationDurationForProgressBar(RenderProgress* renderProgress) const +{ + // On Chromium Windows port, animationProgress() and associated values aren't used. + // So here we can return arbitrary positive value. + return progressAnimationFrameRate; +} + +void RenderThemeChromiumWin::adjustProgressBarStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +bool RenderThemeChromiumWin::paintProgressBar(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + if (!o->isProgress()) + return true; + + RenderProgress* renderProgress = toRenderProgress(o); + // For indeterminate bar, valueRect is ignored and it is computed by the theme engine + // because the animation is a platform detail and WebKit doesn't need to know how. + IntRect valueRect = renderProgress->isDeterminate() ? determinateProgressValueRectFor(renderProgress, r) : IntRect(0, 0, 0, 0); + double animatedSeconds = renderProgress->animationStartTime() ? WTF::currentTime() - renderProgress->animationStartTime() : 0; + ThemePainter painter(i.context, r); + ChromiumBridge::paintProgressBar(painter.context(), r, valueRect, renderProgress->isDeterminate(), animatedSeconds); + return false; +} + +#endif + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderThemeChromiumWin.h b/Source/WebCore/rendering/RenderThemeChromiumWin.h new file mode 100644 index 0000000..661b623 --- /dev/null +++ b/Source/WebCore/rendering/RenderThemeChromiumWin.h @@ -0,0 +1,123 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2006 Apple Computer, Inc. + * Copyright (C) 2008, 2009 Google, Inc. + * + * 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. + * + */ + +#ifndef RenderThemeChromiumWin_h +#define RenderThemeChromiumWin_h + +#include "RenderThemeChromiumSkia.h" + +#if WIN32 +typedef void* HANDLE; +typedef struct HINSTANCE__* HINSTANCE; +typedef HINSTANCE HMODULE; +#endif + +namespace WebCore { + + struct ThemeData { + ThemeData() : m_part(0), m_state(0), m_classicState(0) {} + + unsigned m_part; + unsigned m_state; + unsigned m_classicState; + }; + + class RenderThemeChromiumWin : public RenderThemeChromiumSkia { + public: + static PassRefPtr<RenderTheme> create(); + + // A method asking if the theme is able to draw the focus ring. + virtual bool supportsFocusRing(const RenderStyle*) const; + + // The platform selection color. + virtual Color platformActiveSelectionBackgroundColor() const; + virtual Color platformInactiveSelectionBackgroundColor() const; + virtual Color platformActiveSelectionForegroundColor() const; + virtual Color platformInactiveSelectionForegroundColor() const; + virtual Color platformActiveTextSearchHighlightColor() const; + virtual Color platformInactiveTextSearchHighlightColor() const; + + // System fonts. + virtual void systemFont(int propId, FontDescription&) const; + virtual Color systemColor(int cssValueId) const; + + virtual void adjustSliderThumbSize(RenderObject*) const; + + // Various paint functions. + virtual bool paintCheckbox(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintRadio(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintTextField(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintSliderTrack(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintSliderThumb(RenderObject*, const PaintInfo&, const IntRect&); + + // MenuList refers to an unstyled menulist (meaning a menulist without + // background-color or border set) and MenuListButton refers to a styled + // menulist (a menulist with background-color or border set). They have + // this distinction to support showing aqua style themes whenever they + // possibly can, which is something we don't want to replicate. + // + // In short, we either go down the MenuList code path or the MenuListButton + // codepath. We never go down both. And in both cases, they render the + // entire menulist. + virtual bool paintMenuList(RenderObject*, const PaintInfo&, const IntRect&); + + // Override RenderThemeChromiumSkia's setDefaultFontSize method to also reset the local font property caches. + // See comment in RenderThemeChromiumSkia::setDefaultFontSize() regarding ugliness of this hack. + static void setDefaultFontSize(int); + + virtual void adjustInnerSpinButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintInnerSpinButton(RenderObject*, const PaintInfo&, const IntRect&); + +#if ENABLE(PROGRESS_TAG) + virtual double animationRepeatIntervalForProgressBar(RenderProgress*) const; + virtual double animationDurationForProgressBar(RenderProgress*) const; + virtual void adjustProgressBarStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintProgressBar(RenderObject*, const PaintInfo&, const IntRect&); +#endif + + protected: + virtual double caretBlinkIntervalInternal() const; + + private: + enum ControlSubPart { + None, + SpinButtonDown, + SpinButtonUp, + }; + + RenderThemeChromiumWin() { } + virtual ~RenderThemeChromiumWin() { } + + unsigned determineState(RenderObject*, ControlSubPart = None); + unsigned determineSliderThumbState(RenderObject*); + unsigned determineClassicState(RenderObject*, ControlSubPart = None); + + ThemeData getThemeData(RenderObject*, ControlSubPart = None); + + bool paintTextFieldInternal(RenderObject*, const PaintInfo&, const IntRect&, bool); + }; + +} // namespace WebCore + +#endif diff --git a/Source/WebCore/rendering/RenderThemeMac.h b/Source/WebCore/rendering/RenderThemeMac.h new file mode 100644 index 0000000..95661b8 --- /dev/null +++ b/Source/WebCore/rendering/RenderThemeMac.h @@ -0,0 +1,237 @@ +/* + * This file is part of the theme implementation for form controls in WebCore. + * + * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Apple Computer, Inc. + * + * 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. + * + */ + +#ifndef RenderThemeMac_h +#define RenderThemeMac_h + +#import "RenderTheme.h" +#import <wtf/HashMap.h> +#import <wtf/RetainPtr.h> + +class RenderProgress; + +#ifdef __OBJC__ +@class WebCoreRenderThemeNotificationObserver; +#else +class WebCoreRenderThemeNotificationObserver; +#endif + +namespace WebCore { + +class RenderStyle; + +class RenderThemeMac : public RenderTheme { +public: + static PassRefPtr<RenderTheme> create(); + + // A method asking if the control changes its tint when the window has focus or not. + virtual bool controlSupportsTints(const RenderObject*) const; + + // A general method asking if any control tinting is supported at all. + virtual bool supportsControlTints() const { return true; } + + virtual void adjustRepaintRect(const RenderObject*, IntRect&); + + virtual bool isControlStyled(const RenderStyle*, const BorderData&, + const FillLayer&, const Color& backgroundColor) const; + + virtual Color platformActiveSelectionBackgroundColor() const; + virtual Color platformInactiveSelectionBackgroundColor() const; + virtual Color platformActiveListBoxSelectionBackgroundColor() const; + virtual Color platformActiveListBoxSelectionForegroundColor() const; + virtual Color platformInactiveListBoxSelectionBackgroundColor() const; + virtual Color platformInactiveListBoxSelectionForegroundColor() const; + virtual Color platformFocusRingColor() const; + + virtual ScrollbarControlSize scrollbarControlSizeForPart(ControlPart) { return SmallScrollbar; } + + virtual void platformColorsDidChange(); + + // System fonts. + virtual void systemFont(int cssValueId, FontDescription&) const; + + virtual int minimumMenuListSize(RenderStyle*) const; + + virtual void adjustSliderThumbSize(RenderObject*) const; + + virtual int popupInternalPaddingLeft(RenderStyle*) const; + virtual int popupInternalPaddingRight(RenderStyle*) const; + virtual int popupInternalPaddingTop(RenderStyle*) const; + virtual int popupInternalPaddingBottom(RenderStyle*) const; + + virtual bool paintCapsLockIndicator(RenderObject*, const PaintInfo&, const IntRect&); + +#if ENABLE(METER_TAG) + virtual IntSize meterSizeForBounds(const RenderMeter*, const IntRect&) const; + virtual bool paintMeter(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool supportsMeter(ControlPart, bool isHorizontal) const; +#endif + +#if ENABLE(PROGRESS_TAG) + // Returns the repeat interval of the animation for the progress bar. + virtual double animationRepeatIntervalForProgressBar(RenderProgress*) const; + // Returns the duration of the animation for the progress bar. + virtual double animationDurationForProgressBar(RenderProgress*) const; +#endif + + virtual Color systemColor(int cssValueId) const; + // Controls color values returned from platformFocusRingColor(). systemColor() will be used when false. + virtual bool usesTestModeFocusRingColor() const; + // A view associated to the contained document. Subclasses may not have such a view and return a fake. + virtual NSView* documentViewFor(RenderObject*) const; +protected: + RenderThemeMac(); + virtual ~RenderThemeMac(); + + virtual bool supportsSelectionForegroundColors() const { return false; } + + virtual bool paintTextField(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustTextFieldStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + + virtual bool paintTextArea(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustTextAreaStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + + virtual bool paintMenuList(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustMenuListStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + + virtual bool paintMenuListButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustMenuListButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + +#if ENABLE(PROGRESS_TAG) + virtual void adjustProgressBarStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintProgressBar(RenderObject*, const PaintInfo&, const IntRect&); +#endif + + virtual bool paintSliderTrack(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustSliderTrackStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + + virtual bool paintSliderThumb(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustSliderThumbStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + + virtual bool paintSearchField(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustSearchFieldStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + + virtual void adjustSearchFieldCancelButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldCancelButton(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustSearchFieldDecorationStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldDecoration(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustSearchFieldResultsDecorationStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldResultsDecoration(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustSearchFieldResultsButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldResultsButton(RenderObject*, const PaintInfo&, const IntRect&); + +#if ENABLE(VIDEO) + virtual bool paintMediaFullscreenButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaPlayButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaMuteButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSeekBackButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSeekForwardButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSliderTrack(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSliderThumb(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaRewindButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaReturnToRealtimeButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaToggleClosedCaptionsButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaControlsBackground(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaCurrentTime(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaTimeRemaining(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaVolumeSliderContainer(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaVolumeSliderTrack(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaVolumeSliderThumb(RenderObject*, const PaintInfo&, const IntRect&); + + // Media controls + virtual String extraMediaControlsStyleSheet(); + + virtual bool shouldRenderMediaControlPart(ControlPart, Element*); + virtual void adjustMediaSliderThumbSize(RenderObject*) const; + virtual IntPoint volumeSliderOffsetFromMuteButton(Node*, const IntSize&) const; +#endif + +private: + + IntRect inflateRect(const IntRect&, const IntSize&, const int* margins, float zoomLevel = 1.0f) const; + + FloatRect convertToPaintingRect(const RenderObject* inputRenderer, const RenderObject* partRenderer, const FloatRect& inputRect, const IntRect& r) const; + + // Get the control size based off the font. Used by some of the controls (like buttons). + NSControlSize controlSizeForFont(RenderStyle*) const; + NSControlSize controlSizeForSystemFont(RenderStyle*) const; + void setControlSize(NSCell*, const IntSize* sizes, const IntSize& minSize, float zoomLevel = 1.0f); + void setSizeFromFont(RenderStyle*, const IntSize* sizes) const; + IntSize sizeForFont(RenderStyle*, const IntSize* sizes) const; + IntSize sizeForSystemFont(RenderStyle*, const IntSize* sizes) const; + void setFontFromControlSize(CSSStyleSelector*, RenderStyle*, NSControlSize) const; + + void updateCheckedState(NSCell*, const RenderObject*); + void updateEnabledState(NSCell*, const RenderObject*); + void updateFocusedState(NSCell*, const RenderObject*); + void updatePressedState(NSCell*, const RenderObject*); + // An optional hook for subclasses to update the control tint of NSCell. + virtual void updateActiveState(NSCell*, const RenderObject*) {} + + // Helpers for adjusting appearance and for painting + + void setPopupButtonCellState(const RenderObject*, const IntRect&); + const IntSize* popupButtonSizes() const; + const int* popupButtonMargins() const; + const int* popupButtonPadding(NSControlSize) const; + void paintMenuListButtonGradients(RenderObject*, const PaintInfo&, const IntRect&); + const IntSize* menuListSizes() const; + + const IntSize* searchFieldSizes() const; + const IntSize* cancelButtonSizes() const; + const IntSize* resultsButtonSizes() const; + void setSearchCellState(RenderObject*, const IntRect&); + void setSearchFieldSize(RenderStyle*) const; + + NSPopUpButtonCell* popupButton() const; + NSSearchFieldCell* search() const; + NSMenu* searchMenuTemplate() const; + NSSliderCell* sliderThumbHorizontal() const; + NSSliderCell* sliderThumbVertical() const; + +#if ENABLE(METER_TAG) + NSLevelIndicatorStyle levelIndicatorStyleFor(ControlPart) const; + NSLevelIndicatorCell* levelIndicatorFor(const RenderMeter*) const; +#endif + +private: + mutable RetainPtr<NSPopUpButtonCell> m_popupButton; + mutable RetainPtr<NSSearchFieldCell> m_search; + mutable RetainPtr<NSMenu> m_searchMenuTemplate; + mutable RetainPtr<NSSliderCell> m_sliderThumbHorizontal; + mutable RetainPtr<NSSliderCell> m_sliderThumbVertical; + mutable RetainPtr<NSLevelIndicatorCell> m_levelIndicator; + + bool m_isSliderThumbHorizontalPressed; + bool m_isSliderThumbVerticalPressed; + + mutable HashMap<int, RGBA32> m_systemColorCache; + + RetainPtr<WebCoreRenderThemeNotificationObserver> m_notificationObserver; +}; + +} // namespace WebCore + +#endif // RenderThemeMac_h diff --git a/Source/WebCore/rendering/RenderThemeMac.mm b/Source/WebCore/rendering/RenderThemeMac.mm new file mode 100644 index 0000000..a13b4aa --- /dev/null +++ b/Source/WebCore/rendering/RenderThemeMac.mm @@ -0,0 +1,2064 @@ +/* + * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * 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. + */ + +#import "config.h" +#import "RenderThemeMac.h" + +#import "BitmapImage.h" +#import "ColorMac.h" +#import "CSSStyleSelector.h" +#import "CSSValueKeywords.h" +#import "Document.h" +#import "Element.h" +#import "FrameView.h" +#import "GraphicsContextCG.h" +#import "HTMLInputElement.h" +#import "HTMLMediaElement.h" +#import "HTMLNames.h" +#import "Image.h" +#import "ImageBuffer.h" +#import "LocalCurrentGraphicsContext.h" +#import "MediaControlElements.h" +#import "RenderMedia.h" +#import "RenderSlider.h" +#import "RenderView.h" +#import "SharedBuffer.h" +#import "TimeRanges.h" +#import "ThemeMac.h" +#import "WebCoreSystemInterface.h" +#import "UserAgentStyleSheets.h" +#import <Carbon/Carbon.h> +#import <Cocoa/Cocoa.h> +#import <wtf/RetainPtr.h> +#import <wtf/StdLibExtras.h> +#import <math.h> + +#import "RenderProgress.h" + +#if ENABLE(METER_TAG) +#include "RenderMeter.h" +#include "HTMLMeterElement.h" +#endif + +#ifdef BUILDING_ON_TIGER +typedef int NSInteger; +typedef unsigned NSUInteger; +#endif + +using namespace std; + +// The methods in this file are specific to the Mac OS X platform. + +// FIXME: The platform-independent code in this class should be factored out and merged with RenderThemeSafari. + +// We estimate the animation rate of a Mac OS X progress bar is 33 fps. +// Hard code the value here because we haven't found API for it. +const double progressAnimationFrameRate = 0.033; + +// Mac OS X progress bar animation seems to have 256 frames. +const double progressAnimationNumFrames = 256; + +@interface WebCoreRenderThemeNotificationObserver : NSObject +{ + WebCore::RenderTheme *_theme; +} + +- (id)initWithTheme:(WebCore::RenderTheme *)theme; +- (void)systemColorsDidChange:(NSNotification *)notification; + +@end + +@implementation WebCoreRenderThemeNotificationObserver + +- (id)initWithTheme:(WebCore::RenderTheme *)theme +{ + [super init]; + _theme = theme; + + return self; +} + +- (void)systemColorsDidChange:(NSNotification *)unusedNotification +{ + ASSERT_UNUSED(unusedNotification, [[unusedNotification name] isEqualToString:NSSystemColorsDidChangeNotification]); + _theme->platformColorsDidChange(); +} + +@end + +namespace WebCore { + +using namespace HTMLNames; + +enum { + topMargin, + rightMargin, + bottomMargin, + leftMargin +}; + +enum { + topPadding, + rightPadding, + bottomPadding, + leftPadding +}; + +#if PLATFORM(MAC) +PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page*) +{ + static RenderTheme* rt = RenderThemeMac::create().releaseRef(); + return rt; +} +#endif + +PassRefPtr<RenderTheme> RenderThemeMac::create() +{ + return adoptRef(new RenderThemeMac); +} + +RenderThemeMac::RenderThemeMac() + : m_isSliderThumbHorizontalPressed(false) + , m_isSliderThumbVerticalPressed(false) + , m_notificationObserver(AdoptNS, [[WebCoreRenderThemeNotificationObserver alloc] initWithTheme:this]) +{ + [[NSNotificationCenter defaultCenter] addObserver:m_notificationObserver.get() + selector:@selector(systemColorsDidChange:) + name:NSSystemColorsDidChangeNotification + object:nil]; +} + +RenderThemeMac::~RenderThemeMac() +{ + [[NSNotificationCenter defaultCenter] removeObserver:m_notificationObserver.get()]; +} + +Color RenderThemeMac::platformActiveSelectionBackgroundColor() const +{ + NSColor* color = [[NSColor selectedTextBackgroundColor] colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + return Color(static_cast<int>(255.0 * [color redComponent]), static_cast<int>(255.0 * [color greenComponent]), static_cast<int>(255.0 * [color blueComponent])); +} + +Color RenderThemeMac::platformInactiveSelectionBackgroundColor() const +{ + NSColor* color = [[NSColor secondarySelectedControlColor] colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + return Color(static_cast<int>(255.0 * [color redComponent]), static_cast<int>(255.0 * [color greenComponent]), static_cast<int>(255.0 * [color blueComponent])); +} + +Color RenderThemeMac::platformActiveListBoxSelectionBackgroundColor() const +{ + NSColor* color = [[NSColor alternateSelectedControlColor] colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + return Color(static_cast<int>(255.0 * [color redComponent]), static_cast<int>(255.0 * [color greenComponent]), static_cast<int>(255.0 * [color blueComponent])); +} + +Color RenderThemeMac::platformActiveListBoxSelectionForegroundColor() const +{ + return Color::white; +} + +Color RenderThemeMac::platformInactiveListBoxSelectionForegroundColor() const +{ + return Color::black; +} + +Color RenderThemeMac::platformFocusRingColor() const +{ + if (usesTestModeFocusRingColor()) + return oldAquaFocusRingColor(); + + return systemColor(CSSValueWebkitFocusRingColor); +} + +Color RenderThemeMac::platformInactiveListBoxSelectionBackgroundColor() const +{ + return platformInactiveSelectionBackgroundColor(); +} + +static FontWeight toFontWeight(NSInteger appKitFontWeight) +{ + ASSERT(appKitFontWeight > 0 && appKitFontWeight < 15); + if (appKitFontWeight > 14) + appKitFontWeight = 14; + else if (appKitFontWeight < 1) + appKitFontWeight = 1; + + static FontWeight fontWeights[] = { + FontWeight100, + FontWeight100, + FontWeight200, + FontWeight300, + FontWeight400, + FontWeight500, + FontWeight600, + FontWeight600, + FontWeight700, + FontWeight800, + FontWeight800, + FontWeight900, + FontWeight900, + FontWeight900 + }; + return fontWeights[appKitFontWeight - 1]; +} + +void RenderThemeMac::systemFont(int cssValueId, FontDescription& fontDescription) const +{ + DEFINE_STATIC_LOCAL(FontDescription, systemFont, ()); + DEFINE_STATIC_LOCAL(FontDescription, smallSystemFont, ()); + DEFINE_STATIC_LOCAL(FontDescription, menuFont, ()); + DEFINE_STATIC_LOCAL(FontDescription, labelFont, ()); + DEFINE_STATIC_LOCAL(FontDescription, miniControlFont, ()); + DEFINE_STATIC_LOCAL(FontDescription, smallControlFont, ()); + DEFINE_STATIC_LOCAL(FontDescription, controlFont, ()); + + FontDescription* cachedDesc; + NSFont* font = nil; + switch (cssValueId) { + case CSSValueSmallCaption: + cachedDesc = &smallSystemFont; + if (!smallSystemFont.isAbsoluteSize()) + font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; + break; + case CSSValueMenu: + cachedDesc = &menuFont; + if (!menuFont.isAbsoluteSize()) + font = [NSFont menuFontOfSize:[NSFont systemFontSize]]; + break; + case CSSValueStatusBar: + cachedDesc = &labelFont; + if (!labelFont.isAbsoluteSize()) + font = [NSFont labelFontOfSize:[NSFont labelFontSize]]; + break; + case CSSValueWebkitMiniControl: + cachedDesc = &miniControlFont; + if (!miniControlFont.isAbsoluteSize()) + font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSMiniControlSize]]; + break; + case CSSValueWebkitSmallControl: + cachedDesc = &smallControlFont; + if (!smallControlFont.isAbsoluteSize()) + font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]; + break; + case CSSValueWebkitControl: + cachedDesc = &controlFont; + if (!controlFont.isAbsoluteSize()) + font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]; + break; + default: + cachedDesc = &systemFont; + if (!systemFont.isAbsoluteSize()) + font = [NSFont systemFontOfSize:[NSFont systemFontSize]]; + } + + if (font) { + NSFontManager *fontManager = [NSFontManager sharedFontManager]; + cachedDesc->setIsAbsoluteSize(true); + cachedDesc->setGenericFamily(FontDescription::NoFamily); + cachedDesc->firstFamily().setFamily([font familyName]); + cachedDesc->setSpecifiedSize([font pointSize]); + cachedDesc->setWeight(toFontWeight([fontManager weightOfFont:font])); + cachedDesc->setItalic([fontManager traitsOfFont:font] & NSItalicFontMask); + } + fontDescription = *cachedDesc; +} + +static RGBA32 convertNSColorToColor(NSColor *color) +{ + NSColor *colorInColorSpace = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + if (colorInColorSpace) { + static const double scaleFactor = nextafter(256.0, 0.0); + return makeRGB(static_cast<int>(scaleFactor * [colorInColorSpace redComponent]), + static_cast<int>(scaleFactor * [colorInColorSpace greenComponent]), + static_cast<int>(scaleFactor * [colorInColorSpace blueComponent])); + } + + // This conversion above can fail if the NSColor in question is an NSPatternColor + // (as many system colors are). These colors are actually a repeating pattern + // not just a solid color. To work around this we simply draw a 1x1 image of + // the color and use that pixel's color. It might be better to use an average of + // the colors in the pattern instead. + NSBitmapImageRep *offscreenRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil + pixelsWide:1 + pixelsHigh:1 + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:4 + bitsPerPixel:32]; + + [NSGraphicsContext saveGraphicsState]; + [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep]]; + NSEraseRect(NSMakeRect(0, 0, 1, 1)); + [color drawSwatchInRect:NSMakeRect(0, 0, 1, 1)]; + [NSGraphicsContext restoreGraphicsState]; + + NSUInteger pixel[4]; + [offscreenRep getPixel:pixel atX:0 y:0]; + + [offscreenRep release]; + + return makeRGB(pixel[0], pixel[1], pixel[2]); +} + +static RGBA32 menuBackgroundColor() +{ + NSBitmapImageRep *offscreenRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil + pixelsWide:1 + pixelsHigh:1 + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:4 + bitsPerPixel:32]; + + CGContextRef context = static_cast<CGContextRef>([[NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep] graphicsPort]); + CGRect rect = CGRectMake(0, 0, 1, 1); + HIThemeMenuDrawInfo drawInfo; + drawInfo.version = 0; + drawInfo.menuType = kThemeMenuTypePopUp; + HIThemeDrawMenuBackground(&rect, &drawInfo, context, kHIThemeOrientationInverted); + + NSUInteger pixel[4]; + [offscreenRep getPixel:pixel atX:0 y:0]; + + [offscreenRep release]; + + return makeRGB(pixel[0], pixel[1], pixel[2]); +} + +void RenderThemeMac::platformColorsDidChange() +{ + m_systemColorCache.clear(); + RenderTheme::platformColorsDidChange(); +} + +Color RenderThemeMac::systemColor(int cssValueId) const +{ + if (m_systemColorCache.contains(cssValueId)) + return m_systemColorCache.get(cssValueId); + + Color color; + switch (cssValueId) { + case CSSValueActiveborder: + color = convertNSColorToColor([NSColor keyboardFocusIndicatorColor]); + break; + case CSSValueActivecaption: + color = convertNSColorToColor([NSColor windowFrameTextColor]); + break; + case CSSValueAppworkspace: + color = convertNSColorToColor([NSColor headerColor]); + break; + case CSSValueBackground: + // Use theme independent default + break; + case CSSValueButtonface: + // We use this value instead of NSColor's controlColor to avoid website incompatibilities. + // We may want to change this to use the NSColor in future. + color = 0xFFC0C0C0; + break; + case CSSValueButtonhighlight: + color = convertNSColorToColor([NSColor controlHighlightColor]); + break; + case CSSValueButtonshadow: + color = convertNSColorToColor([NSColor controlShadowColor]); + break; + case CSSValueButtontext: + color = convertNSColorToColor([NSColor controlTextColor]); + break; + case CSSValueCaptiontext: + color = convertNSColorToColor([NSColor textColor]); + break; + case CSSValueGraytext: + color = convertNSColorToColor([NSColor disabledControlTextColor]); + break; + case CSSValueHighlight: + color = convertNSColorToColor([NSColor selectedTextBackgroundColor]); + break; + case CSSValueHighlighttext: + color = convertNSColorToColor([NSColor selectedTextColor]); + break; + case CSSValueInactiveborder: + color = convertNSColorToColor([NSColor controlBackgroundColor]); + break; + case CSSValueInactivecaption: + color = convertNSColorToColor([NSColor controlBackgroundColor]); + break; + case CSSValueInactivecaptiontext: + color = convertNSColorToColor([NSColor textColor]); + break; + case CSSValueInfobackground: + // There is no corresponding NSColor for this so we use a hard coded value. + color = 0xFFFBFCC5; + break; + case CSSValueInfotext: + color = convertNSColorToColor([NSColor textColor]); + break; + case CSSValueMenu: + color = menuBackgroundColor(); + break; + case CSSValueMenutext: + color = convertNSColorToColor([NSColor selectedMenuItemTextColor]); + break; + case CSSValueScrollbar: + color = convertNSColorToColor([NSColor scrollBarColor]); + break; + case CSSValueText: + color = convertNSColorToColor([NSColor textColor]); + break; + case CSSValueThreeddarkshadow: + color = convertNSColorToColor([NSColor controlDarkShadowColor]); + break; + case CSSValueThreedshadow: + color = convertNSColorToColor([NSColor shadowColor]); + break; + case CSSValueThreedface: + // We use this value instead of NSColor's controlColor to avoid website incompatibilities. + // We may want to change this to use the NSColor in future. + color = 0xFFC0C0C0; + break; + case CSSValueThreedhighlight: + color = convertNSColorToColor([NSColor highlightColor]); + break; + case CSSValueThreedlightshadow: + color = convertNSColorToColor([NSColor controlLightHighlightColor]); + break; + case CSSValueWebkitFocusRingColor: + color = convertNSColorToColor([NSColor keyboardFocusIndicatorColor]); + break; + case CSSValueWindow: + color = convertNSColorToColor([NSColor windowBackgroundColor]); + break; + case CSSValueWindowframe: + color = convertNSColorToColor([NSColor windowFrameColor]); + break; + case CSSValueWindowtext: + color = convertNSColorToColor([NSColor windowFrameTextColor]); + break; + } + + if (!color.isValid()) + color = RenderTheme::systemColor(cssValueId); + + if (color.isValid()) + m_systemColorCache.set(cssValueId, color.rgb()); + + return color; +} + +bool RenderThemeMac::usesTestModeFocusRingColor() const +{ + return WebCore::usesTestModeFocusRingColor(); +} + +NSView* RenderThemeMac::documentViewFor(RenderObject* o) const +{ +#if PLATFORM(MAC) + return ThemeMac::ensuredView(o->view()->frameView()); +#else + ASSERT_NOT_REACHED(); + return 0; +#endif +} + +bool RenderThemeMac::isControlStyled(const RenderStyle* style, const BorderData& border, + const FillLayer& background, const Color& backgroundColor) const +{ + if (style->appearance() == TextFieldPart || style->appearance() == TextAreaPart || style->appearance() == ListboxPart) + return style->border() != border; + + // FIXME: This is horrible, but there is not much else that can be done. Menu lists cannot draw properly when + // scaled. They can't really draw properly when transformed either. We can't detect the transform case at style + // adjustment time so that will just have to stay broken. We can however detect that we're zooming. If zooming + // is in effect we treat it like the control is styled. + if (style->appearance() == MenulistPart && style->effectiveZoom() != 1.0f) + return true; + + return RenderTheme::isControlStyled(style, border, background, backgroundColor); +} + +void RenderThemeMac::adjustRepaintRect(const RenderObject* o, IntRect& r) +{ + ControlPart part = o->style()->appearance(); + +#if USE(NEW_THEME) + switch (part) { + case CheckboxPart: + case RadioPart: + case PushButtonPart: + case SquareButtonPart: + case ListButtonPart: + case DefaultButtonPart: + case ButtonPart: + case OuterSpinButtonPart: + return RenderTheme::adjustRepaintRect(o, r); + default: + break; + } +#endif + + float zoomLevel = o->style()->effectiveZoom(); + + if (part == MenulistPart) { + setPopupButtonCellState(o, r); + IntSize size = popupButtonSizes()[[popupButton() controlSize]]; + size.setHeight(size.height() * zoomLevel); + size.setWidth(r.width()); + r = inflateRect(r, size, popupButtonMargins(), zoomLevel); + } +} + +IntRect RenderThemeMac::inflateRect(const IntRect& r, const IntSize& size, const int* margins, float zoomLevel) const +{ + // Only do the inflation if the available width/height are too small. Otherwise try to + // fit the glow/check space into the available box's width/height. + int widthDelta = r.width() - (size.width() + margins[leftMargin] * zoomLevel + margins[rightMargin] * zoomLevel); + int heightDelta = r.height() - (size.height() + margins[topMargin] * zoomLevel + margins[bottomMargin] * zoomLevel); + IntRect result(r); + if (widthDelta < 0) { + result.setX(result.x() - margins[leftMargin] * zoomLevel); + result.setWidth(result.width() - widthDelta); + } + if (heightDelta < 0) { + result.setY(result.y() - margins[topMargin] * zoomLevel); + result.setHeight(result.height() - heightDelta); + } + return result; +} + +FloatRect RenderThemeMac::convertToPaintingRect(const RenderObject* inputRenderer, const RenderObject* partRenderer, const FloatRect& inputRect, const IntRect& r) const +{ + FloatRect partRect(inputRect); + + // Compute an offset between the part renderer and the input renderer + FloatSize offsetFromInputRenderer; + const RenderObject* renderer = partRenderer; + while (renderer && renderer != inputRenderer) { + RenderObject* containingRenderer = renderer->container(); + offsetFromInputRenderer -= renderer->offsetFromContainer(containingRenderer, IntPoint()); + renderer = containingRenderer; + } + // If the input renderer was not a container, something went wrong + ASSERT(renderer == inputRenderer); + // Move the rect into partRenderer's coords + partRect.move(offsetFromInputRenderer); + // Account for the local drawing offset (tx, ty) + partRect.move(r.x(), r.y()); + + return partRect; +} + +void RenderThemeMac::updateCheckedState(NSCell* cell, const RenderObject* o) +{ + bool oldIndeterminate = [cell state] == NSMixedState; + bool indeterminate = isIndeterminate(o); + bool checked = isChecked(o); + + if (oldIndeterminate != indeterminate) { + [cell setState:indeterminate ? NSMixedState : (checked ? NSOnState : NSOffState)]; + return; + } + + bool oldChecked = [cell state] == NSOnState; + if (checked != oldChecked) + [cell setState:checked ? NSOnState : NSOffState]; +} + +void RenderThemeMac::updateEnabledState(NSCell* cell, const RenderObject* o) +{ + bool oldEnabled = [cell isEnabled]; + bool enabled = isEnabled(o); + if (enabled != oldEnabled) + [cell setEnabled:enabled]; +} + +void RenderThemeMac::updateFocusedState(NSCell* cell, const RenderObject* o) +{ + bool oldFocused = [cell showsFirstResponder]; + bool focused = isFocused(o) && o->style()->outlineStyleIsAuto(); + if (focused != oldFocused) + [cell setShowsFirstResponder:focused]; +} + +void RenderThemeMac::updatePressedState(NSCell* cell, const RenderObject* o) +{ + bool oldPressed = [cell isHighlighted]; + bool pressed = (o->node() && o->node()->active()); + if (pressed != oldPressed) + [cell setHighlighted:pressed]; +} + +bool RenderThemeMac::controlSupportsTints(const RenderObject* o) const +{ + // An alternate way to implement this would be to get the appropriate cell object + // and call the private _needRedrawOnWindowChangedKeyState method. An advantage of + // that would be that we would match AppKit behavior more closely, but a disadvantage + // would be that we would rely on an AppKit SPI method. + + if (!isEnabled(o)) + return false; + + // Checkboxes only have tint when checked. + if (o->style()->appearance() == CheckboxPart) + return isChecked(o); + + // For now assume other controls have tint if enabled. + return true; +} + +NSControlSize RenderThemeMac::controlSizeForFont(RenderStyle* style) const +{ + int fontSize = style->fontSize(); + if (fontSize >= 16) + return NSRegularControlSize; + if (fontSize >= 11) + return NSSmallControlSize; + return NSMiniControlSize; +} + +void RenderThemeMac::setControlSize(NSCell* cell, const IntSize* sizes, const IntSize& minSize, float zoomLevel) +{ + NSControlSize size; + if (minSize.width() >= static_cast<int>(sizes[NSRegularControlSize].width() * zoomLevel) && + minSize.height() >= static_cast<int>(sizes[NSRegularControlSize].height() * zoomLevel)) + size = NSRegularControlSize; + else if (minSize.width() >= static_cast<int>(sizes[NSSmallControlSize].width() * zoomLevel) && + minSize.height() >= static_cast<int>(sizes[NSSmallControlSize].height() * zoomLevel)) + size = NSSmallControlSize; + else + size = NSMiniControlSize; + if (size != [cell controlSize]) // Only update if we have to, since AppKit does work even if the size is the same. + [cell setControlSize:size]; +} + +IntSize RenderThemeMac::sizeForFont(RenderStyle* style, const IntSize* sizes) const +{ + if (style->effectiveZoom() != 1.0f) { + IntSize result = sizes[controlSizeForFont(style)]; + return IntSize(result.width() * style->effectiveZoom(), result.height() * style->effectiveZoom()); + } + return sizes[controlSizeForFont(style)]; +} + +IntSize RenderThemeMac::sizeForSystemFont(RenderStyle* style, const IntSize* sizes) const +{ + if (style->effectiveZoom() != 1.0f) { + IntSize result = sizes[controlSizeForSystemFont(style)]; + return IntSize(result.width() * style->effectiveZoom(), result.height() * style->effectiveZoom()); + } + return sizes[controlSizeForSystemFont(style)]; +} + +void RenderThemeMac::setSizeFromFont(RenderStyle* style, const IntSize* sizes) const +{ + // FIXME: Check is flawed, since it doesn't take min-width/max-width into account. + IntSize size = sizeForFont(style, sizes); + if (style->width().isIntrinsicOrAuto() && size.width() > 0) + style->setWidth(Length(size.width(), Fixed)); + if (style->height().isAuto() && size.height() > 0) + style->setHeight(Length(size.height(), Fixed)); +} + +void RenderThemeMac::setFontFromControlSize(CSSStyleSelector*, RenderStyle* style, NSControlSize controlSize) const +{ + FontDescription fontDescription; + fontDescription.setIsAbsoluteSize(true); + fontDescription.setGenericFamily(FontDescription::SerifFamily); + + NSFont* font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:controlSize]]; + fontDescription.firstFamily().setFamily([font familyName]); + fontDescription.setComputedSize([font pointSize] * style->effectiveZoom()); + fontDescription.setSpecifiedSize([font pointSize] * style->effectiveZoom()); + + // Reset line height + style->setLineHeight(RenderStyle::initialLineHeight()); + + if (style->setFontDescription(fontDescription)) + style->font().update(0); +} + +NSControlSize RenderThemeMac::controlSizeForSystemFont(RenderStyle* style) const +{ + int fontSize = style->fontSize(); + if (fontSize >= [NSFont systemFontSizeForControlSize:NSRegularControlSize]) + return NSRegularControlSize; + if (fontSize >= [NSFont systemFontSizeForControlSize:NSSmallControlSize]) + return NSSmallControlSize; + return NSMiniControlSize; +} + +bool RenderThemeMac::paintTextField(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + LocalCurrentGraphicsContext localContext(paintInfo.context); + wkDrawBezeledTextFieldCell(r, isEnabled(o) && !isReadOnlyControl(o)); + return false; +} + +void RenderThemeMac::adjustTextFieldStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +bool RenderThemeMac::paintCapsLockIndicator(RenderObject*, const PaintInfo& paintInfo, const IntRect& r) +{ + if (paintInfo.context->paintingDisabled()) + return true; + + LocalCurrentGraphicsContext localContext(paintInfo.context); + wkDrawCapsLockIndicator(paintInfo.context->platformContext(), r); + + return false; +} + +bool RenderThemeMac::paintTextArea(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + LocalCurrentGraphicsContext localContext(paintInfo.context); + wkDrawBezeledTextArea(r, isEnabled(o) && !isReadOnlyControl(o)); + return false; +} + +void RenderThemeMac::adjustTextAreaStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +const int* RenderThemeMac::popupButtonMargins() const +{ + static const int margins[3][4] = + { + { 0, 3, 1, 3 }, + { 0, 3, 2, 3 }, + { 0, 1, 0, 1 } + }; + return margins[[popupButton() controlSize]]; +} + +const IntSize* RenderThemeMac::popupButtonSizes() const +{ + static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) }; + return sizes; +} + +const int* RenderThemeMac::popupButtonPadding(NSControlSize size) const +{ + static const int padding[3][4] = + { + { 2, 26, 3, 8 }, + { 2, 23, 3, 8 }, + { 2, 22, 3, 10 } + }; + return padding[size]; +} + +bool RenderThemeMac::paintMenuList(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + LocalCurrentGraphicsContext localContext(paintInfo.context); + setPopupButtonCellState(o, r); + + NSPopUpButtonCell* popupButton = this->popupButton(); + + float zoomLevel = o->style()->effectiveZoom(); + IntSize size = popupButtonSizes()[[popupButton controlSize]]; + size.setHeight(size.height() * zoomLevel); + size.setWidth(r.width()); + + // Now inflate it to account for the shadow. + IntRect inflatedRect = r; + if (r.width() >= minimumMenuListSize(o->style())) + inflatedRect = inflateRect(inflatedRect, size, popupButtonMargins(), zoomLevel); + + paintInfo.context->save(); + +#ifndef BUILDING_ON_TIGER + // On Leopard, the cell will draw outside of the given rect, so we have to clip to the rect + paintInfo.context->clip(inflatedRect); +#endif + + if (zoomLevel != 1.0f) { + inflatedRect.setWidth(inflatedRect.width() / zoomLevel); + inflatedRect.setHeight(inflatedRect.height() / zoomLevel); + paintInfo.context->translate(inflatedRect.x(), inflatedRect.y()); + paintInfo.context->scale(FloatSize(zoomLevel, zoomLevel)); + paintInfo.context->translate(-inflatedRect.x(), -inflatedRect.y()); + } + + [popupButton drawWithFrame:inflatedRect inView:documentViewFor(o)]; + [popupButton setControlView:nil]; + + paintInfo.context->restore(); + + return false; +} + +#if ENABLE(METER_TAG) + +IntSize RenderThemeMac::meterSizeForBounds(const RenderMeter* renderMeter, const IntRect& bounds) const +{ + if (NoControlPart == renderMeter->style()->appearance()) + return bounds.size(); + + NSLevelIndicatorCell* cell = levelIndicatorFor(renderMeter); + // Makes enough room for cell's intrinsic size. + NSSize cellSize = [cell cellSizeForBounds:IntRect(IntPoint(), bounds.size())]; + return IntSize(bounds.width() < cellSize.width ? cellSize.width : bounds.width(), + bounds.height() < cellSize.height ? cellSize.height : bounds.height()); +} + +bool RenderThemeMac::paintMeter(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect) +{ + if (!renderObject->isMeter()) + return true; + + LocalCurrentGraphicsContext localContext(paintInfo.context); + + // Becaue NSLevelIndicatorCell doesn't support vertical gauge, we use a portable version + if (rect.width() < rect.height()) + return RenderTheme::paintMeter(renderObject, paintInfo, rect); + + NSLevelIndicatorCell* cell = levelIndicatorFor(toRenderMeter(renderObject)); + paintInfo.context->save(); + [cell drawWithFrame:rect inView:documentViewFor(renderObject)]; + [cell setControlView:nil]; + paintInfo.context->restore(); + + return false; +} + +bool RenderThemeMac::supportsMeter(ControlPart part, bool isHorizontal) const +{ + switch (part) { + case RelevancyLevelIndicatorPart: + case DiscreteCapacityLevelIndicatorPart: + case RatingLevelIndicatorPart: + case MeterPart: + case ContinuousCapacityLevelIndicatorPart: + return isHorizontal; + default: + return false; + } +} + +NSLevelIndicatorStyle RenderThemeMac::levelIndicatorStyleFor(ControlPart part) const +{ + switch (part) { + case RelevancyLevelIndicatorPart: + return NSRelevancyLevelIndicatorStyle; + case DiscreteCapacityLevelIndicatorPart: + return NSDiscreteCapacityLevelIndicatorStyle; + case RatingLevelIndicatorPart: + return NSRatingLevelIndicatorStyle; + case MeterPart: + case ContinuousCapacityLevelIndicatorPart: + default: + return NSContinuousCapacityLevelIndicatorStyle; + } + +} + +NSLevelIndicatorCell* RenderThemeMac::levelIndicatorFor(const RenderMeter* renderMeter) const +{ + RenderStyle* style = renderMeter->style(); + ASSERT(style->appearance() != NoControlPart); + + if (!m_levelIndicator) + m_levelIndicator.adoptNS([[NSLevelIndicatorCell alloc] initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle]); + NSLevelIndicatorCell* cell = m_levelIndicator.get(); + + HTMLMeterElement* element = static_cast<HTMLMeterElement*>(renderMeter->node()); + double value = element->value(); + + // Because NSLevelIndicatorCell does not support optimum-in-the-middle type coloring, + // we explicitly control the color instead giving low and high value to NSLevelIndicatorCell as is. + switch (element->gaugeRegion()) { + case HTMLMeterElement::GaugeRegionOptimum: + // Make meter the green + [cell setWarningValue:value + 1]; + [cell setCriticalValue:value + 2]; + break; + case HTMLMeterElement::GaugeRegionSuboptimal: + // Make the meter yellow + [cell setWarningValue:value - 1]; + [cell setCriticalValue:value + 1]; + break; + case HTMLMeterElement::GaugeRegionEvenLessGood: + // Make the meter red + [cell setWarningValue:value - 2]; + [cell setCriticalValue:value - 1]; + break; + } + + [cell setLevelIndicatorStyle:levelIndicatorStyleFor(style->appearance())]; + [cell setBaseWritingDirection:style->isLeftToRightDirection() ? NSWritingDirectionLeftToRight : NSWritingDirectionRightToLeft]; + [cell setMinValue:element->min()]; + [cell setMaxValue:element->max()]; + RetainPtr<NSNumber> valueObject = [NSNumber numberWithDouble:value]; + [cell setObjectValue:valueObject.get()]; + + return cell; +} + +#endif + +#if ENABLE(PROGRESS_TAG) + +double RenderThemeMac::animationRepeatIntervalForProgressBar(RenderProgress*) const +{ + return progressAnimationFrameRate; +} + +double RenderThemeMac::animationDurationForProgressBar(RenderProgress*) const +{ + return progressAnimationNumFrames * progressAnimationFrameRate; +} + +void RenderThemeMac::adjustProgressBarStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +bool RenderThemeMac::paintProgressBar(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect) +{ + if (!renderObject->isProgress()) + return true; + + RenderProgress* renderProgress = toRenderProgress(renderObject); + HIThemeTrackDrawInfo trackInfo; + trackInfo.version = 0; + trackInfo.kind = renderProgress->position() < 0 ? kThemeLargeIndeterminateBar : kThemeLargeProgressBar; + trackInfo.bounds = IntRect(IntPoint(), rect.size()); + trackInfo.min = 0; + trackInfo.max = numeric_limits<SInt32>::max(); + trackInfo.value = lround(renderProgress->position() * nextafter(trackInfo.max, 0)); + trackInfo.trackInfo.progress.phase = lround(renderProgress->animationProgress() * nextafter(progressAnimationNumFrames, 0)); + trackInfo.attributes = kThemeTrackHorizontal; + trackInfo.enableState = isActive(renderObject) ? kThemeTrackActive : kThemeTrackInactive; + trackInfo.reserved = 0; + trackInfo.filler1 = 0; + + OwnPtr<ImageBuffer> imageBuffer = ImageBuffer::create(rect.size()); + if (!imageBuffer) + return true; + + HIThemeDrawTrack(&trackInfo, 0, imageBuffer->context()->platformContext(), kHIThemeOrientationNormal); + + paintInfo.context->save(); + + if (!renderProgress->style()->isLeftToRightDirection()) { + paintInfo.context->translate(2 * rect.x() + rect.width(), 0); + paintInfo.context->scale(FloatSize(-1, 1)); + } + + paintInfo.context->drawImageBuffer(imageBuffer.get(), ColorSpaceDeviceRGB, rect.location()); + + paintInfo.context->restore(); + return false; +} +#endif + +const float baseFontSize = 11.0f; +const float baseArrowHeight = 4.0f; +const float baseArrowWidth = 5.0f; +const float baseSpaceBetweenArrows = 2.0f; +const int arrowPaddingLeft = 6; +const int arrowPaddingRight = 6; +const int paddingBeforeSeparator = 4; +const int baseBorderRadius = 5; +const int styledPopupPaddingLeft = 8; +const int styledPopupPaddingTop = 1; +const int styledPopupPaddingBottom = 2; + +static void TopGradientInterpolate(void*, const CGFloat* inData, CGFloat* outData) +{ + static float dark[4] = { 1.0f, 1.0f, 1.0f, 0.4f }; + static float light[4] = { 1.0f, 1.0f, 1.0f, 0.15f }; + float a = inData[0]; + int i = 0; + for (i = 0; i < 4; i++) + outData[i] = (1.0f - a) * dark[i] + a * light[i]; +} + +static void BottomGradientInterpolate(void*, const CGFloat* inData, CGFloat* outData) +{ + static float dark[4] = { 1.0f, 1.0f, 1.0f, 0.0f }; + static float light[4] = { 1.0f, 1.0f, 1.0f, 0.3f }; + float a = inData[0]; + int i = 0; + for (i = 0; i < 4; i++) + outData[i] = (1.0f - a) * dark[i] + a * light[i]; +} + +static void MainGradientInterpolate(void*, const CGFloat* inData, CGFloat* outData) +{ + static float dark[4] = { 0.0f, 0.0f, 0.0f, 0.15f }; + static float light[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float a = inData[0]; + int i = 0; + for (i = 0; i < 4; i++) + outData[i] = (1.0f - a) * dark[i] + a * light[i]; +} + +static void TrackGradientInterpolate(void*, const CGFloat* inData, CGFloat* outData) +{ + static float dark[4] = { 0.0f, 0.0f, 0.0f, 0.678f }; + static float light[4] = { 0.0f, 0.0f, 0.0f, 0.13f }; + float a = inData[0]; + int i = 0; + for (i = 0; i < 4; i++) + outData[i] = (1.0f - a) * dark[i] + a * light[i]; +} + +void RenderThemeMac::paintMenuListButtonGradients(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + if (r.isEmpty()) + return; + + CGContextRef context = paintInfo.context->platformContext(); + + paintInfo.context->save(); + + IntSize topLeftRadius; + IntSize topRightRadius; + IntSize bottomLeftRadius; + IntSize bottomRightRadius; + + o->style()->getBorderRadiiForRect(r, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); + + int radius = topLeftRadius.width(); + + CGColorSpaceRef cspace = deviceRGBColorSpaceRef(); + + FloatRect topGradient(r.x(), r.y(), r.width(), r.height() / 2.0f); + struct CGFunctionCallbacks topCallbacks = { 0, TopGradientInterpolate, NULL }; + RetainPtr<CGFunctionRef> topFunction(AdoptCF, CGFunctionCreate(NULL, 1, NULL, 4, NULL, &topCallbacks)); + RetainPtr<CGShadingRef> topShading(AdoptCF, CGShadingCreateAxial(cspace, CGPointMake(topGradient.x(), topGradient.y()), CGPointMake(topGradient.x(), topGradient.bottom()), topFunction.get(), false, false)); + + FloatRect bottomGradient(r.x() + radius, r.y() + r.height() / 2.0f, r.width() - 2.0f * radius, r.height() / 2.0f); + struct CGFunctionCallbacks bottomCallbacks = { 0, BottomGradientInterpolate, NULL }; + RetainPtr<CGFunctionRef> bottomFunction(AdoptCF, CGFunctionCreate(NULL, 1, NULL, 4, NULL, &bottomCallbacks)); + RetainPtr<CGShadingRef> bottomShading(AdoptCF, CGShadingCreateAxial(cspace, CGPointMake(bottomGradient.x(), bottomGradient.y()), CGPointMake(bottomGradient.x(), bottomGradient.bottom()), bottomFunction.get(), false, false)); + + struct CGFunctionCallbacks mainCallbacks = { 0, MainGradientInterpolate, NULL }; + RetainPtr<CGFunctionRef> mainFunction(AdoptCF, CGFunctionCreate(NULL, 1, NULL, 4, NULL, &mainCallbacks)); + RetainPtr<CGShadingRef> mainShading(AdoptCF, CGShadingCreateAxial(cspace, CGPointMake(r.x(), r.y()), CGPointMake(r.x(), r.bottom()), mainFunction.get(), false, false)); + + RetainPtr<CGShadingRef> leftShading(AdoptCF, CGShadingCreateAxial(cspace, CGPointMake(r.x(), r.y()), CGPointMake(r.x() + radius, r.y()), mainFunction.get(), false, false)); + + RetainPtr<CGShadingRef> rightShading(AdoptCF, CGShadingCreateAxial(cspace, CGPointMake(r.right(), r.y()), CGPointMake(r.right() - radius, r.y()), mainFunction.get(), false, false)); + paintInfo.context->save(); + CGContextClipToRect(context, r); + paintInfo.context->addRoundedRectClip(r, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); + CGContextDrawShading(context, mainShading.get()); + paintInfo.context->restore(); + + paintInfo.context->save(); + CGContextClipToRect(context, topGradient); + paintInfo.context->addRoundedRectClip(enclosingIntRect(topGradient), topLeftRadius, topRightRadius, IntSize(), IntSize()); + CGContextDrawShading(context, topShading.get()); + paintInfo.context->restore(); + + if (!bottomGradient.isEmpty()) { + paintInfo.context->save(); + CGContextClipToRect(context, bottomGradient); + paintInfo.context->addRoundedRectClip(enclosingIntRect(bottomGradient), IntSize(), IntSize(), bottomLeftRadius, bottomRightRadius); + CGContextDrawShading(context, bottomShading.get()); + paintInfo.context->restore(); + } + + paintInfo.context->save(); + CGContextClipToRect(context, r); + paintInfo.context->addRoundedRectClip(r, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); + CGContextDrawShading(context, leftShading.get()); + CGContextDrawShading(context, rightShading.get()); + paintInfo.context->restore(); + + paintInfo.context->restore(); +} + +bool RenderThemeMac::paintMenuListButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + IntRect bounds = IntRect(r.x() + o->style()->borderLeftWidth(), + r.y() + o->style()->borderTopWidth(), + r.width() - o->style()->borderLeftWidth() - o->style()->borderRightWidth(), + r.height() - o->style()->borderTopWidth() - o->style()->borderBottomWidth()); + // Draw the gradients to give the styled popup menu a button appearance + paintMenuListButtonGradients(o, paintInfo, bounds); + + // Since we actually know the size of the control here, we restrict the font scale to make sure the arrows will fit vertically in the bounds + float fontScale = min(o->style()->fontSize() / baseFontSize, bounds.height() / (baseArrowHeight * 2 + baseSpaceBetweenArrows)); + float centerY = bounds.y() + bounds.height() / 2.0f; + float arrowHeight = baseArrowHeight * fontScale; + float arrowWidth = baseArrowWidth * fontScale; + float leftEdge = bounds.right() - arrowPaddingRight * o->style()->effectiveZoom() - arrowWidth; + float spaceBetweenArrows = baseSpaceBetweenArrows * fontScale; + + if (bounds.width() < arrowWidth + arrowPaddingLeft * o->style()->effectiveZoom()) + return false; + + paintInfo.context->save(); + + paintInfo.context->setFillColor(o->style()->visitedDependentColor(CSSPropertyColor), o->style()->colorSpace()); + paintInfo.context->setStrokeStyle(NoStroke); + + FloatPoint arrow1[3]; + arrow1[0] = FloatPoint(leftEdge, centerY - spaceBetweenArrows / 2.0f); + arrow1[1] = FloatPoint(leftEdge + arrowWidth, centerY - spaceBetweenArrows / 2.0f); + arrow1[2] = FloatPoint(leftEdge + arrowWidth / 2.0f, centerY - spaceBetweenArrows / 2.0f - arrowHeight); + + // Draw the top arrow + paintInfo.context->drawConvexPolygon(3, arrow1, true); + + FloatPoint arrow2[3]; + arrow2[0] = FloatPoint(leftEdge, centerY + spaceBetweenArrows / 2.0f); + arrow2[1] = FloatPoint(leftEdge + arrowWidth, centerY + spaceBetweenArrows / 2.0f); + arrow2[2] = FloatPoint(leftEdge + arrowWidth / 2.0f, centerY + spaceBetweenArrows / 2.0f + arrowHeight); + + // Draw the bottom arrow + paintInfo.context->drawConvexPolygon(3, arrow2, true); + + Color leftSeparatorColor(0, 0, 0, 40); + Color rightSeparatorColor(255, 255, 255, 40); + + // FIXME: Should the separator thickness and space be scaled up by fontScale? + int separatorSpace = 2; // Deliberately ignores zoom since it looks nicer if it stays thin. + int leftEdgeOfSeparator = static_cast<int>(leftEdge - arrowPaddingLeft * o->style()->effectiveZoom()); // FIXME: Round? + + // Draw the separator to the left of the arrows + paintInfo.context->setStrokeThickness(1.0f); // Deliberately ignores zoom since it looks nicer if it stays thin. + paintInfo.context->setStrokeStyle(SolidStroke); + paintInfo.context->setStrokeColor(leftSeparatorColor, ColorSpaceDeviceRGB); + paintInfo.context->drawLine(IntPoint(leftEdgeOfSeparator, bounds.y()), + IntPoint(leftEdgeOfSeparator, bounds.bottom())); + + paintInfo.context->setStrokeColor(rightSeparatorColor, ColorSpaceDeviceRGB); + paintInfo.context->drawLine(IntPoint(leftEdgeOfSeparator + separatorSpace, bounds.y()), + IntPoint(leftEdgeOfSeparator + separatorSpace, bounds.bottom())); + + paintInfo.context->restore(); + return false; +} + +static const IntSize* menuListButtonSizes() +{ + static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) }; + return sizes; +} + +void RenderThemeMac::adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + NSControlSize controlSize = controlSizeForFont(style); + + style->resetBorder(); + style->resetPadding(); + + // Height is locked to auto. + style->setHeight(Length(Auto)); + + // White-space is locked to pre + style->setWhiteSpace(PRE); + + // Set the foreground color to black or gray when we have the aqua look. + // Cast to RGB32 is to work around a compiler bug. + style->setColor(e && e->isEnabledFormControl() ? static_cast<RGBA32>(Color::black) : Color::darkGray); + + // Set the button's vertical size. + setSizeFromFont(style, menuListButtonSizes()); + + // Our font is locked to the appropriate system font size for the control. To clarify, we first use the CSS-specified font to figure out + // a reasonable control size, but once that control size is determined, we throw that font away and use the appropriate + // system font for the control size instead. + setFontFromControlSize(selector, style, controlSize); + + style->setBoxShadow(0); +} + +int RenderThemeMac::popupInternalPaddingLeft(RenderStyle* style) const +{ + if (style->appearance() == MenulistPart) + return popupButtonPadding(controlSizeForFont(style))[leftPadding] * style->effectiveZoom(); + if (style->appearance() == MenulistButtonPart) + return styledPopupPaddingLeft * style->effectiveZoom(); + return 0; +} + +int RenderThemeMac::popupInternalPaddingRight(RenderStyle* style) const +{ + if (style->appearance() == MenulistPart) + return popupButtonPadding(controlSizeForFont(style))[rightPadding] * style->effectiveZoom(); + if (style->appearance() == MenulistButtonPart) { + float fontScale = style->fontSize() / baseFontSize; + float arrowWidth = baseArrowWidth * fontScale; + return static_cast<int>(ceilf(arrowWidth + (arrowPaddingLeft + arrowPaddingRight + paddingBeforeSeparator) * style->effectiveZoom())); + } + return 0; +} + +int RenderThemeMac::popupInternalPaddingTop(RenderStyle* style) const +{ + if (style->appearance() == MenulistPart) + return popupButtonPadding(controlSizeForFont(style))[topPadding] * style->effectiveZoom(); + if (style->appearance() == MenulistButtonPart) + return styledPopupPaddingTop * style->effectiveZoom(); + return 0; +} + +int RenderThemeMac::popupInternalPaddingBottom(RenderStyle* style) const +{ + if (style->appearance() == MenulistPart) + return popupButtonPadding(controlSizeForFont(style))[bottomPadding] * style->effectiveZoom(); + if (style->appearance() == MenulistButtonPart) + return styledPopupPaddingBottom * style->effectiveZoom(); + return 0; +} + +void RenderThemeMac::adjustMenuListButtonStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + float fontScale = style->fontSize() / baseFontSize; + + style->resetPadding(); + style->setBorderRadius(IntSize(int(baseBorderRadius + fontScale - 1), int(baseBorderRadius + fontScale - 1))); // FIXME: Round up? + + const int minHeight = 15; + style->setMinHeight(Length(minHeight, Fixed)); + + style->setLineHeight(RenderStyle::initialLineHeight()); +} + +void RenderThemeMac::setPopupButtonCellState(const RenderObject* o, const IntRect& r) +{ + NSPopUpButtonCell* popupButton = this->popupButton(); + + // Set the control size based off the rectangle we're painting into. + setControlSize(popupButton, popupButtonSizes(), r.size(), o->style()->effectiveZoom()); + + // Update the various states we respond to. + updateActiveState(popupButton, o); + updateCheckedState(popupButton, o); + updateEnabledState(popupButton, o); + updatePressedState(popupButton, o); + updateFocusedState(popupButton, o); +} + +const IntSize* RenderThemeMac::menuListSizes() const +{ + static const IntSize sizes[3] = { IntSize(9, 0), IntSize(5, 0), IntSize(0, 0) }; + return sizes; +} + +int RenderThemeMac::minimumMenuListSize(RenderStyle* style) const +{ + return sizeForSystemFont(style, menuListSizes()).width(); +} + +const int trackWidth = 5; +const int trackRadius = 2; + +void RenderThemeMac::adjustSliderTrackStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + style->setBoxShadow(0); +} + +bool RenderThemeMac::paintSliderTrack(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + IntRect bounds = r; + float zoomLevel = o->style()->effectiveZoom(); + float zoomedTrackWidth = trackWidth * zoomLevel; + + if (o->style()->appearance() == SliderHorizontalPart || o->style()->appearance() == MediaSliderPart) { + bounds.setHeight(zoomedTrackWidth); + bounds.setY(r.y() + r.height() / 2 - zoomedTrackWidth / 2); + } else if (o->style()->appearance() == SliderVerticalPart) { + bounds.setWidth(zoomedTrackWidth); + bounds.setX(r.x() + r.width() / 2 - zoomedTrackWidth / 2); + } + + LocalCurrentGraphicsContext localContext(paintInfo.context); + CGContextRef context = paintInfo.context->platformContext(); + CGColorSpaceRef cspace = deviceRGBColorSpaceRef(); + + paintInfo.context->save(); + CGContextClipToRect(context, bounds); + + struct CGFunctionCallbacks mainCallbacks = { 0, TrackGradientInterpolate, NULL }; + RetainPtr<CGFunctionRef> mainFunction(AdoptCF, CGFunctionCreate(NULL, 1, NULL, 4, NULL, &mainCallbacks)); + RetainPtr<CGShadingRef> mainShading; + if (o->style()->appearance() == SliderVerticalPart) + mainShading.adoptCF(CGShadingCreateAxial(cspace, CGPointMake(bounds.x(), bounds.bottom()), CGPointMake(bounds.right(), bounds.bottom()), mainFunction.get(), false, false)); + else + mainShading.adoptCF(CGShadingCreateAxial(cspace, CGPointMake(bounds.x(), bounds.y()), CGPointMake(bounds.x(), bounds.bottom()), mainFunction.get(), false, false)); + + IntSize radius(trackRadius, trackRadius); + paintInfo.context->addRoundedRectClip(bounds, + radius, radius, + radius, radius); + CGContextDrawShading(context, mainShading.get()); + paintInfo.context->restore(); + + return false; +} + +void RenderThemeMac::adjustSliderThumbStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + style->setBoxShadow(0); +} + +const float verticalSliderHeightPadding = 0.1f; + +bool RenderThemeMac::paintSliderThumb(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + ASSERT(o->parent()->isSlider()); + + NSSliderCell* sliderThumbCell = o->style()->appearance() == SliderThumbVerticalPart + ? sliderThumbVertical() + : sliderThumbHorizontal(); + + LocalCurrentGraphicsContext localContext(paintInfo.context); + + // Update the various states we respond to. + updateActiveState(sliderThumbCell, o->parent()); + updateEnabledState(sliderThumbCell, o->parent()); + updateFocusedState(sliderThumbCell, o->parent()); + + // Update the pressed state using the NSCell tracking methods, since that's how NSSliderCell keeps track of it. + bool oldPressed; + if (o->style()->appearance() == SliderThumbVerticalPart) + oldPressed = m_isSliderThumbVerticalPressed; + else + oldPressed = m_isSliderThumbHorizontalPressed; + + bool pressed = toRenderSlider(o->parent())->inDragMode(); + + if (o->style()->appearance() == SliderThumbVerticalPart) + m_isSliderThumbVerticalPressed = pressed; + else + m_isSliderThumbHorizontalPressed = pressed; + + if (pressed != oldPressed) { + if (pressed) + [sliderThumbCell startTrackingAt:NSPoint() inView:nil]; + else + [sliderThumbCell stopTracking:NSPoint() at:NSPoint() inView:nil mouseIsUp:YES]; + } + + FloatRect bounds = r; + // Make the height of the vertical slider slightly larger so NSSliderCell will draw a vertical slider. + if (o->style()->appearance() == SliderThumbVerticalPart) + bounds.setHeight(bounds.height() + verticalSliderHeightPadding * o->style()->effectiveZoom()); + + paintInfo.context->save(); + float zoomLevel = o->style()->effectiveZoom(); + + FloatRect unzoomedRect = bounds; + if (zoomLevel != 1.0f) { + unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel); + unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel); + paintInfo.context->translate(unzoomedRect.x(), unzoomedRect.y()); + paintInfo.context->scale(FloatSize(zoomLevel, zoomLevel)); + paintInfo.context->translate(-unzoomedRect.x(), -unzoomedRect.y()); + } + + [sliderThumbCell drawWithFrame:unzoomedRect inView:documentViewFor(o)]; + [sliderThumbCell setControlView:nil]; + + paintInfo.context->restore(); + + return false; +} + +bool RenderThemeMac::paintSearchField(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + LocalCurrentGraphicsContext localContext(paintInfo.context); + NSSearchFieldCell* search = this->search(); + + setSearchCellState(o, r); + + paintInfo.context->save(); + + float zoomLevel = o->style()->effectiveZoom(); + + IntRect unzoomedRect = r; + + if (zoomLevel != 1.0f) { + unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel); + unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel); + paintInfo.context->translate(unzoomedRect.x(), unzoomedRect.y()); + paintInfo.context->scale(FloatSize(zoomLevel, zoomLevel)); + paintInfo.context->translate(-unzoomedRect.x(), -unzoomedRect.y()); + } + + // Set the search button to nil before drawing. Then reset it so we can draw it later. + [search setSearchButtonCell:nil]; + + [search drawWithFrame:NSRect(unzoomedRect) inView:documentViewFor(o)]; +#ifdef BUILDING_ON_TIGER + if ([search showsFirstResponder]) + wkDrawTextFieldCellFocusRing(search, NSRect(unzoomedRect)); +#endif + + [search setControlView:nil]; + [search resetSearchButtonCell]; + + paintInfo.context->restore(); + + return false; +} + +void RenderThemeMac::setSearchCellState(RenderObject* o, const IntRect&) +{ + NSSearchFieldCell* search = this->search(); + + [search setControlSize:controlSizeForFont(o->style())]; + + // Update the various states we respond to. + updateActiveState(search, o); + updateEnabledState(search, o); + updateFocusedState(search, o); +} + +const IntSize* RenderThemeMac::searchFieldSizes() const +{ + static const IntSize sizes[3] = { IntSize(0, 22), IntSize(0, 19), IntSize(0, 17) }; + return sizes; +} + +void RenderThemeMac::setSearchFieldSize(RenderStyle* style) const +{ + // If the width and height are both specified, then we have nothing to do. + if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto()) + return; + + // Use the font size to determine the intrinsic width of the control. + setSizeFromFont(style, searchFieldSizes()); +} + +void RenderThemeMac::adjustSearchFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element*) const +{ + // Override border. + style->resetBorder(); + const short borderWidth = 2 * style->effectiveZoom(); + style->setBorderLeftWidth(borderWidth); + style->setBorderLeftStyle(INSET); + style->setBorderRightWidth(borderWidth); + style->setBorderRightStyle(INSET); + style->setBorderBottomWidth(borderWidth); + style->setBorderBottomStyle(INSET); + style->setBorderTopWidth(borderWidth); + style->setBorderTopStyle(INSET); + + // Override height. + style->setHeight(Length(Auto)); + setSearchFieldSize(style); + + // Override padding size to match AppKit text positioning. + const int padding = 1 * style->effectiveZoom(); + style->setPaddingLeft(Length(padding, Fixed)); + style->setPaddingRight(Length(padding, Fixed)); + style->setPaddingTop(Length(padding, Fixed)); + style->setPaddingBottom(Length(padding, Fixed)); + + NSControlSize controlSize = controlSizeForFont(style); + setFontFromControlSize(selector, style, controlSize); + + style->setBoxShadow(0); +} + +bool RenderThemeMac::paintSearchFieldCancelButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* input = o->node()->shadowAncestorNode(); + if (!input->renderer()->isBox()) + return false; + + LocalCurrentGraphicsContext localContext(paintInfo.context); + setSearchCellState(input->renderer(), r); + + NSSearchFieldCell* search = this->search(); + + updateActiveState([search cancelButtonCell], o); + updatePressedState([search cancelButtonCell], o); + + paintInfo.context->save(); + + float zoomLevel = o->style()->effectiveZoom(); + + FloatRect localBounds = [search cancelButtonRectForBounds:NSRect(input->renderBox()->borderBoxRect())]; + +#if ENABLE(INPUT_SPEECH) + // Take care of cases where the cancel button was not aligned with the right border of the input element (for e.g. + // when speech input is enabled for the input element. + IntRect absBoundingBox = input->renderer()->absoluteBoundingBoxRect(); + int absRight = absBoundingBox.x() + absBoundingBox.width() - input->renderBox()->paddingRight() - input->renderBox()->borderRight(); + int spaceToRightOfCancelButton = absRight - (r.x() + r.width()); + localBounds.setX(localBounds.x() - spaceToRightOfCancelButton); +#endif + + localBounds = convertToPaintingRect(input->renderer(), o, localBounds, r); + + FloatRect unzoomedRect(localBounds); + if (zoomLevel != 1.0f) { + unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel); + unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel); + paintInfo.context->translate(unzoomedRect.x(), unzoomedRect.y()); + paintInfo.context->scale(FloatSize(zoomLevel, zoomLevel)); + paintInfo.context->translate(-unzoomedRect.x(), -unzoomedRect.y()); + } + + [[search cancelButtonCell] drawWithFrame:unzoomedRect inView:documentViewFor(o)]; + [[search cancelButtonCell] setControlView:nil]; + + paintInfo.context->restore(); + return false; +} + +const IntSize* RenderThemeMac::cancelButtonSizes() const +{ + static const IntSize sizes[3] = { IntSize(16, 13), IntSize(13, 11), IntSize(13, 9) }; + return sizes; +} + +void RenderThemeMac::adjustSearchFieldCancelButtonStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + IntSize size = sizeForSystemFont(style, cancelButtonSizes()); + style->setWidth(Length(size.width(), Fixed)); + style->setHeight(Length(size.height(), Fixed)); + style->setBoxShadow(0); +} + +const IntSize* RenderThemeMac::resultsButtonSizes() const +{ + static const IntSize sizes[3] = { IntSize(19, 13), IntSize(17, 11), IntSize(17, 9) }; + return sizes; +} + +const int emptyResultsOffset = 9; +void RenderThemeMac::adjustSearchFieldDecorationStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + IntSize size = sizeForSystemFont(style, resultsButtonSizes()); + style->setWidth(Length(size.width() - emptyResultsOffset, Fixed)); + style->setHeight(Length(size.height(), Fixed)); + style->setBoxShadow(0); +} + +bool RenderThemeMac::paintSearchFieldDecoration(RenderObject*, const PaintInfo&, const IntRect&) +{ + return false; +} + +void RenderThemeMac::adjustSearchFieldResultsDecorationStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + IntSize size = sizeForSystemFont(style, resultsButtonSizes()); + style->setWidth(Length(size.width(), Fixed)); + style->setHeight(Length(size.height(), Fixed)); + style->setBoxShadow(0); +} + +bool RenderThemeMac::paintSearchFieldResultsDecoration(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* input = o->node()->shadowAncestorNode(); + if (!input->renderer()->isBox()) + return false; + + LocalCurrentGraphicsContext localContext(paintInfo.context); + setSearchCellState(input->renderer(), r); + + NSSearchFieldCell* search = this->search(); + + if ([search searchMenuTemplate] != nil) + [search setSearchMenuTemplate:nil]; + + updateActiveState([search searchButtonCell], o); + + FloatRect localBounds = [search searchButtonRectForBounds:NSRect(input->renderBox()->borderBoxRect())]; + localBounds = convertToPaintingRect(input->renderer(), o, localBounds, r); + + [[search searchButtonCell] drawWithFrame:localBounds inView:documentViewFor(o)]; + [[search searchButtonCell] setControlView:nil]; + return false; +} + +const int resultsArrowWidth = 5; +void RenderThemeMac::adjustSearchFieldResultsButtonStyle(CSSStyleSelector*, RenderStyle* style, Element*) const +{ + IntSize size = sizeForSystemFont(style, resultsButtonSizes()); + style->setWidth(Length(size.width() + resultsArrowWidth, Fixed)); + style->setHeight(Length(size.height(), Fixed)); + style->setBoxShadow(0); +} + +bool RenderThemeMac::paintSearchFieldResultsButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* input = o->node()->shadowAncestorNode(); + if (!input->renderer()->isBox()) + return false; + + LocalCurrentGraphicsContext localContext(paintInfo.context); + setSearchCellState(input->renderer(), r); + + NSSearchFieldCell* search = this->search(); + + updateActiveState([search searchButtonCell], o); + + if (![search searchMenuTemplate]) + [search setSearchMenuTemplate:searchMenuTemplate()]; + + paintInfo.context->save(); + + float zoomLevel = o->style()->effectiveZoom(); + + FloatRect localBounds = [search searchButtonRectForBounds:NSRect(input->renderBox()->borderBoxRect())]; + localBounds = convertToPaintingRect(input->renderer(), o, localBounds, r); + + IntRect unzoomedRect(localBounds); + if (zoomLevel != 1.0f) { + unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel); + unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel); + paintInfo.context->translate(unzoomedRect.x(), unzoomedRect.y()); + paintInfo.context->scale(FloatSize(zoomLevel, zoomLevel)); + paintInfo.context->translate(-unzoomedRect.x(), -unzoomedRect.y()); + } + + [[search searchButtonCell] drawWithFrame:unzoomedRect inView:documentViewFor(o)]; + [[search searchButtonCell] setControlView:nil]; + + paintInfo.context->restore(); + + return false; +} + +#if ENABLE(VIDEO) +typedef enum { + MediaControllerThemeClassic = 1, + MediaControllerThemeQuickTime = 2 +} MediaControllerThemeStyle; + +static int mediaControllerTheme() +{ + static int controllerTheme = -1; + + if (controllerTheme != -1) + return controllerTheme; + + controllerTheme = MediaControllerThemeClassic; + + Boolean validKey; + Boolean useQTMediaUIPref = CFPreferencesGetAppBooleanValue(CFSTR("UseQuickTimeMediaUI"), CFSTR("com.apple.WebCore"), &validKey); + +#if !defined(BUILDING_ON_TIGER) + if (validKey && !useQTMediaUIPref) + return controllerTheme; +#else + if (!validKey || !useQTMediaUIPref) + return controllerTheme; +#endif + + controllerTheme = MediaControllerThemeQuickTime; + return controllerTheme; +} +#endif + +const int sliderThumbWidth = 15; +const int sliderThumbHeight = 15; +const int mediaSliderThumbWidth = 13; +const int mediaSliderThumbHeight = 14; + +void RenderThemeMac::adjustSliderThumbSize(RenderObject* o) const +{ + float zoomLevel = o->style()->effectiveZoom(); + if (o->style()->appearance() == SliderThumbHorizontalPart || o->style()->appearance() == SliderThumbVerticalPart) { + o->style()->setWidth(Length(static_cast<int>(sliderThumbWidth * zoomLevel), Fixed)); + o->style()->setHeight(Length(static_cast<int>(sliderThumbHeight * zoomLevel), Fixed)); + } + +#if ENABLE(VIDEO) + adjustMediaSliderThumbSize(o); +#endif +} + +#if ENABLE(VIDEO) + +void RenderThemeMac::adjustMediaSliderThumbSize(RenderObject* o) const +{ + ControlPart part = o->style()->appearance(); + + if (part == MediaSliderThumbPart || part == MediaVolumeSliderThumbPart) { + int width = mediaSliderThumbWidth; + int height = mediaSliderThumbHeight; + + if (mediaControllerTheme() == MediaControllerThemeQuickTime) { + CGSize size; + + wkMeasureMediaUIPart(part == MediaSliderThumbPart ? MediaSliderThumb : MediaVolumeSliderThumb, MediaControllerThemeQuickTime, NULL, &size); + width = size.width; + height = size.height; + } + + float zoomLevel = o->style()->effectiveZoom(); + o->style()->setWidth(Length(static_cast<int>(width * zoomLevel), Fixed)); + o->style()->setHeight(Length(static_cast<int>(height * zoomLevel), Fixed)); + } +} + +enum WKMediaControllerThemeState { + MediaUIPartDisabledFlag = 1 << 0, + MediaUIPartPressedFlag = 1 << 1, + MediaUIPartDrawEndCapsFlag = 1 << 3, +}; + +static unsigned getMediaUIPartStateFlags(Node* node) +{ + unsigned flags = 0; + + if (node->disabled()) + flags |= MediaUIPartDisabledFlag; + else if (node->active()) + flags |= MediaUIPartPressedFlag; + return flags; +} + +// Utility to scale when the UI part are not scaled by wkDrawMediaUIPart +static FloatRect getUnzoomedRectAndAdjustCurrentContext(RenderObject* o, const PaintInfo& paintInfo, const IntRect &originalRect) +{ + float zoomLevel = o->style()->effectiveZoom(); + FloatRect unzoomedRect(originalRect); + if (zoomLevel != 1.0f && mediaControllerTheme() == MediaControllerThemeQuickTime) { + unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel); + unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel); + paintInfo.context->translate(unzoomedRect.x(), unzoomedRect.y()); + paintInfo.context->scale(FloatSize(zoomLevel, zoomLevel)); + paintInfo.context->translate(-unzoomedRect.x(), -unzoomedRect.y()); + } + return unzoomedRect; +} + + +bool RenderThemeMac::paintMediaFullscreenButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* node = o->node(); + if (!node) + return false; + + LocalCurrentGraphicsContext localContext(paintInfo.context); + wkDrawMediaUIPart(MediaFullscreenButton, mediaControllerTheme(), paintInfo.context->platformContext(), r, getMediaUIPartStateFlags(node)); + return false; +} + +bool RenderThemeMac::paintMediaMuteButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* node = o->node(); + Node* mediaNode = node ? node->shadowAncestorNode() : 0; + if (!mediaNode || (!mediaNode->hasTagName(videoTag) && !mediaNode->hasTagName(audioTag))) + return false; + + if (MediaControlMuteButtonElement* btn = static_cast<MediaControlMuteButtonElement*>(node)) { + LocalCurrentGraphicsContext localContext(paintInfo.context); + wkDrawMediaUIPart(btn->displayType(), mediaControllerTheme(), paintInfo.context->platformContext(), r, getMediaUIPartStateFlags(node)); + + } + return false; +} + +bool RenderThemeMac::paintMediaPlayButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* node = o->node(); + Node* mediaNode = node ? node->shadowAncestorNode() : 0; + if (!mediaNode || (!mediaNode->hasTagName(videoTag) && !mediaNode->hasTagName(audioTag))) + return false; + + if (MediaControlPlayButtonElement* btn = static_cast<MediaControlPlayButtonElement*>(node)) { + LocalCurrentGraphicsContext localContext(paintInfo.context); + wkDrawMediaUIPart(btn->displayType(), mediaControllerTheme(), paintInfo.context->platformContext(), r, getMediaUIPartStateFlags(node)); + } + return false; +} + +bool RenderThemeMac::paintMediaSeekBackButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* node = o->node(); + if (!node) + return false; + + LocalCurrentGraphicsContext localContext(paintInfo.context); + wkDrawMediaUIPart(MediaSeekBackButton, mediaControllerTheme(), paintInfo.context->platformContext(), r, getMediaUIPartStateFlags(node)); + return false; +} + +bool RenderThemeMac::paintMediaSeekForwardButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* node = o->node(); + if (!node) + return false; + + LocalCurrentGraphicsContext localContext(paintInfo.context); + wkDrawMediaUIPart(MediaSeekForwardButton, mediaControllerTheme(), paintInfo.context->platformContext(), r, getMediaUIPartStateFlags(node)); + return false; +} + +bool RenderThemeMac::paintMediaSliderTrack(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* node = o->node(); + Node* mediaNode = node ? node->shadowAncestorNode() : 0; + if (!mediaNode || (!mediaNode->hasTagName(videoTag) && !mediaNode->hasTagName(audioTag))) + return false; + + HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(mediaNode); + if (!mediaElement) + return false; + + RefPtr<TimeRanges> timeRanges = mediaElement->buffered(); + ExceptionCode ignoredException; + float timeLoaded = timeRanges->length() ? timeRanges->end(0, ignoredException) : 0; + float currentTime = mediaElement->currentTime(); + float duration = mediaElement->duration(); + if (isnan(duration)) + duration = 0; + + paintInfo.context->save(); + FloatRect unzoomedRect = getUnzoomedRectAndAdjustCurrentContext(o, paintInfo, r); + wkDrawMediaSliderTrack(mediaControllerTheme(), paintInfo.context->platformContext(), unzoomedRect, + timeLoaded, currentTime, duration, getMediaUIPartStateFlags(node)); + + paintInfo.context->restore(); + return false; +} + +bool RenderThemeMac::paintMediaSliderThumb(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* node = o->node(); + if (!node) + return false; + + LocalCurrentGraphicsContext localContext(paintInfo.context); + wkDrawMediaUIPart(MediaSliderThumb, mediaControllerTheme(), paintInfo.context->platformContext(), r, getMediaUIPartStateFlags(node)); + return false; +} + +bool RenderThemeMac::paintMediaRewindButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* node = o->node(); + if (!node) + return false; + + LocalCurrentGraphicsContext localContext(paintInfo.context); + wkDrawMediaUIPart(MediaRewindButton, mediaControllerTheme(), paintInfo.context->platformContext(), r, getMediaUIPartStateFlags(node)); + return false; +} + +bool RenderThemeMac::paintMediaReturnToRealtimeButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* node = o->node(); + if (!node) + return false; + + LocalCurrentGraphicsContext localContext(paintInfo.context); + wkDrawMediaUIPart(MediaReturnToRealtimeButton, mediaControllerTheme(), paintInfo.context->platformContext(), r, getMediaUIPartStateFlags(node)); + return false; +} + +bool RenderThemeMac::paintMediaToggleClosedCaptionsButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + HTMLInputElement* node = static_cast<HTMLInputElement*>(o->node()); + if (!node) + return false; + + MediaControlToggleClosedCaptionsButtonElement* btn = static_cast<MediaControlToggleClosedCaptionsButtonElement*>(node); + if (!btn) + return false; + + LocalCurrentGraphicsContext localContext(paintInfo.context); + wkDrawMediaUIPart(btn->displayType(), mediaControllerTheme(), paintInfo.context->platformContext(), r, getMediaUIPartStateFlags(node)); + + return false; +} + +bool RenderThemeMac::paintMediaControlsBackground(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* node = o->node(); + if (!node) + return false; + + LocalCurrentGraphicsContext localContext(paintInfo.context); + wkDrawMediaUIPart(MediaTimelineContainer, mediaControllerTheme(), paintInfo.context->platformContext(), r, getMediaUIPartStateFlags(node)); + return false; +} + +bool RenderThemeMac::paintMediaCurrentTime(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* node = o->node(); + if (!node) + return false; + + paintInfo.context->save(); + FloatRect unzoomedRect = getUnzoomedRectAndAdjustCurrentContext(o, paintInfo, r); + wkDrawMediaUIPart(MediaCurrentTimeDisplay, mediaControllerTheme(), paintInfo.context->platformContext(), unzoomedRect, getMediaUIPartStateFlags(node)); + paintInfo.context->restore(); + return false; +} + +bool RenderThemeMac::paintMediaTimeRemaining(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* node = o->node(); + if (!node) + return false; + + paintInfo.context->save(); + FloatRect unzoomedRect = getUnzoomedRectAndAdjustCurrentContext(o, paintInfo, r); + wkDrawMediaUIPart(MediaTimeRemainingDisplay, mediaControllerTheme(), paintInfo.context->platformContext(), unzoomedRect, getMediaUIPartStateFlags(node)); + paintInfo.context->restore(); + return false; +} + +bool RenderThemeMac::paintMediaVolumeSliderContainer(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* node = o->node(); + if (!node) + return false; + + LocalCurrentGraphicsContext localContext(paintInfo.context); + wkDrawMediaUIPart(MediaVolumeSliderContainer, mediaControllerTheme(), paintInfo.context->platformContext(), r, getMediaUIPartStateFlags(node)); + return false; +} + +bool RenderThemeMac::paintMediaVolumeSliderTrack(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* node = o->node(); + if (!node) + return false; + + LocalCurrentGraphicsContext localContext(paintInfo.context); + wkDrawMediaUIPart(MediaVolumeSlider, mediaControllerTheme(), paintInfo.context->platformContext(), r, getMediaUIPartStateFlags(node)); + return false; +} + +bool RenderThemeMac::paintMediaVolumeSliderThumb(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Node* node = o->node(); + if (!node) + return false; + + LocalCurrentGraphicsContext localContext(paintInfo.context); + wkDrawMediaUIPart(MediaVolumeSliderThumb, mediaControllerTheme(), paintInfo.context->platformContext(), r, getMediaUIPartStateFlags(node)); + return false; +} + +String RenderThemeMac::extraMediaControlsStyleSheet() +{ +#if PLATFORM(MAC) + if (mediaControllerTheme() == MediaControllerThemeQuickTime) + return String(mediaControlsQuickTimeUserAgentStyleSheet, sizeof(mediaControlsQuickTimeUserAgentStyleSheet)); + + return String(); +#else + ASSERT_NOT_REACHED(); + return String(); +#endif +} + +bool RenderThemeMac::shouldRenderMediaControlPart(ControlPart part, Element* element) +{ + switch (part) { + case MediaVolumeSliderContainerPart: + case MediaVolumeSliderPart: + case MediaVolumeSliderMuteButtonPart: + case MediaVolumeSliderThumbPart: { + HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(element); + return mediaControllerTheme() == MediaControllerThemeQuickTime && mediaElement->hasAudio(); + } + case MediaToggleClosedCaptionsButtonPart: + // We rely on QTKit to render captions so don't enable the button unless it will be able to do so. + if (!element->hasTagName(videoTag)) + return false; + default: + break; + } + + return RenderTheme::shouldRenderMediaControlPart(part, element); +} + +IntPoint RenderThemeMac::volumeSliderOffsetFromMuteButton(Node* muteButton, const IntSize& size) const +{ + static const int xOffset = -4; + static const int yOffset = 5; + + float zoomLevel = muteButton->renderer()->style()->effectiveZoom(); + int y = yOffset * zoomLevel + muteButton->renderBox()->offsetHeight() - size.height(); + FloatPoint absPoint = muteButton->renderer()->localToAbsolute(FloatPoint(muteButton->renderBox()->offsetLeft(), y), true, true); + if (absPoint.y() < 0) + y = muteButton->renderBox()->height(); + return IntPoint(xOffset * zoomLevel, y); +} + +#endif // ENABLE(VIDEO) + +NSPopUpButtonCell* RenderThemeMac::popupButton() const +{ + if (!m_popupButton) { + m_popupButton.adoptNS([[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO]); + [m_popupButton.get() setUsesItemFromMenu:NO]; + [m_popupButton.get() setFocusRingType:NSFocusRingTypeExterior]; + } + + return m_popupButton.get(); +} + +NSSearchFieldCell* RenderThemeMac::search() const +{ + if (!m_search) { + m_search.adoptNS([[NSSearchFieldCell alloc] initTextCell:@""]); + [m_search.get() setBezelStyle:NSTextFieldRoundedBezel]; + [m_search.get() setBezeled:YES]; + [m_search.get() setEditable:YES]; + [m_search.get() setFocusRingType:NSFocusRingTypeExterior]; + } + + return m_search.get(); +} + +NSMenu* RenderThemeMac::searchMenuTemplate() const +{ + if (!m_searchMenuTemplate) + m_searchMenuTemplate.adoptNS([[NSMenu alloc] initWithTitle:@""]); + + return m_searchMenuTemplate.get(); +} + +NSSliderCell* RenderThemeMac::sliderThumbHorizontal() const +{ + if (!m_sliderThumbHorizontal) { + m_sliderThumbHorizontal.adoptNS([[NSSliderCell alloc] init]); + [m_sliderThumbHorizontal.get() setTitle:nil]; + [m_sliderThumbHorizontal.get() setSliderType:NSLinearSlider]; + [m_sliderThumbHorizontal.get() setControlSize:NSSmallControlSize]; + [m_sliderThumbHorizontal.get() setFocusRingType:NSFocusRingTypeExterior]; + } + + return m_sliderThumbHorizontal.get(); +} + +NSSliderCell* RenderThemeMac::sliderThumbVertical() const +{ + if (!m_sliderThumbVertical) { + m_sliderThumbVertical.adoptNS([[NSSliderCell alloc] init]); + [m_sliderThumbVertical.get() setTitle:nil]; + [m_sliderThumbVertical.get() setSliderType:NSLinearSlider]; + [m_sliderThumbVertical.get() setControlSize:NSSmallControlSize]; + [m_sliderThumbVertical.get() setFocusRingType:NSFocusRingTypeExterior]; + } + + return m_sliderThumbVertical.get(); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderThemeSafari.cpp b/Source/WebCore/rendering/RenderThemeSafari.cpp new file mode 100644 index 0000000..6d5d322 --- /dev/null +++ b/Source/WebCore/rendering/RenderThemeSafari.cpp @@ -0,0 +1,1215 @@ +/* + * Copyright (C) 2007, 2008, 2009 Apple Inc. + * Copyright (C) 2009 Kenneth Rohde Christiansen + * + * 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 "RenderThemeSafari.h" +#include "RenderThemeWin.h" +#include "Settings.h" + +#if USE(SAFARI_THEME) + +#include "CSSValueKeywords.h" +#include "Document.h" +#include "Element.h" +#include "Frame.h" +#include "FrameView.h" +#include "GraphicsContextCG.h" +#include "HTMLInputElement.h" +#include "HTMLMediaElement.h" +#include "HTMLNames.h" +#include "RenderMediaControls.h" +#include "RenderSlider.h" +#include "RenderView.h" +#include "RetainPtr.h" +#include "SoftLinking.h" +#include "cssstyleselector.h" +#include <CoreGraphics/CoreGraphics.h> + +using std::min; + +// FIXME: The platform-independent code in this class should be factored out and merged with RenderThemeMac. + +namespace WebCore { + +using namespace HTMLNames; +using namespace SafariTheme; + +enum { + topMargin, + rightMargin, + bottomMargin, + leftMargin +}; + +enum { + topPadding, + rightPadding, + bottomPadding, + leftPadding +}; + +PassRefPtr<RenderTheme> RenderThemeSafari::create() +{ + return adoptRef(new RenderThemeSafari); +} + +PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page* page) +{ + static RenderTheme* safariTheme = RenderThemeSafari::create().releaseRef(); + static RenderTheme* windowsTheme = RenderThemeWin::create().releaseRef(); + + // FIXME: This is called before Settings has been initialized by WebKit, so will return a + // potentially wrong answer the very first time it's called (see + // <https://bugs.webkit.org/show_bug.cgi?id=26493>). + if (Settings::shouldPaintNativeControls()) { + RenderTheme::setCustomFocusRingColor(safariTheme->platformFocusRingColor()); + return windowsTheme; // keep the reference of one. + } + return safariTheme; // keep the reference of one. +} + +#ifdef DEBUG_ALL +SOFT_LINK_DEBUG_LIBRARY(SafariTheme) +#else +SOFT_LINK_LIBRARY(SafariTheme) +#endif + +SOFT_LINK(SafariTheme, paintThemePart, void, __stdcall, (ThemePart part, CGContextRef context, const CGRect& rect, NSControlSize size, ThemeControlState state), (part, context, rect, size, state)) +#if defined(SAFARI_THEME_VERSION) && SAFARI_THEME_VERSION >= 2 +SOFT_LINK(SafariTheme, STPaintProgressIndicator, void, APIENTRY, (ProgressIndicatorType type, CGContextRef context, const CGRect& rect, NSControlSize size, ThemeControlState state, float value), (type, context, rect, size, state, value)) +#endif +SOFT_LINK_OPTIONAL(SafariTheme, STCopyThemeColor, CGColorRef, APIENTRY, (unsigned color, SafariTheme::ThemeControlState)); + +static const unsigned stFocusRingColorID = 4; + +static const unsigned aquaFocusRingColor = 0xFF7DADD9; + +static RGBA32 makeRGBAFromCGColor(CGColorRef color) +{ + const CGFloat* components = CGColorGetComponents(color); + return makeRGBA(255 * components[0], 255 * components[1], 255 * components[2], 255 * components[3]); +} + +ThemeControlState RenderThemeSafari::determineState(RenderObject* o) const +{ + ThemeControlState result = 0; + if (isActive(o)) + result |= SafariTheme::ActiveState; + if (isEnabled(o) && !isReadOnlyControl(o)) + result |= SafariTheme::EnabledState; + if (isPressed(o)) + result |= SafariTheme::PressedState; + if (isChecked(o)) + result |= SafariTheme::CheckedState; + if (isIndeterminate(o)) + result |= SafariTheme::IndeterminateCheckedState; + if (isFocused(o)) + result |= SafariTheme::FocusedState; + if (isDefault(o)) + result |= SafariTheme::DefaultState; + return result; +} + +static NSControlSize controlSizeFromRect(const IntRect& rect, const IntSize sizes[]) +{ + if (sizes[NSRegularControlSize].height() == rect.height()) + return NSRegularControlSize; + else if (sizes[NSMiniControlSize].height() == rect.height()) + return NSMiniControlSize; + + return NSSmallControlSize; +} + +RenderThemeSafari::RenderThemeSafari() +{ +} + +RenderThemeSafari::~RenderThemeSafari() +{ +} + +Color RenderThemeSafari::platformActiveSelectionBackgroundColor() const +{ + return Color(181, 213, 255); +} + +Color RenderThemeSafari::platformInactiveSelectionBackgroundColor() const +{ + return Color(212, 212, 212); +} + +Color RenderThemeSafari::activeListBoxSelectionBackgroundColor() const +{ + // FIXME: This should probably just be a darker version of the platformActiveSelectionBackgroundColor + return Color(56, 117, 215); +} + +Color RenderThemeSafari::platformFocusRingColor() const +{ + static Color focusRingColor; + + if (!focusRingColor.isValid()) { + if (STCopyThemeColorPtr()) { + RetainPtr<CGColorRef> color(AdoptCF, STCopyThemeColorPtr()(stFocusRingColorID, SafariTheme::ActiveState)); + focusRingColor = makeRGBAFromCGColor(color.get()); + } + if (!focusRingColor.isValid()) + focusRingColor = aquaFocusRingColor; + } + + return focusRingColor; +} + +static float systemFontSizeForControlSize(NSControlSize controlSize) +{ + static float sizes[] = { 13.0f, 11.0f, 9.0f }; + + return sizes[controlSize]; +} + +void RenderThemeSafari::systemFont(int propId, FontDescription& fontDescription) const +{ + static FontDescription systemFont; + static FontDescription smallSystemFont; + static FontDescription menuFont; + static FontDescription labelFont; + static FontDescription miniControlFont; + static FontDescription smallControlFont; + static FontDescription controlFont; + + FontDescription* cachedDesc; + float fontSize = 0; + switch (propId) { + case CSSValueSmallCaption: + cachedDesc = &smallSystemFont; + if (!smallSystemFont.isAbsoluteSize()) + fontSize = systemFontSizeForControlSize(NSSmallControlSize); + break; + case CSSValueMenu: + cachedDesc = &menuFont; + if (!menuFont.isAbsoluteSize()) + fontSize = systemFontSizeForControlSize(NSRegularControlSize); + break; + case CSSValueStatusBar: + cachedDesc = &labelFont; + if (!labelFont.isAbsoluteSize()) + fontSize = 10.0f; + break; + case CSSValueWebkitMiniControl: + cachedDesc = &miniControlFont; + if (!miniControlFont.isAbsoluteSize()) + fontSize = systemFontSizeForControlSize(NSMiniControlSize); + break; + case CSSValueWebkitSmallControl: + cachedDesc = &smallControlFont; + if (!smallControlFont.isAbsoluteSize()) + fontSize = systemFontSizeForControlSize(NSSmallControlSize); + break; + case CSSValueWebkitControl: + cachedDesc = &controlFont; + if (!controlFont.isAbsoluteSize()) + fontSize = systemFontSizeForControlSize(NSRegularControlSize); + break; + default: + cachedDesc = &systemFont; + if (!systemFont.isAbsoluteSize()) + fontSize = 13.0f; + } + + if (fontSize) { + cachedDesc->setIsAbsoluteSize(true); + cachedDesc->setGenericFamily(FontDescription::NoFamily); + cachedDesc->firstFamily().setFamily("Lucida Grande"); + cachedDesc->setSpecifiedSize(fontSize); + cachedDesc->setWeight(FontWeightNormal); + cachedDesc->setItalic(false); + } + fontDescription = *cachedDesc; +} + +bool RenderThemeSafari::isControlStyled(const RenderStyle* style, const BorderData& border, + const FillLayer& background, const Color& backgroundColor) const +{ + // If we didn't find SafariTheme.dll we won't be able to paint any themed controls. + if (!SafariThemeLibrary()) + return true; + + if (style->appearance() == TextFieldPart || style->appearance() == TextAreaPart || style->appearance() == ListboxPart) + return style->border() != border; + return RenderTheme::isControlStyled(style, border, background, backgroundColor); +} + +void RenderThemeSafari::adjustRepaintRect(const RenderObject* o, IntRect& r) +{ + NSControlSize controlSize = controlSizeForFont(o->style()); + + switch (o->style()->appearance()) { + case CheckboxPart: { + // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox + // shadow" and the check. We don't consider this part of the bounds of the control in WebKit. + r = inflateRect(r, checkboxSizes()[controlSize], checkboxMargins(controlSize)); + break; + } + case RadioPart: { + // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox + // shadow" and the check. We don't consider this part of the bounds of the control in WebKit. + r = inflateRect(r, radioSizes()[controlSize], radioMargins(controlSize)); + break; + } + case PushButtonPart: + case DefaultButtonPart: + case ButtonPart: { + // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox + // shadow" and the check. We don't consider this part of the bounds of the control in WebKit. + if (r.height() <= buttonSizes()[NSRegularControlSize].height()) + r = inflateRect(r, buttonSizes()[controlSize], buttonMargins(controlSize)); + break; + } + case MenulistPart: { + r = inflateRect(r, popupButtonSizes()[controlSize], popupButtonMargins(controlSize)); + break; + } + default: + break; + } +} + +IntRect RenderThemeSafari::inflateRect(const IntRect& r, const IntSize& size, const int* margins) const +{ + // Only do the inflation if the available width/height are too small. Otherwise try to + // fit the glow/check space into the available box's width/height. + int widthDelta = r.width() - (size.width() + margins[leftMargin] + margins[rightMargin]); + int heightDelta = r.height() - (size.height() + margins[topMargin] + margins[bottomMargin]); + IntRect result(r); + if (widthDelta < 0) { + result.setX(result.x() - margins[leftMargin]); + result.setWidth(result.width() - widthDelta); + } + if (heightDelta < 0) { + result.setY(result.y() - margins[topMargin]); + result.setHeight(result.height() - heightDelta); + } + return result; +} + +int RenderThemeSafari::baselinePosition(const RenderObject* o) const +{ + if (!o->isBox()) + return 0; + + if (o->style()->appearance() == CheckboxPart || o->style()->appearance() == RadioPart) { + const RenderBox* box = toRenderBox(o); + return box->marginTop() + box->height() - 2; // The baseline is 2px up from the bottom of the checkbox/radio in AppKit. + } + + return RenderTheme::baselinePosition(o); +} + +bool RenderThemeSafari::controlSupportsTints(const RenderObject* o) const +{ + if (!isEnabled(o)) + return false; + + // Checkboxes only have tint when checked. + if (o->style()->appearance() == CheckboxPart) + return isChecked(o); + + // For now assume other controls have tint if enabled. + return true; +} + +NSControlSize RenderThemeSafari::controlSizeForFont(RenderStyle* style) const +{ + int fontSize = style->fontSize(); + if (fontSize >= 16) + return NSRegularControlSize; + if (fontSize >= 11) + return NSSmallControlSize; + return NSMiniControlSize; +} +/* +void RenderThemeSafari::setControlSize(NSCell* cell, const IntSize* sizes, const IntSize& minSize) +{ + NSControlSize size; + if (minSize.width() >= sizes[NSRegularControlSize].width() && + minSize.height() >= sizes[NSRegularControlSize].height()) + size = NSRegularControlSize; + else if (minSize.width() >= sizes[NSSmallControlSize].width() && + minSize.height() >= sizes[NSSmallControlSize].height()) + size = NSSmallControlSize; + else + size = NSMiniControlSize; + if (size != [cell controlSize]) // Only update if we have to, since AppKit does work even if the size is the same. + [cell setControlSize:size]; +} +*/ +IntSize RenderThemeSafari::sizeForFont(RenderStyle* style, const IntSize* sizes) const +{ + return sizes[controlSizeForFont(style)]; +} + +IntSize RenderThemeSafari::sizeForSystemFont(RenderStyle* style, const IntSize* sizes) const +{ + return sizes[controlSizeForSystemFont(style)]; +} + +void RenderThemeSafari::setSizeFromFont(RenderStyle* style, const IntSize* sizes) const +{ + // FIXME: Check is flawed, since it doesn't take min-width/max-width into account. + IntSize size = sizeForFont(style, sizes); + if (style->width().isIntrinsicOrAuto() && size.width() > 0) + style->setWidth(Length(size.width(), Fixed)); + if (style->height().isAuto() && size.height() > 0) + style->setHeight(Length(size.height(), Fixed)); +} + +void RenderThemeSafari::setFontFromControlSize(CSSStyleSelector* selector, RenderStyle* style, NSControlSize controlSize) const +{ + FontDescription fontDescription; + fontDescription.setIsAbsoluteSize(true); + fontDescription.setGenericFamily(FontDescription::SerifFamily); + + float fontSize = systemFontSizeForControlSize(controlSize); + fontDescription.firstFamily().setFamily("Lucida Grande"); + fontDescription.setComputedSize(fontSize); + fontDescription.setSpecifiedSize(fontSize); + + // Reset line height + style->setLineHeight(RenderStyle::initialLineHeight()); + + if (style->setFontDescription(fontDescription)) + style->font().update(selector->fontSelector()); +} + +NSControlSize RenderThemeSafari::controlSizeForSystemFont(RenderStyle* style) const +{ + int fontSize = style->fontSize(); + if (fontSize >= 13) + return NSRegularControlSize; + if (fontSize >= 11) + return NSSmallControlSize; + return NSMiniControlSize; +} + +bool RenderThemeSafari::paintCheckbox(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + ASSERT(SafariThemeLibrary()); + + NSControlSize controlSize = controlSizeForFont(o->style()); + + IntRect inflatedRect = inflateRect(r, checkboxSizes()[controlSize], checkboxMargins(controlSize)); + paintThemePart(SafariTheme::CheckboxPart, paintInfo.context->platformContext(), inflatedRect, controlSize, determineState(o)); + + return false; +} + +const IntSize* RenderThemeSafari::checkboxSizes() const +{ + static const IntSize sizes[3] = { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) }; + return sizes; +} + +const int* RenderThemeSafari::checkboxMargins(NSControlSize controlSize) const +{ + static const int margins[3][4] = + { + { 2, 2, 2, 2 }, + { 2, 2, 2, 1 }, + { 1, 0, 0, 0 }, + }; + return margins[controlSize]; +} + +void RenderThemeSafari::setCheckboxSize(RenderStyle* style) const +{ + // If the width and height are both specified, then we have nothing to do. + if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto()) + return; + + // Use the font size to determine the intrinsic width of the control. + setSizeFromFont(style, checkboxSizes()); +} + +bool RenderThemeSafari::paintRadio(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + ASSERT(SafariThemeLibrary()); + + NSControlSize controlSize = controlSizeForFont(o->style()); + + IntRect inflatedRect = inflateRect(r, radioSizes()[controlSize], radioMargins(controlSize)); + paintThemePart(RadioButtonPart, paintInfo.context->platformContext(), inflatedRect, controlSize, determineState(o)); + + return false; +} + +const IntSize* RenderThemeSafari::radioSizes() const +{ + static const IntSize sizes[3] = { IntSize(14, 15), IntSize(12, 13), IntSize(10, 10) }; + return sizes; +} + +const int* RenderThemeSafari::radioMargins(NSControlSize controlSize) const +{ + static const int margins[3][4] = + { + { 1, 2, 2, 2 }, + { 0, 1, 2, 1 }, + { 0, 0, 1, 0 }, + }; + return margins[controlSize]; +} + +void RenderThemeSafari::setRadioSize(RenderStyle* style) const +{ + // If the width and height are both specified, then we have nothing to do. + if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto()) + return; + + // Use the font size to determine the intrinsic width of the control. + setSizeFromFont(style, radioSizes()); +} + +void RenderThemeSafari::setButtonPaddingFromControlSize(RenderStyle* style, NSControlSize size) const +{ + // Just use 8px. AppKit wants to use 11px for mini buttons, but that padding is just too large + // for real-world Web sites (creating a huge necessary minimum width for buttons whose space is + // by definition constrained, since we select mini only for small cramped environments. + // This also guarantees the HTML4 <button> will match our rendering by default, since we're using a consistent + // padding. + const int padding = 8; + style->setPaddingLeft(Length(padding, Fixed)); + style->setPaddingRight(Length(padding, Fixed)); + style->setPaddingTop(Length(0, Fixed)); + style->setPaddingBottom(Length(0, Fixed)); +} + +void RenderThemeSafari::adjustButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + // There are three appearance constants for buttons. + // (1) Push-button is the constant for the default Aqua system button. Push buttons will not scale vertically and will not allow + // custom fonts or colors. <input>s use this constant. This button will allow custom colors and font weights/variants but won't + // scale vertically. + // (2) square-button is the constant for the square button. This button will allow custom fonts and colors and will scale vertically. + // (3) Button is the constant that means "pick the best button as appropriate." <button>s use this constant. This button will + // also scale vertically and allow custom fonts and colors. It will attempt to use Aqua if possible and will make this determination + // solely on the rectangle of the control. + + // Determine our control size based off our font. + NSControlSize controlSize = controlSizeForFont(style); + + if (style->appearance() == PushButtonPart) { + // Ditch the border. + style->resetBorder(); + + // Height is locked to auto. + style->setHeight(Length(Auto)); + + // White-space is locked to pre + style->setWhiteSpace(PRE); + + // Set the button's vertical size. + setButtonSize(style); + + // Add in the padding that we'd like to use. + setButtonPaddingFromControlSize(style, controlSize); + + // Our font is locked to the appropriate system font size for the control. To clarify, we first use the CSS-specified font to figure out + // a reasonable control size, but once that control size is determined, we throw that font away and use the appropriate + // system font for the control size instead. + setFontFromControlSize(selector, style, controlSize); + } else { + // Set a min-height so that we can't get smaller than the mini button. + style->setMinHeight(Length(15, Fixed)); + + // Reset the top and bottom borders. + style->resetBorderTop(); + style->resetBorderBottom(); + } +} + +const IntSize* RenderThemeSafari::buttonSizes() const +{ + static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) }; + return sizes; +} + +const int* RenderThemeSafari::buttonMargins(NSControlSize controlSize) const +{ + static const int margins[3][4] = + { + { 4, 6, 7, 6 }, + { 4, 5, 6, 5 }, + { 0, 1, 1, 1 }, + }; + return margins[controlSize]; +} + +void RenderThemeSafari::setButtonSize(RenderStyle* style) const +{ + // If the width and height are both specified, then we have nothing to do. + if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto()) + return; + + // Use the font size to determine the intrinsic width of the control. + setSizeFromFont(style, buttonSizes()); +} + +bool RenderThemeSafari::paintButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + ASSERT(SafariThemeLibrary()); + + // We inflate the rect as needed to account for padding included in the cell to accommodate the button + // shadow. We don't consider this part of the bounds of the control in WebKit. + + NSControlSize controlSize = controlSizeFromRect(r, buttonSizes()); + IntRect inflatedRect = r; + + ThemePart part; + if (r.height() <= buttonSizes()[NSRegularControlSize].height()) { + // Push button + part = SafariTheme::PushButtonPart; + + IntSize size = buttonSizes()[controlSize]; + size.setWidth(r.width()); + + // Center the button within the available space. + if (inflatedRect.height() > size.height()) { + inflatedRect.setY(inflatedRect.y() + (inflatedRect.height() - size.height()) / 2); + inflatedRect.setHeight(size.height()); + } + + // Now inflate it to account for the shadow. + inflatedRect = inflateRect(inflatedRect, size, buttonMargins(controlSize)); + } else + part = SafariTheme::SquareButtonPart; + + paintThemePart(part, paintInfo.context->platformContext(), inflatedRect, controlSize, determineState(o)); + return false; +} + +bool RenderThemeSafari::paintTextField(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + ASSERT(SafariThemeLibrary()); + + paintThemePart(SafariTheme::TextFieldPart, paintInfo.context->platformContext(), r, (NSControlSize)0, determineState(o) & ~FocusedState); + return false; +} + +void RenderThemeSafari::adjustTextFieldStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +bool RenderThemeSafari::paintCapsLockIndicator(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ +#if defined(SAFARI_THEME_VERSION) && SAFARI_THEME_VERSION >= 1 + ASSERT(SafariThemeLibrary()); + + if (paintInfo.context->paintingDisabled()) + return true; + + paintThemePart(CapsLockPart, paintInfo.context->platformContext(), r, (NSControlSize)0, (ThemeControlState)0); + + return false; +#else + return true; +#endif +} + +bool RenderThemeSafari::paintTextArea(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + ASSERT(SafariThemeLibrary()); + + paintThemePart(SafariTheme::TextAreaPart, paintInfo.context->platformContext(), r, (NSControlSize)0, determineState(o) & ~FocusedState); + return false; +} + +void RenderThemeSafari::adjustTextAreaStyle(CSSStyleSelector*, RenderStyle*, Element*) const +{ +} + +const int* RenderThemeSafari::popupButtonMargins(NSControlSize size) const +{ + static const int margins[3][4] = + { + { 2, 3, 3, 3 }, + { 1, 3, 3, 3 }, + { 0, 1, 0, 1 } + }; + return margins[size]; +} + +const IntSize* RenderThemeSafari::popupButtonSizes() const +{ + static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) }; + return sizes; +} + +const int* RenderThemeSafari::popupButtonPadding(NSControlSize size) const +{ + static const int padding[3][4] = + { + { 2, 26, 3, 8 }, + { 2, 23, 3, 8 }, + { 2, 22, 3, 10 } + }; + return padding[size]; +} + +bool RenderThemeSafari::paintMenuList(RenderObject* o, const PaintInfo& info, const IntRect& r) +{ + ASSERT(SafariThemeLibrary()); + + NSControlSize controlSize = controlSizeFromRect(r, popupButtonSizes()); + IntRect inflatedRect = r; + IntSize size = popupButtonSizes()[controlSize]; + size.setWidth(r.width()); + + // Now inflate it to account for the shadow. + if (r.width() >= minimumMenuListSize(o->style())) + inflatedRect = inflateRect(inflatedRect, size, popupButtonMargins(controlSize)); + + paintThemePart(DropDownButtonPart, info.context->platformContext(), inflatedRect, controlSize, determineState(o)); + + return false; +} + +const float baseFontSize = 11.0f; +const float baseArrowHeight = 5.0f; +const float baseArrowWidth = 7.0f; +const int arrowPaddingLeft = 5; +const int arrowPaddingRight = 5; +const int paddingBeforeSeparator = 4; +const int baseBorderRadius = 5; +const int styledPopupPaddingLeft = 8; +const int styledPopupPaddingTop = 1; +const int styledPopupPaddingBottom = 2; + +static void TopGradientInterpolate(void* info, const CGFloat* inData, CGFloat* outData) +{ + static float dark[4] = { 1.0f, 1.0f, 1.0f, 0.4f }; + static float light[4] = { 1.0f, 1.0f, 1.0f, 0.15f }; + float a = inData[0]; + int i = 0; + for (i = 0; i < 4; i++) + outData[i] = (1.0f - a) * dark[i] + a * light[i]; +} + +static void BottomGradientInterpolate(void* info, const CGFloat* inData, CGFloat* outData) +{ + static float dark[4] = { 1.0f, 1.0f, 1.0f, 0.0f }; + static float light[4] = { 1.0f, 1.0f, 1.0f, 0.3f }; + float a = inData[0]; + int i = 0; + for (i = 0; i < 4; i++) + outData[i] = (1.0f - a) * dark[i] + a * light[i]; +} + +static void MainGradientInterpolate(void* info, const CGFloat* inData, CGFloat* outData) +{ + static float dark[4] = { 0.0f, 0.0f, 0.0f, 0.15f }; + static float light[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float a = inData[0]; + int i = 0; + for (i = 0; i < 4; i++) + outData[i] = (1.0f - a) * dark[i] + a * light[i]; +} + +static void TrackGradientInterpolate(void* info, const CGFloat* inData, CGFloat* outData) +{ + static float dark[4] = { 0.0f, 0.0f, 0.0f, 0.678f }; + static float light[4] = { 0.0f, 0.0f, 0.0f, 0.13f }; + float a = inData[0]; + int i = 0; + for (i = 0; i < 4; i++) + outData[i] = (1.0f - a) * dark[i] + a * light[i]; +} + +void RenderThemeSafari::paintMenuListButtonGradients(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + if (r.isEmpty()) + return; + + CGContextRef context = paintInfo.context->platformContext(); + + paintInfo.context->save(); + + IntSize topLeftRadius; + IntSize topRightRadius; + IntSize bottomLeftRadius; + IntSize bottomRightRadius; + + o->style()->getBorderRadiiForRect(r, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); + + int radius = topLeftRadius.width(); + + CGColorSpaceRef cspace = deviceRGBColorSpaceRef(); + + FloatRect topGradient(r.x(), r.y(), r.width(), r.height() / 2.0f); + struct CGFunctionCallbacks topCallbacks = { 0, TopGradientInterpolate, NULL }; + RetainPtr<CGFunctionRef> topFunction(AdoptCF, CGFunctionCreate(NULL, 1, NULL, 4, NULL, &topCallbacks)); + RetainPtr<CGShadingRef> topShading(AdoptCF, CGShadingCreateAxial(cspace, CGPointMake(topGradient.x(), topGradient.y()), CGPointMake(topGradient.x(), topGradient.bottom()), topFunction.get(), false, false)); + + FloatRect bottomGradient(r.x() + radius, r.y() + r.height() / 2.0f, r.width() - 2.0f * radius, r.height() / 2.0f); + struct CGFunctionCallbacks bottomCallbacks = { 0, BottomGradientInterpolate, NULL }; + RetainPtr<CGFunctionRef> bottomFunction(AdoptCF, CGFunctionCreate(NULL, 1, NULL, 4, NULL, &bottomCallbacks)); + RetainPtr<CGShadingRef> bottomShading(AdoptCF, CGShadingCreateAxial(cspace, CGPointMake(bottomGradient.x(), bottomGradient.y()), CGPointMake(bottomGradient.x(), bottomGradient.bottom()), bottomFunction.get(), false, false)); + + struct CGFunctionCallbacks mainCallbacks = { 0, MainGradientInterpolate, NULL }; + RetainPtr<CGFunctionRef> mainFunction(AdoptCF, CGFunctionCreate(NULL, 1, NULL, 4, NULL, &mainCallbacks)); + RetainPtr<CGShadingRef> mainShading(AdoptCF, CGShadingCreateAxial(cspace, CGPointMake(r.x(), r.y()), CGPointMake(r.x(), r.bottom()), mainFunction.get(), false, false)); + + RetainPtr<CGShadingRef> leftShading(AdoptCF, CGShadingCreateAxial(cspace, CGPointMake(r.x(), r.y()), CGPointMake(r.x() + radius, r.y()), mainFunction.get(), false, false)); + + RetainPtr<CGShadingRef> rightShading(AdoptCF, CGShadingCreateAxial(cspace, CGPointMake(r.right(), r.y()), CGPointMake(r.right() - radius, r.y()), mainFunction.get(), false, false)); + paintInfo.context->save(); + CGContextClipToRect(context, r); + paintInfo.context->addRoundedRectClip(r, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); + CGContextDrawShading(context, mainShading.get()); + paintInfo.context->restore(); + + paintInfo.context->save(); + CGContextClipToRect(context, topGradient); + paintInfo.context->addRoundedRectClip(enclosingIntRect(topGradient), topLeftRadius, topRightRadius, IntSize(), IntSize()); + CGContextDrawShading(context, topShading.get()); + paintInfo.context->restore(); + + if (!bottomGradient.isEmpty()) { + paintInfo.context->save(); + CGContextClipToRect(context, bottomGradient); + paintInfo.context->addRoundedRectClip(enclosingIntRect(bottomGradient), IntSize(), IntSize(), bottomLeftRadius, bottomRightRadius); + CGContextDrawShading(context, bottomShading.get()); + paintInfo.context->restore(); + } + + paintInfo.context->save(); + CGContextClipToRect(context, r); + paintInfo.context->addRoundedRectClip(r, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); + CGContextDrawShading(context, leftShading.get()); + CGContextDrawShading(context, rightShading.get()); + paintInfo.context->restore(); + + paintInfo.context->restore(); +} + +bool RenderThemeSafari::paintMenuListButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + IntRect bounds = IntRect(r.x() + o->style()->borderLeftWidth(), + r.y() + o->style()->borderTopWidth(), + r.width() - o->style()->borderLeftWidth() - o->style()->borderRightWidth(), + r.height() - o->style()->borderTopWidth() - o->style()->borderBottomWidth()); + // Draw the gradients to give the styled popup menu a button appearance + paintMenuListButtonGradients(o, paintInfo, bounds); + + // Since we actually know the size of the control here, we restrict the font scale to make sure the arrow will fit vertically in the bounds + float fontScale = min(o->style()->fontSize() / baseFontSize, bounds.height() / baseArrowHeight); + float centerY = bounds.y() + bounds.height() / 2.0f; + float arrowHeight = baseArrowHeight * fontScale; + float arrowWidth = baseArrowWidth * fontScale; + float leftEdge = bounds.right() - arrowPaddingRight - arrowWidth; + + if (bounds.width() < arrowWidth + arrowPaddingLeft) + return false; + + paintInfo.context->save(); + + paintInfo.context->setFillColor(o->style()->visitedDependentColor(CSSPropertyColor), ColorSpaceDeviceRGB); + paintInfo.context->setStrokeColor(NoStroke, ColorSpaceDeviceRGB); + + FloatPoint arrow[3]; + arrow[0] = FloatPoint(leftEdge, centerY - arrowHeight / 2.0f); + arrow[1] = FloatPoint(leftEdge + arrowWidth, centerY - arrowHeight / 2.0f); + arrow[2] = FloatPoint(leftEdge + arrowWidth / 2.0f, centerY + arrowHeight / 2.0f); + + // Draw the arrow + paintInfo.context->drawConvexPolygon(3, arrow, true); + + Color leftSeparatorColor(0, 0, 0, 40); + Color rightSeparatorColor(255, 255, 255, 40); + + // FIXME: Should the separator thickness and space be scaled up by fontScale? + int separatorSpace = 2; + int leftEdgeOfSeparator = static_cast<int>(leftEdge - arrowPaddingLeft); // FIXME: Round? + + // Draw the separator to the left of the arrows + paintInfo.context->setStrokeThickness(1.0f); + paintInfo.context->setStrokeStyle(SolidStroke); + paintInfo.context->setStrokeColor(leftSeparatorColor, ColorSpaceDeviceRGB); + paintInfo.context->drawLine(IntPoint(leftEdgeOfSeparator, bounds.y()), + IntPoint(leftEdgeOfSeparator, bounds.bottom())); + + paintInfo.context->setStrokeColor(rightSeparatorColor, ColorSpaceDeviceRGB); + paintInfo.context->drawLine(IntPoint(leftEdgeOfSeparator + separatorSpace, bounds.y()), + IntPoint(leftEdgeOfSeparator + separatorSpace, bounds.bottom())); + + paintInfo.context->restore(); + return false; +} + +void RenderThemeSafari::adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + NSControlSize controlSize = controlSizeForFont(style); + + style->resetBorder(); + style->resetPadding(); + + // Height is locked to auto. + style->setHeight(Length(Auto)); + + // White-space is locked to pre + style->setWhiteSpace(PRE); + + // Set the foreground color to black or gray when we have the aqua look. + // Cast to RGB32 is to work around a compiler bug. + style->setColor(e && e->isEnabledFormControl() ? static_cast<RGBA32>(Color::black) : Color::darkGray); + + // Set the button's vertical size. + setButtonSize(style); + + // Our font is locked to the appropriate system font size for the control. To clarify, we first use the CSS-specified font to figure out + // a reasonable control size, but once that control size is determined, we throw that font away and use the appropriate + // system font for the control size instead. + setFontFromControlSize(selector, style, controlSize); +} + +int RenderThemeSafari::popupInternalPaddingLeft(RenderStyle* style) const +{ + if (style->appearance() == MenulistPart) + return popupButtonPadding(controlSizeForFont(style))[leftPadding]; + if (style->appearance() == MenulistButtonPart) + return styledPopupPaddingLeft; + return 0; +} + +int RenderThemeSafari::popupInternalPaddingRight(RenderStyle* style) const +{ + if (style->appearance() == MenulistPart) + return popupButtonPadding(controlSizeForFont(style))[rightPadding]; + if (style->appearance() == MenulistButtonPart) { + float fontScale = style->fontSize() / baseFontSize; + float arrowWidth = baseArrowWidth * fontScale; + return static_cast<int>(ceilf(arrowWidth + arrowPaddingLeft + arrowPaddingRight + paddingBeforeSeparator)); + } + return 0; +} + +int RenderThemeSafari::popupInternalPaddingTop(RenderStyle* style) const +{ + if (style->appearance() == MenulistPart) + return popupButtonPadding(controlSizeForFont(style))[topPadding]; + if (style->appearance() == MenulistButtonPart) + return styledPopupPaddingTop; + return 0; +} + +int RenderThemeSafari::popupInternalPaddingBottom(RenderStyle* style) const +{ + if (style->appearance() == MenulistPart) + return popupButtonPadding(controlSizeForFont(style))[bottomPadding]; + if (style->appearance() == MenulistButtonPart) + return styledPopupPaddingBottom; + return 0; +} + +void RenderThemeSafari::adjustMenuListButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + float fontScale = style->fontSize() / baseFontSize; + + style->resetPadding(); + style->setBorderRadius(IntSize(int(baseBorderRadius + fontScale - 1), int(baseBorderRadius + fontScale - 1))); // FIXME: Round up? + + const int minHeight = 15; + style->setMinHeight(Length(minHeight, Fixed)); + + style->setLineHeight(RenderStyle::initialLineHeight()); +} + +const IntSize* RenderThemeSafari::menuListSizes() const +{ + static const IntSize sizes[3] = { IntSize(9, 0), IntSize(5, 0), IntSize(0, 0) }; + return sizes; +} + +int RenderThemeSafari::minimumMenuListSize(RenderStyle* style) const +{ + return sizeForSystemFont(style, menuListSizes()).width(); +} + +const int trackWidth = 5; +const int trackRadius = 2; + +bool RenderThemeSafari::paintSliderTrack(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + IntRect bounds = r; + + if (o->style()->appearance() == SliderHorizontalPart) { + bounds.setHeight(trackWidth); + bounds.setY(r.y() + r.height() / 2 - trackWidth / 2); + } else if (o->style()->appearance() == SliderVerticalPart) { + bounds.setWidth(trackWidth); + bounds.setX(r.x() + r.width() / 2 - trackWidth / 2); + } + + CGContextRef context = paintInfo.context->platformContext(); + CGColorSpaceRef cspace = deviceRGBColorSpaceRef(); + + paintInfo.context->save(); + CGContextClipToRect(context, bounds); + + struct CGFunctionCallbacks mainCallbacks = { 0, TrackGradientInterpolate, NULL }; + RetainPtr<CGFunctionRef> mainFunction(AdoptCF, CGFunctionCreate(NULL, 1, NULL, 4, NULL, &mainCallbacks)); + RetainPtr<CGShadingRef> mainShading; + if (o->style()->appearance() == SliderVerticalPart) + mainShading.adoptCF(CGShadingCreateAxial(cspace, CGPointMake(bounds.x(), bounds.bottom()), CGPointMake(bounds.right(), bounds.bottom()), mainFunction.get(), false, false)); + else + mainShading.adoptCF(CGShadingCreateAxial(cspace, CGPointMake(bounds.x(), bounds.y()), CGPointMake(bounds.x(), bounds.bottom()), mainFunction.get(), false, false)); + + IntSize radius(trackRadius, trackRadius); + paintInfo.context->addRoundedRectClip(bounds, + radius, radius, + radius, radius); + CGContextDrawShading(context, mainShading.get()); + paintInfo.context->restore(); + + return false; +} + +void RenderThemeSafari::adjustSliderThumbStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + style->setBoxShadow(0); +} + +const float verticalSliderHeightPadding = 0.1f; + +bool RenderThemeSafari::paintSliderThumb(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + ASSERT(SafariThemeLibrary()); + + ASSERT(o->parent()->isSlider()); + + bool pressed = toRenderSlider(o->parent())->inDragMode(); + ThemeControlState state = determineState(o->parent()); + state &= ~SafariTheme::PressedState; + if (pressed) + state |= SafariTheme::PressedState; + + paintThemePart(SliderThumbPart, paintInfo.context->platformContext(), r, NSSmallControlSize, state); + return false; +} + +const int sliderThumbWidth = 15; +const int sliderThumbHeight = 15; + +void RenderThemeSafari::adjustSliderThumbSize(RenderObject* o) const +{ + if (o->style()->appearance() == SliderThumbHorizontalPart || o->style()->appearance() == SliderThumbVerticalPart) { + o->style()->setWidth(Length(sliderThumbWidth, Fixed)); + o->style()->setHeight(Length(sliderThumbHeight, Fixed)); + } +#if ENABLE(VIDEO) + else if (o->style()->appearance() == MediaSliderThumbPart) + RenderMediaControls::adjustMediaSliderThumbSize(o); +#endif +} + +bool RenderThemeSafari::paintSearchField(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + ASSERT(SafariThemeLibrary()); + + paintThemePart(SafariTheme::SearchFieldPart, paintInfo.context->platformContext(), r, controlSizeFromRect(r, searchFieldSizes()), determineState(o)); + return false; +} + +const IntSize* RenderThemeSafari::searchFieldSizes() const +{ + static const IntSize sizes[3] = { IntSize(0, 22), IntSize(0, 19), IntSize(0, 15) }; + return sizes; +} + +void RenderThemeSafari::setSearchFieldSize(RenderStyle* style) const +{ + // If the width and height are both specified, then we have nothing to do. + if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto()) + return; + + // Use the font size to determine the intrinsic width of the control. + setSizeFromFont(style, searchFieldSizes()); +} + +void RenderThemeSafari::adjustSearchFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + // Override border. + style->resetBorder(); + const short borderWidth = 2; + style->setBorderLeftWidth(borderWidth); + style->setBorderLeftStyle(INSET); + style->setBorderRightWidth(borderWidth); + style->setBorderRightStyle(INSET); + style->setBorderBottomWidth(borderWidth); + style->setBorderBottomStyle(INSET); + style->setBorderTopWidth(borderWidth); + style->setBorderTopStyle(INSET); + + // Override height. + style->setHeight(Length(Auto)); + setSearchFieldSize(style); + + // Override padding size to match AppKit text positioning. + const int padding = 1; + style->setPaddingLeft(Length(padding, Fixed)); + style->setPaddingRight(Length(padding, Fixed)); + style->setPaddingTop(Length(padding, Fixed)); + style->setPaddingBottom(Length(padding, Fixed)); + + NSControlSize controlSize = controlSizeForFont(style); + setFontFromControlSize(selector, style, controlSize); +} + +bool RenderThemeSafari::paintSearchFieldCancelButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect&) +{ + ASSERT(SafariThemeLibrary()); + + Node* input = o->node()->shadowAncestorNode(); + ASSERT(input); + RenderObject* renderer = input->renderer(); + ASSERT(renderer); + + IntRect searchRect = renderer->absoluteBoundingBoxRect(); + + paintThemePart(SafariTheme::SearchFieldCancelButtonPart, paintInfo.context->platformContext(), searchRect, controlSizeFromRect(searchRect, searchFieldSizes()), determineState(o)); + return false; +} + +const IntSize* RenderThemeSafari::cancelButtonSizes() const +{ + static const IntSize sizes[3] = { IntSize(16, 13), IntSize(13, 11), IntSize(13, 9) }; + return sizes; +} + +void RenderThemeSafari::adjustSearchFieldCancelButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + IntSize size = sizeForSystemFont(style, cancelButtonSizes()); + style->setWidth(Length(size.width(), Fixed)); + style->setHeight(Length(size.height(), Fixed)); +} + +const IntSize* RenderThemeSafari::resultsButtonSizes() const +{ + static const IntSize sizes[3] = { IntSize(19, 13), IntSize(17, 11), IntSize(17, 9) }; + return sizes; +} + +const int emptyResultsOffset = 9; +void RenderThemeSafari::adjustSearchFieldDecorationStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + IntSize size = sizeForSystemFont(style, resultsButtonSizes()); + style->setWidth(Length(size.width() - emptyResultsOffset, Fixed)); + style->setHeight(Length(size.height(), Fixed)); +} + +bool RenderThemeSafari::paintSearchFieldDecoration(RenderObject*, const PaintInfo&, const IntRect&) +{ + return false; +} + +void RenderThemeSafari::adjustSearchFieldResultsDecorationStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + IntSize size = sizeForSystemFont(style, resultsButtonSizes()); + style->setWidth(Length(size.width(), Fixed)); + style->setHeight(Length(size.height(), Fixed)); +} + +bool RenderThemeSafari::paintSearchFieldResultsDecoration(RenderObject* o, const PaintInfo& paintInfo, const IntRect&) +{ + ASSERT(SafariThemeLibrary()); + + Node* input = o->node()->shadowAncestorNode(); + ASSERT(input); + RenderObject* renderer = input->renderer(); + ASSERT(renderer); + + IntRect searchRect = renderer->absoluteBoundingBoxRect(); + + paintThemePart(SafariTheme::SearchFieldResultsDecorationPart, paintInfo.context->platformContext(), searchRect, controlSizeFromRect(searchRect, searchFieldSizes()), determineState(o)); + return false; +} + +const int resultsArrowWidth = 5; +void RenderThemeSafari::adjustSearchFieldResultsButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + IntSize size = sizeForSystemFont(style, resultsButtonSizes()); + style->setWidth(Length(size.width() + resultsArrowWidth, Fixed)); + style->setHeight(Length(size.height(), Fixed)); +} + +bool RenderThemeSafari::paintSearchFieldResultsButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect&) +{ + ASSERT(SafariThemeLibrary()); + + Node* input = o->node()->shadowAncestorNode(); + ASSERT(input); + RenderObject* renderer = input->renderer(); + ASSERT(renderer); + + IntRect searchRect = renderer->absoluteBoundingBoxRect(); + + paintThemePart(SafariTheme::SearchFieldResultsButtonPart, paintInfo.context->platformContext(), searchRect, controlSizeFromRect(searchRect, searchFieldSizes()), determineState(o)); + return false; +} +#if ENABLE(VIDEO) +bool RenderThemeSafari::paintMediaFullscreenButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaFullscreenButton, o, paintInfo, r); +} + +bool RenderThemeSafari::paintMediaMuteButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaMuteButton, o, paintInfo, r); +} + +bool RenderThemeSafari::paintMediaPlayButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaPlayButton, o, paintInfo, r); +} + +bool RenderThemeSafari::paintMediaSeekBackButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaSeekBackButton, o, paintInfo, r); +} + +bool RenderThemeSafari::paintMediaSeekForwardButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaSeekForwardButton, o, paintInfo, r); +} + +bool RenderThemeSafari::paintMediaSliderTrack(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaSlider, o, paintInfo, r); +} + +bool RenderThemeSafari::paintMediaSliderThumb(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaSliderThumb, o, paintInfo, r); +} +#endif + +} // namespace WebCore + +#endif // #if USE(SAFARI_THEME) diff --git a/Source/WebCore/rendering/RenderThemeSafari.h b/Source/WebCore/rendering/RenderThemeSafari.h new file mode 100644 index 0000000..b91e28e --- /dev/null +++ b/Source/WebCore/rendering/RenderThemeSafari.h @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. + * Copyright (C) 2009 Kenneth Rohde Christiansen + * + * 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. + * + */ + +#ifndef RenderThemeSafari_h +#define RenderThemeSafari_h + +#if USE(SAFARI_THEME) + +#include "RenderTheme.h" + +// If you have an empty placeholder SafariThemeConstants.h, then include SafariTheme.h +// This is a workaround until a version of WebKitSupportLibrary is released with an updated SafariThemeConstants.h +#include <SafariTheme/SafariThemeConstants.h> +#ifndef SafariThemeConstants_h +#include <SafariTheme/SafariTheme.h> +#endif + +#if PLATFORM(WIN) +typedef void* HANDLE; +typedef struct HINSTANCE__* HINSTANCE; +typedef HINSTANCE HMODULE; +#endif + +namespace WebCore { + +using namespace SafariTheme; + +class RenderStyle; + +class RenderThemeSafari : public RenderTheme { +public: + static PassRefPtr<RenderTheme> create(); + + // A method to obtain the baseline position for a "leaf" control. This will only be used if a baseline + // position cannot be determined by examining child content. Checkboxes and radio buttons are examples of + // controls that need to do this. + virtual int baselinePosition(const RenderObject*) const; + + // A method asking if the control changes its tint when the window has focus or not. + virtual bool controlSupportsTints(const RenderObject*) const; + + // A general method asking if any control tinting is supported at all. + virtual bool supportsControlTints() const { return true; } + + virtual void adjustRepaintRect(const RenderObject*, IntRect&); + + virtual bool isControlStyled(const RenderStyle*, const BorderData&, + const FillLayer&, const Color& backgroundColor) const; + + virtual Color platformActiveSelectionBackgroundColor() const; + virtual Color platformInactiveSelectionBackgroundColor() const; + virtual Color activeListBoxSelectionBackgroundColor() const; + + virtual Color platformFocusRingColor() const; + + // System fonts. + virtual void systemFont(int propId, FontDescription&) const; + + virtual int minimumMenuListSize(RenderStyle*) const; + + virtual void adjustSliderThumbSize(RenderObject*) const; + virtual void adjustSliderThumbStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + + virtual int popupInternalPaddingLeft(RenderStyle*) const; + virtual int popupInternalPaddingRight(RenderStyle*) const; + virtual int popupInternalPaddingTop(RenderStyle*) const; + virtual int popupInternalPaddingBottom(RenderStyle*) const; + +protected: + // Methods for each appearance value. + virtual bool paintCheckbox(RenderObject*, const PaintInfo&, const IntRect&); + virtual void setCheckboxSize(RenderStyle*) const; + + virtual bool paintRadio(RenderObject*, const PaintInfo&, const IntRect&); + virtual void setRadioSize(RenderStyle*) const; + + virtual void adjustButtonStyle(CSSStyleSelector*, RenderStyle*, WebCore::Element*) const; + virtual bool paintButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual void setButtonSize(RenderStyle*) const; + + virtual bool paintTextField(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustTextFieldStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + + virtual bool paintTextArea(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustTextAreaStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + + virtual bool paintMenuList(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustMenuListStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + + virtual bool paintMenuListButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustMenuListButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + + virtual bool paintSliderTrack(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintSliderThumb(RenderObject*, const PaintInfo&, const IntRect&); + + virtual bool paintSearchField(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustSearchFieldStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + + virtual void adjustSearchFieldCancelButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldCancelButton(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustSearchFieldDecorationStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldDecoration(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustSearchFieldResultsDecorationStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldResultsDecoration(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustSearchFieldResultsButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldResultsButton(RenderObject*, const PaintInfo&, const IntRect&); + + virtual bool paintCapsLockIndicator(RenderObject*, const PaintInfo&, const IntRect&); + +#if ENABLE(VIDEO) + virtual bool paintMediaFullscreenButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaPlayButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaMuteButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSeekBackButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSeekForwardButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSliderTrack(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSliderThumb(RenderObject*, const PaintInfo&, const IntRect&); +#endif + +private: + RenderThemeSafari(); + virtual ~RenderThemeSafari(); + + IntRect inflateRect(const IntRect&, const IntSize&, const int* margins) const; + + // Get the control size based off the font. Used by some of the controls (like buttons). + + NSControlSize controlSizeForFont(RenderStyle*) const; + NSControlSize controlSizeForSystemFont(RenderStyle*) const; + //void setControlSize(NSCell*, const IntSize* sizes, const IntSize& minSize); + void setSizeFromFont(RenderStyle*, const IntSize* sizes) const; + IntSize sizeForFont(RenderStyle*, const IntSize* sizes) const; + IntSize sizeForSystemFont(RenderStyle*, const IntSize* sizes) const; + void setFontFromControlSize(CSSStyleSelector*, RenderStyle*, NSControlSize) const; + + // Helpers for adjusting appearance and for painting + const IntSize* checkboxSizes() const; + const int* checkboxMargins(NSControlSize) const; + + const IntSize* radioSizes() const; + const int* radioMargins(NSControlSize) const; + + void setButtonPaddingFromControlSize(RenderStyle*, NSControlSize) const; + const IntSize* buttonSizes() const; + const int* buttonMargins(NSControlSize) const; + + const IntSize* popupButtonSizes() const; + const int* popupButtonMargins(NSControlSize) const; + const int* popupButtonPadding(NSControlSize) const; + void paintMenuListButtonGradients(RenderObject*, const PaintInfo&, const IntRect&); + const IntSize* menuListSizes() const; + + const IntSize* searchFieldSizes() const; + const IntSize* cancelButtonSizes() const; + const IntSize* resultsButtonSizes() const; + void setSearchFieldSize(RenderStyle*) const; + + ThemeControlState determineState(RenderObject*) const; +}; + +} // namespace WebCore + +#endif // #if USE(SAFARI_THEME) + +#endif // RenderThemeSafari_h diff --git a/Source/WebCore/rendering/RenderThemeWin.cpp b/Source/WebCore/rendering/RenderThemeWin.cpp new file mode 100644 index 0000000..f0f8268 --- /dev/null +++ b/Source/WebCore/rendering/RenderThemeWin.cpp @@ -0,0 +1,1118 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. + * Copyright (C) 2009 Kenneth Rohde Christiansen + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" +#include "RenderThemeWin.h" + +#include "CSSValueKeywords.h" +#include "Element.h" +#include "Frame.h" +#include "GraphicsContext.h" +#include "LocalWindowsContext.h" +#include "RenderSlider.h" +#include "Settings.h" +#include "SoftLinking.h" +#include "SystemInfo.h" +#include "UserAgentStyleSheets.h" + +#if ENABLE(VIDEO) +#include "RenderMediaControls.h" +#endif + +#include <tchar.h> + +/* + * The following constants are used to determine how a widget is drawn using + * Windows' Theme API. For more information on theme parts and states see + * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/userex/topics/partsandstates.asp + */ + +// Generic state constants +#define TS_NORMAL 1 +#define TS_HOVER 2 +#define TS_ACTIVE 3 +#define TS_DISABLED 4 +#define TS_FOCUSED 5 + +// Button constants +#define BP_BUTTON 1 +#define BP_RADIO 2 +#define BP_CHECKBOX 3 + +// Textfield constants +#define TFP_TEXTFIELD 1 +#define EP_EDITBORDER_NOSCROLL 6 +#define TFS_READONLY 6 + +// ComboBox constants (from vsstyle.h) +#define CP_DROPDOWNBUTTON 1 +#define CP_BORDER 4 +#define CP_READONLY 5 +#define CP_DROPDOWNBUTTONRIGHT 6 + +// TrackBar (slider) parts +#define TKP_TRACK 1 +#define TKP_TRACKVERT 2 + +// TrackBar (slider) thumb parts +#define TKP_THUMBBOTTOM 4 +#define TKP_THUMBTOP 5 +#define TKP_THUMBLEFT 7 +#define TKP_THUMBRIGHT 8 + +// Trackbar (slider) thumb states +#define TUS_NORMAL 1 +#define TUS_HOT 2 +#define TUS_PRESSED 3 +#define TUS_FOCUSED 4 +#define TUS_DISABLED 5 + +// button states +#define PBS_NORMAL 1 +#define PBS_HOT 2 +#define PBS_PRESSED 3 +#define PBS_DISABLED 4 +#define PBS_DEFAULTED 5 + +// Spin button parts +#define SPNP_UP 1 +#define SPNP_DOWN 2 + +// Spin button states +#define DNS_NORMAL 1 +#define DNS_HOT 2 +#define DNS_PRESSED 3 +#define DNS_DISABLED 4 +#define UPS_NORMAL 1 +#define UPS_HOT 2 +#define UPS_PRESSED 3 +#define UPS_DISABLED 4 + + +SOFT_LINK_LIBRARY(uxtheme) +SOFT_LINK(uxtheme, OpenThemeData, HANDLE, WINAPI, (HWND hwnd, LPCWSTR pszClassList), (hwnd, pszClassList)) +SOFT_LINK(uxtheme, CloseThemeData, HRESULT, WINAPI, (HANDLE hTheme), (hTheme)) +SOFT_LINK(uxtheme, DrawThemeBackground, HRESULT, WINAPI, (HANDLE hTheme, HDC hdc, int iPartId, int iStateId, const RECT* pRect, const RECT* pClipRect), (hTheme, hdc, iPartId, iStateId, pRect, pClipRect)) +SOFT_LINK(uxtheme, IsThemeActive, BOOL, WINAPI, (), ()) +SOFT_LINK(uxtheme, IsThemeBackgroundPartiallyTransparent, BOOL, WINAPI, (HANDLE hTheme, int iPartId, int iStateId), (hTheme, iPartId, iStateId)) + +static bool haveTheme; + +static const unsigned vistaMenuListButtonOutset = 1; + +using namespace std; + +namespace WebCore { + +// This is the fixed width IE and Firefox use for buttons on dropdown menus +static const int dropDownButtonWidth = 17; + +static const int shell32MagnifierIconIndex = 22; + +// Default font size to match Firefox. +static const float defaultControlFontPixelSize = 13; + +static const float defaultCancelButtonSize = 9; +static const float minCancelButtonSize = 5; +static const float maxCancelButtonSize = 21; +static const float defaultSearchFieldResultsDecorationSize = 13; +static const float minSearchFieldResultsDecorationSize = 9; +static const float maxSearchFieldResultsDecorationSize = 30; +static const float defaultSearchFieldResultsButtonWidth = 18; + +static bool gWebKitIsBeingUnloaded; + +static bool documentIsInApplicationChromeMode(const Document* document) +{ + Settings* settings = document->settings(); + return settings && settings->inApplicationChromeMode(); +} + +void RenderThemeWin::setWebKitIsBeingUnloaded() +{ + gWebKitIsBeingUnloaded = true; +} + +PassRefPtr<RenderTheme> RenderThemeWin::create() +{ + return adoptRef(new RenderThemeWin); +} + +#if !USE(SAFARI_THEME) +PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page* page) +{ + static RenderTheme* winTheme = RenderThemeWin::create().releaseRef(); + return winTheme; +} +#endif + +RenderThemeWin::RenderThemeWin() + : m_buttonTheme(0) + , m_textFieldTheme(0) + , m_menuListTheme(0) + , m_sliderTheme(0) + , m_spinButtonTheme(0) +{ + haveTheme = uxthemeLibrary() && IsThemeActive(); +} + +RenderThemeWin::~RenderThemeWin() +{ + // If WebKit is being unloaded, then uxtheme.dll is no longer available. + if (gWebKitIsBeingUnloaded || !uxthemeLibrary()) + return; + close(); +} + +HANDLE RenderThemeWin::buttonTheme() const +{ + if (haveTheme && !m_buttonTheme) + m_buttonTheme = OpenThemeData(0, L"Button"); + return m_buttonTheme; +} + +HANDLE RenderThemeWin::textFieldTheme() const +{ + if (haveTheme && !m_textFieldTheme) + m_textFieldTheme = OpenThemeData(0, L"Edit"); + return m_textFieldTheme; +} + +HANDLE RenderThemeWin::menuListTheme() const +{ + if (haveTheme && !m_menuListTheme) + m_menuListTheme = OpenThemeData(0, L"ComboBox"); + return m_menuListTheme; +} + +HANDLE RenderThemeWin::sliderTheme() const +{ + if (haveTheme && !m_sliderTheme) + m_sliderTheme = OpenThemeData(0, L"TrackBar"); + return m_sliderTheme; +} + +HANDLE RenderThemeWin::spinButtonTheme() const +{ + if (haveTheme && !m_spinButtonTheme) + m_spinButtonTheme = OpenThemeData(0, L"Spin"); + return m_spinButtonTheme; +} + +void RenderThemeWin::close() +{ + // This method will need to be called when the OS theme changes to flush our cached themes. + if (m_buttonTheme) + CloseThemeData(m_buttonTheme); + if (m_textFieldTheme) + CloseThemeData(m_textFieldTheme); + if (m_menuListTheme) + CloseThemeData(m_menuListTheme); + if (m_sliderTheme) + CloseThemeData(m_sliderTheme); + if (m_spinButtonTheme) + CloseThemeData(m_spinButtonTheme); + m_buttonTheme = m_textFieldTheme = m_menuListTheme = m_sliderTheme = m_spinButtonTheme = 0; + + haveTheme = uxthemeLibrary() && IsThemeActive(); +} + +void RenderThemeWin::themeChanged() +{ + close(); +} + +String RenderThemeWin::extraDefaultStyleSheet() +{ + return String(themeWinUserAgentStyleSheet, sizeof(themeWinUserAgentStyleSheet)); +} + +String RenderThemeWin::extraQuirksStyleSheet() +{ + return String(themeWinQuirksUserAgentStyleSheet, sizeof(themeWinQuirksUserAgentStyleSheet)); +} + +bool RenderThemeWin::supportsHover(const RenderStyle*) const +{ + // The Classic/2k look has no hover effects. + return haveTheme; +} + +Color RenderThemeWin::platformActiveSelectionBackgroundColor() const +{ + COLORREF color = GetSysColor(COLOR_HIGHLIGHT); + return Color(GetRValue(color), GetGValue(color), GetBValue(color)); +} + +Color RenderThemeWin::platformInactiveSelectionBackgroundColor() const +{ + // This color matches Firefox. + return Color(176, 176, 176); +} + +Color RenderThemeWin::platformActiveSelectionForegroundColor() const +{ + COLORREF color = GetSysColor(COLOR_HIGHLIGHTTEXT); + return Color(GetRValue(color), GetGValue(color), GetBValue(color)); +} + +Color RenderThemeWin::platformInactiveSelectionForegroundColor() const +{ + return platformActiveSelectionForegroundColor(); +} + +static void fillFontDescription(FontDescription& fontDescription, LOGFONT& logFont, float fontSize) +{ + fontDescription.setIsAbsoluteSize(true); + fontDescription.setGenericFamily(FontDescription::NoFamily); + fontDescription.firstFamily().setFamily(String(logFont.lfFaceName)); + fontDescription.setSpecifiedSize(fontSize); + fontDescription.setWeight(logFont.lfWeight >= 700 ? FontWeightBold : FontWeightNormal); // FIXME: Use real weight. + fontDescription.setItalic(logFont.lfItalic); +} + +static void fillFontDescription(FontDescription& fontDescription, LOGFONT& logFont) +{ + fillFontDescription(fontDescription, logFont, abs(logFont.lfHeight)); +} + +void RenderThemeWin::systemFont(int propId, FontDescription& fontDescription) const +{ + static FontDescription captionFont; + static FontDescription controlFont; + static FontDescription smallCaptionFont; + static FontDescription menuFont; + static FontDescription iconFont; + static FontDescription messageBoxFont; + static FontDescription statusBarFont; + static FontDescription systemFont; + + static bool initialized; + static NONCLIENTMETRICS ncm; + + if (!initialized) { + initialized = true; + ncm.cbSize = sizeof(NONCLIENTMETRICS); + ::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0); + } + + switch (propId) { + case CSSValueIcon: { + if (!iconFont.isAbsoluteSize()) { + LOGFONT logFont; + ::SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(logFont), &logFont, 0); + fillFontDescription(iconFont, logFont); + } + fontDescription = iconFont; + break; + } + case CSSValueMenu: + if (!menuFont.isAbsoluteSize()) + fillFontDescription(menuFont, ncm.lfMenuFont); + fontDescription = menuFont; + break; + case CSSValueMessageBox: + if (!messageBoxFont.isAbsoluteSize()) + fillFontDescription(messageBoxFont, ncm.lfMessageFont); + fontDescription = messageBoxFont; + break; + case CSSValueStatusBar: + if (!statusBarFont.isAbsoluteSize()) + fillFontDescription(statusBarFont, ncm.lfStatusFont); + fontDescription = statusBarFont; + break; + case CSSValueCaption: + if (!captionFont.isAbsoluteSize()) + fillFontDescription(captionFont, ncm.lfCaptionFont); + fontDescription = captionFont; + break; + case CSSValueSmallCaption: + if (!smallCaptionFont.isAbsoluteSize()) + fillFontDescription(smallCaptionFont, ncm.lfSmCaptionFont); + fontDescription = smallCaptionFont; + break; + case CSSValueWebkitSmallControl: + case CSSValueWebkitMiniControl: // Just map to small. + case CSSValueWebkitControl: // Just map to small. + if (!controlFont.isAbsoluteSize()) { + HGDIOBJ hGDI = ::GetStockObject(DEFAULT_GUI_FONT); + if (hGDI) { + LOGFONT logFont; + if (::GetObject(hGDI, sizeof(logFont), &logFont) > 0) + fillFontDescription(controlFont, logFont, defaultControlFontPixelSize); + } + } + fontDescription = controlFont; + break; + default: { // Everything else uses the stock GUI font. + if (!systemFont.isAbsoluteSize()) { + HGDIOBJ hGDI = ::GetStockObject(DEFAULT_GUI_FONT); + if (hGDI) { + LOGFONT logFont; + if (::GetObject(hGDI, sizeof(logFont), &logFont) > 0) + fillFontDescription(systemFont, logFont); + } + } + fontDescription = systemFont; + } + } +} + +bool RenderThemeWin::supportsFocus(ControlPart appearance) const +{ + switch (appearance) { + case PushButtonPart: + case ButtonPart: + case DefaultButtonPart: + return true; + default: + return false; + } +} + +bool RenderThemeWin::supportsFocusRing(const RenderStyle* style) const +{ + return supportsFocus(style->appearance()); +} + +unsigned RenderThemeWin::determineClassicState(RenderObject* o, ControlSubPart subPart) +{ + unsigned state = 0; + switch (o->style()->appearance()) { + case PushButtonPart: + case ButtonPart: + case DefaultButtonPart: + state = DFCS_BUTTONPUSH; + if (!isEnabled(o)) + state |= DFCS_INACTIVE; + else if (isPressed(o)) + state |= DFCS_PUSHED; + break; + case RadioPart: + case CheckboxPart: + state = (o->style()->appearance() == RadioPart) ? DFCS_BUTTONRADIO : DFCS_BUTTONCHECK; + if (isChecked(o)) + state |= DFCS_CHECKED; + if (!isEnabled(o)) + state |= DFCS_INACTIVE; + else if (isPressed(o)) + state |= DFCS_PUSHED; + break; + case MenulistPart: + state = DFCS_SCROLLCOMBOBOX; + if (!isEnabled(o)) + state |= DFCS_INACTIVE; + else if (isPressed(o)) + state |= DFCS_PUSHED; + break; + case InnerSpinButtonPart: { + bool isUpButton = subPart == SpinButtonUp; + state = isUpButton ? DFCS_SCROLLUP : DFCS_SCROLLDOWN; + if (!isEnabled(o) || isReadOnlyControl(o)) + state |= DFCS_INACTIVE; + else if (isPressed(o) && isUpButton == isSpinUpButtonPartPressed(o)) + state |= DFCS_PUSHED; + else if (isHovered(o) && isUpButton == isSpinUpButtonPartHovered(o)) + state |= DFCS_HOT; + break; + } + default: + break; + } + return state; +} + +unsigned RenderThemeWin::determineState(RenderObject* o) +{ + unsigned result = TS_NORMAL; + ControlPart appearance = o->style()->appearance(); + if (!isEnabled(o)) + result = TS_DISABLED; + else if (isReadOnlyControl(o) && (TextFieldPart == appearance || TextAreaPart == appearance || SearchFieldPart == appearance)) + result = TFS_READONLY; // Readonly is supported on textfields. + else if (isPressed(o)) // Active overrides hover and focused. + result = TS_ACTIVE; + else if (supportsFocus(appearance) && isFocused(o)) + result = TS_FOCUSED; + else if (isHovered(o)) + result = TS_HOVER; + if (isChecked(o)) + result += 4; // 4 unchecked states, 4 checked states. + return result; +} + +unsigned RenderThemeWin::determineSliderThumbState(RenderObject* o) +{ + unsigned result = TUS_NORMAL; + if (!isEnabled(o->parent())) + result = TUS_DISABLED; + else if (supportsFocus(o->style()->appearance()) && isFocused(o->parent())) + result = TUS_FOCUSED; + else if (toRenderSlider(o->parent())->inDragMode()) + result = TUS_PRESSED; + else if (isHovered(o)) + result = TUS_HOT; + return result; +} + +unsigned RenderThemeWin::determineButtonState(RenderObject* o) +{ + unsigned result = PBS_NORMAL; + if (!isEnabled(o)) + result = PBS_DISABLED; + else if (isPressed(o)) + result = PBS_PRESSED; + else if (supportsFocus(o->style()->appearance()) && isFocused(o)) + result = PBS_DEFAULTED; + else if (isHovered(o)) + result = PBS_HOT; + else if (isDefault(o)) + result = PBS_DEFAULTED; + return result; +} + +unsigned RenderThemeWin::determineSpinButtonState(RenderObject* o, ControlSubPart subPart) +{ + bool isUpButton = subPart == SpinButtonUp; + unsigned result = isUpButton ? UPS_NORMAL : DNS_NORMAL; + if (!isEnabled(o) || isReadOnlyControl(o)) + result = isUpButton ? UPS_DISABLED : DNS_DISABLED; + else if (isPressed(o) && isUpButton == isSpinUpButtonPartPressed(o)) + result = isUpButton ? UPS_PRESSED : DNS_PRESSED; + else if (isHovered(o) && isUpButton == isSpinUpButtonPartHovered(o)) + result = isUpButton ? UPS_HOT : DNS_HOT; + return result; +} + +ThemeData RenderThemeWin::getClassicThemeData(RenderObject* o, ControlSubPart subPart) +{ + ThemeData result; + switch (o->style()->appearance()) { + case PushButtonPart: + case ButtonPart: + case DefaultButtonPart: + case CheckboxPart: + case RadioPart: + result.m_part = DFC_BUTTON; + result.m_state = determineClassicState(o); + break; + case MenulistPart: + result.m_part = DFC_SCROLL; + result.m_state = determineClassicState(o); + break; + case SearchFieldPart: + case TextFieldPart: + case TextAreaPart: + result.m_part = TFP_TEXTFIELD; + result.m_state = determineState(o); + break; + case SliderHorizontalPart: + result.m_part = TKP_TRACK; + result.m_state = TS_NORMAL; + break; + case SliderVerticalPart: + result.m_part = TKP_TRACKVERT; + result.m_state = TS_NORMAL; + break; + case SliderThumbHorizontalPart: + result.m_part = TKP_THUMBBOTTOM; + result.m_state = determineSliderThumbState(o); + break; + case SliderThumbVerticalPart: + result.m_part = TKP_THUMBRIGHT; + result.m_state = determineSliderThumbState(o); + break; + case InnerSpinButtonPart: + result.m_part = DFC_SCROLL; + result.m_state = determineClassicState(o, subPart); + break; + default: + break; + } + return result; +} + +ThemeData RenderThemeWin::getThemeData(RenderObject* o, ControlSubPart subPart) +{ + if (!haveTheme) + return getClassicThemeData(o, subPart); + + ThemeData result; + switch (o->style()->appearance()) { + case PushButtonPart: + case ButtonPart: + case DefaultButtonPart: + result.m_part = BP_BUTTON; + result.m_state = determineButtonState(o); + break; + case CheckboxPart: + result.m_part = BP_CHECKBOX; + result.m_state = determineState(o); + break; + case MenulistPart: + case MenulistButtonPart: + result.m_part = isRunningOnVistaOrLater() ? CP_DROPDOWNBUTTONRIGHT : CP_DROPDOWNBUTTON; + if (isRunningOnVistaOrLater() && documentIsInApplicationChromeMode(o->document())) { + // The "readonly" look we use in application chrome mode + // only uses a "normal" look for the drop down button. + result.m_state = TS_NORMAL; + } else + result.m_state = determineState(o); + break; + case RadioPart: + result.m_part = BP_RADIO; + result.m_state = determineState(o); + break; + case SearchFieldPart: + case TextFieldPart: + case TextAreaPart: + result.m_part = isRunningOnVistaOrLater() ? EP_EDITBORDER_NOSCROLL : TFP_TEXTFIELD; + result.m_state = determineState(o); + break; + case SliderHorizontalPart: + result.m_part = TKP_TRACK; + result.m_state = TS_NORMAL; + break; + case SliderVerticalPart: + result.m_part = TKP_TRACKVERT; + result.m_state = TS_NORMAL; + break; + case SliderThumbHorizontalPart: + result.m_part = TKP_THUMBBOTTOM; + result.m_state = determineSliderThumbState(o); + break; + case SliderThumbVerticalPart: + result.m_part = TKP_THUMBRIGHT; + result.m_state = determineSliderThumbState(o); + break; + case InnerSpinButtonPart: + result.m_part = subPart == SpinButtonUp ? SPNP_UP : SPNP_DOWN; + result.m_state = determineSpinButtonState(o, subPart); + break; + } + + return result; +} + +static void drawControl(GraphicsContext* context, RenderObject* o, HANDLE theme, const ThemeData& themeData, const IntRect& r) +{ + bool alphaBlend = false; + if (theme) + alphaBlend = IsThemeBackgroundPartiallyTransparent(theme, themeData.m_part, themeData.m_state); + LocalWindowsContext windowsContext(context, r, alphaBlend); + RECT widgetRect = r; + if (theme) + DrawThemeBackground(theme, windowsContext.hdc(), themeData.m_part, themeData.m_state, &widgetRect, 0); + else { + HDC hdc = windowsContext.hdc(); + if (themeData.m_part == TFP_TEXTFIELD) { + ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); + if (themeData.m_state == TS_DISABLED || themeData.m_state == TFS_READONLY) + ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE+1)); + else + ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_WINDOW+1)); + } else if (themeData.m_part == TKP_TRACK || themeData.m_part == TKP_TRACKVERT) { + ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); + ::FillRect(hdc, &widgetRect, (HBRUSH)GetStockObject(GRAY_BRUSH)); + } else if ((o->style()->appearance() == SliderThumbHorizontalPart || + o->style()->appearance() == SliderThumbVerticalPart) && + (themeData.m_part == TKP_THUMBBOTTOM || themeData.m_part == TKP_THUMBTOP || + themeData.m_part == TKP_THUMBLEFT || themeData.m_part == TKP_THUMBRIGHT)) { + ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RECT | BF_SOFT | BF_MIDDLE | BF_ADJUST); + if (themeData.m_state == TUS_DISABLED) { + static WORD patternBits[8] = {0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55}; + HBITMAP patternBmp = ::CreateBitmap(8, 8, 1, 1, patternBits); + if (patternBmp) { + HBRUSH brush = (HBRUSH) ::CreatePatternBrush(patternBmp); + COLORREF oldForeColor = ::SetTextColor(hdc, ::GetSysColor(COLOR_3DFACE)); + COLORREF oldBackColor = ::SetBkColor(hdc, ::GetSysColor(COLOR_3DHILIGHT)); + POINT p; + ::GetViewportOrgEx(hdc, &p); + ::SetBrushOrgEx(hdc, p.x + widgetRect.left, p.y + widgetRect.top, NULL); + HBRUSH oldBrush = (HBRUSH) ::SelectObject(hdc, brush); + ::FillRect(hdc, &widgetRect, brush); + ::SetTextColor(hdc, oldForeColor); + ::SetBkColor(hdc, oldBackColor); + ::SelectObject(hdc, oldBrush); + ::DeleteObject(brush); + } else + ::FillRect(hdc, &widgetRect, (HBRUSH)COLOR_3DHILIGHT); + ::DeleteObject(patternBmp); + } + } else { + // Push buttons, buttons, checkboxes and radios, and the dropdown arrow in menulists. + if (o->style()->appearance() == DefaultButtonPart) { + HBRUSH brush = ::GetSysColorBrush(COLOR_3DDKSHADOW); + ::FrameRect(hdc, &widgetRect, brush); + ::InflateRect(&widgetRect, -1, -1); + ::DrawEdge(hdc, &widgetRect, BDR_RAISEDOUTER, BF_RECT | BF_MIDDLE); + } + ::DrawFrameControl(hdc, &widgetRect, themeData.m_part, themeData.m_state); + } + } +} + +bool RenderThemeWin::paintButton(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + drawControl(i.context, o, buttonTheme(), getThemeData(o), r); + return false; +} + +void RenderThemeWin::adjustInnerSpinButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + int width = ::GetSystemMetrics(SM_CXVSCROLL); + if (width <= 0) + width = 17; // Vista's default. + style->setWidth(Length(width, Fixed)); + style->setMinWidth(Length(width, Fixed)); +} + +bool RenderThemeWin::paintInnerSpinButton(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + // We split the specified rectangle into two vertically. We can't draw a + // spin button of which height is less than 2px. + if (r.height() < 2) + return false; + IntRect upRect(r); + upRect.setHeight(r.height() / 2); + IntRect downRect(r); + downRect.setY(upRect.bottom()); + downRect.setHeight(r.height() - upRect.height()); + drawControl(i.context, o, spinButtonTheme(), getThemeData(o, SpinButtonUp), upRect); + drawControl(i.context, o, spinButtonTheme(), getThemeData(o, SpinButtonDown), downRect); + return false; +} + +void RenderThemeWin::setCheckboxSize(RenderStyle* style) const +{ + // If the width and height are both specified, then we have nothing to do. + if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto()) + return; + + // FIXME: A hard-coded size of 13 is used. This is wrong but necessary for now. It matches Firefox. + // At different DPI settings on Windows, querying the theme gives you a larger size that accounts for + // the higher DPI. Until our entire engine honors a DPI setting other than 96, we can't rely on the theme's + // metrics. + if (style->width().isIntrinsicOrAuto()) + style->setWidth(Length(13, Fixed)); + if (style->height().isAuto()) + style->setHeight(Length(13, Fixed)); +} + +bool RenderThemeWin::paintTextField(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + drawControl(i.context, o, textFieldTheme(), getThemeData(o), r); + return false; +} + +bool RenderThemeWin::paintMenuList(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + HANDLE theme; + int part; + if (haveTheme && isRunningOnVistaOrLater()) { + theme = menuListTheme(); + if (documentIsInApplicationChromeMode(o->document())) + part = CP_READONLY; + else + part = CP_BORDER; + } else { + theme = textFieldTheme(); + part = TFP_TEXTFIELD; + } + + drawControl(i.context, o, theme, ThemeData(part, determineState(o)), r); + + return paintMenuListButton(o, i, r); +} + +void RenderThemeWin::adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + style->resetBorder(); + adjustMenuListButtonStyle(selector, style, e); +} + +void RenderThemeWin::adjustMenuListButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + // These are the paddings needed to place the text correctly in the <select> box + const int dropDownBoxPaddingTop = 2; + const int dropDownBoxPaddingRight = style->direction() == LTR ? 4 + dropDownButtonWidth : 4; + const int dropDownBoxPaddingBottom = 2; + const int dropDownBoxPaddingLeft = style->direction() == LTR ? 4 : 4 + dropDownButtonWidth; + // The <select> box must be at least 12px high for the button to render nicely on Windows + const int dropDownBoxMinHeight = 12; + + // Position the text correctly within the select box and make the box wide enough to fit the dropdown button + style->setPaddingTop(Length(dropDownBoxPaddingTop, Fixed)); + style->setPaddingRight(Length(dropDownBoxPaddingRight, Fixed)); + style->setPaddingBottom(Length(dropDownBoxPaddingBottom, Fixed)); + style->setPaddingLeft(Length(dropDownBoxPaddingLeft, Fixed)); + + // Height is locked to auto + style->setHeight(Length(Auto)); + + // Calculate our min-height + int minHeight = style->font().height(); + minHeight = max(minHeight, dropDownBoxMinHeight); + + style->setMinHeight(Length(minHeight, Fixed)); + + // White-space is locked to pre + style->setWhiteSpace(PRE); +} + +bool RenderThemeWin::paintMenuListButton(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + // FIXME: Don't make hardcoded assumptions about the thickness of the textfield border. + int borderThickness = haveTheme ? 1 : 2; + + // Paint the dropdown button on the inner edge of the text field, + // leaving space for the text field's 1px border + IntRect buttonRect(r); + buttonRect.inflate(-borderThickness); + if (o->style()->direction() == LTR) + buttonRect.setX(buttonRect.right() - dropDownButtonWidth); + buttonRect.setWidth(dropDownButtonWidth); + + if (isRunningOnVistaOrLater()) { + // Outset the top, right, and bottom borders of the button so that they coincide with the <select>'s border. + buttonRect.setY(buttonRect.y() - vistaMenuListButtonOutset); + buttonRect.setHeight(buttonRect.height() + 2 * vistaMenuListButtonOutset); + buttonRect.setWidth(buttonRect.width() + vistaMenuListButtonOutset); + } + + drawControl(i.context, o, menuListTheme(), getThemeData(o), buttonRect); + + return false; +} + +const int trackWidth = 4; + +bool RenderThemeWin::paintSliderTrack(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + IntRect bounds = r; + + if (o->style()->appearance() == SliderHorizontalPart) { + bounds.setHeight(trackWidth); + bounds.setY(r.y() + r.height() / 2 - trackWidth / 2); + } else if (o->style()->appearance() == SliderVerticalPart) { + bounds.setWidth(trackWidth); + bounds.setX(r.x() + r.width() / 2 - trackWidth / 2); + } + + drawControl(i.context, o, sliderTheme(), getThemeData(o), bounds); + return false; +} + +bool RenderThemeWin::paintSliderThumb(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + drawControl(i.context, o, sliderTheme(), getThemeData(o), r); + return false; +} + +const int sliderThumbWidth = 7; +const int sliderThumbHeight = 15; + +void RenderThemeWin::adjustSliderThumbSize(RenderObject* o) const +{ + ControlPart part = o->style()->appearance(); + if (part == SliderThumbVerticalPart) { + o->style()->setWidth(Length(sliderThumbHeight, Fixed)); + o->style()->setHeight(Length(sliderThumbWidth, Fixed)); + } else if (part == SliderThumbHorizontalPart) { + o->style()->setWidth(Length(sliderThumbWidth, Fixed)); + o->style()->setHeight(Length(sliderThumbHeight, Fixed)); + } +#if ENABLE(VIDEO) + else if (part == MediaSliderThumbPart || part == MediaVolumeSliderThumbPart) + RenderMediaControls::adjustMediaSliderThumbSize(o); +#endif +} + +bool RenderThemeWin::paintSearchField(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + return paintTextField(o, i, r); +} + +void RenderThemeWin::adjustSearchFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + // Override paddingSize to match AppKit text positioning. + const int padding = 1; + style->setPaddingLeft(Length(padding, Fixed)); + style->setPaddingRight(Length(padding, Fixed)); + style->setPaddingTop(Length(padding, Fixed)); + style->setPaddingBottom(Length(padding, Fixed)); + if (e && e->focused() && e->document()->frame()->selection()->isFocusedAndActive()) + style->setOutlineOffset(-2); +} + +bool RenderThemeWin::paintSearchFieldCancelButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + IntRect bounds = r; + ASSERT(o->parent()); + if (!o->parent() || !o->parent()->isBox()) + return false; + + RenderBox* parentRenderBox = toRenderBox(o->parent()); + + IntRect parentBox = parentRenderBox->absoluteContentBox(); + + // Make sure the scaled button stays square and will fit in its parent's box + bounds.setHeight(min(parentBox.width(), min(parentBox.height(), bounds.height()))); + bounds.setWidth(bounds.height()); + + // Center the button vertically. Round up though, so if it has to be one pixel off-center, it will + // be one pixel closer to the bottom of the field. This tends to look better with the text. + bounds.setY(parentBox.y() + (parentBox.height() - bounds.height() + 1) / 2); + + static Image* cancelImage = Image::loadPlatformResource("searchCancel").releaseRef(); + static Image* cancelPressedImage = Image::loadPlatformResource("searchCancelPressed").releaseRef(); + paintInfo.context->drawImage(isPressed(o) ? cancelPressedImage : cancelImage, o->style()->colorSpace(), bounds); + return false; +} + +void RenderThemeWin::adjustSearchFieldCancelButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + // Scale the button size based on the font size + float fontScale = style->fontSize() / defaultControlFontPixelSize; + int cancelButtonSize = lroundf(min(max(minCancelButtonSize, defaultCancelButtonSize * fontScale), maxCancelButtonSize)); + style->setWidth(Length(cancelButtonSize, Fixed)); + style->setHeight(Length(cancelButtonSize, Fixed)); +} + +void RenderThemeWin::adjustSearchFieldDecorationStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + IntSize emptySize(1, 11); + style->setWidth(Length(emptySize.width(), Fixed)); + style->setHeight(Length(emptySize.height(), Fixed)); +} + +void RenderThemeWin::adjustSearchFieldResultsDecorationStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + // Scale the decoration size based on the font size + float fontScale = style->fontSize() / defaultControlFontPixelSize; + int magnifierSize = lroundf(min(max(minSearchFieldResultsDecorationSize, defaultSearchFieldResultsDecorationSize * fontScale), + maxSearchFieldResultsDecorationSize)); + style->setWidth(Length(magnifierSize, Fixed)); + style->setHeight(Length(magnifierSize, Fixed)); +} + +bool RenderThemeWin::paintSearchFieldResultsDecoration(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + IntRect bounds = r; + ASSERT(o->parent()); + if (!o->parent() || !o->parent()->isBox()) + return false; + + RenderBox* parentRenderBox = toRenderBox(o->parent()); + IntRect parentBox = parentRenderBox->absoluteContentBox(); + + // Make sure the scaled decoration stays square and will fit in its parent's box + bounds.setHeight(min(parentBox.width(), min(parentBox.height(), bounds.height()))); + bounds.setWidth(bounds.height()); + + // Center the decoration vertically. Round up though, so if it has to be one pixel off-center, it will + // be one pixel closer to the bottom of the field. This tends to look better with the text. + bounds.setY(parentBox.y() + (parentBox.height() - bounds.height() + 1) / 2); + + static Image* magnifierImage = Image::loadPlatformResource("searchMagnifier").releaseRef(); + paintInfo.context->drawImage(magnifierImage, o->style()->colorSpace(), bounds); + return false; +} + +void RenderThemeWin::adjustSearchFieldResultsButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + // Scale the button size based on the font size + float fontScale = style->fontSize() / defaultControlFontPixelSize; + int magnifierHeight = lroundf(min(max(minSearchFieldResultsDecorationSize, defaultSearchFieldResultsDecorationSize * fontScale), + maxSearchFieldResultsDecorationSize)); + int magnifierWidth = lroundf(magnifierHeight * defaultSearchFieldResultsButtonWidth / defaultSearchFieldResultsDecorationSize); + style->setWidth(Length(magnifierWidth, Fixed)); + style->setHeight(Length(magnifierHeight, Fixed)); +} + +bool RenderThemeWin::paintSearchFieldResultsButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + IntRect bounds = r; + ASSERT(o->parent()); + if (!o->parent()) + return false; + if (!o->parent() || !o->parent()->isBox()) + return false; + + RenderBox* parentRenderBox = toRenderBox(o->parent()); + IntRect parentBox = parentRenderBox->absoluteContentBox(); + + // Make sure the scaled decoration will fit in its parent's box + bounds.setHeight(min(parentBox.height(), bounds.height())); + bounds.setWidth(min(parentBox.width(), static_cast<int>(bounds.height() * defaultSearchFieldResultsButtonWidth / defaultSearchFieldResultsDecorationSize))); + + // Center the button vertically. Round up though, so if it has to be one pixel off-center, it will + // be one pixel closer to the bottom of the field. This tends to look better with the text. + bounds.setY(parentBox.y() + (parentBox.height() - bounds.height() + 1) / 2); + + static Image* magnifierImage = Image::loadPlatformResource("searchMagnifierResults").releaseRef(); + paintInfo.context->drawImage(magnifierImage, o->style()->colorSpace(), bounds); + return false; +} + +// Map a CSSValue* system color to an index understood by GetSysColor +static int cssValueIdToSysColorIndex(int cssValueId) +{ + switch (cssValueId) { + case CSSValueActiveborder: return COLOR_ACTIVEBORDER; + case CSSValueActivecaption: return COLOR_ACTIVECAPTION; + case CSSValueAppworkspace: return COLOR_APPWORKSPACE; + case CSSValueBackground: return COLOR_BACKGROUND; + case CSSValueButtonface: return COLOR_BTNFACE; + case CSSValueButtonhighlight: return COLOR_BTNHIGHLIGHT; + case CSSValueButtonshadow: return COLOR_BTNSHADOW; + case CSSValueButtontext: return COLOR_BTNTEXT; + case CSSValueCaptiontext: return COLOR_CAPTIONTEXT; + case CSSValueGraytext: return COLOR_GRAYTEXT; + case CSSValueHighlight: return COLOR_HIGHLIGHT; + case CSSValueHighlighttext: return COLOR_HIGHLIGHTTEXT; + case CSSValueInactiveborder: return COLOR_INACTIVEBORDER; + case CSSValueInactivecaption: return COLOR_INACTIVECAPTION; + case CSSValueInactivecaptiontext: return COLOR_INACTIVECAPTIONTEXT; + case CSSValueInfobackground: return COLOR_INFOBK; + case CSSValueInfotext: return COLOR_INFOTEXT; + case CSSValueMenu: return COLOR_MENU; + case CSSValueMenutext: return COLOR_MENUTEXT; + case CSSValueScrollbar: return COLOR_SCROLLBAR; + case CSSValueThreeddarkshadow: return COLOR_3DDKSHADOW; + case CSSValueThreedface: return COLOR_3DFACE; + case CSSValueThreedhighlight: return COLOR_3DHIGHLIGHT; + case CSSValueThreedlightshadow: return COLOR_3DLIGHT; + case CSSValueThreedshadow: return COLOR_3DSHADOW; + case CSSValueWindow: return COLOR_WINDOW; + case CSSValueWindowframe: return COLOR_WINDOWFRAME; + case CSSValueWindowtext: return COLOR_WINDOWTEXT; + default: return -1; // Unsupported CSSValue + } +} + +Color RenderThemeWin::systemColor(int cssValueId) const +{ + int sysColorIndex = cssValueIdToSysColorIndex(cssValueId); + if (sysColorIndex == -1) + return RenderTheme::systemColor(cssValueId); + + COLORREF color = GetSysColor(sysColorIndex); + return Color(GetRValue(color), GetGValue(color), GetBValue(color)); +} + +#if ENABLE(VIDEO) + +String RenderThemeWin::extraMediaControlsStyleSheet() +{ + return String(mediaControlsQuickTimeUserAgentStyleSheet, sizeof(mediaControlsQuickTimeUserAgentStyleSheet)); +} + +bool RenderThemeWin::shouldRenderMediaControlPart(ControlPart part, Element* element) +{ + if (part == MediaToggleClosedCaptionsButtonPart) { + // We rely on QuickTime to render captions so only enable the button for a video element. +#if SAFARI_THEME_VERSION >= 4 + if (!element->hasTagName(videoTag)) + return false; +#else + return false; +#endif + } + + return RenderTheme::shouldRenderMediaControlPart(part, element); +} + + +bool RenderThemeWin::paintMediaFullscreenButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaFullscreenButton, o, paintInfo, r); +} + +bool RenderThemeWin::paintMediaMuteButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaMuteButton, o, paintInfo, r); +} + +bool RenderThemeWin::paintMediaPlayButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaPlayButton, o, paintInfo, r); +} + +bool RenderThemeWin::paintMediaRewindButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaRewindButton, o, paintInfo, r); +} + +bool RenderThemeWin::paintMediaSeekBackButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaSeekBackButton, o, paintInfo, r); +} + +bool RenderThemeWin::paintMediaSeekForwardButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaSeekForwardButton, o, paintInfo, r); +} + +bool RenderThemeWin::paintMediaSliderTrack(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaSlider, o, paintInfo, r); +} + +bool RenderThemeWin::paintMediaSliderThumb(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaSliderThumb, o, paintInfo, r); +} + +bool RenderThemeWin::paintMediaToggleClosedCaptionsButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaShowClosedCaptionsButton, o, paintInfo, r); +} + +bool RenderThemeWin::paintMediaControlsBackground(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaTimelineContainer, o, paintInfo, r); +} + +bool RenderThemeWin::paintMediaVolumeSliderContainer(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaVolumeSliderContainer, o, paintInfo, r); +} + +bool RenderThemeWin::paintMediaVolumeSliderTrack(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaVolumeSlider, o, paintInfo, r); +} + +bool RenderThemeWin::paintMediaVolumeSliderThumb(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return RenderMediaControls::paintMediaControlsPart(MediaVolumeSliderThumb, o, paintInfo, r); +} + +IntPoint RenderThemeWin::volumeSliderOffsetFromMuteButton(Node* muteButton, const IntSize& size) const +{ + return RenderMediaControls::volumeSliderOffsetFromMuteButton(muteButton, size); +} + + +#endif + +} diff --git a/Source/WebCore/rendering/RenderThemeWin.h b/Source/WebCore/rendering/RenderThemeWin.h new file mode 100644 index 0000000..c05d2ee --- /dev/null +++ b/Source/WebCore/rendering/RenderThemeWin.h @@ -0,0 +1,183 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2006, 2008 Apple Computer, Inc. + * Copyright (C) 2009 Kenneth Rohde Christiansen + * + * 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. + * + */ + +#ifndef RenderThemeWin_h +#define RenderThemeWin_h + +#include "RenderTheme.h" + +#if WIN32 +typedef void* HANDLE; +typedef struct HINSTANCE__* HINSTANCE; +typedef HINSTANCE HMODULE; +#endif + +namespace WebCore { + +struct ThemeData { + ThemeData() :m_part(0), m_state(0), m_classicState(0) {} + ThemeData(int part, int state) + : m_part(part) + , m_state(state) + , m_classicState(0) + { } + + unsigned m_part; + unsigned m_state; + unsigned m_classicState; +}; + +class RenderThemeWin : public RenderTheme { +public: + static PassRefPtr<RenderTheme> create(); + + virtual String extraDefaultStyleSheet(); + virtual String extraQuirksStyleSheet(); + + // A method asking if the theme's controls actually care about redrawing when hovered. + virtual bool supportsHover(const RenderStyle*) const; + + virtual Color platformActiveSelectionBackgroundColor() const; + virtual Color platformInactiveSelectionBackgroundColor() const; + virtual Color platformActiveSelectionForegroundColor() const; + virtual Color platformInactiveSelectionForegroundColor() const; + + // System fonts. + virtual void systemFont(int propId, FontDescription&) const; + virtual Color systemColor(int cssValueId) const; + + virtual bool paintCheckbox(RenderObject* o, const PaintInfo& i, const IntRect& r) + { return paintButton(o, i, r); } + virtual void setCheckboxSize(RenderStyle*) const; + + virtual bool paintRadio(RenderObject* o, const PaintInfo& i, const IntRect& r) + { return paintButton(o, i, r); } + virtual void setRadioSize(RenderStyle* style) const + { return setCheckboxSize(style); } + + virtual bool paintButton(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustInnerSpinButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintInnerSpinButton(RenderObject*, const PaintInfo&, const IntRect&); + + virtual bool paintTextField(RenderObject*, const PaintInfo&, const IntRect&); + + virtual bool paintTextArea(RenderObject* o, const PaintInfo& i, const IntRect& r) + { return paintTextField(o, i, r); } + + virtual void adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const; + virtual bool paintMenuList(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustMenuListButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const; + + virtual bool paintMenuListButton(RenderObject*, const PaintInfo&, const IntRect&); + + virtual bool paintSliderTrack(RenderObject* o, const PaintInfo& i, const IntRect& r); + virtual bool paintSliderThumb(RenderObject* o, const PaintInfo& i, const IntRect& r); + virtual void adjustSliderThumbSize(RenderObject*) const; + + virtual bool popupOptionSupportsTextIndent() const { return true; } + + virtual void adjustSearchFieldStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchField(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustSearchFieldCancelButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldCancelButton(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustSearchFieldDecorationStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldDecoration(RenderObject*, const PaintInfo&, const IntRect&) { return false; } + + virtual void adjustSearchFieldResultsDecorationStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldResultsDecoration(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustSearchFieldResultsButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldResultsButton(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void themeChanged(); + + virtual void adjustButtonStyle(CSSStyleSelector*, RenderStyle* style, Element*) const {} + virtual void adjustTextFieldStyle(CSSStyleSelector*, RenderStyle* style, Element*) const {} + virtual void adjustTextAreaStyle(CSSStyleSelector*, RenderStyle* style, Element*) const {} + + static void setWebKitIsBeingUnloaded(); + + virtual bool supportsFocusRing(const RenderStyle*) const; + +#if ENABLE(VIDEO) + virtual String extraMediaControlsStyleSheet(); + virtual bool shouldRenderMediaControlPart(ControlPart, Element*); + virtual bool paintMediaControlsBackground(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaFullscreenButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaMuteButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaPlayButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaRewindButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSeekBackButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSeekForwardButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSliderTrack(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSliderThumb(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaToggleClosedCaptionsButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaVolumeSliderContainer(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaVolumeSliderTrack(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaVolumeSliderThumb(RenderObject*, const PaintInfo&, const IntRect&); + virtual IntPoint volumeSliderOffsetFromMuteButton(Node*, const IntSize&) const; +#endif + +private: + enum ControlSubPart { + None, + SpinButtonDown, + SpinButtonUp, + }; + + RenderThemeWin(); + ~RenderThemeWin(); + + void addIntrinsicMargins(RenderStyle*) const; + void close(); + + unsigned determineState(RenderObject*); + unsigned determineClassicState(RenderObject*, ControlSubPart = None); + unsigned determineSliderThumbState(RenderObject*); + unsigned determineButtonState(RenderObject*); + unsigned determineSpinButtonState(RenderObject*, ControlSubPart = None); + + bool supportsFocus(ControlPart) const; + + ThemeData getThemeData(RenderObject*, ControlSubPart = None); + ThemeData getClassicThemeData(RenderObject* o, ControlSubPart = None); + + HANDLE buttonTheme() const; + HANDLE textFieldTheme() const; + HANDLE menuListTheme() const; + HANDLE sliderTheme() const; + HANDLE spinButtonTheme() const; + + mutable HANDLE m_buttonTheme; + mutable HANDLE m_textFieldTheme; + mutable HANDLE m_menuListTheme; + mutable HANDLE m_sliderTheme; + mutable HANDLE m_spinButtonTheme; +}; + +}; + +#endif diff --git a/Source/WebCore/rendering/RenderThemeWinCE.cpp b/Source/WebCore/rendering/RenderThemeWinCE.cpp new file mode 100644 index 0000000..27b8783 --- /dev/null +++ b/Source/WebCore/rendering/RenderThemeWinCE.cpp @@ -0,0 +1,647 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2006, 2007 Apple Computer, Inc. + * Copyright (C) 2007-2009 Torch Mobile, Inc. + * + * 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 "RenderThemeWinCE.h" + +#include "CSSStyleSheet.h" +#include "CSSValueKeywords.h" +#include "Document.h" +#include "GraphicsContext.h" +#include "NotImplemented.h" +#if ENABLE(VIDEO) +#include "HTMLMediaElement.h" +#endif + +#include <windows.h> + +/* + * The following constants are used to determine how a widget is drawn using + * Windows' Theme API. For more information on theme parts and states see + * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/userex/topics/partsandstates.asp + */ +#define THEME_COLOR 204 +#define THEME_FONT 210 + +// Generic state constants +#define TS_NORMAL 1 +#define TS_HOVER 2 +#define TS_ACTIVE 3 +#define TS_DISABLED 4 +#define TS_FOCUSED 5 + +// Button constants +#define BP_BUTTON 1 +#define BP_RADIO 2 +#define BP_CHECKBOX 3 + +// Textfield constants +#define TFP_TEXTFIELD 1 +#define TFS_READONLY 6 + +typedef HANDLE (WINAPI*openThemeDataPtr)(HWND hwnd, LPCWSTR pszClassList); +typedef HRESULT (WINAPI*closeThemeDataPtr)(HANDLE hTheme); +typedef HRESULT (WINAPI*drawThemeBackgroundPtr)(HANDLE hTheme, HDC hdc, int iPartId, + int iStateId, const RECT *pRect, + const RECT* pClipRect); +typedef HRESULT (WINAPI*drawThemeEdgePtr)(HANDLE hTheme, HDC hdc, int iPartId, + int iStateId, const RECT *pRect, + unsigned uEdge, unsigned uFlags, + const RECT* pClipRect); +typedef HRESULT (WINAPI*getThemeContentRectPtr)(HANDLE hTheme, HDC hdc, int iPartId, + int iStateId, const RECT* pRect, + RECT* pContentRect); +typedef HRESULT (WINAPI*getThemePartSizePtr)(HANDLE hTheme, HDC hdc, int iPartId, + int iStateId, RECT* prc, int ts, + SIZE* psz); +typedef HRESULT (WINAPI*getThemeSysFontPtr)(HANDLE hTheme, int iFontId, OUT LOGFONT* pFont); +typedef HRESULT (WINAPI*getThemeColorPtr)(HANDLE hTheme, HDC hdc, int iPartId, + int iStateId, int iPropId, OUT COLORREF* pFont); + +namespace WebCore { + +static const int dropDownButtonWidth = 17; +static const int trackWidth = 4; + +PassRefPtr<RenderTheme> RenderThemeWinCE::create() +{ + return adoptRef(new RenderThemeWinCE); +} + +PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page* page) +{ + static RenderTheme* winceTheme = RenderThemeWinCE::create().releaseRef(); + return winceTheme; +} + +RenderThemeWinCE::RenderThemeWinCE() +{ +} + +RenderThemeWinCE::~RenderThemeWinCE() +{ +} + +Color RenderThemeWinCE::platformActiveSelectionBackgroundColor() const +{ + COLORREF color = GetSysColor(COLOR_HIGHLIGHT); + return Color(GetRValue(color), GetGValue(color), GetBValue(color), 255); +} + +Color RenderThemeWinCE::platformInactiveSelectionBackgroundColor() const +{ + COLORREF color = GetSysColor(COLOR_GRAYTEXT); + return Color(GetRValue(color), GetGValue(color), GetBValue(color), 255); +} + +Color RenderThemeWinCE::platformActiveSelectionForegroundColor() const +{ + COLORREF color = GetSysColor(COLOR_HIGHLIGHTTEXT); + return Color(GetRValue(color), GetGValue(color), GetBValue(color), 255); +} + +Color RenderThemeWinCE::platformInactiveSelectionForegroundColor() const +{ + return Color::white; +} + +bool RenderThemeWinCE::supportsFocus(ControlPart appearance) const +{ + switch (appearance) { + case PushButtonPart: + case ButtonPart: + case TextFieldPart: + case TextAreaPart: + return true; + default: + return false; + } + + return false; +} + +bool RenderThemeWinCE::supportsFocusRing(const RenderStyle *style) const +{ + return supportsFocus(style->appearance()); +} + +unsigned RenderThemeWinCE::determineClassicState(RenderObject* o) +{ + unsigned result = 0; + if (!isEnabled(o) || isReadOnlyControl(o)) + result = DFCS_INACTIVE; + else if (isPressed(o)) // Active supersedes hover + result = DFCS_PUSHED; + + if (isChecked(o)) + result |= DFCS_CHECKED; + return result; +} + +ThemeData RenderThemeWinCE::getThemeData(RenderObject* o) +{ + ThemeData result; + switch (o->style()->appearance()) { + case PushButtonPart: + case ButtonPart: + result.m_part = BP_BUTTON; + result.m_classicState = DFCS_BUTTONPUSH; + break; + case CheckboxPart: + result.m_part = BP_CHECKBOX; + result.m_classicState = DFCS_BUTTONCHECK; + break; + case RadioPart: + result.m_part = BP_RADIO; + result.m_classicState = DFCS_BUTTONRADIO; + break; + case ListboxPart: + case MenulistPart: + case TextFieldPart: + case TextAreaPart: + result.m_part = TFP_TEXTFIELD; + break; + } + + result.m_classicState |= determineClassicState(o); + + return result; +} + +bool RenderThemeWinCE::paintButton(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + // Get the correct theme data for a button + ThemeData themeData = getThemeData(o); + + // Now paint the button. + i.context->drawFrameControl(r, DFC_BUTTON, themeData.m_classicState); + if (isFocused(o)) { + if (themeData.m_part == BP_BUTTON) { + IntRect focusRect(r); + focusRect.inflate(-2); + i.context->drawFocusRect(focusRect); + } else + i.context->drawFocusRect(r); + } + + return false; +} + +void RenderThemeWinCE::setCheckboxSize(RenderStyle* style) const +{ + // If the width and height are both specified, then we have nothing to do. + if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto()) + return; + + // FIXME: A hard-coded size of 13 is used. This is wrong but necessary for now. It matches Firefox. + // At different DPI settings on Windows, querying the theme gives you a larger size that accounts for + // the higher DPI. Until our entire engine honors a DPI setting other than 96, we can't rely on the theme's + // metrics. + if (style->width().isIntrinsicOrAuto()) + style->setWidth(Length(13, Fixed)); + if (style->height().isAuto()) + style->setHeight(Length(13, Fixed)); +} + +bool RenderThemeWinCE::paintTextField(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + // Get the correct theme data for a textfield + ThemeData themeData = getThemeData(o); + + // Now paint the text field. + i.context->paintTextField(r, themeData.m_classicState); + + return false; +} + +void RenderThemeWinCE::adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + style->resetBorder(); + adjustMenuListButtonStyle(selector, style, e); +} + +bool RenderThemeWinCE::paintMenuList(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + paintTextField(o, i, r); + paintMenuListButton(o, i, r); + return true; +} + +bool RenderThemeWinCE::paintMenuListButton(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + IntRect buttonRect(r.right() - dropDownButtonWidth - 1, r.y(), dropDownButtonWidth, r.height()); + buttonRect.inflateY(-1); + i.context->drawFrameControl(buttonRect, DFC_SCROLL, DFCS_SCROLLCOMBOBOX | determineClassicState(o)); + return true; +} + +void RenderThemeWinCE::systemFont(int propId, FontDescription& fontDescription) const +{ + notImplemented(); +} + +void RenderThemeWinCE::themeChanged() +{ +} + +String RenderThemeWinCE::extraDefaultStyleSheet() +{ + notImplemented(); + return String(); +} + +String RenderThemeWinCE::extraQuirksStyleSheet() +{ + notImplemented(); + return String(); +} + +bool RenderThemeWinCE::supportsHover(const RenderStyle*) const +{ + return false; +} + +// Map a CSSValue* system color to an index understood by GetSysColor +static int cssValueIdToSysColorIndex(int cssValueId) +{ + switch (cssValueId) { + case CSSValueActiveborder: return COLOR_ACTIVEBORDER; + case CSSValueActivecaption: return COLOR_ACTIVECAPTION; + case CSSValueAppworkspace: return COLOR_APPWORKSPACE; + case CSSValueBackground: return COLOR_BACKGROUND; + case CSSValueButtonface: return COLOR_BTNFACE; + case CSSValueButtonhighlight: return COLOR_BTNHIGHLIGHT; + case CSSValueButtonshadow: return COLOR_BTNSHADOW; + case CSSValueButtontext: return COLOR_BTNTEXT; + case CSSValueCaptiontext: return COLOR_CAPTIONTEXT; + case CSSValueGraytext: return COLOR_GRAYTEXT; + case CSSValueHighlight: return COLOR_HIGHLIGHT; + case CSSValueHighlighttext: return COLOR_HIGHLIGHTTEXT; + case CSSValueInactiveborder: return COLOR_INACTIVEBORDER; + case CSSValueInactivecaption: return COLOR_INACTIVECAPTION; + case CSSValueInactivecaptiontext: return COLOR_INACTIVECAPTIONTEXT; + case CSSValueInfobackground: return COLOR_INFOBK; + case CSSValueInfotext: return COLOR_INFOTEXT; + case CSSValueMenu: return COLOR_MENU; + case CSSValueMenutext: return COLOR_MENUTEXT; + case CSSValueScrollbar: return COLOR_SCROLLBAR; + case CSSValueThreeddarkshadow: return COLOR_3DDKSHADOW; + case CSSValueThreedface: return COLOR_3DFACE; + case CSSValueThreedhighlight: return COLOR_3DHIGHLIGHT; + case CSSValueThreedlightshadow: return COLOR_3DLIGHT; + case CSSValueThreedshadow: return COLOR_3DSHADOW; + case CSSValueWindow: return COLOR_WINDOW; + case CSSValueWindowframe: return COLOR_WINDOWFRAME; + case CSSValueWindowtext: return COLOR_WINDOWTEXT; + default: return -1; // Unsupported CSSValue + } +} + +Color RenderThemeWinCE::systemColor(int cssValueId) const +{ + int sysColorIndex = cssValueIdToSysColorIndex(cssValueId); + if (sysColorIndex == -1) + return RenderTheme::systemColor(cssValueId); + + COLORREF color = GetSysColor(sysColorIndex); + return Color(GetRValue(color), GetGValue(color), GetBValue(color)); +} + +const int sliderThumbWidth = 7; +const int sliderThumbHeight = 15; + +void RenderThemeWinCE::adjustSliderThumbSize(RenderObject* o) const +{ + if (o->style()->appearance() == SliderThumbVerticalPart) { + o->style()->setWidth(Length(sliderThumbHeight, Fixed)); + o->style()->setHeight(Length(sliderThumbWidth, Fixed)); + } else if (o->style()->appearance() == SliderThumbHorizontalPart) { + o->style()->setWidth(Length(sliderThumbWidth, Fixed)); + o->style()->setHeight(Length(sliderThumbHeight, Fixed)); + } +} + +#if 0 +void RenderThemeWinCE::adjustButtonInnerStyle(RenderStyle* style) const +{ + // This inner padding matches Firefox. + style->setPaddingTop(Length(1, Fixed)); + style->setPaddingRight(Length(3, Fixed)); + style->setPaddingBottom(Length(1, Fixed)); + style->setPaddingLeft(Length(3, Fixed)); +} + +void RenderThemeWinCE::adjustSearchFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + // Override padding size to match AppKit text positioning. + const int padding = 1; + style->setPaddingLeft(Length(padding, Fixed)); + style->setPaddingRight(Length(padding, Fixed)); + style->setPaddingTop(Length(padding, Fixed)); + style->setPaddingBottom(Length(padding, Fixed)); +} +#endif + +bool RenderThemeWinCE::paintSearchField(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + return paintTextField(o, i, r); +} + +bool RenderThemeWinCE::paintSearchFieldCancelButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + Color buttonColor = (o->node() && o->node()->active()) ? Color(138, 138, 138) : Color(186, 186, 186); + + IntSize cancelSize(10, 10); + IntSize cancelRadius(cancelSize.width() / 2, cancelSize.height() / 2); + int x = r.x() + (r.width() - cancelSize.width()) / 2; + int y = r.y() + (r.height() - cancelSize.height()) / 2 + 1; + IntRect cancelBounds(IntPoint(x, y), cancelSize); + paintInfo.context->save(); + paintInfo.context->addRoundedRectClip(cancelBounds, cancelRadius, cancelRadius, cancelRadius, cancelRadius); + paintInfo.context->fillRect(cancelBounds, buttonColor, ColorSpaceDeviceRGB); + + // Draw the 'x' + IntSize xSize(3, 3); + IntRect xBounds(cancelBounds.location() + IntSize(3, 3), xSize); + paintInfo.context->setStrokeColor(Color::white, ColorSpaceDeviceRGB); + paintInfo.context->drawLine(xBounds.location(), xBounds.location() + xBounds.size()); + paintInfo.context->drawLine(IntPoint(xBounds.right(), xBounds.y()), IntPoint(xBounds.x(), xBounds.bottom())); + + paintInfo.context->restore(); + return false; +} + +void RenderThemeWinCE::adjustSearchFieldCancelButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + IntSize cancelSize(13, 11); + style->setWidth(Length(cancelSize.width(), Fixed)); + style->setHeight(Length(cancelSize.height(), Fixed)); +} + +void RenderThemeWinCE::adjustSearchFieldDecorationStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + IntSize emptySize(1, 11); + style->setWidth(Length(emptySize.width(), Fixed)); + style->setHeight(Length(emptySize.height(), Fixed)); +} + +void RenderThemeWinCE::adjustSearchFieldResultsDecorationStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + IntSize magnifierSize(15, 11); + style->setWidth(Length(magnifierSize.width(), Fixed)); + style->setHeight(Length(magnifierSize.height(), Fixed)); +} + +bool RenderThemeWinCE::paintSearchFieldResultsDecoration(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + notImplemented(); + return false; +} + +void RenderThemeWinCE::adjustSearchFieldResultsButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + IntSize magnifierSize(15, 11); + style->setWidth(Length(magnifierSize.width(), Fixed)); + style->setHeight(Length(magnifierSize.height(), Fixed)); +} + +bool RenderThemeWinCE::paintSearchFieldResultsButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + paintSearchFieldResultsDecoration(o, paintInfo, r); + return false; +} + +void RenderThemeWinCE::adjustMenuListButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + // These are the paddings needed to place the text correctly in the <select> box + const int dropDownBoxPaddingTop = 2; + const int dropDownBoxPaddingRight = style->direction() == LTR ? 4 + dropDownButtonWidth : 4; + const int dropDownBoxPaddingBottom = 2; + const int dropDownBoxPaddingLeft = style->direction() == LTR ? 4 : 4 + dropDownButtonWidth; + // The <select> box must be at least 12px high for the button to render nicely on Windows + const int dropDownBoxMinHeight = 12; + + // Position the text correctly within the select box and make the box wide enough to fit the dropdown button + style->setPaddingTop(Length(dropDownBoxPaddingTop, Fixed)); + style->setPaddingRight(Length(dropDownBoxPaddingRight, Fixed)); + style->setPaddingBottom(Length(dropDownBoxPaddingBottom, Fixed)); + style->setPaddingLeft(Length(dropDownBoxPaddingLeft, Fixed)); + + // Height is locked to auto + style->setHeight(Length(Auto)); + + // Calculate our min-height + int minHeight = style->font().height(); + minHeight = max(minHeight, dropDownBoxMinHeight); + + style->setMinHeight(Length(minHeight, Fixed)); + + // White-space is locked to pre + style->setWhiteSpace(PRE); + + DWORD colorMenu = GetSysColor(COLOR_MENU); + DWORD colorMenuText = GetSysColor(COLOR_MENUTEXT); + Color bgColor(GetRValue(colorMenu), GetGValue(colorMenu), GetBValue(colorMenu), 255); + Color textColor(GetRValue(colorMenuText), GetGValue(colorMenuText), GetBValue(colorMenuText), 255); + if (bgColor == textColor) + textColor.setRGB((~bgColor.rgb()) | 0xFF000000); + style->clearBackgroundLayers(); + style->accessBackgroundLayers()->setClip(ContentFillBox); + style->setBackgroundColor(bgColor); + style->setColor(textColor); +} + +#if ENABLE(VIDEO) +// Attempt to retrieve a HTMLMediaElement from a Node. Returns 0 if one cannot be found. +static HTMLMediaElement* mediaElementParent(Node* node) +{ + if (!node) + return 0; + Node* mediaNode = node->shadowAncestorNode(); + if (!mediaNode || (!mediaNode->hasTagName(HTMLNames::videoTag) && !mediaNode->hasTagName(HTMLNames::audioTag))) + return 0; + + return static_cast<HTMLMediaElement*>(mediaNode); +} +#endif + +bool RenderThemeWinCE::paintSliderTrack(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + bool rc = RenderTheme::paintSliderTrack(o, i, r); + IntPoint left = IntPoint(r.x() + 2, (r.y() + r.bottom()) / 2); + i.context->save(); + i.context->setStrokeColor(Color::gray, ColorSpaceDeviceRGB); + i.context->setFillColor(Color::gray, ColorSpaceDeviceRGB); + i.context->fillRect(r); +#if ENABLE(VIDEO) + HTMLMediaElement* mediaElement = mediaElementParent(o->node()); + if (mediaElement) { + i.context->setStrokeColor(Color(0, 0xff, 0)); + IntPoint right = IntPoint(left.x() + mediaElement->percentLoaded() * (r.right() - r.x() - 4), (r.y() + r.bottom()) / 2); + i.context->drawLine(left, right); + left = right; + } +#endif + i.context->setStrokeColor(Color::black, ColorSpaceDeviceRGB); + i.context->drawLine(left, IntPoint(r.right() - 2, left.y())); + i.context->restore(); + return rc; +} + +bool RenderThemeWinCE::paintSliderThumb(RenderObject* o, const PaintInfo& i, const IntRect& r) +{ + bool rc = RenderTheme::paintSliderThumb(o, i, r); + i.context->save(); + i.context->setStrokeColor(Color::black, ColorSpaceDeviceRGB); + i.context->setFillColor(Color::black, ColorSpaceDeviceRGB); +#if ENABLE(VIDEO) + HTMLMediaElement* mediaElement = mediaElementParent(o->node()); + if (mediaElement) { + float pt = (mediaElement->currentTime() - mediaElement->startTime()) / mediaElement->duration(); + FloatRect intRect = r; + intRect.setX(intRect.x() + intRect.width() * pt - 2); + intRect.setWidth(5); + i.context->fillRect(intRect); + } +#endif + i.context->restore(); + return rc; +} + +void RenderThemeWinCE::adjustSearchFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const +{ + const int padding = 1; + style->setPaddingLeft(Length(padding, Fixed)); + style->setPaddingRight(Length(padding, Fixed)); + style->setPaddingTop(Length(padding, Fixed)); + style->setPaddingBottom(Length(padding, Fixed)); +} + +#if ENABLE(VIDEO) + +bool RenderThemeWinCE::paintMediaFullscreenButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + bool rc = paintButton(o, paintInfo, r); + FloatRect imRect = r; + imRect.inflate(-2); + paintInfo.context->save(); + paintInfo.context->setStrokeColor(Color::black); + paintInfo.context->setFillColor(Color::gray); + paintInfo.context->fillRect(imRect); + paintInfo.context->restore(); + return rc; +} + +bool RenderThemeWinCE::paintMediaMuteButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + bool rc = paintButton(o, paintInfo, r); + HTMLMediaElement* mediaElement = mediaElementParent(o->node()); + bool muted = !mediaElement || mediaElement->muted(); + FloatRect imRect = r; + imRect.inflate(-2); + paintInfo.context->save(); + paintInfo.context->setStrokeColor(Color::black); + paintInfo.context->setFillColor(Color::black); + FloatPoint pts[6] = { + FloatPoint(imRect.x() + 1, imRect.y() + imRect.height() / 3.0), + FloatPoint(imRect.x() + 1 + imRect.width() / 2.0, imRect.y() + imRect.height() / 3.0), + FloatPoint(imRect.right() - 1, imRect.y()), + FloatPoint(imRect.right() - 1, imRect.bottom()), + FloatPoint(imRect.x() + 1 + imRect.width() / 2.0, imRect.y() + 2.0 * imRect.height() / 3.0), + FloatPoint(imRect.x() + 1, imRect.y() + 2.0 * imRect.height() / 3.0) + }; + paintInfo.context->drawConvexPolygon(6, pts); + if (muted) + paintInfo.context->drawLine(IntPoint(imRect.right(), imRect.y()), IntPoint(imRect.x(), imRect.bottom())); + paintInfo.context->restore(); + return rc; +} + +bool RenderThemeWinCE::paintMediaPlayButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + bool rc = paintButton(o, paintInfo, r); + FloatRect imRect = r; + imRect.inflate(-3); + paintInfo.context->save(); + paintInfo.context->setStrokeColor(Color::black); + paintInfo.context->setFillColor(Color::black); + HTMLMediaElement* mediaElement = mediaElementParent(o->node()); + bool paused = !mediaElement || mediaElement->paused(); + if (paused) { + float width = imRect.width(); + imRect.setWidth(width / 3.0); + paintInfo.context->fillRect(imRect); + imRect.move(2.0 * width / 3.0, 0); + paintInfo.context->fillRect(imRect); + } else { + FloatPoint pts[3] = { FloatPoint(imRect.x(), imRect.y()), FloatPoint(imRect.right(), (imRect.y() + imRect.bottom()) / 2.0), FloatPoint(imRect.x(), imRect.bottom()) }; + paintInfo.context->drawConvexPolygon(3, pts); + } + paintInfo.context->restore(); + return rc; +} + +bool RenderThemeWinCE::paintMediaSeekBackButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + bool rc = paintButton(o, paintInfo, r); + FloatRect imRect = r; + imRect.inflate(-3); + FloatPoint pts[3] = { FloatPoint((imRect.x() + imRect.right()) / 2.0, imRect.y()), FloatPoint(imRect.x(), (imRect.y() + imRect.bottom()) / 2.0), FloatPoint((imRect.x() + imRect.right()) / 2.0, imRect.bottom()) }; + FloatPoint pts2[3] = { FloatPoint(imRect.right(), imRect.y()), FloatPoint((imRect.x() + imRect.right()) / 2.0, (imRect.y() + imRect.bottom()) / 2.0), FloatPoint(imRect.right(), imRect.bottom()) }; + paintInfo.context->save(); + paintInfo.context->setStrokeColor(Color::black); + paintInfo.context->setFillColor(Color::black); + paintInfo.context->drawConvexPolygon(3, pts); + paintInfo.context->drawConvexPolygon(3, pts2); + paintInfo.context->restore(); + return rc; +} + +bool RenderThemeWinCE::paintMediaSeekForwardButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + bool rc = paintButton(o, paintInfo, r); + FloatRect imRect = r; + imRect.inflate(-3); + FloatPoint pts[3] = { FloatPoint(imRect.x(), imRect.y()), FloatPoint((imRect.x() + imRect.right()) / 2.0, (imRect.y() + imRect.bottom()) / 2.0), FloatPoint(imRect.x(), imRect.bottom()) }; + FloatPoint pts2[3] = { FloatPoint((imRect.x() + imRect.right()) / 2.0, imRect.y()), FloatPoint(imRect.right(), (imRect.y() + imRect.bottom()) / 2.0), FloatPoint((imRect.x() + imRect.right()) / 2.0, imRect.bottom()) }; + paintInfo.context->save(); + paintInfo.context->setStrokeColor(Color::black); + paintInfo.context->setFillColor(Color::black); + paintInfo.context->drawConvexPolygon(3, pts); + paintInfo.context->drawConvexPolygon(3, pts2); + paintInfo.context->restore(); + return rc; +} + +bool RenderThemeWinCE::paintMediaSliderTrack(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return paintSliderTrack(o, paintInfo, r); +} + +bool RenderThemeWinCE::paintMediaSliderThumb(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) +{ + return paintSliderThumb(o, paintInfo, r); +} +#endif + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderThemeWinCE.h b/Source/WebCore/rendering/RenderThemeWinCE.h new file mode 100644 index 0000000..14a5b12 --- /dev/null +++ b/Source/WebCore/rendering/RenderThemeWinCE.h @@ -0,0 +1,142 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2006, 2008 Apple Computer, Inc. + * Copyright (C) 2009 Torch Mobile, Inc. + * + * 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. + * + */ + +#ifndef RenderThemeWinCE_h +#define RenderThemeWinCE_h + +#include "RenderTheme.h" + +typedef void* HANDLE; +typedef struct HINSTANCE__* HINSTANCE; +typedef HINSTANCE HMODULE; + +namespace WebCore { + + struct ThemeData { + ThemeData() :m_part(0), m_state(0), m_classicState(0) {} + ThemeData(int part, int state) + : m_part(part) + , m_state(state) + , m_classicState(0) + { } + + unsigned m_part; + unsigned m_state; + unsigned m_classicState; + }; + + class RenderThemeWinCE : public RenderTheme { + public: + static PassRefPtr<RenderTheme> create(); + ~RenderThemeWinCE(); + + virtual String extraDefaultStyleSheet(); + virtual String extraQuirksStyleSheet(); + + // A method asking if the theme's controls actually care about redrawing when hovered. + virtual bool supportsHover(const RenderStyle*) const; + + virtual Color platformActiveSelectionBackgroundColor() const; + virtual Color platformInactiveSelectionBackgroundColor() const; + virtual Color platformActiveSelectionForegroundColor() const; + virtual Color platformInactiveSelectionForegroundColor() const; + + // System fonts. + virtual void systemFont(int propId, FontDescription&) const; + virtual Color systemColor(int cssValueId) const; + + virtual bool paintCheckbox(RenderObject* o, const PaintInfo& i, const IntRect& r) + { return paintButton(o, i, r); } + virtual void setCheckboxSize(RenderStyle*) const; + + virtual bool paintRadio(RenderObject* o, const PaintInfo& i, const IntRect& r) + { return paintButton(o, i, r); } + virtual void setRadioSize(RenderStyle* style) const + { return setCheckboxSize(style); } + + virtual bool paintButton(RenderObject*, const PaintInfo&, const IntRect&); + + virtual bool paintTextField(RenderObject*, const PaintInfo&, const IntRect&); + + virtual bool paintTextArea(RenderObject* o, const PaintInfo& i, const IntRect& r) + { return paintTextField(o, i, r); } + + virtual void adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const; + virtual bool paintMenuList(RenderObject*, const PaintInfo&, const IntRect&); + virtual void adjustMenuListButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const; + + virtual bool paintMenuListButton(RenderObject*, const PaintInfo&, const IntRect&); + + virtual bool paintSliderTrack(RenderObject* o, const PaintInfo& i, const IntRect& r); + virtual bool paintSliderThumb(RenderObject* o, const PaintInfo& i, const IntRect& r); + virtual void adjustSliderThumbSize(RenderObject*) const; + + virtual bool popupOptionSupportsTextIndent() const { return true; } + + virtual void adjustSearchFieldStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchField(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustSearchFieldCancelButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldCancelButton(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustSearchFieldDecorationStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldDecoration(RenderObject*, const PaintInfo&, const IntRect&) { return false; } + + virtual void adjustSearchFieldResultsDecorationStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldResultsDecoration(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void adjustSearchFieldResultsButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const; + virtual bool paintSearchFieldResultsButton(RenderObject*, const PaintInfo&, const IntRect&); + + virtual void themeChanged(); + + virtual void adjustButtonStyle(CSSStyleSelector*, RenderStyle* style, Element*) const {} + virtual void adjustTextFieldStyle(CSSStyleSelector*, RenderStyle* style, Element*) const {} + virtual void adjustTextAreaStyle(CSSStyleSelector*, RenderStyle* style, Element*) const {} + + static void setWebKitIsBeingUnloaded(); + + virtual bool supportsFocusRing(const RenderStyle*) const; + + #if ENABLE(VIDEO) + virtual bool paintMediaFullscreenButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaPlayButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaMuteButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSeekBackButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSeekForwardButton(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSliderTrack(RenderObject*, const PaintInfo&, const IntRect&); + virtual bool paintMediaSliderThumb(RenderObject*, const PaintInfo&, const IntRect&); + #endif + + private: + RenderThemeWinCE(); + + unsigned determineClassicState(RenderObject*); + bool supportsFocus(ControlPart) const; + + ThemeData getThemeData(RenderObject*); + }; + +}; + +#endif // RenderThemeWinCE_h diff --git a/Source/WebCore/rendering/RenderTreeAsText.cpp b/Source/WebCore/rendering/RenderTreeAsText.cpp new file mode 100644 index 0000000..2e64999 --- /dev/null +++ b/Source/WebCore/rendering/RenderTreeAsText.cpp @@ -0,0 +1,796 @@ +/* + * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderTreeAsText.h" + +#include "CSSMutableStyleDeclaration.h" +#include "CharacterNames.h" +#include "Document.h" +#include "Frame.h" +#include "FrameView.h" +#include "HTMLElement.h" +#include "HTMLNames.h" +#include "InlineTextBox.h" +#include "PrintContext.h" +#include "RenderBR.h" +#include "RenderFileUploadControl.h" +#include "RenderInline.h" +#include "RenderLayer.h" +#include "RenderListItem.h" +#include "RenderListMarker.h" +#include "RenderPart.h" +#include "RenderTableCell.h" +#include "RenderView.h" +#include "RenderWidget.h" +#include "SelectionController.h" +#include <wtf/UnusedParam.h> +#include <wtf/Vector.h> + +#if ENABLE(SVG) +#include "RenderSVGContainer.h" +#include "RenderSVGGradientStop.h" +#include "RenderSVGImage.h" +#include "RenderSVGInlineText.h" +#include "RenderSVGPath.h" +#include "RenderSVGRoot.h" +#include "RenderSVGText.h" +#include "SVGRenderTreeAsText.h" +#endif + +#if USE(ACCELERATED_COMPOSITING) +#include "RenderLayerBacking.h" +#endif + +#if PLATFORM(QT) +#include <QWidget> +#endif + +namespace WebCore { + +using namespace HTMLNames; + +static void writeLayers(TextStream&, const RenderLayer* rootLayer, RenderLayer*, const IntRect& paintDirtyRect, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal); + +bool hasFractions(double val) +{ + static const double s_epsilon = 0.0001; + int ival = static_cast<int>(val); + double dval = static_cast<double>(ival); + return fabs(val - dval) > s_epsilon; +} + +TextStream& operator<<(TextStream& ts, const IntRect& r) +{ + return ts << "at (" << r.x() << "," << r.y() << ") size " << r.width() << "x" << r.height(); +} + +TextStream& operator<<(TextStream& ts, const IntPoint& p) +{ + return ts << "(" << p.x() << "," << p.y() << ")"; +} + +TextStream& operator<<(TextStream& ts, const FloatPoint& p) +{ + ts << "("; + if (hasFractions(p.x())) + ts << p.x(); + else + ts << int(p.x()); + ts << ","; + if (hasFractions(p.y())) + ts << p.y(); + else + ts << int(p.y()); + return ts << ")"; +} + +TextStream& operator<<(TextStream& ts, const FloatSize& s) +{ + ts << "width="; + if (hasFractions(s.width())) + ts << s.width(); + else + ts << int(s.width()); + ts << " height="; + if (hasFractions(s.height())) + ts << s.height(); + else + ts << int(s.height()); + return ts; +} + +void writeIndent(TextStream& ts, int indent) +{ + for (int i = 0; i != indent; ++i) + ts << " "; +} + +static void printBorderStyle(TextStream& ts, const EBorderStyle borderStyle) +{ + switch (borderStyle) { + case BNONE: + ts << "none"; + break; + case BHIDDEN: + ts << "hidden"; + break; + case INSET: + ts << "inset"; + break; + case GROOVE: + ts << "groove"; + break; + case RIDGE: + ts << "ridge"; + break; + case OUTSET: + ts << "outset"; + break; + case DOTTED: + ts << "dotted"; + break; + case DASHED: + ts << "dashed"; + break; + case SOLID: + ts << "solid"; + break; + case DOUBLE: + ts << "double"; + break; + } + + ts << " "; +} + +static String getTagName(Node* n) +{ + if (n->isDocumentNode()) + return ""; + if (n->isCommentNode()) + return "COMMENT"; + return n->nodeName(); +} + +static bool isEmptyOrUnstyledAppleStyleSpan(const Node* node) +{ + if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag)) + return false; + + const HTMLElement* elem = static_cast<const HTMLElement*>(node); + if (elem->getAttribute(classAttr) != "Apple-style-span") + return false; + + if (!node->hasChildNodes()) + return true; + + CSSMutableStyleDeclaration* inlineStyleDecl = elem->inlineStyleDecl(); + return (!inlineStyleDecl || inlineStyleDecl->length() == 0); +} + +String quoteAndEscapeNonPrintables(const String& s) +{ + Vector<UChar> result; + result.append('"'); + for (unsigned i = 0; i != s.length(); ++i) { + UChar c = s[i]; + if (c == '\\') { + result.append('\\'); + result.append('\\'); + } else if (c == '"') { + result.append('\\'); + result.append('"'); + } else if (c == '\n' || c == noBreakSpace) + result.append(' '); + else { + if (c >= 0x20 && c < 0x7F) + result.append(c); + else { + unsigned u = c; + String hex = String::format("\\x{%X}", u); + unsigned len = hex.length(); + for (unsigned i = 0; i < len; ++i) + result.append(hex[i]); + } + } + } + result.append('"'); + return String::adopt(result); +} + +void RenderTreeAsText::writeRenderObject(TextStream& ts, const RenderObject& o, RenderAsTextBehavior behavior) +{ + ts << o.renderName(); + + if (behavior & RenderAsTextShowAddresses) + ts << " " << static_cast<const void*>(&o); + + if (o.style() && o.style()->zIndex()) + ts << " zI: " << o.style()->zIndex(); + + if (o.node()) { + String tagName = getTagName(o.node()); + if (!tagName.isEmpty()) { + ts << " {" << tagName << "}"; + // flag empty or unstyled AppleStyleSpan because we never + // want to leave them in the DOM + if (isEmptyOrUnstyledAppleStyleSpan(o.node())) + ts << " *empty or unstyled AppleStyleSpan*"; + } + } + + bool adjustForTableCells = o.containingBlock()->isTableCell(); + + IntRect r; + if (o.isText()) { + // FIXME: Would be better to dump the bounding box x and y rather than the first run's x and y, but that would involve updating + // many test results. + const RenderText& text = *toRenderText(&o); + IntRect linesBox = text.linesBoundingBox(); + r = IntRect(text.firstRunX(), text.firstRunY(), linesBox.width(), linesBox.height()); + if (adjustForTableCells && !text.firstTextBox()) + adjustForTableCells = false; + } else if (o.isRenderInline()) { + // FIXME: Would be better not to just dump 0, 0 as the x and y here. + const RenderInline& inlineFlow = *toRenderInline(&o); + r = IntRect(0, 0, inlineFlow.linesBoundingBox().width(), inlineFlow.linesBoundingBox().height()); + adjustForTableCells = false; + } else if (o.isTableCell()) { + // FIXME: Deliberately dump the "inner" box of table cells, since that is what current results reflect. We'd like + // to clean up the results to dump both the outer box and the intrinsic padding so that both bits of information are + // captured by the results. + const RenderTableCell& cell = *toRenderTableCell(&o); + r = IntRect(cell.x(), cell.y() + cell.intrinsicPaddingBefore(), cell.width(), cell.height() - cell.intrinsicPaddingBefore() - cell.intrinsicPaddingAfter()); + } else if (o.isBox()) + r = toRenderBox(&o)->frameRect(); + + // FIXME: Temporary in order to ensure compatibility with existing layout test results. + if (adjustForTableCells) + r.move(0, -toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore()); + + ts << " " << r; + + if (!(o.isText() && !o.isBR())) { + if (o.isFileUploadControl()) + ts << " " << quoteAndEscapeNonPrintables(toRenderFileUploadControl(&o)->fileTextValue()); + + if (o.parent() && (o.parent()->style()->color() != o.style()->color())) + ts << " [color=" << o.style()->color().name() << "]"; + + if (o.parent() && (o.parent()->style()->backgroundColor() != o.style()->backgroundColor()) && + o.style()->backgroundColor().isValid() && o.style()->backgroundColor().rgb()) + // Do not dump invalid or transparent backgrounds, since that is the default. + ts << " [bgcolor=" << o.style()->backgroundColor().name() << "]"; + + if (o.parent() && (o.parent()->style()->textFillColor() != o.style()->textFillColor()) && + o.style()->textFillColor().isValid() && o.style()->textFillColor() != o.style()->color() && + o.style()->textFillColor().rgb()) + ts << " [textFillColor=" << o.style()->textFillColor().name() << "]"; + + if (o.parent() && (o.parent()->style()->textStrokeColor() != o.style()->textStrokeColor()) && + o.style()->textStrokeColor().isValid() && o.style()->textStrokeColor() != o.style()->color() && + o.style()->textStrokeColor().rgb()) + ts << " [textStrokeColor=" << o.style()->textStrokeColor().name() << "]"; + + if (o.parent() && (o.parent()->style()->textStrokeWidth() != o.style()->textStrokeWidth()) && + o.style()->textStrokeWidth() > 0) + ts << " [textStrokeWidth=" << o.style()->textStrokeWidth() << "]"; + + if (!o.isBoxModelObject()) + return; + + const RenderBoxModelObject& box = *toRenderBoxModelObject(&o); + if (box.borderTop() || box.borderRight() || box.borderBottom() || box.borderLeft()) { + ts << " [border:"; + + BorderValue prevBorder; + if (o.style()->borderTop() != prevBorder) { + prevBorder = o.style()->borderTop(); + if (!box.borderTop()) + ts << " none"; + else { + ts << " (" << box.borderTop() << "px "; + printBorderStyle(ts, o.style()->borderTopStyle()); + Color col = o.style()->borderTopColor(); + if (!col.isValid()) + col = o.style()->color(); + ts << col.name() << ")"; + } + } + + if (o.style()->borderRight() != prevBorder) { + prevBorder = o.style()->borderRight(); + if (!box.borderRight()) + ts << " none"; + else { + ts << " (" << box.borderRight() << "px "; + printBorderStyle(ts, o.style()->borderRightStyle()); + Color col = o.style()->borderRightColor(); + if (!col.isValid()) + col = o.style()->color(); + ts << col.name() << ")"; + } + } + + if (o.style()->borderBottom() != prevBorder) { + prevBorder = box.style()->borderBottom(); + if (!box.borderBottom()) + ts << " none"; + else { + ts << " (" << box.borderBottom() << "px "; + printBorderStyle(ts, o.style()->borderBottomStyle()); + Color col = o.style()->borderBottomColor(); + if (!col.isValid()) + col = o.style()->color(); + ts << col.name() << ")"; + } + } + + if (o.style()->borderLeft() != prevBorder) { + prevBorder = o.style()->borderLeft(); + if (!box.borderLeft()) + ts << " none"; + else { + ts << " (" << box.borderLeft() << "px "; + printBorderStyle(ts, o.style()->borderLeftStyle()); + Color col = o.style()->borderLeftColor(); + if (!col.isValid()) + col = o.style()->color(); + ts << col.name() << ")"; + } + } + + ts << "]"; + } + } + + if (o.isTableCell()) { + const RenderTableCell& c = *toRenderTableCell(&o); + ts << " [r=" << c.row() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]"; + } + + if (o.isListMarker()) { + String text = toRenderListMarker(&o)->text(); + if (!text.isEmpty()) { + if (text.length() != 1) + text = quoteAndEscapeNonPrintables(text); + else { + switch (text[0]) { + case bullet: + text = "bullet"; + break; + case blackSquare: + text = "black square"; + break; + case whiteBullet: + text = "white bullet"; + break; + default: + text = quoteAndEscapeNonPrintables(text); + } + } + ts << ": " << text; + } + } + + if (behavior & RenderAsTextShowIDAndClass) { + if (Node* node = o.node()) { + if (node->hasID()) + ts << " id=\"" + static_cast<Element*>(node)->getIdAttribute() + "\""; + + if (node->hasClass()) { + StyledElement* styledElement = static_cast<StyledElement*>(node); + String classes; + for (size_t i = 0; i < styledElement->classNames().size(); ++i) { + if (i > 0) + classes += " "; + classes += styledElement->classNames()[i]; + } + ts << " class=\"" + classes + "\""; + } + } + } + + if (behavior & RenderAsTextShowLayoutState) { + bool needsLayout = o.selfNeedsLayout() || o.needsPositionedMovementLayout() || o.posChildNeedsLayout() || o.normalChildNeedsLayout(); + if (needsLayout) + ts << " (needs layout:"; + + bool havePrevious = false; + if (o.selfNeedsLayout()) { + ts << " self"; + havePrevious = true; + } + + if (o.needsPositionedMovementLayout()) { + if (havePrevious) + ts << ","; + havePrevious = true; + ts << " positioned movement"; + } + + if (o.normalChildNeedsLayout()) { + if (havePrevious) + ts << ","; + havePrevious = true; + ts << " child"; + } + + if (o.posChildNeedsLayout()) { + if (havePrevious) + ts << ","; + ts << " positioned child"; + } + + if (needsLayout) + ts << ")"; + } + +#if PLATFORM(QT) + // Print attributes of embedded QWidgets. E.g. when the WebCore::Widget + // is invisible the QWidget should be invisible too. + if (o.isRenderPart()) { + const RenderPart* part = toRenderPart(const_cast<RenderObject*>(&o)); + if (part->widget() && part->widget()->platformWidget()) { + QWidget* wid = part->widget()->platformWidget(); + + ts << " [QT: "; + ts << "geometry: {" << wid->geometry() << "} "; + ts << "isHidden: " << wid->isHidden() << " "; + ts << "isSelfVisible: " << part->widget()->isSelfVisible() << " "; + ts << "isParentVisible: " << part->widget()->isParentVisible() << " "; + ts << "mask: {" << wid->mask().boundingRect() << "} ] "; + } + } +#endif +} + +static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run) +{ + // FIXME: Table cell adjustment is temporary until results can be updated. + int y = run.m_y; + if (o.containingBlock()->isTableCell()) + y -= toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore(); + ts << "text run at (" << run.m_x << "," << y << ") width " << run.m_logicalWidth; + if (!run.isLeftToRightDirection() || run.m_dirOverride) { + ts << (!run.isLeftToRightDirection() ? " RTL" : " LTR"); + if (run.m_dirOverride) + ts << " override"; + } + ts << ": " + << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len())) + << "\n"; +} + +void write(TextStream& ts, const RenderObject& o, int indent, RenderAsTextBehavior behavior) +{ +#if ENABLE(SVG) + if (o.isSVGPath()) { + write(ts, *toRenderSVGPath(&o), indent); + return; + } + if (o.isSVGGradientStop()) { + writeSVGGradientStop(ts, *toRenderSVGGradientStop(&o), indent); + return; + } + if (o.isSVGResourceContainer()) { + writeSVGResourceContainer(ts, o, indent); + return; + } + if (o.isSVGContainer()) { + writeSVGContainer(ts, o, indent); + return; + } + if (o.isSVGRoot()) { + write(ts, *toRenderSVGRoot(&o), indent); + return; + } + if (o.isSVGText()) { + writeSVGText(ts, *toRenderBlock(&o), indent); + return; + } + if (o.isSVGInlineText()) { + writeSVGInlineText(ts, *toRenderText(&o), indent); + return; + } + if (o.isSVGImage()) { + writeSVGImage(ts, *toRenderSVGImage(&o), indent); + return; + } +#endif + + writeIndent(ts, indent); + + RenderTreeAsText::writeRenderObject(ts, o, behavior); + ts << "\n"; + + if (o.isText() && !o.isBR()) { + const RenderText& text = *toRenderText(&o); + for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) { + writeIndent(ts, indent + 1); + writeTextRun(ts, text, *box); + } + } + + for (RenderObject* child = o.firstChild(); child; child = child->nextSibling()) { + if (child->hasLayer()) + continue; + write(ts, *child, indent + 1, behavior); + } + + if (o.isWidget()) { + Widget* widget = toRenderWidget(&o)->widget(); + if (widget && widget->isFrameView()) { + FrameView* view = static_cast<FrameView*>(widget); + RenderView* root = view->frame()->contentRenderer(); + if (root) { + view->layout(); + RenderLayer* l = root->layer(); + if (l) + writeLayers(ts, l, l, IntRect(l->x(), l->y(), l->width(), l->height()), indent + 1, behavior); + } + } + } +} + +enum LayerPaintPhase { + LayerPaintPhaseAll = 0, + LayerPaintPhaseBackground = -1, + LayerPaintPhaseForeground = 1 +}; + +static void write(TextStream& ts, RenderLayer& l, + const IntRect& layerBounds, const IntRect& backgroundClipRect, const IntRect& clipRect, const IntRect& outlineClipRect, + LayerPaintPhase paintPhase = LayerPaintPhaseAll, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal) +{ + writeIndent(ts, indent); + + ts << "layer "; + + if (behavior & RenderAsTextShowAddresses) + ts << static_cast<const void*>(&l) << " "; + + ts << layerBounds; + + if (!layerBounds.isEmpty()) { + if (!backgroundClipRect.contains(layerBounds)) + ts << " backgroundClip " << backgroundClipRect; + if (!clipRect.contains(layerBounds)) + ts << " clip " << clipRect; + if (!outlineClipRect.contains(layerBounds)) + ts << " outlineClip " << outlineClipRect; + } + + if (l.renderer()->hasOverflowClip()) { + if (l.scrollXOffset()) + ts << " scrollX " << l.scrollXOffset(); + if (l.scrollYOffset()) + ts << " scrollY " << l.scrollYOffset(); + if (l.renderBox() && l.renderBox()->clientWidth() != l.scrollWidth()) + ts << " scrollWidth " << l.scrollWidth(); + if (l.renderBox() && l.renderBox()->clientHeight() != l.scrollHeight()) + ts << " scrollHeight " << l.scrollHeight(); + } + + if (paintPhase == LayerPaintPhaseBackground) + ts << " layerType: background only"; + else if (paintPhase == LayerPaintPhaseForeground) + ts << " layerType: foreground only"; + +#if USE(ACCELERATED_COMPOSITING) + if (behavior & RenderAsTextShowCompositedLayers) { + if (l.isComposited()) + ts << " (composited, bounds " << l.backing()->compositedBounds() << ")"; + } +#else + UNUSED_PARAM(behavior); +#endif + + ts << "\n"; + + if (paintPhase != LayerPaintPhaseBackground) + write(ts, *l.renderer(), indent + 1, behavior); +} + +static void writeLayers(TextStream& ts, const RenderLayer* rootLayer, RenderLayer* l, + const IntRect& paintRect, int indent, RenderAsTextBehavior behavior) +{ + // FIXME: Apply overflow to the root layer to not break every test. Complete hack. Sigh. + IntRect paintDirtyRect(paintRect); + if (rootLayer == l) { + paintDirtyRect.setWidth(max(paintDirtyRect.width(), rootLayer->renderBox()->rightLayoutOverflow())); + paintDirtyRect.setHeight(max(paintDirtyRect.height(), rootLayer->renderBox()->bottomLayoutOverflow())); + l->setWidth(max(l->width(), l->renderBox()->rightLayoutOverflow())); + l->setHeight(max(l->height(), l->renderBox()->bottomLayoutOverflow())); + } + + // Calculate the clip rects we should use. + IntRect layerBounds, damageRect, clipRectToApply, outlineRect; + l->calculateRects(rootLayer, paintDirtyRect, layerBounds, damageRect, clipRectToApply, outlineRect, true); + + // Ensure our lists are up-to-date. + l->updateZOrderLists(); + l->updateNormalFlowList(); + + bool shouldPaint = (behavior & RenderAsTextShowAllLayers) ? true : l->intersectsDamageRect(layerBounds, damageRect, rootLayer); + Vector<RenderLayer*>* negList = l->negZOrderList(); + bool paintsBackgroundSeparately = negList && negList->size() > 0; + if (shouldPaint && paintsBackgroundSeparately) + write(ts, *l, layerBounds, damageRect, clipRectToApply, outlineRect, LayerPaintPhaseBackground, indent, behavior); + + if (negList) { + int currIndent = indent; + if (behavior & RenderAsTextShowLayerNesting) { + writeIndent(ts, indent); + ts << " negative z-order list(" << negList->size() << ")\n"; + ++currIndent; + } + for (unsigned i = 0; i != negList->size(); ++i) + writeLayers(ts, rootLayer, negList->at(i), paintDirtyRect, currIndent, behavior); + } + + if (shouldPaint) + write(ts, *l, layerBounds, damageRect, clipRectToApply, outlineRect, paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, indent, behavior); + + if (Vector<RenderLayer*>* normalFlowList = l->normalFlowList()) { + int currIndent = indent; + if (behavior & RenderAsTextShowLayerNesting) { + writeIndent(ts, indent); + ts << " normal flow list(" << normalFlowList->size() << ")\n"; + ++currIndent; + } + for (unsigned i = 0; i != normalFlowList->size(); ++i) + writeLayers(ts, rootLayer, normalFlowList->at(i), paintDirtyRect, currIndent, behavior); + } + + if (Vector<RenderLayer*>* posList = l->posZOrderList()) { + int currIndent = indent; + if (behavior & RenderAsTextShowLayerNesting) { + writeIndent(ts, indent); + ts << " positive z-order list(" << posList->size() << ")\n"; + ++currIndent; + } + for (unsigned i = 0; i != posList->size(); ++i) + writeLayers(ts, rootLayer, posList->at(i), paintDirtyRect, currIndent, behavior); + } +} + +static String nodePosition(Node* node) +{ + String result; + + Element* body = node->document()->body(); + Node* parent; + for (Node* n = node; n; n = parent) { + parent = n->parentOrHostNode(); + if (n != node) + result += " of "; + if (parent) { + if (body && n == body) { + // We don't care what offset body may be in the document. + result += "body"; + break; + } + result += "child " + String::number(n->nodeIndex()) + " {" + getTagName(n) + "}"; + } else + result += "document"; + } + + return result; +} + +static void writeSelection(TextStream& ts, const RenderObject* o) +{ + Node* n = o->node(); + if (!n || !n->isDocumentNode()) + return; + + Document* doc = static_cast<Document*>(n); + Frame* frame = doc->frame(); + if (!frame) + return; + + VisibleSelection selection = frame->selection()->selection(); + if (selection.isCaret()) { + ts << "caret: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().node()); + if (selection.affinity() == UPSTREAM) + ts << " (upstream affinity)"; + ts << "\n"; + } else if (selection.isRange()) + ts << "selection start: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().node()) << "\n" + << "selection end: position " << selection.end().deprecatedEditingOffset() << " of " << nodePosition(selection.end().node()) << "\n"; +} + +String externalRepresentation(Frame* frame, RenderAsTextBehavior behavior) +{ + PrintContext printContext(frame); + if (behavior & RenderAsTextPrintingMode) { + if (!frame->contentRenderer()) + return String(); + printContext.begin(frame->contentRenderer()->width()); + } + + if (!(behavior & RenderAsTextDontUpdateLayout)) + frame->document()->updateLayout(); + + RenderObject* o = frame->contentRenderer(); + if (!o) + return String(); + + TextStream ts; + if (o->hasLayer()) { + RenderLayer* l = toRenderBox(o)->layer(); + writeLayers(ts, l, l, IntRect(l->x(), l->y(), l->width(), l->height()), 0, behavior); + writeSelection(ts, o); + } + return ts.release(); +} + +static void writeCounterValuesFromChildren(TextStream& stream, RenderObject* parent, bool& isFirstCounter) +{ + for (RenderObject* child = parent->firstChild(); child; child = child->nextSibling()) { + if (child->isCounter()) { + if (!isFirstCounter) + stream << " "; + isFirstCounter = false; + String str(toRenderText(child)->text()); + stream << str; + } + } +} + +String counterValueForElement(Element* element) +{ + // Make sure the element is not freed during the layout. + RefPtr<Element> elementRef(element); + element->document()->updateLayout(); + TextStream stream; + bool isFirstCounter = true; + // The counter renderers should be children of anonymous children + // (i.e., :before or :after pseudo-elements). + if (RenderObject* renderer = element->renderer()) { + for (RenderObject* child = renderer->firstChild(); child; child = child->nextSibling()) { + if (child->isAnonymous()) + writeCounterValuesFromChildren(stream, child, isFirstCounter); + } + } + return stream.release(); +} + +String markerTextForListItem(Element* element) +{ + // Make sure the element is not freed during the layout. + RefPtr<Element> elementRef(element); + element->document()->updateLayout(); + + RenderObject* renderer = element->renderer(); + if (!renderer || !renderer->isListItem()) + return String(); + + return toRenderListItem(renderer)->markerText(); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderTreeAsText.h b/Source/WebCore/rendering/RenderTreeAsText.h new file mode 100644 index 0000000..4c8b145 --- /dev/null +++ b/Source/WebCore/rendering/RenderTreeAsText.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2003, 2006, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderTreeAsText_h +#define RenderTreeAsText_h +#include "TextStream.h" + +#include <wtf/Forward.h> +#include <wtf/MathExtras.h> + +namespace WebCore { + +class Element; +class FloatPoint; +class FloatSize; +class Frame; +class IntPoint; +class IntRect; +class RenderObject; +class TextStream; + +enum RenderAsTextBehaviorFlags { + RenderAsTextBehaviorNormal = 0, + RenderAsTextShowAllLayers = 1 << 0, // Dump all layers, not just those that would paint. + RenderAsTextShowLayerNesting = 1 << 1, // Annotate the layer lists. + RenderAsTextShowCompositedLayers = 1 << 2, // Show which layers are composited. + RenderAsTextShowAddresses = 1 << 3, // Show layer and renderer addresses. + RenderAsTextShowIDAndClass = 1 << 4, // Show id and class attributes + RenderAsTextPrintingMode = 1 << 5, // Dump the tree in printing mode. + RenderAsTextDontUpdateLayout = 1 << 6, // Don't update layout, to make it safe to call showLayerTree() from the debugger inside layout or painting code. + RenderAsTextShowLayoutState = 1 << 7 // Print the various 'needs layout' bits on renderers. +}; +typedef unsigned RenderAsTextBehavior; + +// You don't need pageWidthInPixels if you don't specify RenderAsTextInPrintingMode. +String externalRepresentation(Frame*, RenderAsTextBehavior = RenderAsTextBehaviorNormal); +void write(TextStream&, const RenderObject&, int indent = 0, RenderAsTextBehavior = RenderAsTextBehaviorNormal); +void writeIndent(TextStream&, int indent); + +class RenderTreeAsText { +// FIXME: This is a cheesy hack to allow easy access to RenderStyle colors. It won't be needed if we convert +// it to use visitedDependentColor instead. (This just involves rebaselining many results though, so for now it's +// not being done). +public: +static void writeRenderObject(TextStream& ts, const RenderObject& o, RenderAsTextBehavior behavior); +}; + +TextStream& operator<<(TextStream&, const IntPoint&); +TextStream& operator<<(TextStream&, const IntRect&); +TextStream& operator<<(TextStream&, const FloatPoint&); +TextStream& operator<<(TextStream&, const FloatSize&); + +template<typename Item> +TextStream& operator<<(TextStream& ts, const Vector<Item>& vector) +{ + ts << "["; + + unsigned size = vector.size(); + for (unsigned i = 0; i < size; ++i) { + ts << vector[i]; + if (i < size - 1) + ts << ", "; + } + + ts << "]"; + return ts; +} + +// Helper function shared with SVGRenderTreeAsText +String quoteAndEscapeNonPrintables(const String&); + +String counterValueForElement(Element*); + +String markerTextForListItem(Element*); + +bool hasFractions(double val); + +} // namespace WebCore + +#endif // RenderTreeAsText_h diff --git a/Source/WebCore/rendering/RenderVideo.cpp b/Source/WebCore/rendering/RenderVideo.cpp new file mode 100644 index 0000000..5b82deb --- /dev/null +++ b/Source/WebCore/rendering/RenderVideo.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if ENABLE(VIDEO) +#include "RenderVideo.h" + +#include "Document.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "HTMLNames.h" +#include "HTMLVideoElement.h" +#include "MediaPlayer.h" +#include "RenderView.h" + +#if USE(ACCELERATED_COMPOSITING) +#include "RenderLayer.h" +#include "RenderLayerBacking.h" +#endif + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +RenderVideo::RenderVideo(HTMLVideoElement* video) + : RenderMedia(video) +{ + setIntrinsicSize(calculateIntrinsicSize()); +} + +RenderVideo::~RenderVideo() +{ + if (MediaPlayer* p = player()) { + p->setVisible(false); + p->setFrameView(0); + } +} + +IntSize RenderVideo::defaultSize() +{ + // These values are specified in the spec. + static const int cDefaultWidth = 300; + static const int cDefaultHeight = 150; + + return IntSize(cDefaultWidth, cDefaultHeight); +} + +void RenderVideo::intrinsicSizeChanged() +{ + if (videoElement()->shouldDisplayPosterImage()) + RenderMedia::intrinsicSizeChanged(); + updateIntrinsicSize(); +} + +void RenderVideo::updateIntrinsicSize() +{ + IntSize size = calculateIntrinsicSize(); + size.scale(style()->effectiveZoom()); + + // Never set the element size to zero when in a media document. + if (size.isEmpty() && node()->ownerDocument() && node()->ownerDocument()->isMediaDocument()) + return; + + if (size == intrinsicSize()) + return; + + setIntrinsicSize(size); + setPreferredLogicalWidthsDirty(true); + setNeedsLayout(true); +} + +IntSize RenderVideo::calculateIntrinsicSize() +{ + HTMLVideoElement* video = videoElement(); + + // Spec text from 4.8.6 + // + // The intrinsic width of a video element's playback area is the intrinsic width + // of the video resource, if that is available; otherwise it is the intrinsic + // width of the poster frame, if that is available; otherwise it is 300 CSS pixels. + // + // The intrinsic height of a video element's playback area is the intrinsic height + // of the video resource, if that is available; otherwise it is the intrinsic + // height of the poster frame, if that is available; otherwise it is 150 CSS pixels. + + if (player() && video->readyState() >= HTMLVideoElement::HAVE_METADATA) + return player()->naturalSize(); + + if (video->shouldDisplayPosterImage() && !m_cachedImageSize.isEmpty() && !imageResource()->errorOccurred()) + return m_cachedImageSize; + + // When the natural size of the video is unavailable, we use the provided + // width and height attributes of the video element as the intrinsic size until + // better values become available. + if (video->hasAttribute(widthAttr) && video->hasAttribute(heightAttr)) + return IntSize(video->width(), video->height()); + + // <video> in standalone media documents should not use the default 300x150 + // size since they also have audio-only files. By setting the intrinsic + // size to 300x1 the video will resize itself in these cases, and audio will + // have the correct height (it needs to be > 0 for controls to render properly). + if (video->ownerDocument() && video->ownerDocument()->isMediaDocument()) + return IntSize(defaultSize().width(), 1); + + return defaultSize(); +} + +void RenderVideo::imageChanged(WrappedImagePtr newImage, const IntRect* rect) +{ + RenderMedia::imageChanged(newImage, rect); + + // Cache the image intrinsic size so we can continue to use it to draw the image correctly + // even if we know the video intrinsic size but aren't able to draw video frames yet + // (we don't want to scale the poster to the video size without keeping aspect ratio). + if (videoElement()->shouldDisplayPosterImage()) + m_cachedImageSize = intrinsicSize(); + + // The intrinsic size is now that of the image, but in case we already had the + // intrinsic size of the video we call this here to restore the video size. + updateIntrinsicSize(); +} + +IntRect RenderVideo::videoBox() const +{ + if (m_cachedImageSize.isEmpty() && videoElement()->shouldDisplayPosterImage()) + return IntRect(); + + IntSize elementSize; + if (videoElement()->shouldDisplayPosterImage()) + elementSize = m_cachedImageSize; + else + elementSize = intrinsicSize(); + + IntRect contentRect = contentBoxRect(); + if (elementSize.isEmpty() || contentRect.isEmpty()) + return IntRect(); + + IntRect renderBox = contentRect; + int ratio = renderBox.width() * elementSize.height() - renderBox.height() * elementSize.width(); + if (ratio > 0) { + int newWidth = renderBox.height() * elementSize.width() / elementSize.height(); + // Just fill the whole area if the difference is one pixel or less (in both sides) + if (renderBox.width() - newWidth > 2) + renderBox.setWidth(newWidth); + renderBox.move((contentRect.width() - renderBox.width()) / 2, 0); + } else if (ratio < 0) { + int newHeight = renderBox.width() * elementSize.height() / elementSize.width(); + if (renderBox.height() - newHeight > 2) + renderBox.setHeight(newHeight); + renderBox.move(0, (contentRect.height() - renderBox.height()) / 2); + } + + return renderBox; +} + +bool RenderVideo::shouldDisplayVideo() const +{ + return !videoElement()->shouldDisplayPosterImage(); +} + +void RenderVideo::paintReplaced(PaintInfo& paintInfo, int tx, int ty) +{ + MediaPlayer* mediaPlayer = player(); + bool displayingPoster = videoElement()->shouldDisplayPosterImage(); + + if (!displayingPoster) { + if (!mediaPlayer) + return; + updatePlayer(); + } + + IntRect rect = videoBox(); + if (rect.isEmpty()) + return; + rect.move(tx, ty); + + if (displayingPoster) + paintIntoRect(paintInfo.context, rect); + else + mediaPlayer->paint(paintInfo.context, rect); +} + +void RenderVideo::layout() +{ + RenderMedia::layout(); + updatePlayer(); +} + +HTMLVideoElement* RenderVideo::videoElement() const +{ + ASSERT(node()->hasTagName(videoTag)); + return static_cast<HTMLVideoElement*>(node()); +} + +void RenderVideo::updateFromElement() +{ + RenderMedia::updateFromElement(); + updatePlayer(); +} + +void RenderVideo::updatePlayer() +{ + updateIntrinsicSize(); + + MediaPlayer* mediaPlayer = player(); + if (!mediaPlayer) + return; + + if (!videoElement()->inActiveDocument()) { + mediaPlayer->setVisible(false); + return; + } + +#if USE(ACCELERATED_COMPOSITING) + layer()->contentChanged(RenderLayer::VideoChanged); +#endif + + IntRect videoBounds = videoBox(); + mediaPlayer->setFrameView(document()->view()); + mediaPlayer->setSize(IntSize(videoBounds.width(), videoBounds.height())); + mediaPlayer->setVisible(true); +} + +int RenderVideo::computeReplacedLogicalWidth(bool includeMaxWidth) const +{ + return RenderReplaced::computeReplacedLogicalWidth(includeMaxWidth); +} + +int RenderVideo::computeReplacedLogicalHeight() const +{ + return RenderReplaced::computeReplacedLogicalHeight(); +} + +int RenderVideo::minimumReplacedHeight() const +{ + return RenderReplaced::minimumReplacedHeight(); +} + +#if USE(ACCELERATED_COMPOSITING) +bool RenderVideo::supportsAcceleratedRendering() const +{ + MediaPlayer* p = player(); + if (p) + return p->supportsAcceleratedRendering(); + + return false; +} + +void RenderVideo::acceleratedRenderingStateChanged() +{ + MediaPlayer* p = player(); + if (p) + p->acceleratedRenderingStateChanged(); +} +#endif // USE(ACCELERATED_COMPOSITING) + +} // namespace WebCore + +#endif diff --git a/Source/WebCore/rendering/RenderVideo.h b/Source/WebCore/rendering/RenderVideo.h new file mode 100644 index 0000000..9091490 --- /dev/null +++ b/Source/WebCore/rendering/RenderVideo.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2007, 2009, 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderVideo_h +#define RenderVideo_h + +#if ENABLE(VIDEO) + +#include "RenderMedia.h" + +namespace WebCore { + +class HTMLMediaElement; +class HTMLVideoElement; + +class RenderVideo : public RenderMedia { +public: + RenderVideo(HTMLVideoElement*); + virtual ~RenderVideo(); + + IntRect videoBox() const; + + static IntSize defaultSize(); + +#if USE(ACCELERATED_COMPOSITING) + bool supportsAcceleratedRendering() const; + void acceleratedRenderingStateChanged(); +#endif + + virtual bool shouldDisplayVideo() const; + +private: + virtual void updateFromElement(); + inline HTMLVideoElement* videoElement() const; + + virtual void intrinsicSizeChanged(); + IntSize calculateIntrinsicSize(); + void updateIntrinsicSize(); + + virtual void imageChanged(WrappedImagePtr, const IntRect*); + + virtual const char* renderName() const { return "RenderVideo"; } + + virtual bool requiresLayer() const { return true; } + virtual bool isVideo() const { return true; } + + virtual void paintReplaced(PaintInfo&, int tx, int ty); + + virtual void layout(); + + virtual int computeReplacedLogicalWidth(bool includeMaxWidth = true) const; + virtual int computeReplacedLogicalHeight() const; + virtual int minimumReplacedHeight() const; + + void updatePlayer(); + + IntSize m_cachedImageSize; +}; + +inline RenderVideo* toRenderVideo(RenderObject* object) +{ + ASSERT(!object || object->isVideo()); + return static_cast<RenderVideo*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderVideo(const RenderVideo*); + +} // namespace WebCore + +#endif +#endif // RenderVideo_h diff --git a/Source/WebCore/rendering/RenderView.cpp b/Source/WebCore/rendering/RenderView.cpp new file mode 100644 index 0000000..6820e34 --- /dev/null +++ b/Source/WebCore/rendering/RenderView.cpp @@ -0,0 +1,802 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * + * 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 "RenderView.h" + +#include "Document.h" +#include "Element.h" +#include "FloatQuad.h" +#include "Frame.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "HTMLFrameOwnerElement.h" +#include "HitTestResult.h" +#include "RenderLayer.h" +#include "RenderSelectionInfo.h" +#include "RenderWidget.h" +#include "RenderWidgetProtector.h" +#include "TransformState.h" + +#if USE(ACCELERATED_COMPOSITING) +#include "RenderLayerCompositor.h" +#endif + +#if defined(ANDROID_LAYOUT) || defined(ANDROID_FIXED_ELEMENTS) +#include "Settings.h" +#endif + +namespace WebCore { + +RenderView::RenderView(Node* node, FrameView* view) + : RenderBlock(node) + , m_frameView(view) + , m_selectionStart(0) + , m_selectionEnd(0) + , m_selectionStartPos(-1) + , m_selectionEndPos(-1) + , m_maximalOutlineSize(0) + , m_pageLogicalHeight(0) + , m_pageLogicalHeightChanged(false) + , m_layoutState(0) + , m_layoutStateDisableCount(0) +{ + // Clear our anonymous bit, set because RenderObject assumes + // any renderer with document as the node is anonymous. + setIsAnonymous(false); + + // init RenderObject attributes + setInline(false); + + m_minPreferredLogicalWidth = 0; + m_maxPreferredLogicalWidth = 0; + + setPreferredLogicalWidthsDirty(true, false); + + setPositioned(true); // to 0,0 :) +} + +RenderView::~RenderView() +{ +} + +void RenderView::computeLogicalHeight() +{ + if (!printing() && m_frameView) + setLogicalHeight(viewLogicalHeight()); +} + +void RenderView::computeLogicalWidth() +{ + if (!printing() && m_frameView) + setLogicalWidth(viewLogicalWidth()); +#ifdef ANDROID_LAYOUT + setVisibleWidth(m_frameView->textWrapWidth()); + const Settings * settings = document()->settings(); + ASSERT(settings); + if (settings->useWideViewport() && settings->viewportWidth() == -1 && width() < minPreferredLogicalWidth()) + setWidth(m_minPreferredLogicalWidth); +#endif +} + +void RenderView::computePreferredLogicalWidths() +{ + ASSERT(preferredLogicalWidthsDirty()); + + RenderBlock::computePreferredLogicalWidths(); + + m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth; +} + +bool RenderView::isChildAllowed(RenderObject* child, RenderStyle*) const +{ + return child->isBox(); +} + +void RenderView::layout() +{ + if (!document()->paginated()) + setPageLogicalHeight(0); + + if (printing()) + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = width(); + + // Use calcWidth/Height to get the new width/height, since this will take the full page zoom factor into account. + bool relayoutChildren = !printing() && (!m_frameView || width() != viewWidth() || height() != viewHeight()); + if (relayoutChildren) { + setChildNeedsLayout(true, false); + for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { + if (child->style()->logicalHeight().isPercent() || child->style()->logicalMinHeight().isPercent() || child->style()->logicalMaxHeight().isPercent()) + child->setChildNeedsLayout(true, false); + } + } + + ASSERT(!m_layoutState); + LayoutState state; + // FIXME: May be better to push a clip and avoid issuing offscreen repaints. + state.m_clipped = false; + state.m_pageLogicalHeight = m_pageLogicalHeight; + state.m_pageLogicalHeightChanged = m_pageLogicalHeightChanged; + m_pageLogicalHeightChanged = false; + m_layoutState = &state; + + if (needsLayout()) + RenderBlock::layout(); + + ASSERT(layoutDelta() == IntSize()); + ASSERT(m_layoutStateDisableCount == 0); + ASSERT(m_layoutState == &state); + m_layoutState = 0; + setNeedsLayout(false); +} + +void RenderView::mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool fixed, bool /*useTransforms*/, TransformState& transformState) const +{ + // If a container was specified, and was not 0 or the RenderView, + // then we should have found it by now. + ASSERT_UNUSED(repaintContainer, !repaintContainer || repaintContainer == this); + + if (fixed && m_frameView) + transformState.move(m_frameView->scrollOffset()); +} + +void RenderView::mapAbsoluteToLocalPoint(bool fixed, bool /*useTransforms*/, TransformState& transformState) const +{ + if (fixed && m_frameView) + transformState.move(-m_frameView->scrollOffset()); +} + +void RenderView::paint(PaintInfo& paintInfo, int tx, int ty) +{ + // If we ever require layout but receive a paint anyway, something has gone horribly wrong. + ASSERT(!needsLayout()); + paintObject(paintInfo, tx, ty); +} + +static inline bool isComposited(RenderObject* object) +{ + return object->hasLayer() && toRenderBoxModelObject(object)->layer()->isComposited(); +} + +static inline bool rendererObscuresBackground(RenderObject* object) +{ + return object && object->style()->visibility() == VISIBLE + && object->style()->opacity() == 1 + && !object->style()->hasTransform() + && !isComposited(object); +} + +void RenderView::paintBoxDecorations(PaintInfo& paintInfo, int, int) +{ + // Check to see if we are enclosed by a layer that requires complex painting rules. If so, we cannot blit + // when scrolling, and we need to use slow repaints. Examples of layers that require this are transparent layers, + // layers with reflections, or transformed layers. + // FIXME: This needs to be dynamic. We should be able to go back to blitting if we ever stop being inside + // a transform, transparency layer, etc. + Element* elt; + for (elt = document()->ownerElement(); view() && elt && elt->renderer(); elt = elt->document()->ownerElement()) { + RenderLayer* layer = elt->renderer()->enclosingLayer(); + if (layer->requiresSlowRepaints()) { + frameView()->setUseSlowRepaints(); + break; + } + +#if USE(ACCELERATED_COMPOSITING) + if (RenderLayer* compositingLayer = layer->enclosingCompositingLayer()) { + if (!compositingLayer->backing()->paintingGoesToWindow()) { + frameView()->setUseSlowRepaints(); + break; + } + } +#endif + } + + if (document()->ownerElement() || !view()) + return; + + bool rootFillsViewport = false; + Node* documentElement = document()->documentElement(); + if (RenderObject* rootRenderer = documentElement ? documentElement->renderer() : 0) { + // The document element's renderer is currently forced to be a block, but may not always be. + RenderBox* rootBox = rootRenderer->isBox() ? toRenderBox(rootRenderer) : 0; + rootFillsViewport = rootBox && !rootBox->x() && !rootBox->y() && rootBox->width() >= width() && rootBox->height() >= height(); + } + + // If painting will entirely fill the view, no need to fill the background. + if (rootFillsViewport && rendererObscuresBackground(firstChild())) + return; + + // This code typically only executes if the root element's visibility has been set to hidden, + // or there is a transform on the <html>. + // Only fill with the base background color (typically white) if we're the root document, + // since iframes/frames with no background in the child document should show the parent's background. + if (frameView()->isTransparent()) // FIXME: This needs to be dynamic. We should be able to go back to blitting if we ever stop being transparent. + frameView()->setUseSlowRepaints(); // The parent must show behind the child. + else { + Color baseColor = frameView()->baseBackgroundColor(); + if (baseColor.alpha() > 0) { + CompositeOperator previousOperator = paintInfo.context->compositeOperation(); + paintInfo.context->setCompositeOperation(CompositeCopy); + paintInfo.context->fillRect(paintInfo.rect, baseColor, style()->colorSpace()); + paintInfo.context->setCompositeOperation(previousOperator); + } else + paintInfo.context->clearRect(paintInfo.rect); + } +} + +bool RenderView::shouldRepaint(const IntRect& r) const +{ + if (printing() || r.width() == 0 || r.height() == 0) + return false; + + if (!m_frameView) + return false; + + return true; +} + +void RenderView::repaintViewRectangle(const IntRect& ur, bool immediate) +{ + if (!shouldRepaint(ur)) + return; + + // We always just invalidate the root view, since we could be an iframe that is clipped out + // or even invisible. + Element* elt = document()->ownerElement(); + if (!elt) + m_frameView->repaintContentRectangle(ur, immediate); + else if (RenderBox* obj = elt->renderBox()) { + IntRect vr = viewRect(); + IntRect r = intersection(ur, vr); + + // Subtract out the contentsX and contentsY offsets to get our coords within the viewing + // rectangle. + r.move(-vr.x(), -vr.y()); + + // FIXME: Hardcoded offsets here are not good. + r.move(obj->borderLeft() + obj->paddingLeft(), + obj->borderTop() + obj->paddingTop()); + obj->repaintRectangle(r, immediate); + } +} + +void RenderView::repaintRectangleInViewAndCompositedLayers(const IntRect& ur, bool immediate) +{ + if (!shouldRepaint(ur)) + return; + + repaintViewRectangle(ur, immediate); + +#if USE(ACCELERATED_COMPOSITING) + if (compositor()->inCompositingMode()) + compositor()->repaintCompositedLayersAbsoluteRect(ur); +#endif +} + +void RenderView::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& rect, bool fixed) +{ + // If a container was specified, and was not 0 or the RenderView, + // then we should have found it by now. + ASSERT_UNUSED(repaintContainer, !repaintContainer || repaintContainer == this); + + if (printing()) + return; + + if (style()->isFlippedBlocksWritingMode()) { + // We have to flip by hand since the view's logical height has not been determined. We + // can use the viewport width and height. + if (style()->isHorizontalWritingMode()) + rect.setY(viewHeight() - rect.bottom()); + else + rect.setX(viewWidth() - rect.right()); + } + + if (fixed && m_frameView) + rect.move(m_frameView->scrollX(), m_frameView->scrollY()); + + // Apply our transform if we have one (because of full page zooming). + if (m_layer && m_layer->transform()) + rect = m_layer->transform()->mapRect(rect); +} + +void RenderView::absoluteRects(Vector<IntRect>& rects, int tx, int ty) +{ + rects.append(IntRect(tx, ty, m_layer->width(), m_layer->height())); +} + +void RenderView::absoluteQuads(Vector<FloatQuad>& quads) +{ + quads.append(FloatRect(0, 0, m_layer->width(), m_layer->height())); +} + +static RenderObject* rendererAfterPosition(RenderObject* object, unsigned offset) +{ + if (!object) + return 0; + + RenderObject* child = object->childAt(offset); + return child ? child : object->nextInPreOrderAfterChildren(); +} + +IntRect RenderView::selectionBounds(bool clipToVisibleContent) const +{ + document()->updateStyleIfNeeded(); + + typedef HashMap<RenderObject*, RenderSelectionInfo*> SelectionMap; + SelectionMap selectedObjects; + + RenderObject* os = m_selectionStart; + RenderObject* stop = rendererAfterPosition(m_selectionEnd, m_selectionEndPos); + while (os && os != stop) { + if ((os->canBeSelectionLeaf() || os == m_selectionStart || os == m_selectionEnd) && os->selectionState() != SelectionNone) { + // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well. + selectedObjects.set(os, new RenderSelectionInfo(os, clipToVisibleContent)); + RenderBlock* cb = os->containingBlock(); + while (cb && !cb->isRenderView()) { + RenderSelectionInfo* blockInfo = selectedObjects.get(cb); + if (blockInfo) + break; + selectedObjects.set(cb, new RenderSelectionInfo(cb, clipToVisibleContent)); + cb = cb->containingBlock(); + } + } + + os = os->nextInPreOrder(); + } + + // Now create a single bounding box rect that encloses the whole selection. + IntRect selRect; + SelectionMap::iterator end = selectedObjects.end(); + for (SelectionMap::iterator i = selectedObjects.begin(); i != end; ++i) { + RenderSelectionInfo* info = i->second; + // RenderSelectionInfo::rect() is in the coordinates of the repaintContainer, so map to page coordinates. + IntRect currRect = info->rect(); + if (RenderBoxModelObject* repaintContainer = info->repaintContainer()) { + FloatQuad absQuad = repaintContainer->localToAbsoluteQuad(FloatRect(currRect)); + currRect = absQuad.enclosingBoundingBox(); + } + selRect.unite(currRect); + delete info; + } + return selRect; +} + +#if USE(ACCELERATED_COMPOSITING) +// Compositing layer dimensions take outline size into account, so we have to recompute layer +// bounds when it changes. +// FIXME: This is ugly; it would be nice to have a better way to do this. +void RenderView::setMaximalOutlineSize(int o) +{ + if (o != m_maximalOutlineSize) { + m_maximalOutlineSize = o; + + // maximalOutlineSize affects compositing layer dimensions. + compositor()->setCompositingLayersNeedRebuild(); // FIXME: this really just needs to be a geometry update. + } +} +#endif + +void RenderView::setSelection(RenderObject* start, int startPos, RenderObject* end, int endPos, SelectionRepaintMode blockRepaintMode) +{ + // Make sure both our start and end objects are defined. + // Check www.msnbc.com and try clicking around to find the case where this happened. + if ((start && !end) || (end && !start)) + return; + + // Just return if the selection hasn't changed. + if (m_selectionStart == start && m_selectionStartPos == startPos && + m_selectionEnd == end && m_selectionEndPos == endPos) + return; + + // Record the old selected objects. These will be used later + // when we compare against the new selected objects. + int oldStartPos = m_selectionStartPos; + int oldEndPos = m_selectionEndPos; + + // Objects each have a single selection rect to examine. + typedef HashMap<RenderObject*, RenderSelectionInfo*> SelectedObjectMap; + SelectedObjectMap oldSelectedObjects; + SelectedObjectMap newSelectedObjects; + + // Blocks contain selected objects and fill gaps between them, either on the left, right, or in between lines and blocks. + // In order to get the repaint rect right, we have to examine left, middle, and right rects individually, since otherwise + // the union of those rects might remain the same even when changes have occurred. + typedef HashMap<RenderBlock*, RenderBlockSelectionInfo*> SelectedBlockMap; + SelectedBlockMap oldSelectedBlocks; + SelectedBlockMap newSelectedBlocks; + + RenderObject* os = m_selectionStart; + RenderObject* stop = rendererAfterPosition(m_selectionEnd, m_selectionEndPos); + while (os && os != stop) { + if ((os->canBeSelectionLeaf() || os == m_selectionStart || os == m_selectionEnd) && os->selectionState() != SelectionNone) { + // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well. + oldSelectedObjects.set(os, new RenderSelectionInfo(os, true)); + if (blockRepaintMode == RepaintNewXOROld) { + RenderBlock* cb = os->containingBlock(); + while (cb && !cb->isRenderView()) { + RenderBlockSelectionInfo* blockInfo = oldSelectedBlocks.get(cb); + if (blockInfo) + break; + oldSelectedBlocks.set(cb, new RenderBlockSelectionInfo(cb)); + cb = cb->containingBlock(); + } + } + } + + os = os->nextInPreOrder(); + } + + // Now clear the selection. + SelectedObjectMap::iterator oldObjectsEnd = oldSelectedObjects.end(); + for (SelectedObjectMap::iterator i = oldSelectedObjects.begin(); i != oldObjectsEnd; ++i) + i->first->setSelectionState(SelectionNone); + + // set selection start and end + m_selectionStart = start; + m_selectionStartPos = startPos; + m_selectionEnd = end; + m_selectionEndPos = endPos; + + // Update the selection status of all objects between m_selectionStart and m_selectionEnd + if (start && start == end) + start->setSelectionState(SelectionBoth); + else { + if (start) + start->setSelectionState(SelectionStart); + if (end) + end->setSelectionState(SelectionEnd); + } + + RenderObject* o = start; + stop = rendererAfterPosition(end, endPos); + + while (o && o != stop) { + if (o != start && o != end && o->canBeSelectionLeaf()) + o->setSelectionState(SelectionInside); + o = o->nextInPreOrder(); + } + + m_layer->clearBlockSelectionGapsBounds(); + + // Now that the selection state has been updated for the new objects, walk them again and + // put them in the new objects list. + o = start; + while (o && o != stop) { + if ((o->canBeSelectionLeaf() || o == start || o == end) && o->selectionState() != SelectionNone) { + newSelectedObjects.set(o, new RenderSelectionInfo(o, true)); + RenderBlock* cb = o->containingBlock(); + while (cb && !cb->isRenderView()) { + RenderBlockSelectionInfo* blockInfo = newSelectedBlocks.get(cb); + if (blockInfo) + break; + newSelectedBlocks.set(cb, new RenderBlockSelectionInfo(cb)); + cb = cb->containingBlock(); + } + } + + o = o->nextInPreOrder(); + } + + if (!m_frameView) { + // We built the maps, but we aren't going to use them. + // We need to delete the values, otherwise they'll all leak! + deleteAllValues(oldSelectedObjects); + deleteAllValues(newSelectedObjects); + deleteAllValues(oldSelectedBlocks); + deleteAllValues(newSelectedBlocks); + return; + } + + m_frameView->beginDeferredRepaints(); + + // Have any of the old selected objects changed compared to the new selection? + for (SelectedObjectMap::iterator i = oldSelectedObjects.begin(); i != oldObjectsEnd; ++i) { + RenderObject* obj = i->first; + RenderSelectionInfo* newInfo = newSelectedObjects.get(obj); + RenderSelectionInfo* oldInfo = i->second; + if (!newInfo || oldInfo->rect() != newInfo->rect() || oldInfo->state() != newInfo->state() || + (m_selectionStart == obj && oldStartPos != m_selectionStartPos) || + (m_selectionEnd == obj && oldEndPos != m_selectionEndPos)) { + oldInfo->repaint(); + if (newInfo) { + newInfo->repaint(); + newSelectedObjects.remove(obj); + delete newInfo; + } + } + delete oldInfo; + } + + // Any new objects that remain were not found in the old objects dict, and so they need to be updated. + SelectedObjectMap::iterator newObjectsEnd = newSelectedObjects.end(); + for (SelectedObjectMap::iterator i = newSelectedObjects.begin(); i != newObjectsEnd; ++i) { + RenderSelectionInfo* newInfo = i->second; + newInfo->repaint(); + delete newInfo; + } + + // Have any of the old blocks changed? + SelectedBlockMap::iterator oldBlocksEnd = oldSelectedBlocks.end(); + for (SelectedBlockMap::iterator i = oldSelectedBlocks.begin(); i != oldBlocksEnd; ++i) { + RenderBlock* block = i->first; + RenderBlockSelectionInfo* newInfo = newSelectedBlocks.get(block); + RenderBlockSelectionInfo* oldInfo = i->second; + if (!newInfo || oldInfo->rects() != newInfo->rects() || oldInfo->state() != newInfo->state()) { + oldInfo->repaint(); + if (newInfo) { + newInfo->repaint(); + newSelectedBlocks.remove(block); + delete newInfo; + } + } + delete oldInfo; + } + + // Any new blocks that remain were not found in the old blocks dict, and so they need to be updated. + SelectedBlockMap::iterator newBlocksEnd = newSelectedBlocks.end(); + for (SelectedBlockMap::iterator i = newSelectedBlocks.begin(); i != newBlocksEnd; ++i) { + RenderBlockSelectionInfo* newInfo = i->second; + newInfo->repaint(); + delete newInfo; + } + + m_frameView->endDeferredRepaints(); +} + +void RenderView::clearSelection() +{ + m_layer->repaintBlockSelectionGaps(); + setSelection(0, -1, 0, -1, RepaintNewMinusOld); +} + +void RenderView::selectionStartEnd(int& startPos, int& endPos) const +{ + startPos = m_selectionStartPos; + endPos = m_selectionEndPos; +} + +bool RenderView::printing() const +{ + return document()->printing(); +} + +size_t RenderView::getRetainedWidgets(Vector<RenderWidget*>& renderWidgets) +{ + size_t size = m_widgets.size(); + + renderWidgets.reserveCapacity(size); + + RenderWidgetSet::const_iterator end = m_widgets.end(); + for (RenderWidgetSet::const_iterator it = m_widgets.begin(); it != end; ++it) { + renderWidgets.uncheckedAppend(*it); + (*it)->ref(); + } + + return size; +} + +void RenderView::releaseWidgets(Vector<RenderWidget*>& renderWidgets) +{ + size_t size = renderWidgets.size(); + + for (size_t i = 0; i < size; ++i) + renderWidgets[i]->deref(renderArena()); +} + +void RenderView::updateWidgetPositions() +{ + // updateWidgetPosition() can possibly cause layout to be re-entered (via plug-ins running + // scripts in response to NPP_SetWindow, for example), so we need to keep the Widgets + // alive during enumeration. + + Vector<RenderWidget*> renderWidgets; + size_t size = getRetainedWidgets(renderWidgets); + + for (size_t i = 0; i < size; ++i) + renderWidgets[i]->updateWidgetPosition(); + + for (size_t i = 0; i < size; ++i) + renderWidgets[i]->widgetPositionsUpdated(); + + releaseWidgets(renderWidgets); +} + +void RenderView::addWidget(RenderWidget* o) +{ + m_widgets.add(o); +} + +void RenderView::removeWidget(RenderWidget* o) +{ + m_widgets.remove(o); +} + +void RenderView::notifyWidgets(WidgetNotification notification) +{ + Vector<RenderWidget*> renderWidgets; + size_t size = getRetainedWidgets(renderWidgets); + + for (size_t i = 0; i < size; ++i) + renderWidgets[i]->notifyWidget(notification); + + releaseWidgets(renderWidgets); +} + +IntRect RenderView::viewRect() const +{ + if (printing()) + return IntRect(0, 0, width(), height()); + if (m_frameView) + return m_frameView->visibleContentRect(); + return IntRect(); +} + +int RenderView::docTop() const +{ + IntRect overflowRect(0, topLayoutOverflow(), 0, bottomLayoutOverflow() - topLayoutOverflow()); + flipForWritingMode(overflowRect); + if (hasTransform()) + overflowRect = layer()->currentTransform().mapRect(overflowRect); + return overflowRect.y(); +} + +int RenderView::docBottom() const +{ + IntRect overflowRect(layoutOverflowRect()); + flipForWritingMode(overflowRect); + if (hasTransform()) + overflowRect = layer()->currentTransform().mapRect(overflowRect); + return overflowRect.bottom(); +} + +int RenderView::docLeft() const +{ + IntRect overflowRect(layoutOverflowRect()); + flipForWritingMode(overflowRect); + if (hasTransform()) + overflowRect = layer()->currentTransform().mapRect(overflowRect); + return overflowRect.x(); +} + +int RenderView::docRight() const +{ + IntRect overflowRect(layoutOverflowRect()); + flipForWritingMode(overflowRect); + if (hasTransform()) + overflowRect = layer()->currentTransform().mapRect(overflowRect); + return overflowRect.right(); +} + +int RenderView::viewHeight() const +{ + int height = 0; + if (!printing() && m_frameView) { + height = m_frameView->layoutHeight(); + height = m_frameView->useFixedLayout() ? ceilf(style()->effectiveZoom() * float(height)) : height; + } + return height; +} + +int RenderView::viewWidth() const +{ + int width = 0; + if (!printing() && m_frameView) { + width = m_frameView->layoutWidth(); + width = m_frameView->useFixedLayout() ? ceilf(style()->effectiveZoom() * float(width)) : width; + } + return width; +} + +float RenderView::zoomFactor() const +{ + Frame* frame = m_frameView->frame(); + return frame ? frame->pageZoomFactor() : 1; +} + +void RenderView::pushLayoutState(RenderObject* root) +{ + ASSERT(m_layoutStateDisableCount == 0); + ASSERT(m_layoutState == 0); + + m_layoutState = new (renderArena()) LayoutState(root); +} + +bool RenderView::shouldDisableLayoutStateForSubtree(RenderObject* renderer) const +{ + RenderObject* o = renderer; + while (o) { + if (o->hasColumns() || o->hasTransform() || o->hasReflection()) + return true; + o = o->container(); + } + return false; +} + +void RenderView::updateHitTestResult(HitTestResult& result, const IntPoint& point) +{ + if (result.innerNode()) + return; + + Node* node = document()->documentElement(); + if (node) { + result.setInnerNode(node); + if (!result.innerNonSharedNode()) + result.setInnerNonSharedNode(node); + result.setLocalPoint(point); + } +} + +// FIXME: This function is obsolete and only used by embedded WebViews inside AppKit NSViews. +// Do not add callers of this function! +// The idea here is to take into account what object is moving the pagination point, and +// thus choose the best place to chop it. +void RenderView::setBestTruncatedAt(int y, RenderBoxModelObject* forRenderer, bool forcedBreak) +{ + // Nobody else can set a page break once we have a forced break. + if (m_legacyPrinting.m_forcedPageBreak) + return; + + // Forced breaks always win over unforced breaks. + if (forcedBreak) { + m_legacyPrinting.m_forcedPageBreak = true; + m_legacyPrinting.m_bestTruncatedAt = y; + return; + } + + // Prefer the widest object that tries to move the pagination point + IntRect boundingBox = forRenderer->borderBoundingBox(); + if (boundingBox.width() > m_legacyPrinting.m_truncatorWidth) { + m_legacyPrinting.m_truncatorWidth = boundingBox.width(); + m_legacyPrinting.m_bestTruncatedAt = y; + } +} + +#if USE(ACCELERATED_COMPOSITING) +bool RenderView::usesCompositing() const +{ + return m_compositor && m_compositor->inCompositingMode(); +} + +RenderLayerCompositor* RenderView::compositor() +{ + if (!m_compositor) + m_compositor.set(new RenderLayerCompositor(this)); + + return m_compositor.get(); +} +#endif + +void RenderView::didMoveOnscreen() +{ +#if USE(ACCELERATED_COMPOSITING) + if (m_compositor) + m_compositor->didMoveOnscreen(); +#endif +} + +void RenderView::willMoveOffscreen() +{ +#if USE(ACCELERATED_COMPOSITING) + if (m_compositor) + m_compositor->willMoveOffscreen(); +#endif +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderView.h b/Source/WebCore/rendering/RenderView.h new file mode 100644 index 0000000..2915998 --- /dev/null +++ b/Source/WebCore/rendering/RenderView.h @@ -0,0 +1,334 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * Copyright (C) 2006 Apple Computer, Inc. + * + * 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. + * + */ + +#ifndef RenderView_h +#define RenderView_h + +#include "FrameView.h" +#include "LayoutState.h" +#include "RenderBlock.h" +#include <wtf/OwnPtr.h> + +namespace WebCore { + +class RenderWidget; + +#if USE(ACCELERATED_COMPOSITING) +class RenderLayerCompositor; +#endif + +class RenderView : public RenderBlock { +public: + RenderView(Node*, FrameView*); + virtual ~RenderView(); + + virtual const char* renderName() const { return "RenderView"; } + + virtual bool isRenderView() const { return true; } + + virtual bool requiresLayer() const { return true; } + + virtual bool isChildAllowed(RenderObject*, RenderStyle*) const; + + virtual void layout(); + virtual void computeLogicalWidth(); + virtual void computeLogicalHeight(); + virtual void computePreferredLogicalWidths(); + + // The same as the FrameView's layoutHeight/layoutWidth but with null check guards. + int viewHeight() const; + int viewWidth() const; + int viewLogicalWidth() const { return style()->isHorizontalWritingMode() ? viewWidth() : viewHeight(); } + int viewLogicalHeight() const { return style()->isHorizontalWritingMode() ? viewHeight() : viewWidth(); } + + float zoomFactor() const; + + FrameView* frameView() const { return m_frameView; } + + virtual void computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect&, bool fixed = false); + virtual void repaintViewRectangle(const IntRect&, bool immediate = false); + // Repaint the view, and all composited layers that intersect the given absolute rectangle. + // FIXME: ideally we'd never have to do this, if all repaints are container-relative. + virtual void repaintRectangleInViewAndCompositedLayers(const IntRect&, bool immediate = false); + + virtual void paint(PaintInfo&, int tx, int ty); + virtual void paintBoxDecorations(PaintInfo&, int tx, int ty); + + enum SelectionRepaintMode { RepaintNewXOROld, RepaintNewMinusOld }; + void setSelection(RenderObject* start, int startPos, RenderObject* end, int endPos, SelectionRepaintMode = RepaintNewXOROld); + void clearSelection(); + RenderObject* selectionStart() const { return m_selectionStart; } + RenderObject* selectionEnd() const { return m_selectionEnd; } + IntRect selectionBounds(bool clipToVisibleContent = true) const; + void selectionStartEnd(int& startPos, int& endPos) const; + + bool printing() const; + + virtual void absoluteRects(Vector<IntRect>&, int tx, int ty); + virtual void absoluteQuads(Vector<FloatQuad>&); + +#if USE(ACCELERATED_COMPOSITING) + void setMaximalOutlineSize(int o); +#else + void setMaximalOutlineSize(int o) { m_maximalOutlineSize = o; } +#endif + int maximalOutlineSize() const { return m_maximalOutlineSize; } + + virtual IntRect viewRect() const; + + void updateWidgetPositions(); + void addWidget(RenderWidget*); + void removeWidget(RenderWidget*); + + void notifyWidgets(WidgetNotification); +#ifdef ANDROID_PLUGINS + const HashSet<RenderWidget*>& widgets() const { return m_widgets; } +#endif + + // layoutDelta is used transiently during layout to store how far an object has moved from its + // last layout location, in order to repaint correctly. + // If we're doing a full repaint m_layoutState will be 0, but in that case layoutDelta doesn't matter. + IntSize layoutDelta() const + { + return m_layoutState ? m_layoutState->m_layoutDelta : IntSize(); + } + void addLayoutDelta(const IntSize& delta) + { + if (m_layoutState) + m_layoutState->m_layoutDelta += delta; + } + + bool doingFullRepaint() const { return m_frameView->needsFullRepaint(); } + + // Subtree push/pop + void pushLayoutState(RenderObject*); + void popLayoutState(RenderObject*) { return popLayoutState(); } // Just doing this to keep popLayoutState() private and to make the subtree calls symmetrical. + + bool shouldDisableLayoutStateForSubtree(RenderObject*) const; + + // Returns true if layoutState should be used for its cached offset and clip. + bool layoutStateEnabled() const { return m_layoutStateDisableCount == 0 && m_layoutState; } + LayoutState* layoutState() const { return m_layoutState; } + + // Suspends the LayoutState optimization. Used under transforms that cannot be represented by + // LayoutState (common in SVG) and when manipulating the render tree during layout in ways + // that can trigger repaint of a non-child (e.g. when a list item moves its list marker around). + // Note that even when disabled, LayoutState is still used to store layoutDelta. + void disableLayoutState() { m_layoutStateDisableCount++; } + void enableLayoutState() { ASSERT(m_layoutStateDisableCount > 0); m_layoutStateDisableCount--; } + + virtual void updateHitTestResult(HitTestResult&, const IntPoint&); + + unsigned pageLogicalHeight() const { return m_pageLogicalHeight; } + void setPageLogicalHeight(unsigned height) + { + if (m_pageLogicalHeight != height) { + m_pageLogicalHeight = height; + m_pageLogicalHeightChanged = true; + } + } + + // FIXME: These functions are deprecated. No code should be added that uses these. + int bestTruncatedAt() const { return m_legacyPrinting.m_bestTruncatedAt; } + void setBestTruncatedAt(int y, RenderBoxModelObject* forRenderer, bool forcedBreak = false); + int truncatedAt() const { return m_legacyPrinting.m_truncatedAt; } + void setTruncatedAt(int y) + { + m_legacyPrinting.m_truncatedAt = y; + m_legacyPrinting.m_bestTruncatedAt = 0; + m_legacyPrinting.m_truncatorWidth = 0; + m_legacyPrinting.m_forcedPageBreak = false; + } + const IntRect& printRect() const { return m_legacyPrinting.m_printRect; } + void setPrintRect(const IntRect& r) { m_legacyPrinting.m_printRect = r; } + // End deprecated functions. + + // Notifications that this view became visible in a window, or will be + // removed from the window. + void didMoveOnscreen(); + void willMoveOffscreen(); + +#if USE(ACCELERATED_COMPOSITING) + RenderLayerCompositor* compositor(); + bool usesCompositing() const; +#endif + + int docTop() const; + int docBottom() const; + int docHeight() const { return docBottom() - docTop(); } + int docLeft() const; + int docRight() const; + int docWidth() const { return docRight() - docLeft(); } + IntRect documentRect() const { return IntRect(docLeft(), docTop(), docWidth(), docHeight()); } + +protected: + virtual void mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool useTransforms, bool fixed, TransformState&) const; + virtual void mapAbsoluteToLocalPoint(bool fixed, bool useTransforms, TransformState&) const; + +private: + bool shouldRepaint(const IntRect& r) const; + + // These functions may only be accessed by LayoutStateMaintainer. + bool pushLayoutState(RenderBox* renderer, const IntSize& offset, int pageHeight = 0, bool pageHeightChanged = false, ColumnInfo* colInfo = 0) + { + // We push LayoutState even if layoutState is disabled because it stores layoutDelta too. + if (!doingFullRepaint() || renderer->hasColumns() || m_layoutState->isPaginated()) { + m_layoutState = new (renderArena()) LayoutState(m_layoutState, renderer, offset, pageHeight, pageHeightChanged, colInfo); + return true; + } + return false; + } + + void popLayoutState() + { + LayoutState* state = m_layoutState; + m_layoutState = state->m_next; + state->destroy(renderArena()); + } + + size_t getRetainedWidgets(Vector<RenderWidget*>&); + void releaseWidgets(Vector<RenderWidget*>&); + + friend class LayoutStateMaintainer; + +protected: + FrameView* m_frameView; + + RenderObject* m_selectionStart; + RenderObject* m_selectionEnd; + int m_selectionStartPos; + int m_selectionEndPos; + + // FIXME: Only used by embedded WebViews inside AppKit NSViews. Find a way to remove. + struct LegacyPrinting { + LegacyPrinting() + : m_bestTruncatedAt(0) + , m_truncatedAt(0) + , m_truncatorWidth(0) + , m_forcedPageBreak(false) + { } + + int m_bestTruncatedAt; + int m_truncatedAt; + int m_truncatorWidth; + IntRect m_printRect; + bool m_forcedPageBreak; + }; + LegacyPrinting m_legacyPrinting; + // End deprecated members. + + int m_maximalOutlineSize; // Used to apply a fudge factor to dirty-rect checks on blocks/tables. + + typedef HashSet<RenderWidget*> RenderWidgetSet; + RenderWidgetSet m_widgets; + +private: + unsigned m_pageLogicalHeight; + bool m_pageLogicalHeightChanged; + LayoutState* m_layoutState; + unsigned m_layoutStateDisableCount; +#if USE(ACCELERATED_COMPOSITING) + OwnPtr<RenderLayerCompositor> m_compositor; +#endif +}; + +inline RenderView* toRenderView(RenderObject* object) +{ + ASSERT(!object || object->isRenderView()); + return static_cast<RenderView*>(object); +} + +inline const RenderView* toRenderView(const RenderObject* object) +{ + ASSERT(!object || object->isRenderView()); + return static_cast<const RenderView*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderView(const RenderView*); + + +// Stack-based class to assist with LayoutState push/pop +class LayoutStateMaintainer : public Noncopyable { +public: + // ctor to push now + LayoutStateMaintainer(RenderView* view, RenderBox* root, IntSize offset, bool disableState = false, int pageHeight = 0, bool pageHeightChanged = false, ColumnInfo* colInfo = 0) + : m_view(view) + , m_disabled(disableState) + , m_didStart(false) + , m_didEnd(false) + , m_didCreateLayoutState(false) + { + push(root, offset, pageHeight, pageHeightChanged, colInfo); + } + + // ctor to maybe push later + LayoutStateMaintainer(RenderView* view) + : m_view(view) + , m_disabled(false) + , m_didStart(false) + , m_didEnd(false) + , m_didCreateLayoutState(false) + { + } + + ~LayoutStateMaintainer() + { + ASSERT(m_didStart == m_didEnd); // if this fires, it means that someone did a push(), but forgot to pop(). + } + + void push(RenderBox* root, IntSize offset, int pageHeight = 0, bool pageHeightChanged = false, ColumnInfo* colInfo = 0) + { + ASSERT(!m_didStart); + // We push state even if disabled, because we still need to store layoutDelta + m_didCreateLayoutState = m_view->pushLayoutState(root, offset, pageHeight, pageHeightChanged, colInfo); + if (m_disabled && m_didCreateLayoutState) + m_view->disableLayoutState(); + m_didStart = true; + } + + void pop() + { + if (m_didStart) { + ASSERT(!m_didEnd); + if (m_didCreateLayoutState) { + m_view->popLayoutState(); + if (m_disabled) + m_view->enableLayoutState(); + } + + m_didEnd = true; + } + } + + bool didPush() const { return m_didStart; } + +private: + RenderView* m_view; + bool m_disabled : 1; // true if the offset and clip part of layoutState is disabled + bool m_didStart : 1; // true if we did a push or disable + bool m_didEnd : 1; // true if we popped or re-enabled + bool m_didCreateLayoutState : 1; // true if we actually made a layout state. +}; + +} // namespace WebCore + +#endif // RenderView_h diff --git a/Source/WebCore/rendering/RenderWidget.cpp b/Source/WebCore/rendering/RenderWidget.cpp new file mode 100644 index 0000000..152bb2f --- /dev/null +++ b/Source/WebCore/rendering/RenderWidget.cpp @@ -0,0 +1,409 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * Copyright (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2006, 2009, 2010 Apple Inc. All rights reserved. + * + * 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 "RenderWidget.h" + +#include "AXObjectCache.h" +#include "AnimationController.h" +#include "GraphicsContext.h" +#include "HitTestResult.h" +#include "RenderCounter.h" +#include "RenderLayer.h" +#include "RenderView.h" +#include "RenderWidgetProtector.h" + +#if USE(ACCELERATED_COMPOSITING) +#include "RenderLayerBacking.h" +#endif + +using namespace std; + +namespace WebCore { + +static HashMap<const Widget*, RenderWidget*>& widgetRendererMap() +{ + static HashMap<const Widget*, RenderWidget*>* staticWidgetRendererMap = new HashMap<const Widget*, RenderWidget*>; + return *staticWidgetRendererMap; +} + +static size_t widgetHierarchyUpdateSuspendCount; + +typedef HashMap<RefPtr<Widget>, FrameView*> WidgetToParentMap; + +static WidgetToParentMap& widgetNewParentMap() +{ + DEFINE_STATIC_LOCAL(WidgetToParentMap, map, ()); + return map; +} + +void RenderWidget::suspendWidgetHierarchyUpdates() +{ + widgetHierarchyUpdateSuspendCount++; +} + +void RenderWidget::resumeWidgetHierarchyUpdates() +{ + ASSERT(widgetHierarchyUpdateSuspendCount); + if (widgetHierarchyUpdateSuspendCount == 1) { + WidgetToParentMap map = widgetNewParentMap(); + widgetNewParentMap().clear(); + WidgetToParentMap::iterator end = map.end(); + for (WidgetToParentMap::iterator it = map.begin(); it != end; ++it) { + Widget* child = it->first.get(); + ScrollView* currentParent = child->parent(); + FrameView* newParent = it->second; + if (newParent != currentParent) { + if (currentParent) + currentParent->removeChild(child); + if (newParent) + newParent->addChild(child); + } + } + } + widgetHierarchyUpdateSuspendCount--; +} + +static void moveWidgetToParentSoon(Widget* child, FrameView* parent) +{ + if (!widgetHierarchyUpdateSuspendCount) { + if (parent) + parent->addChild(child); + else + child->removeFromParent(); + return; + } + widgetNewParentMap().set(child, parent); +} + +RenderWidget::RenderWidget(Node* node) + : RenderReplaced(node) + , m_widget(0) + , m_frameView(node->document()->view()) + // Reference counting is used to prevent the widget from being + // destroyed while inside the Widget code, which might not be + // able to handle that. + , m_refCount(1) +{ + view()->addWidget(this); +} + +void RenderWidget::destroy() +{ + // We can't call the base class's destroy because we don't + // want to unconditionally delete ourselves (we're ref-counted). + // So the code below includes copied and pasted contents of + // both RenderBox::destroy() and RenderObject::destroy(). + // Fix originally made for <rdar://problem/4228818>. + + animation()->cancelAnimations(this); + + if (RenderView* v = view()) + v->removeWidget(this); + + if (m_hasCounterNodeMap) + RenderCounter::destroyCounterNodes(this); + + if (AXObjectCache::accessibilityEnabled()) { + document()->axObjectCache()->childrenChanged(this->parent()); + document()->axObjectCache()->remove(this); + } + remove(); + + setWidget(0); + + // removes from override size map + if (hasOverrideSize()) + setOverrideSize(-1); + + if (style() && (style()->height().isPercent() || style()->minHeight().isPercent() || style()->maxHeight().isPercent())) + RenderBlock::removePercentHeightDescendant(this); + + if (hasLayer()) { + layer()->clearClipRects(); + setHasLayer(false); + destroyLayer(); + } + + // Grab the arena from node()->document()->renderArena() before clearing the node pointer. + // Clear the node before deref-ing, as this may be deleted when deref is called. + RenderArena* arena = renderArena(); + setNode(0); + deref(arena); +} + +RenderWidget::~RenderWidget() +{ + ASSERT(m_refCount <= 0); + clearWidget(); +} + +bool RenderWidget::setWidgetGeometry(const IntRect& frame) +{ + ASSERT(!widgetHierarchyUpdateSuspendCount); + if (!node()) + return false; + + IntRect clipRect = enclosingLayer()->childrenClipRect(); + bool clipChanged = m_clipRect != clipRect; + bool boundsChanged = m_widget->frameRect() != frame; + + if (!boundsChanged && !clipChanged) + return false; + + m_clipRect = clipRect; + + RenderWidgetProtector protector(this); + RefPtr<Node> protectedNode(node()); + m_widget->setFrameRect(frame); + +#if USE(ACCELERATED_COMPOSITING) + if (hasLayer() && layer()->isComposited()) + layer()->backing()->updateAfterWidgetResize(); +#endif + + return boundsChanged; +} + +void RenderWidget::setWidget(PassRefPtr<Widget> widget) +{ + if (widget == m_widget) + return; + + if (m_widget) { + moveWidgetToParentSoon(m_widget.get(), 0); + widgetRendererMap().remove(m_widget.get()); + clearWidget(); + } + m_widget = widget; + if (m_widget) { + widgetRendererMap().add(m_widget.get(), this); + // If we've already received a layout, apply the calculated space to the + // widget immediately, but we have to have really been fully constructed (with a non-null + // style pointer). + if (style()) { + if (!needsLayout()) + setWidgetGeometry(absoluteContentBox()); + if (style()->visibility() != VISIBLE) + m_widget->hide(); + else + m_widget->show(); + } + moveWidgetToParentSoon(m_widget.get(), m_frameView); + } +} + +void RenderWidget::layout() +{ + ASSERT(needsLayout()); + + setNeedsLayout(false); +} + +void RenderWidget::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderReplaced::styleDidChange(diff, oldStyle); + if (m_widget) { + if (style()->visibility() != VISIBLE) + m_widget->hide(); + else + m_widget->show(); + } +} + +void RenderWidget::showSubstituteImage(PassRefPtr<Image> prpImage) +{ + m_substituteImage = prpImage; + repaint(); +} + +void RenderWidget::notifyWidget(WidgetNotification notification) +{ + if (m_widget) + m_widget->notifyWidget(notification); +} + +void RenderWidget::paint(PaintInfo& paintInfo, int tx, int ty) +{ + if (!shouldPaint(paintInfo, tx, ty)) + return; + + tx += x(); + ty += y(); + + if (hasBoxDecorations() && (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection)) + paintBoxDecorations(paintInfo, tx, ty); + + if (paintInfo.phase == PaintPhaseMask) { + paintMask(paintInfo, tx, ty); + return; + } + + if (!m_frameView || paintInfo.phase != PaintPhaseForeground || style()->visibility() != VISIBLE) + return; + +#if PLATFORM(MAC) + if (style()->highlight() != nullAtom && !paintInfo.context->paintingDisabled()) + paintCustomHighlight(tx - x(), ty - y(), style()->highlight(), true); +#endif + + if (style()->hasBorderRadius()) { + IntRect borderRect = IntRect(tx, ty, width(), height()); + + if (borderRect.isEmpty()) + return; + + // Push a clip if we have a border radius, since we want to round the foreground content that gets painted. + paintInfo.context->save(); + + IntSize topLeft, topRight, bottomLeft, bottomRight; + style()->getBorderRadiiForRect(borderRect, topLeft, topRight, bottomLeft, bottomRight); + + paintInfo.context->addRoundedRectClip(borderRect, topLeft, topRight, bottomLeft, bottomRight); + } + + if (m_widget) { + // Tell the widget to paint now. This is the only time the widget is allowed + // to paint itself. That way it will composite properly with z-indexed layers. + if (m_substituteImage) + paintInfo.context->drawImage(m_substituteImage.get(), style()->colorSpace(), m_widget->frameRect()); + else { + IntPoint widgetLocation = m_widget->frameRect().location(); + IntPoint paintLocation(tx + borderLeft() + paddingLeft(), ty + borderTop() + paddingTop()); + IntRect paintRect = paintInfo.rect; + + IntSize paintOffset = paintLocation - widgetLocation; + // When painting widgets into compositing layers, tx and ty are relative to the enclosing compositing layer, + // not the root. In this case, shift the CTM and adjust the paintRect to be root-relative to fix plug-in drawing. + if (!paintOffset.isZero()) { + paintInfo.context->translate(paintOffset); + paintRect.move(-paintOffset); + } + m_widget->paint(paintInfo.context, paintRect); + + if (!paintOffset.isZero()) + paintInfo.context->translate(-paintOffset); + } + + if (m_widget->isFrameView()) { + FrameView* frameView = static_cast<FrameView*>(m_widget.get()); + bool runOverlapTests = !frameView->useSlowRepaintsIfNotOverlapped() || frameView->hasCompositedContentIncludingDescendants(); + if (paintInfo.overlapTestRequests && runOverlapTests) { + ASSERT(!paintInfo.overlapTestRequests->contains(this)); + paintInfo.overlapTestRequests->set(this, m_widget->frameRect()); + } + } + } + + if (style()->hasBorderRadius()) + paintInfo.context->restore(); + + // Paint a partially transparent wash over selected widgets. + if (isSelected() && !document()->printing()) { + // FIXME: selectionRect() is in absolute, not painting coordinates. + paintInfo.context->fillRect(selectionRect(), selectionBackgroundColor(), style()->colorSpace()); + } +} + +void RenderWidget::setOverlapTestResult(bool isOverlapped) +{ + ASSERT(m_widget); + ASSERT(m_widget->isFrameView()); + static_cast<FrameView*>(m_widget.get())->setIsOverlapped(isOverlapped); +} + +void RenderWidget::deref(RenderArena *arena) +{ + if (--m_refCount <= 0) + arenaDelete(arena, this); +} + +void RenderWidget::updateWidgetPosition() +{ + if (!m_widget || !node()) // Check the node in case destroy() has been called. + return; + + // FIXME: This doesn't work correctly with transforms. + FloatPoint absPos = localToAbsolute(); + absPos.move(borderLeft() + paddingLeft(), borderTop() + paddingTop()); + + int w = width() - borderAndPaddingWidth(); + int h = height() - borderAndPaddingHeight(); + + bool boundsChanged = setWidgetGeometry(IntRect(absPos.x(), absPos.y(), w, h)); + + // if the frame bounds got changed, or if view needs layout (possibly indicating + // content size is wrong) we have to do a layout to set the right widget size + if (m_widget && m_widget->isFrameView()) { + FrameView* frameView = static_cast<FrameView*>(m_widget.get()); + // Check the frame's page to make sure that the frame isn't in the process of being destroyed. + if ((boundsChanged || frameView->needsLayout()) && frameView->frame()->page()) + frameView->layout(); + } +} + +void RenderWidget::widgetPositionsUpdated() +{ + if (!m_widget) + return; + m_widget->widgetPositionsUpdated(); +} + +IntRect RenderWidget::windowClipRect() const +{ + if (!m_frameView) + return IntRect(); + + return intersection(m_frameView->contentsToWindow(m_clipRect), m_frameView->windowClipRect()); +} + +void RenderWidget::setSelectionState(SelectionState state) +{ + if (selectionState() != state) { + RenderReplaced::setSelectionState(state); + if (m_widget) + m_widget->setIsSelected(isSelected()); + } +} + +void RenderWidget::clearWidget() +{ + m_widget = 0; +} + +RenderWidget* RenderWidget::find(const Widget* widget) +{ + return widgetRendererMap().get(widget); +} + +bool RenderWidget::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction action) +{ + bool hadResult = result.innerNode(); + bool inside = RenderReplaced::nodeAtPoint(request, result, x, y, tx, ty, action); + + // Check to see if we are really over the widget itself (and not just in the border/padding area). + if ((inside || result.isRectBasedTest()) && !hadResult && result.innerNode() == node()) + result.setIsOverWidget(contentBoxRect().contains(result.localPoint())); + return inside; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderWidget.h b/Source/WebCore/rendering/RenderWidget.h new file mode 100644 index 0000000..d2ec096 --- /dev/null +++ b/Source/WebCore/rendering/RenderWidget.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * Copyright (C) 2004, 2005, 2006, 2009, 2010 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderWidget_h +#define RenderWidget_h + +#include "OverlapTestRequestClient.h" +#include "RenderReplaced.h" +#include "Widget.h" + +namespace WebCore { + +class RenderWidget : public RenderReplaced, private OverlapTestRequestClient { +public: + virtual ~RenderWidget(); + + Widget* widget() const { return m_widget.get(); } + virtual void setWidget(PassRefPtr<Widget>); + + static RenderWidget* find(const Widget*); + + void updateWidgetPosition(); + void widgetPositionsUpdated(); + IntRect windowClipRect() const; + + void showSubstituteImage(PassRefPtr<Image>); + + void notifyWidget(WidgetNotification); + + static void suspendWidgetHierarchyUpdates(); + static void resumeWidgetHierarchyUpdates(); + + RenderArena* ref() { ++m_refCount; return renderArena(); } + void deref(RenderArena*); + +protected: + RenderWidget(Node*); + + FrameView* frameView() const { return m_frameView; } + + void clearWidget(); + + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + virtual void layout(); + virtual void paint(PaintInfo&, int x, int y); + +private: + virtual bool isWidget() const { return true; } + + virtual void destroy(); + virtual void setSelectionState(SelectionState); + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + virtual void setOverlapTestResult(bool); + + bool setWidgetGeometry(const IntRect&); + + RefPtr<Widget> m_widget; + RefPtr<Image> m_substituteImage; + FrameView* m_frameView; + IntRect m_clipRect; // The rectangle needs to remain correct after scrolling, so it is stored in content view coordinates, and not clipped to window. + int m_refCount; +}; + +inline RenderWidget* toRenderWidget(RenderObject* object) +{ + ASSERT(!object || object->isWidget()); + return static_cast<RenderWidget*>(object); +} + +inline const RenderWidget* toRenderWidget(const RenderObject* object) +{ + ASSERT(!object || object->isWidget()); + return static_cast<const RenderWidget*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderWidget(const RenderWidget*); + +} // namespace WebCore + +#endif // RenderWidget_h diff --git a/Source/WebCore/rendering/RenderWidgetProtector.h b/Source/WebCore/rendering/RenderWidgetProtector.h new file mode 100644 index 0000000..788304c --- /dev/null +++ b/Source/WebCore/rendering/RenderWidgetProtector.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderWidgetProtector_h +#define RenderWidgetProtector_h + +#include "RenderWidget.h" + +namespace WebCore { + +class RenderWidgetProtector : private Noncopyable { +public: + RenderWidgetProtector(RenderWidget* object) + : m_object(object) + , m_arena(object->ref()) + { + } + + ~RenderWidgetProtector() + { + m_object->deref(m_arena); + } + +private: + RenderWidget* m_object; + RenderArena* m_arena; +}; + +} + +#endif // RenderWidgetProtector_h diff --git a/Source/WebCore/rendering/RenderWordBreak.cpp b/Source/WebCore/rendering/RenderWordBreak.cpp new file mode 100644 index 0000000..a620560 --- /dev/null +++ b/Source/WebCore/rendering/RenderWordBreak.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "config.h" +#include "RenderWordBreak.h" + +#include "HTMLElement.h" + +namespace WebCore { + +RenderWordBreak::RenderWordBreak(HTMLElement* element) + : RenderText(element, StringImpl::empty()) +{ +} + +const char* RenderWordBreak::renderName() const +{ + return "RenderWordBreak"; +} + +bool RenderWordBreak::isWordBreak() const +{ + return true; +} + +} diff --git a/Source/WebCore/rendering/RenderWordBreak.h b/Source/WebCore/rendering/RenderWordBreak.h new file mode 100644 index 0000000..db31eec --- /dev/null +++ b/Source/WebCore/rendering/RenderWordBreak.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef RenderWordBreak_h +#define RenderWordBreak_h + +#include "RenderText.h" + +namespace WebCore { + +class HTMLElement; + +class RenderWordBreak : public RenderText { +public: + explicit RenderWordBreak(HTMLElement*); + + virtual const char* renderName() const; + virtual bool isWordBreak() const; +}; + +} + +#endif diff --git a/Source/WebCore/rendering/RenderingAllInOne.cpp b/Source/WebCore/rendering/RenderingAllInOne.cpp new file mode 100644 index 0000000..37ba704 --- /dev/null +++ b/Source/WebCore/rendering/RenderingAllInOne.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2010 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// This all-in-one cpp file cuts down on template bloat to allow us to build our Windows release build. + +#include "AutoTableLayout.cpp" +#include "BidiRun.cpp" +#include "CounterNode.cpp" +#include "EllipsisBox.cpp" +#include "FixedTableLayout.cpp" +#include "HitTestResult.cpp" +#include "InlineBox.cpp" +#include "InlineFlowBox.cpp" +#include "InlineTextBox.cpp" +#include "LayoutState.cpp" +#include "MediaControlElements.cpp" +#include "PointerEventsHitRules.cpp" +#include "RenderApplet.cpp" +#include "RenderArena.cpp" +#include "RenderBR.cpp" +#include "RenderBlock.cpp" +#include "RenderBlockLineLayout.cpp" +#include "RenderBox.cpp" +#include "RenderBoxModelObject.cpp" +#include "RenderButton.cpp" +#include "RenderCounter.cpp" +#include "RenderDataGrid.cpp" +#include "RenderDetails.cpp" +#include "RenderDetailsMarker.cpp" +#include "RenderEmbeddedObject.cpp" +#include "RenderFieldset.cpp" +#include "RenderFileUploadControl.cpp" +#include "RenderFlexibleBox.cpp" +#include "RenderForeignObject.cpp" +#include "RenderFrame.cpp" +#include "RenderFrameBase.cpp" +#include "RenderFrameSet.cpp" +#include "RenderHTMLCanvas.cpp" +#include "RenderIFrame.cpp" +#include "RenderImage.cpp" +#include "RenderImageResource.cpp" +#include "RenderImageResourceStyleImage.cpp" +#include "RenderIndicator.cpp" +#include "RenderInline.cpp" +#include "RenderLayer.cpp" +#include "RenderLayerCompositor.cpp" +#include "RenderLineBoxList.cpp" +#include "RenderListBox.cpp" +#include "RenderListItem.cpp" +#include "RenderListMarker.cpp" +#include "RenderMarquee.cpp" +#include "RenderMedia.cpp" +#include "RenderMediaControls.cpp" +#include "RenderMenuList.cpp" +#include "RenderMeter.cpp" +#include "RenderObject.cpp" +#include "RenderObjectChildList.cpp" +#include "RenderPart.cpp" +#include "RenderProgress.cpp" +#include "RenderReplaced.cpp" +#include "RenderReplica.cpp" +#include "RenderRuby.cpp" +#include "RenderRubyBase.cpp" +#include "RenderRubyRun.cpp" +#include "RenderRubyText.cpp" +#include "RenderScrollbar.cpp" +#include "RenderScrollbarPart.cpp" +#include "RenderScrollbarTheme.cpp" +#include "RenderSlider.cpp" +#include "RenderSummary.cpp" +#include "RenderTable.cpp" +#include "RenderTableCell.cpp" +#include "RenderTableCol.cpp" +#include "RenderTableRow.cpp" +#include "RenderTableSection.cpp" +#include "RenderText.cpp" +#include "RenderTextControl.cpp" +#include "RenderTextControlMultiLine.cpp" +#include "RenderTextControlSingleLine.cpp" +#include "RenderTextFragment.cpp" +#include "RenderTheme.cpp" +#include "RenderThemeWin.cpp" +#include "RenderTreeAsText.cpp" +#include "RenderVideo.cpp" +#include "RenderView.cpp" +#include "RenderWidget.cpp" +#include "RenderWordBreak.cpp" +#include "RootInlineBox.cpp" +#include "ScrollBehavior.cpp" +#include "ShadowElement.cpp" +#include "TextControlInnerElements.cpp" +#include "TransformState.cpp" +#include "break_lines.cpp" diff --git a/Source/WebCore/rendering/RootInlineBox.cpp b/Source/WebCore/rendering/RootInlineBox.cpp new file mode 100644 index 0000000..710224e --- /dev/null +++ b/Source/WebCore/rendering/RootInlineBox.cpp @@ -0,0 +1,557 @@ +/* + * Copyright (C) 2003, 2006, 2008 Apple Inc. All rights reserved. + * + * 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 "RootInlineBox.h" + +#include "BidiResolver.h" +#include "Chrome.h" +#include "ChromeClient.h" +#include "Document.h" +#include "EllipsisBox.h" +#include "Frame.h" +#include "GraphicsContext.h" +#include "HitTestResult.h" +#include "Page.h" +#include "RenderArena.h" +#include "RenderBlock.h" + +using namespace std; + +namespace WebCore { + +typedef WTF::HashMap<const RootInlineBox*, EllipsisBox*> EllipsisBoxMap; +static EllipsisBoxMap* gEllipsisBoxMap = 0; + +RootInlineBox::RootInlineBox(RenderBlock* block) + : InlineFlowBox(block) + , m_lineBreakObj(0) + , m_lineBreakPos(0) + , m_lineTop(0) + , m_lineBottom(0) + , m_paginationStrut(0) + , m_blockLogicalHeight(0) + , m_baselineType(AlphabeticBaseline) + , m_hasAnnotationsBefore(false) + , m_hasAnnotationsAfter(false) +{ + setIsHorizontal(block->style()->isHorizontalWritingMode()); +} + + +void RootInlineBox::destroy(RenderArena* arena) +{ + detachEllipsisBox(arena); + InlineFlowBox::destroy(arena); +} + +void RootInlineBox::detachEllipsisBox(RenderArena* arena) +{ + if (hasEllipsisBox()) { + EllipsisBox* box = gEllipsisBoxMap->take(this); + box->setParent(0); + box->destroy(arena); + setHasEllipsisBox(false); + } +} + +RenderLineBoxList* RootInlineBox::rendererLineBoxes() const +{ + return block()->lineBoxes(); +} + +void RootInlineBox::clearTruncation() +{ + if (hasEllipsisBox()) { + detachEllipsisBox(renderer()->renderArena()); + InlineFlowBox::clearTruncation(); + } +} + +bool RootInlineBox::canAccommodateEllipsis(bool ltr, int blockEdge, int lineBoxEdge, int ellipsisWidth) +{ + // First sanity-check the unoverflowed width of the whole line to see if there is sufficient room. + int delta = ltr ? lineBoxEdge - blockEdge : blockEdge - lineBoxEdge; + if (logicalWidth() - delta < ellipsisWidth) + return false; + + // Next iterate over all the line boxes on the line. If we find a replaced element that intersects + // then we refuse to accommodate the ellipsis. Otherwise we're ok. + return InlineFlowBox::canAccommodateEllipsis(ltr, blockEdge, ellipsisWidth); +} + +void RootInlineBox::placeEllipsis(const AtomicString& ellipsisStr, bool ltr, int blockLeftEdge, int blockRightEdge, int ellipsisWidth, + InlineBox* markupBox) +{ + // Create an ellipsis box. + EllipsisBox* ellipsisBox = new (renderer()->renderArena()) EllipsisBox(renderer(), ellipsisStr, this, + ellipsisWidth - (markupBox ? markupBox->logicalWidth() : 0), logicalHeight(), + y(), !prevRootBox(), isHorizontal(), markupBox); + + if (!gEllipsisBoxMap) + gEllipsisBoxMap = new EllipsisBoxMap(); + gEllipsisBoxMap->add(this, ellipsisBox); + setHasEllipsisBox(true); + + // FIXME: Do we need an RTL version of this? + if (ltr && (x() + logicalWidth() + ellipsisWidth) <= blockRightEdge) { + ellipsisBox->m_x = x() + logicalWidth(); + return; + } + + // Now attempt to find the nearest glyph horizontally and place just to the right (or left in RTL) + // of that glyph. Mark all of the objects that intersect the ellipsis box as not painting (as being + // truncated). + bool foundBox = false; + ellipsisBox->m_x = placeEllipsisBox(ltr, blockLeftEdge, blockRightEdge, ellipsisWidth, foundBox); +} + +int RootInlineBox::placeEllipsisBox(bool ltr, int blockLeftEdge, int blockRightEdge, int ellipsisWidth, bool& foundBox) +{ + int result = InlineFlowBox::placeEllipsisBox(ltr, blockLeftEdge, blockRightEdge, ellipsisWidth, foundBox); + if (result == -1) + result = ltr ? blockRightEdge - ellipsisWidth : blockLeftEdge; + return result; +} + +void RootInlineBox::paintEllipsisBox(PaintInfo& paintInfo, int tx, int ty) const +{ + if (hasEllipsisBox() && paintInfo.shouldPaintWithinRoot(renderer()) && renderer()->style()->visibility() == VISIBLE + && paintInfo.phase == PaintPhaseForeground) + ellipsisBox()->paint(paintInfo, tx, ty); +} + +#if PLATFORM(MAC) + +void RootInlineBox::addHighlightOverflow() +{ + Frame* frame = renderer()->frame(); + if (!frame) + return; + Page* page = frame->page(); + if (!page) + return; + + // Highlight acts as a selection inflation. + FloatRect rootRect(0, selectionTop(), logicalWidth(), selectionHeight()); + IntRect inflatedRect = enclosingIntRect(page->chrome()->client()->customHighlightRect(renderer()->node(), renderer()->style()->highlight(), rootRect)); + setOverflowFromLogicalRects(inflatedRect, inflatedRect); +} + +void RootInlineBox::paintCustomHighlight(PaintInfo& paintInfo, int tx, int ty, const AtomicString& highlightType) +{ + if (!paintInfo.shouldPaintWithinRoot(renderer()) || renderer()->style()->visibility() != VISIBLE || paintInfo.phase != PaintPhaseForeground) + return; + + Frame* frame = renderer()->frame(); + if (!frame) + return; + Page* page = frame->page(); + if (!page) + return; + + // Get the inflated rect so that we can properly hit test. + FloatRect rootRect(tx + x(), ty + selectionTop(), logicalWidth(), selectionHeight()); + FloatRect inflatedRect = page->chrome()->client()->customHighlightRect(renderer()->node(), highlightType, rootRect); + if (inflatedRect.intersects(paintInfo.rect)) + page->chrome()->client()->paintCustomHighlight(renderer()->node(), highlightType, rootRect, rootRect, false, true); +} + +#endif + +void RootInlineBox::paint(PaintInfo& paintInfo, int tx, int ty) +{ + InlineFlowBox::paint(paintInfo, tx, ty); + paintEllipsisBox(paintInfo, tx, ty); +#if PLATFORM(MAC) + RenderStyle* styleToUse = renderer()->style(m_firstLine); + if (styleToUse->highlight() != nullAtom && !paintInfo.context->paintingDisabled()) + paintCustomHighlight(paintInfo, tx, ty, styleToUse->highlight()); +#endif +} + +bool RootInlineBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty) +{ + if (hasEllipsisBox() && visibleToHitTesting()) { + if (ellipsisBox()->nodeAtPoint(request, result, x, y, tx, ty)) { + renderer()->updateHitTestResult(result, IntPoint(x - tx, y - ty)); + return true; + } + } + return InlineFlowBox::nodeAtPoint(request, result, x, y, tx, ty); +} + +void RootInlineBox::adjustPosition(int dx, int dy) +{ + InlineFlowBox::adjustPosition(dx, dy); + int blockDirectionDelta = isHorizontal() ? dy : dx; + m_lineTop += blockDirectionDelta; + m_lineBottom += blockDirectionDelta; + m_blockLogicalHeight += blockDirectionDelta; +} + +void RootInlineBox::childRemoved(InlineBox* box) +{ + if (box->renderer() == m_lineBreakObj) + setLineBreakInfo(0, 0, BidiStatus()); + + for (RootInlineBox* prev = prevRootBox(); prev && prev->lineBreakObj() == box->renderer(); prev = prev->prevRootBox()) { + prev->setLineBreakInfo(0, 0, BidiStatus()); + prev->markDirty(); + } +} + +int RootInlineBox::alignBoxesInBlockDirection(int heightOfBlock, GlyphOverflowAndFallbackFontsMap& textBoxDataMap, VerticalPositionCache& verticalPositionCache) +{ +#if ENABLE(SVG) + // SVG will handle vertical alignment on its own. + if (isSVGRootInlineBox()) + return 0; +#endif + + int maxPositionTop = 0; + int maxPositionBottom = 0; + int maxAscent = 0; + int maxDescent = 0; + bool setMaxAscent = false; + bool setMaxDescent = false; + + // Figure out if we're in no-quirks mode. + bool noQuirksMode = renderer()->document()->inNoQuirksMode(); + + m_baselineType = requiresIdeographicBaseline(textBoxDataMap) ? IdeographicBaseline : AlphabeticBaseline; + + computeLogicalBoxHeights(maxPositionTop, maxPositionBottom, maxAscent, maxDescent, setMaxAscent, setMaxDescent, noQuirksMode, + textBoxDataMap, baselineType(), verticalPositionCache); + + if (maxAscent + maxDescent < max(maxPositionTop, maxPositionBottom)) + adjustMaxAscentAndDescent(maxAscent, maxDescent, maxPositionTop, maxPositionBottom); + + int maxHeight = maxAscent + maxDescent; + int lineTop = heightOfBlock; + int lineBottom = heightOfBlock; + int lineTopIncludingMargins = heightOfBlock; + int lineBottomIncludingMargins = heightOfBlock; + bool setLineTop = false; + bool hasAnnotationsBefore = false; + bool hasAnnotationsAfter = false; + placeBoxesInBlockDirection(heightOfBlock, maxHeight, maxAscent, noQuirksMode, lineTop, lineBottom, setLineTop, + lineTopIncludingMargins, lineBottomIncludingMargins, hasAnnotationsBefore, hasAnnotationsAfter, baselineType()); + m_hasAnnotationsBefore = hasAnnotationsBefore; + m_hasAnnotationsAfter = hasAnnotationsAfter; + setLineTopBottomPositions(lineTop, lineBottom); + + int annotationsAdjustment = beforeAnnotationsAdjustment(); + if (annotationsAdjustment) { + // FIXME: Need to handle pagination here. We might have to move to the next page/column as a result of the + // ruby expansion. + adjustBlockDirectionPosition(annotationsAdjustment); + heightOfBlock += annotationsAdjustment; + } + + // Detect integer overflow. + if (heightOfBlock > numeric_limits<int>::max() - maxHeight) + return numeric_limits<int>::max(); + + return heightOfBlock + maxHeight; +} + +int RootInlineBox::beforeAnnotationsAdjustment() const +{ + int result = 0; + + if (!renderer()->style()->isFlippedLinesWritingMode()) { + // Annotations under the previous line may push us down. + if (prevRootBox() && prevRootBox()->hasAnnotationsAfter()) + result = prevRootBox()->computeUnderAnnotationAdjustment(lineTop()); + + if (!hasAnnotationsBefore()) + return result; + + // Annotations over this line may push us further down. + int highestAllowedPosition = prevRootBox() ? min(prevRootBox()->lineBottom(), lineTop()) + result : block()->borderBefore(); + result = computeOverAnnotationAdjustment(highestAllowedPosition); + } else { + // Annotations under this line may push us up. + if (hasAnnotationsBefore()) + result = computeUnderAnnotationAdjustment(prevRootBox() ? prevRootBox()->lineBottom() : block()->borderBefore()); + + if (!prevRootBox() || !prevRootBox()->hasAnnotationsAfter()) + return result; + + // We have to compute the expansion for annotations over the previous line to see how much we should move. + int lowestAllowedPosition = max(prevRootBox()->lineBottom(), lineTop()) - result; + result = prevRootBox()->computeOverAnnotationAdjustment(lowestAllowedPosition); + } + + return result; +} + +GapRects RootInlineBox::lineSelectionGap(RenderBlock* rootBlock, const IntPoint& rootBlockPhysicalPosition, const IntSize& offsetFromRootBlock, + int selTop, int selHeight, const PaintInfo* paintInfo) +{ + RenderObject::SelectionState lineState = selectionState(); + + bool leftGap, rightGap; + block()->getSelectionGapInfo(lineState, leftGap, rightGap); + + GapRects result; + + InlineBox* firstBox = firstSelectedBox(); + InlineBox* lastBox = lastSelectedBox(); + if (leftGap) + result.uniteLeft(block()->logicalLeftSelectionGap(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, + firstBox->parent()->renderer(), firstBox->logicalLeft(), selTop, selHeight, paintInfo)); + if (rightGap) + result.uniteRight(block()->logicalRightSelectionGap(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, + lastBox->parent()->renderer(), lastBox->logicalRight(), selTop, selHeight, paintInfo)); + + // When dealing with bidi text, a non-contiguous selection region is possible. + // e.g. The logical text aaaAAAbbb (capitals denote RTL text and non-capitals LTR) is layed out + // visually as 3 text runs |aaa|bbb|AAA| if we select 4 characters from the start of the text the + // selection will look like (underline denotes selection): + // |aaa|bbb|AAA| + // ___ _ + // We can see that the |bbb| run is not part of the selection while the runs around it are. + if (firstBox && firstBox != lastBox) { + // Now fill in any gaps on the line that occurred between two selected elements. + int lastLogicalLeft = firstBox->logicalRight(); + bool isPreviousBoxSelected = firstBox->selectionState() != RenderObject::SelectionNone; + for (InlineBox* box = firstBox->nextLeafChild(); box; box = box->nextLeafChild()) { + if (box->selectionState() != RenderObject::SelectionNone) { + IntRect logicalRect(lastLogicalLeft, selTop, box->logicalLeft() - lastLogicalLeft, selHeight); + logicalRect.move(renderer()->style()->isHorizontalWritingMode() ? offsetFromRootBlock : IntSize(offsetFromRootBlock.height(), offsetFromRootBlock.width())); + IntRect gapRect = rootBlock->logicalRectToPhysicalRect(rootBlockPhysicalPosition, logicalRect); + if (isPreviousBoxSelected && gapRect.width() > 0 && gapRect.height() > 0) { + if (paintInfo && box->parent()->renderer()->style()->visibility() == VISIBLE) + paintInfo->context->fillRect(gapRect, box->parent()->renderer()->selectionBackgroundColor(), box->parent()->renderer()->style()->colorSpace()); + // VisibleSelection may be non-contiguous, see comment above. + result.uniteCenter(gapRect); + } + lastLogicalLeft = box->logicalRight(); + } + if (box == lastBox) + break; + isPreviousBoxSelected = box->selectionState() != RenderObject::SelectionNone; + } + } + + return result; +} + +void RootInlineBox::setHasSelectedChildren(bool b) +{ + if (m_hasSelectedChildren == b) + return; + m_hasSelectedChildren = b; +} + +RenderObject::SelectionState RootInlineBox::selectionState() +{ + // Walk over all of the selected boxes. + RenderObject::SelectionState state = RenderObject::SelectionNone; + for (InlineBox* box = firstLeafChild(); box; box = box->nextLeafChild()) { + RenderObject::SelectionState boxState = box->selectionState(); + if ((boxState == RenderObject::SelectionStart && state == RenderObject::SelectionEnd) || + (boxState == RenderObject::SelectionEnd && state == RenderObject::SelectionStart)) + state = RenderObject::SelectionBoth; + else if (state == RenderObject::SelectionNone || + ((boxState == RenderObject::SelectionStart || boxState == RenderObject::SelectionEnd) && + (state == RenderObject::SelectionNone || state == RenderObject::SelectionInside))) + state = boxState; + if (state == RenderObject::SelectionBoth) + break; + } + + return state; +} + +InlineBox* RootInlineBox::firstSelectedBox() +{ + for (InlineBox* box = firstLeafChild(); box; box = box->nextLeafChild()) { + if (box->selectionState() != RenderObject::SelectionNone) + return box; + } + + return 0; +} + +InlineBox* RootInlineBox::lastSelectedBox() +{ + for (InlineBox* box = lastLeafChild(); box; box = box->prevLeafChild()) { + if (box->selectionState() != RenderObject::SelectionNone) + return box; + } + + return 0; +} + +int RootInlineBox::selectionTop() const +{ + int selectionTop = m_lineTop; + + if (m_hasAnnotationsBefore) + selectionTop -= !renderer()->style()->isFlippedLinesWritingMode() ? computeOverAnnotationAdjustment(m_lineTop) : computeUnderAnnotationAdjustment(m_lineTop); + + if (renderer()->style()->isFlippedLinesWritingMode()) + return selectionTop; + + int prevBottom = prevRootBox() ? prevRootBox()->selectionBottom() : block()->borderBefore() + block()->paddingBefore(); + if (prevBottom < selectionTop && block()->containsFloats()) { + // This line has actually been moved further down, probably from a large line-height, but possibly because the + // line was forced to clear floats. If so, let's check the offsets, and only be willing to use the previous + // line's bottom if the offsets are greater on both sides. + int prevLeft = block()->logicalLeftOffsetForLine(prevBottom, false); + int prevRight = block()->logicalRightOffsetForLine(prevBottom, false); + int newLeft = block()->logicalLeftOffsetForLine(selectionTop, false); + int newRight = block()->logicalRightOffsetForLine(selectionTop, false); + if (prevLeft > newLeft || prevRight < newRight) + return selectionTop; + } + + return prevBottom; +} + +int RootInlineBox::selectionBottom() const +{ + int selectionBottom = m_lineBottom; + + if (m_hasAnnotationsAfter) + selectionBottom += !renderer()->style()->isFlippedLinesWritingMode() ? computeUnderAnnotationAdjustment(m_lineBottom) : computeOverAnnotationAdjustment(m_lineBottom); + + if (!renderer()->style()->isFlippedLinesWritingMode() || !nextRootBox()) + return selectionBottom; + + int nextTop = nextRootBox()->selectionTop(); + if (nextTop > selectionBottom && block()->containsFloats()) { + // The next line has actually been moved further over, probably from a large line-height, but possibly because the + // line was forced to clear floats. If so, let's check the offsets, and only be willing to use the next + // line's top if the offsets are greater on both sides. + int nextLeft = block()->logicalLeftOffsetForLine(nextTop, false); + int nextRight = block()->logicalRightOffsetForLine(nextTop, false); + int newLeft = block()->logicalLeftOffsetForLine(selectionBottom, false); + int newRight = block()->logicalRightOffsetForLine(selectionBottom, false); + if (nextLeft > newLeft || nextRight < newRight) + return selectionBottom; + } + + return nextTop; +} + +RenderBlock* RootInlineBox::block() const +{ + return toRenderBlock(renderer()); +} + +static bool isEditableLeaf(InlineBox* leaf) +{ + return leaf && leaf->renderer() && leaf->renderer()->node() && leaf->renderer()->node()->isContentEditable(); +} + +InlineBox* RootInlineBox::closestLeafChildForLogicalLeftPosition(int leftPosition, bool onlyEditableLeaves) +{ + InlineBox* firstLeaf = firstLeafChild(); + InlineBox* lastLeaf = lastLeafChild(); + if (firstLeaf == lastLeaf && (!onlyEditableLeaves || isEditableLeaf(firstLeaf))) + return firstLeaf; + + // Avoid returning a list marker when possible. + if (leftPosition <= firstLeaf->logicalLeft() && !firstLeaf->renderer()->isListMarker() && (!onlyEditableLeaves || isEditableLeaf(firstLeaf))) + // The leftPosition coordinate is less or equal to left edge of the firstLeaf. + // Return it. + return firstLeaf; + + if (leftPosition >= lastLeaf->logicalRight() && !lastLeaf->renderer()->isListMarker() && (!onlyEditableLeaves || isEditableLeaf(lastLeaf))) + // The leftPosition coordinate is greater or equal to right edge of the lastLeaf. + // Return it. + return lastLeaf; + + InlineBox* closestLeaf = 0; + for (InlineBox* leaf = firstLeaf; leaf; leaf = leaf->nextLeafChild()) { + if (!leaf->renderer()->isListMarker() && (!onlyEditableLeaves || isEditableLeaf(leaf))) { + closestLeaf = leaf; + if (leftPosition < leaf->logicalRight()) + // The x coordinate is less than the right edge of the box. + // Return it. + return leaf; + } + } + + return closestLeaf ? closestLeaf : lastLeaf; +} + +BidiStatus RootInlineBox::lineBreakBidiStatus() const +{ + return BidiStatus(m_lineBreakBidiStatusEor, m_lineBreakBidiStatusLastStrong, m_lineBreakBidiStatusLast, m_lineBreakContext); +} + +void RootInlineBox::setLineBreakInfo(RenderObject* obj, unsigned breakPos, const BidiStatus& status) +{ + m_lineBreakObj = obj; + m_lineBreakPos = breakPos; + m_lineBreakBidiStatusEor = status.eor; + m_lineBreakBidiStatusLastStrong = status.lastStrong; + m_lineBreakBidiStatusLast = status.last; + m_lineBreakContext = status.context; +} + +EllipsisBox* RootInlineBox::ellipsisBox() const +{ + if (!hasEllipsisBox()) + return 0; + return gEllipsisBoxMap->get(this); +} + +void RootInlineBox::removeLineBoxFromRenderObject() +{ + block()->lineBoxes()->removeLineBox(this); +} + +void RootInlineBox::extractLineBoxFromRenderObject() +{ + block()->lineBoxes()->extractLineBox(this); +} + +void RootInlineBox::attachLineBoxToRenderObject() +{ + block()->lineBoxes()->attachLineBox(this); +} + +IntRect RootInlineBox::paddedLayoutOverflowRect(int endPadding) const +{ + IntRect lineLayoutOverflow = layoutOverflowRect(); + if (!endPadding) + return lineLayoutOverflow; + + if (isHorizontal()) { + if (isLeftToRightDirection()) + lineLayoutOverflow.shiftRightEdgeTo(max(lineLayoutOverflow.right(), logicalRight() + endPadding)); + else + lineLayoutOverflow.shiftLeftEdgeTo(min(lineLayoutOverflow.x(), logicalLeft() - endPadding)); + } else { + if (isLeftToRightDirection()) + lineLayoutOverflow.shiftBottomEdgeTo(max(lineLayoutOverflow.bottom(), logicalRight() + endPadding)); + else + lineLayoutOverflow.shiftTopEdgeTo(min(lineLayoutOverflow.y(), logicalRight() - endPadding)); + } + + return lineLayoutOverflow; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RootInlineBox.h b/Source/WebCore/rendering/RootInlineBox.h new file mode 100644 index 0000000..7c4b15c --- /dev/null +++ b/Source/WebCore/rendering/RootInlineBox.h @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2003, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RootInlineBox_h +#define RootInlineBox_h + +#include "BidiContext.h" +#include "InlineFlowBox.h" + +namespace WebCore { + +class EllipsisBox; +class HitTestResult; + +struct BidiStatus; +struct GapRects; + +class RootInlineBox : public InlineFlowBox { +public: + RootInlineBox(RenderBlock* block); + + virtual void destroy(RenderArena*); + + virtual bool isRootInlineBox() const { return true; } + + void detachEllipsisBox(RenderArena*); + + RootInlineBox* nextRootBox() const { return static_cast<RootInlineBox*>(m_nextLineBox); } + RootInlineBox* prevRootBox() const { return static_cast<RootInlineBox*>(m_prevLineBox); } + + virtual void adjustPosition(int dx, int dy); + + int lineTop() const { return m_lineTop; } + int lineBottom() const { return m_lineBottom; } + + int paginationStrut() const { return m_paginationStrut; } + void setPaginationStrut(int s) { m_paginationStrut = s; } + + int selectionTop() const; + int selectionBottom() const; + int selectionHeight() const { return max(0, selectionBottom() - selectionTop()); } + + int alignBoxesInBlockDirection(int heightOfBlock, GlyphOverflowAndFallbackFontsMap&, VerticalPositionCache&); + void setLineTopBottomPositions(int top, int bottom); + + virtual RenderLineBoxList* rendererLineBoxes() const; + + RenderObject* lineBreakObj() const { return m_lineBreakObj; } + BidiStatus lineBreakBidiStatus() const; + void setLineBreakInfo(RenderObject*, unsigned breakPos, const BidiStatus&); + + unsigned lineBreakPos() const { return m_lineBreakPos; } + void setLineBreakPos(unsigned p) { m_lineBreakPos = p; } + + int blockLogicalHeight() const { return m_blockLogicalHeight; } + void setBlockLogicalHeight(int h) { m_blockLogicalHeight = h; } + + bool endsWithBreak() const { return m_endsWithBreak; } + void setEndsWithBreak(bool b) { m_endsWithBreak = b; } + + void childRemoved(InlineBox* box); + + bool canAccommodateEllipsis(bool ltr, int blockEdge, int lineBoxEdge, int ellipsisWidth); + void placeEllipsis(const AtomicString& ellipsisStr, bool ltr, int blockLeftEdge, int blockRightEdge, int ellipsisWidth, InlineBox* markupBox = 0); + virtual int placeEllipsisBox(bool ltr, int blockLeftEdge, int blockRightEdge, int ellipsisWidth, bool& foundBox); + + EllipsisBox* ellipsisBox() const; + + void paintEllipsisBox(PaintInfo&, int tx, int ty) const; + bool hitTestEllipsisBox(HitTestResult&, int x, int y, int tx, int ty, HitTestAction, bool); + + virtual void clearTruncation(); + + virtual int baselinePosition(FontBaseline baselineType) const { return boxModelObject()->baselinePosition(baselineType, m_firstLine, isHorizontal() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes); } + virtual int lineHeight() const { return boxModelObject()->lineHeight(m_firstLine, isHorizontal() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes); } + +#if PLATFORM(MAC) + void addHighlightOverflow(); + void paintCustomHighlight(PaintInfo&, int tx, int ty, const AtomicString& highlightType); +#endif + + virtual void paint(PaintInfo&, int tx, int ty); + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int, int, int, int); + + bool hasSelectedChildren() const { return m_hasSelectedChildren; } + void setHasSelectedChildren(bool); + + virtual RenderObject::SelectionState selectionState(); + InlineBox* firstSelectedBox(); + InlineBox* lastSelectedBox(); + + GapRects lineSelectionGap(RenderBlock* rootBlock, const IntPoint& rootBlockPhysicalPosition, const IntSize& offsetFromRootBlock, int selTop, int selHeight, const PaintInfo*); + + RenderBlock* block() const; + + InlineBox* closestLeafChildForLogicalLeftPosition(int, bool onlyEditableLeaves = false); + + Vector<RenderBox*>& floats() + { + ASSERT(!isDirty()); + if (!m_floats) + m_floats= adoptPtr(new Vector<RenderBox*>); + return *m_floats; + } + + Vector<RenderBox*>* floatsPtr() { ASSERT(!isDirty()); return m_floats.get(); } + + virtual void extractLineBoxFromRenderObject(); + virtual void attachLineBoxToRenderObject(); + virtual void removeLineBoxFromRenderObject(); + + FontBaseline baselineType() const { return static_cast<FontBaseline>(m_baselineType); } + + bool hasAnnotationsBefore() const { return m_hasAnnotationsBefore; } + bool hasAnnotationsAfter() const { return m_hasAnnotationsAfter; } + + IntRect paddedLayoutOverflowRect(int endPadding) const; + +private: + bool hasEllipsisBox() const { return m_hasEllipsisBoxOrHyphen; } + void setHasEllipsisBox(bool hasEllipsisBox) { m_hasEllipsisBoxOrHyphen = hasEllipsisBox; } + + int beforeAnnotationsAdjustment() const; + + // Where this line ended. The exact object and the position within that object are stored so that + // we can create an InlineIterator beginning just after the end of this line. + RenderObject* m_lineBreakObj; + unsigned m_lineBreakPos; + RefPtr<BidiContext> m_lineBreakContext; + + int m_lineTop; + int m_lineBottom; + + int m_paginationStrut; + + // Floats hanging off the line are pushed into this vector during layout. It is only + // good for as long as the line has not been marked dirty. + OwnPtr<Vector<RenderBox*> > m_floats; + + // The logical height of the block at the end of this line. This is where the next line starts. + int m_blockLogicalHeight; + + // Whether or not this line uses alphabetic or ideographic baselines by default. + unsigned m_baselineType : 1; // FontBaseline + + // If the line contains any ruby runs, then this will be true. + bool m_hasAnnotationsBefore : 1; + bool m_hasAnnotationsAfter : 1; + + WTF::Unicode::Direction m_lineBreakBidiStatusEor : 5; + WTF::Unicode::Direction m_lineBreakBidiStatusLastStrong : 5; + WTF::Unicode::Direction m_lineBreakBidiStatusLast : 5; +}; + +inline void RootInlineBox::setLineTopBottomPositions(int top, int bottom) +{ + m_lineTop = top; + m_lineBottom = bottom; +} + +} // namespace WebCore + +#endif // RootInlineBox_h diff --git a/Source/WebCore/rendering/SVGImageBufferTools.cpp b/Source/WebCore/rendering/SVGImageBufferTools.cpp new file mode 100644 index 0000000..2dc0a6f --- /dev/null +++ b/Source/WebCore/rendering/SVGImageBufferTools.cpp @@ -0,0 +1,128 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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" + +#if ENABLE(SVG) +#include "SVGImageBufferTools.h" + +#include "FrameView.h" +#include "GraphicsContext.h" +#include "RenderObject.h" +#include "RenderSVGContainer.h" +#include "RenderSVGRoot.h" + +namespace WebCore { + +static AffineTransform& currentContentTransformation() +{ + DEFINE_STATIC_LOCAL(AffineTransform, s_currentContentTransformation, ()); + return s_currentContentTransformation; +} + +void SVGImageBufferTools::calculateTransformationToOutermostSVGCoordinateSystem(const RenderObject* renderer, AffineTransform& absoluteTransform) +{ + const RenderObject* current = renderer; + ASSERT(current); + + absoluteTransform = currentContentTransformation(); + while (current) { + absoluteTransform.multiply(current->localToParentTransform()); + if (current->isSVGRoot()) + break; + current = current->parent(); + } +} + +bool SVGImageBufferTools::createImageBuffer(const FloatRect& absoluteTargetRect, const FloatRect& clampedAbsoluteTargetRect, OwnPtr<ImageBuffer>& imageBuffer, ColorSpace colorSpace) +{ + IntSize imageSize(roundedImageBufferSize(clampedAbsoluteTargetRect.size())); + IntSize unclampedImageSize(SVGImageBufferTools::roundedImageBufferSize(absoluteTargetRect.size())); + + // Don't create empty ImageBuffers. + if (imageSize.isEmpty()) + return false; + + OwnPtr<ImageBuffer> image = ImageBuffer::create(imageSize, colorSpace); + if (!image) + return false; + + GraphicsContext* imageContext = image->context(); + ASSERT(imageContext); + + // Compensate rounding effects, as the absolute target rect is using floating-point numbers and the image buffer size is integer. + imageContext->scale(FloatSize(unclampedImageSize.width() / absoluteTargetRect.width(), unclampedImageSize.height() / absoluteTargetRect.height())); + + imageBuffer = image.release(); + return true; +} + +void SVGImageBufferTools::renderSubtreeToImageBuffer(ImageBuffer* image, RenderObject* item, const AffineTransform& subtreeContentTransformation) +{ + ASSERT(item); + ASSERT(image); + ASSERT(image->context()); + + PaintInfo info(image->context(), PaintInfo::infiniteRect(), PaintPhaseForeground, 0, 0, 0); + + AffineTransform& contentTransformation = currentContentTransformation(); + AffineTransform savedContentTransformation = contentTransformation; + contentTransformation.multiply(subtreeContentTransformation); + + item->layoutIfNeeded(); + item->paint(info, 0, 0); + + contentTransformation = savedContentTransformation; +} + +void SVGImageBufferTools::clipToImageBuffer(GraphicsContext* context, const AffineTransform& absoluteTransform, const FloatRect& clampedAbsoluteTargetRect, OwnPtr<ImageBuffer>& imageBuffer) +{ + ASSERT(context); + ASSERT(imageBuffer); + + // The mask image has been created in the absolute coordinate space, as the image should not be scaled. + // So the actual masking process has to be done in the absolute coordinate space as well. + context->concatCTM(absoluteTransform.inverse()); + context->clipToImageBuffer(imageBuffer.get(), clampedAbsoluteTargetRect); + context->concatCTM(absoluteTransform); + + // When nesting resources, with objectBoundingBox as content unit types, there's no use in caching the + // resulting image buffer as the parent resource already caches the result. + if (!currentContentTransformation().isIdentity()) + imageBuffer.clear(); +} + +IntSize SVGImageBufferTools::roundedImageBufferSize(const FloatSize& size) +{ + return IntSize(static_cast<int>(lroundf(size.width())), static_cast<int>(lroundf(size.height()))); +} + +FloatRect SVGImageBufferTools::clampedAbsoluteTargetRectForRenderer(const RenderObject* renderer, const FloatRect& absoluteTargetRect) +{ + ASSERT(renderer); + + const RenderSVGRoot* svgRoot = SVGRenderSupport::findTreeRootObject(renderer); + FloatRect clampedAbsoluteTargetRect = absoluteTargetRect; + clampedAbsoluteTargetRect.intersect(svgRoot->contentBoxRect()); + return clampedAbsoluteTargetRect; +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/SVGImageBufferTools.h b/Source/WebCore/rendering/SVGImageBufferTools.h new file mode 100644 index 0000000..9bcc7a4 --- /dev/null +++ b/Source/WebCore/rendering/SVGImageBufferTools.h @@ -0,0 +1,53 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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 + aint 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. +*/ + +#ifndef SVGImageBufferTools_h +#define SVGImageBufferTools_h + +#if ENABLE(SVG) +#include "ImageBuffer.h" +#include <wtf/Noncopyable.h> + +namespace WebCore { + +class AffineTransform; +class FloatRect; +class FloatSize; +class GraphicsContext; +class RenderObject; + +class SVGImageBufferTools : public Noncopyable { +public: + static bool createImageBuffer(const FloatRect& absoluteTargetRect, const FloatRect& clampedAbsoluteTargetRect, OwnPtr<ImageBuffer>&, ColorSpace); + static void renderSubtreeToImageBuffer(ImageBuffer*, RenderObject*, const AffineTransform&); + static void clipToImageBuffer(GraphicsContext*, const AffineTransform& absoluteTransform, const FloatRect& clampedAbsoluteTargetRect, OwnPtr<ImageBuffer>&); + + static void calculateTransformationToOutermostSVGCoordinateSystem(const RenderObject*, AffineTransform& absoluteTransform); + static FloatRect clampedAbsoluteTargetRectForRenderer(const RenderObject*, const FloatRect& absoluteTargetRect); + static IntSize roundedImageBufferSize(const FloatSize&); + +private: + SVGImageBufferTools() { } + ~SVGImageBufferTools() { } +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/SVGMarkerData.h b/Source/WebCore/rendering/SVGMarkerData.h new file mode 100644 index 0000000..ba11e7e --- /dev/null +++ b/Source/WebCore/rendering/SVGMarkerData.h @@ -0,0 +1,134 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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 + aint 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. +*/ + +#ifndef SVGMarkerData_h +#define SVGMarkerData_h + +#if ENABLE(SVG) +#include "FloatConversion.h" +#include "Path.h" +#include <wtf/MathExtras.h> + +namespace WebCore { + +class RenderSVGResourceMarker; + +class SVGMarkerData { +public: + enum Type { + Unknown = 0, + Start, + Mid, + End + }; + + SVGMarkerData(const Type& type = Unknown, RenderSVGResourceMarker* marker = 0) + : m_type(type) + , m_marker(marker) + { + } + + FloatPoint origin() const { return m_origin; } + RenderSVGResourceMarker* marker() const { return m_marker; } + + float currentAngle() const + { + FloatSize inslopeChange = m_inslopePoints[1] - m_inslopePoints[0]; + FloatSize outslopeChange = m_outslopePoints[1] - m_outslopePoints[0]; + + double inslope = rad2deg(atan2(inslopeChange.height(), inslopeChange.width())); + double outslope = rad2deg(atan2(outslopeChange.height(), outslopeChange.width())); + + double angle = 0; + switch (m_type) { + case Start: + angle = outslope; + break; + case Mid: + angle = (inslope + outslope) / 2; + break; + case End: + angle = inslope; + break; + default: + ASSERT_NOT_REACHED(); + break; + } + + return narrowPrecisionToFloat(angle); + } + + void updateTypeAndMarker(const Type& type, RenderSVGResourceMarker* marker) + { + m_type = type; + m_marker = marker; + } + + void updateOutslope(const FloatPoint& point) + { + m_outslopePoints[0] = m_origin; + m_outslopePoints[1] = point; + } + + void updateMarkerDataForPathElement(const PathElement* element) + { + FloatPoint* points = element->points; + + switch (element->type) { + case PathElementAddQuadCurveToPoint: + // FIXME: https://bugs.webkit.org/show_bug.cgi?id=33115 (PathElementAddQuadCurveToPoint not handled for <marker>) + m_origin = points[1]; + break; + case PathElementAddCurveToPoint: + m_inslopePoints[0] = points[1]; + m_inslopePoints[1] = points[2]; + m_origin = points[2]; + break; + case PathElementMoveToPoint: + m_subpathStart = points[0]; + case PathElementAddLineToPoint: + updateInslope(points[0]); + m_origin = points[0]; + break; + case PathElementCloseSubpath: + updateInslope(points[0]); + m_origin = m_subpathStart; + m_subpathStart = FloatPoint(); + } + } + +private: + void updateInslope(const FloatPoint& point) + { + m_inslopePoints[0] = m_origin; + m_inslopePoints[1] = point; + } + + Type m_type; + RenderSVGResourceMarker* m_marker; + FloatPoint m_origin; + FloatPoint m_subpathStart; + FloatPoint m_inslopePoints[2]; + FloatPoint m_outslopePoints[2]; +}; + +} + +#endif // ENABLE(SVG) +#endif // SVGMarkerData_h diff --git a/Source/WebCore/rendering/SVGMarkerLayoutInfo.cpp b/Source/WebCore/rendering/SVGMarkerLayoutInfo.cpp new file mode 100644 index 0000000..aed756a --- /dev/null +++ b/Source/WebCore/rendering/SVGMarkerLayoutInfo.cpp @@ -0,0 +1,123 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> + 2004, 2005, 2008 Rob Buis <buis@kde.org> + 2005, 2007 Eric Seidel <eric@webkit.org> + 2009 Google, Inc. + + 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 + aint 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" + +#if ENABLE(SVG) +#include "SVGMarkerLayoutInfo.h" + +#include "RenderSVGResourceMarker.h" + +namespace WebCore { + +SVGMarkerLayoutInfo::SVGMarkerLayoutInfo() + : m_midMarker(0) + , m_elementIndex(0) + , m_strokeWidth(0) +{ +} + +SVGMarkerLayoutInfo::~SVGMarkerLayoutInfo() +{ +} + +static inline void processStartAndMidMarkers(void* infoPtr, const PathElement* element) +{ + SVGMarkerLayoutInfo& info = *reinterpret_cast<SVGMarkerLayoutInfo*>(infoPtr); + SVGMarkerData& markerData = info.markerData(); + int& elementIndex = info.elementIndex(); + + // First update the outslope for the previous element + markerData.updateOutslope(element->points[0]); + + // Draw the marker for the previous element + RenderSVGResourceMarker* marker = markerData.marker(); + if (elementIndex > 0 && marker) + info.addLayoutedMarker(marker, markerData.origin(), markerData.currentAngle()); + + // Update our marker data for this element + markerData.updateMarkerDataForPathElement(element); + + // After drawing the start marker, switch to drawing mid markers + if (elementIndex == 1) + markerData.updateTypeAndMarker(SVGMarkerData::Mid, info.midMarker()); + + ++elementIndex; +} + +FloatRect SVGMarkerLayoutInfo::calculateBoundaries(RenderSVGResourceMarker* startMarker, RenderSVGResourceMarker* midMarker, RenderSVGResourceMarker* endMarker, float strokeWidth, const Path& path) +{ + m_layout.clear(); + m_midMarker = midMarker; + m_strokeWidth = strokeWidth; + m_elementIndex = 0; + m_markerData = SVGMarkerData(SVGMarkerData::Start, startMarker); + path.apply(this, processStartAndMidMarkers); + + if (endMarker) { + m_markerData.updateTypeAndMarker(SVGMarkerData::End, endMarker); + addLayoutedMarker(endMarker, m_markerData.origin(), m_markerData.currentAngle()); + } + + if (m_layout.isEmpty()) + return FloatRect(); + + Vector<MarkerLayout>::iterator it = m_layout.begin(); + Vector<MarkerLayout>::iterator end = m_layout.end(); + + FloatRect bounds; + for (; it != end; ++it) { + MarkerLayout& layout = *it; + + RenderSVGResourceMarker* markerContent = layout.marker; + ASSERT(markerContent); + + bounds.unite(markerContent->markerBoundaries(layout.matrix)); + } + + return bounds; +} + +void SVGMarkerLayoutInfo::drawMarkers(PaintInfo& paintInfo) +{ + if (m_layout.isEmpty()) + return; + + Vector<MarkerLayout>::iterator it = m_layout.begin(); + Vector<MarkerLayout>::iterator end = m_layout.end(); + + for (; it != end; ++it) { + MarkerLayout& layout = *it; + layout.marker->draw(paintInfo, layout.matrix); + } +} + +void SVGMarkerLayoutInfo::addLayoutedMarker(RenderSVGResourceMarker* marker, const FloatPoint& origin, float angle) +{ + ASSERT(marker); + m_layout.append(MarkerLayout(marker, marker->markerTransformation(origin, angle, m_strokeWidth))); +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/SVGMarkerLayoutInfo.h b/Source/WebCore/rendering/SVGMarkerLayoutInfo.h new file mode 100644 index 0000000..8f59703 --- /dev/null +++ b/Source/WebCore/rendering/SVGMarkerLayoutInfo.h @@ -0,0 +1,74 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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 + aint 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. +*/ + +#ifndef SVGMarkerLayoutInfo_h +#define SVGMarkerLayoutInfo_h + +#if ENABLE(SVG) +#include "RenderObject.h" +#include "SVGMarkerData.h" +#include <wtf/Noncopyable.h> + +namespace WebCore { + +class Path; +class RenderSVGResourceMarker; + +struct MarkerLayout { + MarkerLayout(RenderSVGResourceMarker* markerObj = 0, AffineTransform matrixObj = AffineTransform()) + : marker(markerObj) + , matrix(matrixObj) + { + ASSERT(marker); + } + + RenderSVGResourceMarker* marker; + AffineTransform matrix; +}; + +class SVGMarkerLayoutInfo : public Noncopyable { +public: + SVGMarkerLayoutInfo(); + ~SVGMarkerLayoutInfo(); + + FloatRect calculateBoundaries(RenderSVGResourceMarker* startMarker, RenderSVGResourceMarker* midMarker, RenderSVGResourceMarker* endMarker, float strokeWidth, const Path&); + void drawMarkers(PaintInfo&); + + // Used by static inline helper functions in SVGMarkerLayoutInfo.cpp + SVGMarkerData& markerData() { return m_markerData; } + RenderSVGResourceMarker* midMarker() const { return m_midMarker; } + int& elementIndex() { return m_elementIndex; } + void addLayoutedMarker(RenderSVGResourceMarker*, const FloatPoint& origin, float angle); + +private: + RenderSVGResourceMarker* m_midMarker; + + // Used while layouting markers + int m_elementIndex; + SVGMarkerData m_markerData; + float m_strokeWidth; + + // Holds the final computed result + Vector<MarkerLayout> m_layout; +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/SVGRenderSupport.cpp b/Source/WebCore/rendering/SVGRenderSupport.cpp new file mode 100644 index 0000000..cbde49b --- /dev/null +++ b/Source/WebCore/rendering/SVGRenderSupport.cpp @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2007, 2008 Rob Buis <buis@kde.org> + * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * (C) 2007 Eric Seidel <eric@webkit.org> + * (C) 2009 Google, Inc. All rights reserved. + * (C) 2009 Dirk Schulze <krit@webkit.org> + * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "SVGRenderSupport.h" + +#include "FrameView.h" +#include "ImageBuffer.h" +#include "NodeRenderStyle.h" +#include "RenderLayer.h" +#include "RenderSVGPath.h" +#include "RenderSVGResource.h" +#include "RenderSVGResourceClipper.h" +#include "RenderSVGResourceFilter.h" +#include "RenderSVGResourceMarker.h" +#include "RenderSVGResourceMasker.h" +#include "RenderSVGRoot.h" +#include "SVGResources.h" +#include "SVGStyledElement.h" +#include "TransformState.h" +#include <wtf/UnusedParam.h> + +namespace WebCore { + +IntRect SVGRenderSupport::clippedOverflowRectForRepaint(RenderObject* object, RenderBoxModelObject* repaintContainer) +{ + // Return early for any cases where we don't actually paint + if (object->style()->visibility() != VISIBLE && !object->enclosingLayer()->hasVisibleContent()) + return IntRect(); + + // Pass our local paint rect to computeRectForRepaint() which will + // map to parent coords and recurse up the parent chain. + IntRect repaintRect = enclosingIntRect(object->repaintRectInLocalCoordinates()); + object->computeRectForRepaint(repaintContainer, repaintRect); + return repaintRect; +} + +void SVGRenderSupport::computeRectForRepaint(RenderObject* object, RenderBoxModelObject* repaintContainer, IntRect& repaintRect, bool fixed) +{ + const SVGRenderStyle* svgStyle = object->style()->svgStyle(); + if (const ShadowData* shadow = svgStyle->shadow()) + shadow->adjustRectForShadow(repaintRect); + + // Translate to coords in our parent renderer, and then call computeRectForRepaint on our parent + repaintRect = object->localToParentTransform().mapRect(repaintRect); + object->parent()->computeRectForRepaint(repaintContainer, repaintRect, fixed); +} + +void SVGRenderSupport::mapLocalToContainer(const RenderObject* object, RenderBoxModelObject* repaintContainer, bool fixed , bool useTransforms, TransformState& transformState) +{ + ASSERT(!fixed); // We should have no fixed content in the SVG rendering tree. + ASSERT(useTransforms); // Mapping a point through SVG w/o respecting transforms is useless. + transformState.applyTransform(object->localToParentTransform()); + object->parent()->mapLocalToContainer(repaintContainer, fixed, useTransforms, transformState); +} + +bool SVGRenderSupport::prepareToRenderSVGContent(RenderObject* object, PaintInfo& paintInfo) +{ + ASSERT(object); + + RenderStyle* style = object->style(); + ASSERT(style); + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + + // Setup transparency layers before setting up SVG resources! + float opacity = style->opacity(); + const ShadowData* shadow = svgStyle->shadow(); + if (opacity < 1 || shadow) { + FloatRect repaintRect = object->repaintRectInLocalCoordinates(); + + if (opacity < 1) { + paintInfo.context->clip(repaintRect); + paintInfo.context->beginTransparencyLayer(opacity); + } + + if (shadow) { + paintInfo.context->clip(repaintRect); + paintInfo.context->setShadow(IntSize(shadow->x(), shadow->y()), shadow->blur(), shadow->color(), style->colorSpace()); + paintInfo.context->beginTransparencyLayer(1); + } + } + + SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(object); + if (!resources) + return true; + + if (RenderSVGResourceMasker* masker = resources->masker()) { + if (!masker->applyResource(object, style, paintInfo.context, ApplyToDefaultMode)) + return false; + } + + if (RenderSVGResourceClipper* clipper = resources->clipper()) { + if (!clipper->applyResource(object, style, paintInfo.context, ApplyToDefaultMode)) + return false; + } + +#if ENABLE(FILTERS) + if (RenderSVGResourceFilter* filter = resources->filter()) { + if (!filter->applyResource(object, style, paintInfo.context, ApplyToDefaultMode)) + return false; + } +#endif + + return true; +} + +void SVGRenderSupport::finishRenderSVGContent(RenderObject* object, PaintInfo& paintInfo, GraphicsContext* savedContext) +{ +#if !ENABLE(FILTERS) + UNUSED_PARAM(savedContext); +#endif + + ASSERT(object); + + const RenderStyle* style = object->style(); + ASSERT(style); + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + +#if ENABLE(FILTERS) + SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(object); + if (resources) { + if (RenderSVGResourceFilter* filter = resources->filter()) { + filter->postApplyResource(object, paintInfo.context, ApplyToDefaultMode, /* path */0); + paintInfo.context = savedContext; + } + } +#endif + + if (style->opacity() < 1) + paintInfo.context->endTransparencyLayer(); + + if (svgStyle->shadow()) + paintInfo.context->endTransparencyLayer(); +} + +void SVGRenderSupport::computeContainerBoundingBoxes(const RenderObject* container, FloatRect& objectBoundingBox, FloatRect& strokeBoundingBox, FloatRect& repaintBoundingBox) +{ + for (RenderObject* current = container->firstChild(); current; current = current->nextSibling()) { + if (current->isSVGHiddenContainer()) + continue; + + const AffineTransform& transform = current->localToParentTransform(); + if (transform.isIdentity()) { + objectBoundingBox.unite(current->objectBoundingBox()); + strokeBoundingBox.unite(current->strokeBoundingBox()); + repaintBoundingBox.unite(current->repaintRectInLocalCoordinates()); + } else { + objectBoundingBox.unite(transform.mapRect(current->objectBoundingBox())); + strokeBoundingBox.unite(transform.mapRect(current->strokeBoundingBox())); + repaintBoundingBox.unite(transform.mapRect(current->repaintRectInLocalCoordinates())); + } + } +} + +bool SVGRenderSupport::paintInfoIntersectsRepaintRect(const FloatRect& localRepaintRect, const AffineTransform& localTransform, const PaintInfo& paintInfo) +{ + if (localTransform.isIdentity()) + return localRepaintRect.intersects(paintInfo.rect); + + return localTransform.mapRect(localRepaintRect).intersects(paintInfo.rect); +} + +const RenderSVGRoot* SVGRenderSupport::findTreeRootObject(const RenderObject* start) +{ + while (start && !start->isSVGRoot()) + start = start->parent(); + + ASSERT(start); + ASSERT(start->isSVGRoot()); + return toRenderSVGRoot(start); +} + +static inline void invalidateResourcesOfChildren(RenderObject* start) +{ + ASSERT(!start->needsLayout()); + if (SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(start)) + resources->removeClientFromCache(start, false); + + for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) + invalidateResourcesOfChildren(child); +} + +void SVGRenderSupport::layoutChildren(RenderObject* start, bool selfNeedsLayout) +{ + bool layoutSizeChanged = findTreeRootObject(start)->isLayoutSizeChanged(); + HashSet<RenderObject*> notlayoutedObjects; + + for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { + bool needsLayout = selfNeedsLayout; + + if (layoutSizeChanged) { + // When selfNeedsLayout is false and the layout size changed, we have to check whether this child uses relative lengths + if (SVGElement* element = child->node()->isSVGElement() ? static_cast<SVGElement*>(child->node()) : 0) { + if (element->isStyled() && static_cast<SVGStyledElement*>(element)->hasRelativeLengths()) { + // When the layout size changed and when using relative values tell the RenderSVGPath to update its Path object + if (child->isSVGPath()) + toRenderSVGPath(child)->setNeedsPathUpdate(); + + needsLayout = true; + } + } + } + + if (needsLayout) { + child->setNeedsLayout(true, false); + child->layout(); + } else { + if (child->needsLayout()) + child->layout(); + else if (layoutSizeChanged) + notlayoutedObjects.add(child); + } + + ASSERT(!child->needsLayout()); + } + + if (!layoutSizeChanged) { + ASSERT(notlayoutedObjects.isEmpty()); + return; + } + + // If the layout size changed, invalidate all resources of all children that didn't go through the layout() code path. + HashSet<RenderObject*>::iterator end = notlayoutedObjects.end(); + for (HashSet<RenderObject*>::iterator it = notlayoutedObjects.begin(); it != end; ++it) + invalidateResourcesOfChildren(*it); +} + +bool SVGRenderSupport::isOverflowHidden(const RenderObject* object) +{ + // SVG doesn't support independent x/y overflow + ASSERT(object->style()->overflowX() == object->style()->overflowY()); + + // OSCROLL is never set for SVG - see CSSStyleSelector::adjustRenderStyle + ASSERT(object->style()->overflowX() != OSCROLL); + + // RenderSVGRoot should never query for overflow state - it should always clip itself to the initial viewport size. + ASSERT(!object->isRoot()); + + return object->style()->overflowX() == OHIDDEN; +} + +void SVGRenderSupport::intersectRepaintRectWithResources(const RenderObject* object, FloatRect& repaintRect) +{ + ASSERT(object); + + RenderStyle* style = object->style(); + ASSERT(style); + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + + RenderObject* renderer = const_cast<RenderObject*>(object); + SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(renderer); + if (!resources) { + if (const ShadowData* shadow = svgStyle->shadow()) + shadow->adjustRectForShadow(repaintRect); + return; + } + +#if ENABLE(FILTERS) + if (RenderSVGResourceFilter* filter = resources->filter()) + repaintRect = filter->resourceBoundingBox(renderer); +#endif + + if (RenderSVGResourceClipper* clipper = resources->clipper()) + repaintRect.intersect(clipper->resourceBoundingBox(renderer)); + + if (RenderSVGResourceMasker* masker = resources->masker()) + repaintRect.intersect(masker->resourceBoundingBox(renderer)); + + if (const ShadowData* shadow = svgStyle->shadow()) + shadow->adjustRectForShadow(repaintRect); +} + +bool SVGRenderSupport::pointInClippingArea(RenderObject* object, const FloatPoint& point) +{ + ASSERT(object); + + // We just take clippers into account to determine if a point is on the node. The Specification may + // change later and we also need to check maskers. + SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(object); + if (!resources) + return true; + + if (RenderSVGResourceClipper* clipper = resources->clipper()) + return clipper->hitTestClipContent(object->objectBoundingBox(), point); + + return true; +} + +void SVGRenderSupport::applyStrokeStyleToContext(GraphicsContext* context, const RenderStyle* style, const RenderObject* object) +{ + ASSERT(context); + ASSERT(style); + ASSERT(object); + ASSERT(object->node()); + ASSERT(object->node()->isSVGElement()); + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + + SVGElement* lengthContext = static_cast<SVGElement*>(object->node()); + context->setStrokeThickness(svgStyle->strokeWidth().value(lengthContext)); + context->setLineCap(svgStyle->capStyle()); + context->setLineJoin(svgStyle->joinStyle()); + if (svgStyle->joinStyle() == MiterJoin) + context->setMiterLimit(svgStyle->strokeMiterLimit()); + + const Vector<SVGLength>& dashes = svgStyle->strokeDashArray(); + if (dashes.isEmpty()) + context->setStrokeStyle(SolidStroke); + else { + DashArray dashArray; + const Vector<SVGLength>::const_iterator end = dashes.end(); + for (Vector<SVGLength>::const_iterator it = dashes.begin(); it != end; ++it) + dashArray.append((*it).value(lengthContext)); + + context->setLineDash(dashArray, svgStyle->strokeDashOffset().value(lengthContext)); + } +} + +} + +#endif diff --git a/Source/WebCore/rendering/SVGRenderSupport.h b/Source/WebCore/rendering/SVGRenderSupport.h new file mode 100644 index 0000000..7814863 --- /dev/null +++ b/Source/WebCore/rendering/SVGRenderSupport.h @@ -0,0 +1,84 @@ +/** + * Copyright (C) 2007 Rob Buis <buis@kde.org> + * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * (C) 2007 Eric Seidel <eric@webkit.org> + * Copyright (C) 2009 Google, Inc. All rights reserved. + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + * + */ + +#ifndef SVGRenderSupport_h +#define SVGRenderSupport_h + +#if ENABLE(SVG) +#include "PaintInfo.h" + +namespace WebCore { + +class FloatPoint; +class FloatRect; +class ImageBuffer; +class RenderBoxModelObject; +class RenderObject; +class RenderStyle; +class RenderSVGRoot; +class TransformState; + +// SVGRendererSupport is a helper class sharing code between all SVG renderers. +class SVGRenderSupport { +public: + // Used by all SVG renderers who apply clip/filter/etc. resources to the renderer content + static bool prepareToRenderSVGContent(RenderObject*, PaintInfo&); + static void finishRenderSVGContent(RenderObject*, PaintInfo&, GraphicsContext* savedContext); + + // Shares child layouting code between RenderSVGRoot/RenderSVG(Hidden)Container + static void layoutChildren(RenderObject*, bool selfNeedsLayout); + + // Helper function determining wheter overflow is hidden + static bool isOverflowHidden(const RenderObject*); + + // Calculates the repaintRect in combination with filter, clipper and masker in local coordinates. + static void intersectRepaintRectWithResources(const RenderObject*, FloatRect&); + + // Determines whether the passed point lies in a clipping area + static bool pointInClippingArea(RenderObject*, const FloatPoint&); + + static void computeContainerBoundingBoxes(const RenderObject* container, FloatRect& objectBoundingBox, FloatRect& strokeBoundingBox, FloatRect& repaintBoundingBox); + static bool paintInfoIntersectsRepaintRect(const FloatRect& localRepaintRect, const AffineTransform& localTransform, const PaintInfo& paintInfo); + + // Important functions used by nearly all SVG renderers centralizing coordinate transformations / repaint rect calculations + static IntRect clippedOverflowRectForRepaint(RenderObject*, RenderBoxModelObject* repaintContainer); + static void computeRectForRepaint(RenderObject*, RenderBoxModelObject* repaintContainer, IntRect&, bool fixed); + static void mapLocalToContainer(const RenderObject*, RenderBoxModelObject* repaintContainer, bool useTransforms, bool fixed, TransformState&); + + // Shared between SVG renderers and resources. + static void applyStrokeStyleToContext(GraphicsContext*, const RenderStyle*, const RenderObject*); + + // FIXME: These methods do not belong here. + static const RenderSVGRoot* findTreeRootObject(const RenderObject* start); + +private: + // This class is not constructable. + SVGRenderSupport(); + ~SVGRenderSupport(); +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif // SVGRenderSupport_h diff --git a/Source/WebCore/rendering/SVGRenderTreeAsText.cpp b/Source/WebCore/rendering/SVGRenderTreeAsText.cpp new file mode 100644 index 0000000..ea843c2 --- /dev/null +++ b/Source/WebCore/rendering/SVGRenderTreeAsText.cpp @@ -0,0 +1,748 @@ +/* + * Copyright (C) 2004, 2005, 2007, 2009 Apple Inc. All rights reserved. + * (C) 2005 Rob Buis <buis@kde.org> + * (C) 2006 Alexander Kellett <lypanov@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if ENABLE(SVG) +#include "SVGRenderTreeAsText.h" + +#include "GraphicsTypes.h" +#include "HTMLNames.h" +#include "InlineTextBox.h" +#include "LinearGradientAttributes.h" +#include "NodeRenderStyle.h" +#include "Path.h" +#include "PatternAttributes.h" +#include "RadialGradientAttributes.h" +#include "RenderImage.h" +#include "RenderSVGContainer.h" +#include "RenderSVGGradientStop.h" +#include "RenderSVGImage.h" +#include "RenderSVGInlineText.h" +#include "RenderSVGPath.h" +#include "RenderSVGResourceClipper.h" +#include "RenderSVGResourceFilter.h" +#include "RenderSVGResourceGradient.h" +#include "RenderSVGResourceLinearGradient.h" +#include "RenderSVGResourceMarker.h" +#include "RenderSVGResourceMasker.h" +#include "RenderSVGResourcePattern.h" +#include "RenderSVGResourceRadialGradient.h" +#include "RenderSVGResourceSolidColor.h" +#include "RenderSVGRoot.h" +#include "RenderSVGText.h" +#include "RenderTreeAsText.h" +#include "SVGCircleElement.h" +#include "SVGEllipseElement.h" +#include "SVGInlineTextBox.h" +#include "SVGLineElement.h" +#include "SVGLinearGradientElement.h" +#include "SVGNames.h" +#include "SVGPathElement.h" +#include "SVGPathParserFactory.h" +#include "SVGPatternElement.h" +#include "SVGPointList.h" +#include "SVGPolyElement.h" +#include "SVGRadialGradientElement.h" +#include "SVGRectElement.h" +#include "SVGRootInlineBox.h" +#include "SVGStopElement.h" +#include "SVGStyledElement.h" + +#include <math.h> + +namespace WebCore { + +/** class + iomanip to help streaming list separators, i.e. ", " in string "a, b, c, d" + * Can be used in cases where you don't know which item in the list is the first + * one to be printed, but still want to avoid strings like ", b, c". + */ +class TextStreamSeparator { +public: + TextStreamSeparator(const String& s) + : m_separator(s) + , m_needToSeparate(false) + { + } + +private: + friend TextStream& operator<<(TextStream&, TextStreamSeparator&); + + String m_separator; + bool m_needToSeparate; +}; + +TextStream& operator<<(TextStream& ts, TextStreamSeparator& sep) +{ + if (sep.m_needToSeparate) + ts << sep.m_separator; + else + sep.m_needToSeparate = true; + return ts; +} + +template<typename ValueType> +static void writeNameValuePair(TextStream& ts, const char* name, ValueType value) +{ + ts << " [" << name << "=" << value << "]"; +} + +template<typename ValueType> +static void writeNameAndQuotedValue(TextStream& ts, const char* name, ValueType value) +{ + ts << " [" << name << "=\"" << value << "\"]"; +} + +static void writeIfNotEmpty(TextStream& ts, const char* name, const String& value) +{ + if (!value.isEmpty()) + writeNameValuePair(ts, name, value); +} + +template<typename ValueType> +static void writeIfNotDefault(TextStream& ts, const char* name, ValueType value, ValueType defaultValue) +{ + if (value != defaultValue) + writeNameValuePair(ts, name, value); +} + +TextStream& operator<<(TextStream& ts, const FloatRect &r) +{ + ts << "at ("; + if (hasFractions(r.x())) + ts << r.x(); + else + ts << int(r.x()); + ts << ","; + if (hasFractions(r.y())) + ts << r.y(); + else + ts << int(r.y()); + ts << ") size "; + if (hasFractions(r.width())) + ts << r.width(); + else + ts << int(r.width()); + ts << "x"; + if (hasFractions(r.height())) + ts << r.height(); + else + ts << int(r.height()); + return ts; +} + +TextStream& operator<<(TextStream& ts, const AffineTransform& transform) +{ + if (transform.isIdentity()) + ts << "identity"; + else + ts << "{m=((" + << transform.a() << "," << transform.b() + << ")(" + << transform.c() << "," << transform.d() + << ")) t=(" + << transform.e() << "," << transform.f() + << ")}"; + + return ts; +} + +static TextStream& operator<<(TextStream& ts, const WindRule rule) +{ + switch (rule) { + case RULE_NONZERO: + ts << "NON-ZERO"; + break; + case RULE_EVENODD: + ts << "EVEN-ODD"; + break; + } + + return ts; +} + +static TextStream& operator<<(TextStream& ts, const SVGUnitTypes::SVGUnitType& unitType) +{ + switch (unitType) { + case SVGUnitTypes::SVG_UNIT_TYPE_UNKNOWN: + ts << "unknown"; + break; + case SVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE: + ts << "userSpaceOnUse"; + break; + case SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX: + ts << "objectBoundingBox"; + break; + } + + return ts; +} + +static TextStream& operator<<(TextStream& ts, const SVGMarkerElement::SVGMarkerUnitsType& markerUnit) +{ + switch (markerUnit) { + case SVGMarkerElement::SVG_MARKERUNITS_UNKNOWN: + ts << "unknown"; + break; + case SVGMarkerElement::SVG_MARKERUNITS_USERSPACEONUSE: + ts << "userSpaceOnUse"; + break; + case SVGMarkerElement::SVG_MARKERUNITS_STROKEWIDTH: + ts << "strokeWidth"; + break; + } + + return ts; +} + +TextStream& operator<<(TextStream& ts, const Color& c) +{ + return ts << c.name(); +} + +// FIXME: Maybe this should be in KCanvasRenderingStyle.cpp +static TextStream& operator<<(TextStream& ts, const DashArray& a) +{ + ts << "{"; + DashArray::const_iterator end = a.end(); + for (DashArray::const_iterator it = a.begin(); it != end; ++it) { + if (it != a.begin()) + ts << ", "; + ts << *it; + } + ts << "}"; + return ts; +} + +// FIXME: Maybe this should be in GraphicsTypes.cpp +static TextStream& operator<<(TextStream& ts, LineCap style) +{ + switch (style) { + case ButtCap: + ts << "BUTT"; + break; + case RoundCap: + ts << "ROUND"; + break; + case SquareCap: + ts << "SQUARE"; + break; + } + return ts; +} + +// FIXME: Maybe this should be in GraphicsTypes.cpp +static TextStream& operator<<(TextStream& ts, LineJoin style) +{ + switch (style) { + case MiterJoin: + ts << "MITER"; + break; + case RoundJoin: + ts << "ROUND"; + break; + case BevelJoin: + ts << "BEVEL"; + break; + } + return ts; +} + +// FIXME: Maybe this should be in Gradient.cpp +static TextStream& operator<<(TextStream& ts, GradientSpreadMethod mode) +{ + switch (mode) { + case SpreadMethodPad: + ts << "PAD"; + break; + case SpreadMethodRepeat: + ts << "REPEAT"; + break; + case SpreadMethodReflect: + ts << "REFLECT"; + break; + } + + return ts; +} + +static void writeSVGPaintingResource(TextStream& ts, RenderSVGResource* resource) +{ + if (resource->resourceType() == SolidColorResourceType) { + ts << "[type=SOLID] [color=" << static_cast<RenderSVGResourceSolidColor*>(resource)->color() << "]"; + return; + } + + // All other resources derive from RenderSVGResourceContainer + RenderSVGResourceContainer* container = static_cast<RenderSVGResourceContainer*>(resource); + Node* node = container->node(); + ASSERT(node); + ASSERT(node->isSVGElement()); + + if (resource->resourceType() == PatternResourceType) + ts << "[type=PATTERN]"; + else if (resource->resourceType() == LinearGradientResourceType) + ts << "[type=LINEAR-GRADIENT]"; + else if (resource->resourceType() == RadialGradientResourceType) + ts << "[type=RADIAL-GRADIENT]"; + + ts << " [id=\"" << static_cast<SVGElement*>(node)->getIdAttribute() << "\"]"; +} + +static void writeStyle(TextStream& ts, const RenderObject& object) +{ + const RenderStyle* style = object.style(); + const SVGRenderStyle* svgStyle = style->svgStyle(); + + if (!object.localTransform().isIdentity()) + writeNameValuePair(ts, "transform", object.localTransform()); + writeIfNotDefault(ts, "image rendering", svgStyle->imageRendering(), SVGRenderStyle::initialImageRendering()); + writeIfNotDefault(ts, "opacity", style->opacity(), RenderStyle::initialOpacity()); + if (object.isSVGPath()) { + const RenderSVGPath& path = static_cast<const RenderSVGPath&>(object); + ASSERT(path.node()); + ASSERT(path.node()->isSVGElement()); + + Color fallbackColor; + if (RenderSVGResource* strokePaintingResource = RenderSVGResource::strokePaintingResource(const_cast<RenderSVGPath*>(&path), path.style(), fallbackColor)) { + TextStreamSeparator s(" "); + ts << " [stroke={" << s; + writeSVGPaintingResource(ts, strokePaintingResource); + + SVGElement* element = static_cast<SVGElement*>(path.node()); + double dashOffset = svgStyle->strokeDashOffset().value(element); + double strokeWidth = svgStyle->strokeWidth().value(element); + const Vector<SVGLength>& dashes = svgStyle->strokeDashArray(); + + DashArray dashArray; + const Vector<SVGLength>::const_iterator end = dashes.end(); + for (Vector<SVGLength>::const_iterator it = dashes.begin(); it != end; ++it) + dashArray.append((*it).value(element)); + + writeIfNotDefault(ts, "opacity", svgStyle->strokeOpacity(), 1.0f); + writeIfNotDefault(ts, "stroke width", strokeWidth, 1.0); + writeIfNotDefault(ts, "miter limit", svgStyle->strokeMiterLimit(), 4.0f); + writeIfNotDefault(ts, "line cap", svgStyle->capStyle(), ButtCap); + writeIfNotDefault(ts, "line join", svgStyle->joinStyle(), MiterJoin); + writeIfNotDefault(ts, "dash offset", dashOffset, 0.0); + if (!dashArray.isEmpty()) + writeNameValuePair(ts, "dash array", dashArray); + + ts << "}]"; + } + + if (RenderSVGResource* fillPaintingResource = RenderSVGResource::fillPaintingResource(const_cast<RenderSVGPath*>(&path), path.style(), fallbackColor)) { + TextStreamSeparator s(" "); + ts << " [fill={" << s; + writeSVGPaintingResource(ts, fillPaintingResource); + + writeIfNotDefault(ts, "opacity", svgStyle->fillOpacity(), 1.0f); + writeIfNotDefault(ts, "fill rule", svgStyle->fillRule(), RULE_NONZERO); + ts << "}]"; + } + writeIfNotDefault(ts, "clip rule", svgStyle->clipRule(), RULE_NONZERO); + } + + writeIfNotEmpty(ts, "start marker", svgStyle->markerStartResource()); + writeIfNotEmpty(ts, "middle marker", svgStyle->markerMidResource()); + writeIfNotEmpty(ts, "end marker", svgStyle->markerEndResource()); +} + +static TextStream& writePositionAndStyle(TextStream& ts, const RenderObject& object) +{ + ts << " " << const_cast<RenderObject&>(object).absoluteClippedOverflowRect(); + writeStyle(ts, object); + return ts; +} + +static TextStream& operator<<(TextStream& ts, const RenderSVGPath& path) +{ + writePositionAndStyle(ts, path); + + ASSERT(path.node()->isSVGElement()); + SVGElement* svgElement = static_cast<SVGElement*>(path.node()); + + if (svgElement->hasTagName(SVGNames::rectTag)) { + SVGRectElement* element = static_cast<SVGRectElement*>(svgElement); + writeNameValuePair(ts, "x", element->x().value(element)); + writeNameValuePair(ts, "y", element->y().value(element)); + writeNameValuePair(ts, "width", element->width().value(element)); + writeNameValuePair(ts, "height", element->height().value(element)); + } else if (svgElement->hasTagName(SVGNames::lineTag)) { + SVGLineElement* element = static_cast<SVGLineElement*>(svgElement); + writeNameValuePair(ts, "x1", element->x1().value(element)); + writeNameValuePair(ts, "y1", element->y1().value(element)); + writeNameValuePair(ts, "x2", element->x2().value(element)); + writeNameValuePair(ts, "y2", element->y2().value(element)); + } else if (svgElement->hasTagName(SVGNames::ellipseTag)) { + SVGEllipseElement* element = static_cast<SVGEllipseElement*>(svgElement); + writeNameValuePair(ts, "cx", element->cx().value(element)); + writeNameValuePair(ts, "cy", element->cy().value(element)); + writeNameValuePair(ts, "rx", element->rx().value(element)); + writeNameValuePair(ts, "ry", element->ry().value(element)); + } else if (svgElement->hasTagName(SVGNames::circleTag)) { + SVGCircleElement* element = static_cast<SVGCircleElement*>(svgElement); + writeNameValuePair(ts, "cx", element->cx().value(element)); + writeNameValuePair(ts, "cy", element->cy().value(element)); + writeNameValuePair(ts, "r", element->r().value(element)); + } else if (svgElement->hasTagName(SVGNames::polygonTag) || svgElement->hasTagName(SVGNames::polylineTag)) { + SVGPolyElement* element = static_cast<SVGPolyElement*>(svgElement); + writeNameAndQuotedValue(ts, "points", element->pointList().valueAsString()); + } else if (svgElement->hasTagName(SVGNames::pathTag)) { + SVGPathElement* element = static_cast<SVGPathElement*>(svgElement); + String pathString; + // FIXME: We should switch to UnalteredParsing here - this will affect the path dumping output of dozens of tests. + SVGPathParserFactory::self()->buildStringFromByteStream(element->pathByteStream(), pathString, NormalizedParsing); + writeNameAndQuotedValue(ts, "data", pathString); + } else + ASSERT_NOT_REACHED(); + return ts; +} + +static TextStream& operator<<(TextStream& ts, const RenderSVGRoot& root) +{ + return writePositionAndStyle(ts, root); +} + +static void writeRenderSVGTextBox(TextStream& ts, const RenderBlock& text) +{ + SVGRootInlineBox* box = static_cast<SVGRootInlineBox*>(text.firstRootBox()); + if (!box) + return; + + ts << " at (" << text.x() << "," << text.y() << ") size " << box->logicalWidth() << "x" << box->logicalHeight(); + + // FIXME: Remove this hack, once the new text layout engine is completly landed. We want to preserve the old layout test results for now. + ts << " contains 1 chunk(s)"; + + if (text.parent() && (text.parent()->style()->visitedDependentColor(CSSPropertyColor) != text.style()->visitedDependentColor(CSSPropertyColor))) + writeNameValuePair(ts, "color", text.style()->visitedDependentColor(CSSPropertyColor).name()); +} + +static inline void writeSVGInlineTextBox(TextStream& ts, SVGInlineTextBox* textBox, int indent) +{ + Vector<SVGTextFragment>& fragments = textBox->textFragments(); + if (fragments.isEmpty()) + return; + + RenderSVGInlineText* textRenderer = toRenderSVGInlineText(textBox->textRenderer()); + ASSERT(textRenderer); + + const SVGRenderStyle* svgStyle = textRenderer->style()->svgStyle(); + String text = textBox->textRenderer()->text(); + + unsigned fragmentsSize = fragments.size(); + for (unsigned i = 0; i < fragmentsSize; ++i) { + SVGTextFragment& fragment = fragments.at(i); + writeIndent(ts, indent + 1); + + unsigned startOffset = fragment.positionListOffset; + unsigned endOffset = fragment.positionListOffset + fragment.length; + + // FIXME: Remove this hack, once the new text layout engine is completly landed. We want to preserve the old layout test results for now. + ts << "chunk 1 "; + ETextAnchor anchor = svgStyle->textAnchor(); + bool isVerticalText = svgStyle->isVerticalWritingMode(); + if (anchor == TA_MIDDLE) { + ts << "(middle anchor"; + if (isVerticalText) + ts << ", vertical"; + ts << ") "; + } else if (anchor == TA_END) { + ts << "(end anchor"; + if (isVerticalText) + ts << ", vertical"; + ts << ") "; + } else if (isVerticalText) + ts << "(vertical) "; + startOffset -= textBox->start(); + endOffset -= textBox->start(); + // </hack> + + ts << "text run " << i + 1 << " at (" << fragment.x << "," << fragment.y << ")"; + ts << " startOffset " << startOffset << " endOffset " << endOffset; + if (isVerticalText) + ts << " height " << fragment.height; + else + ts << " width " << fragment.width; + + if (!textBox->isLeftToRightDirection() || textBox->m_dirOverride) { + ts << (textBox->isLeftToRightDirection() ? " LTR" : " RTL"); + if (textBox->m_dirOverride) + ts << " override"; + } + + ts << ": " << quoteAndEscapeNonPrintables(text.substring(fragment.positionListOffset, fragment.length)) << "\n"; + } +} + +static inline void writeSVGInlineTextBoxes(TextStream& ts, const RenderText& text, int indent) +{ + for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) + writeSVGInlineTextBox(ts, static_cast<SVGInlineTextBox*>(box), indent); +} + +static void writeStandardPrefix(TextStream& ts, const RenderObject& object, int indent) +{ + writeIndent(ts, indent); + ts << object.renderName(); + + if (object.node()) + ts << " {" << object.node()->nodeName() << "}"; +} + +static void writeChildren(TextStream& ts, const RenderObject& object, int indent) +{ + for (RenderObject* child = object.firstChild(); child; child = child->nextSibling()) + write(ts, *child, indent + 1); +} + +static inline String boundingBoxModeString(bool boundingBoxMode) +{ + return boundingBoxMode ? "objectBoundingBox" : "userSpaceOnUse"; +} + +static inline void writeCommonGradientProperties(TextStream& ts, GradientSpreadMethod spreadMethod, const AffineTransform& gradientTransform, bool boundingBoxMode) +{ + writeNameValuePair(ts, "gradientUnits", boundingBoxModeString(boundingBoxMode)); + + if (spreadMethod != SpreadMethodPad) + ts << " [spreadMethod=" << spreadMethod << "]"; + + if (!gradientTransform.isIdentity()) + ts << " [gradientTransform=" << gradientTransform << "]"; +} + +void writeSVGResourceContainer(TextStream& ts, const RenderObject& object, int indent) +{ + writeStandardPrefix(ts, object, indent); + + Element* element = static_cast<Element*>(object.node()); + const AtomicString& id = element->getIdAttribute(); + writeNameAndQuotedValue(ts, "id", id); + + RenderSVGResourceContainer* resource = const_cast<RenderObject&>(object).toRenderSVGResourceContainer(); + ASSERT(resource); + + if (resource->resourceType() == MaskerResourceType) { + RenderSVGResourceMasker* masker = static_cast<RenderSVGResourceMasker*>(resource); + writeNameValuePair(ts, "maskUnits", masker->maskUnits()); + writeNameValuePair(ts, "maskContentUnits", masker->maskContentUnits()); + ts << "\n"; +#if ENABLE(FILTERS) + } else if (resource->resourceType() == FilterResourceType) { + RenderSVGResourceFilter* filter = static_cast<RenderSVGResourceFilter*>(resource); + writeNameValuePair(ts, "filterUnits", filter->filterUnits()); + writeNameValuePair(ts, "primitiveUnits", filter->primitiveUnits()); + ts << "\n"; + // Creating a placeholder filter which is passed to the builder. + FloatRect dummyRect; + RefPtr<SVGFilter> dummyFilter = SVGFilter::create(AffineTransform(), dummyRect, dummyRect, dummyRect, true); + if (RefPtr<SVGFilterBuilder> builder = filter->buildPrimitives(dummyFilter.get())) { + if (FilterEffect* lastEffect = builder->lastEffect()) + lastEffect->externalRepresentation(ts, indent + 1); + } +#endif + } else if (resource->resourceType() == ClipperResourceType) { + RenderSVGResourceClipper* clipper = static_cast<RenderSVGResourceClipper*>(resource); + writeNameValuePair(ts, "clipPathUnits", clipper->clipPathUnits()); + ts << "\n"; + } else if (resource->resourceType() == MarkerResourceType) { + RenderSVGResourceMarker* marker = static_cast<RenderSVGResourceMarker*>(resource); + writeNameValuePair(ts, "markerUnits", marker->markerUnits()); + ts << " [ref at " << marker->referencePoint() << "]"; + ts << " [angle="; + if (marker->angle() == -1) + ts << "auto" << "]\n"; + else + ts << marker->angle() << "]\n"; + } else if (resource->resourceType() == PatternResourceType) { + RenderSVGResourcePattern* pattern = static_cast<RenderSVGResourcePattern*>(resource); + + // Dump final results that are used for rendering. No use in asking SVGPatternElement for its patternUnits(), as it may + // link to other patterns using xlink:href, we need to build the full inheritance chain, aka. collectPatternProperties() + PatternAttributes attributes; + static_cast<SVGPatternElement*>(pattern->node())->collectPatternAttributes(attributes); + + writeNameValuePair(ts, "patternUnits", boundingBoxModeString(attributes.boundingBoxMode())); + writeNameValuePair(ts, "patternContentUnits", boundingBoxModeString(attributes.boundingBoxModeContent())); + + AffineTransform transform = attributes.patternTransform(); + if (!transform.isIdentity()) + ts << " [patternTransform=" << transform << "]"; + ts << "\n"; + } else if (resource->resourceType() == LinearGradientResourceType) { + RenderSVGResourceLinearGradient* gradient = static_cast<RenderSVGResourceLinearGradient*>(resource); + + // Dump final results that are used for rendering. No use in asking SVGGradientElement for its gradientUnits(), as it may + // link to other gradients using xlink:href, we need to build the full inheritance chain, aka. collectGradientProperties() + SVGLinearGradientElement* linearGradientElement = static_cast<SVGLinearGradientElement*>(gradient->node()); + + LinearGradientAttributes attributes; + linearGradientElement->collectGradientAttributes(attributes); + writeCommonGradientProperties(ts, attributes.spreadMethod(), attributes.gradientTransform(), attributes.boundingBoxMode()); + + FloatPoint startPoint; + FloatPoint endPoint; + linearGradientElement->calculateStartEndPoints(attributes, startPoint, endPoint); + + ts << " [start=" << startPoint << "] [end=" << endPoint << "]\n"; + } else if (resource->resourceType() == RadialGradientResourceType) { + RenderSVGResourceRadialGradient* gradient = static_cast<RenderSVGResourceRadialGradient*>(resource); + + // Dump final results that are used for rendering. No use in asking SVGGradientElement for its gradientUnits(), as it may + // link to other gradients using xlink:href, we need to build the full inheritance chain, aka. collectGradientProperties() + SVGRadialGradientElement* radialGradientElement = static_cast<SVGRadialGradientElement*>(gradient->node()); + + RadialGradientAttributes attributes; + radialGradientElement->collectGradientAttributes(attributes); + writeCommonGradientProperties(ts, attributes.spreadMethod(), attributes.gradientTransform(), attributes.boundingBoxMode()); + + FloatPoint focalPoint; + FloatPoint centerPoint; + float radius; + radialGradientElement->calculateFocalCenterPointsAndRadius(attributes, focalPoint, centerPoint, radius); + + ts << " [center=" << centerPoint << "] [focal=" << focalPoint << "] [radius=" << radius << "]\n"; + } else + ts << "\n"; + writeChildren(ts, object, indent); +} + +void writeSVGContainer(TextStream& ts, const RenderObject& container, int indent) +{ + // Currently RenderSVGResourceFilterPrimitive has no meaningful output. + if (container.isSVGResourceFilterPrimitive()) + return; + writeStandardPrefix(ts, container, indent); + writePositionAndStyle(ts, container); + ts << "\n"; + writeResources(ts, container, indent); + writeChildren(ts, container, indent); +} + +void write(TextStream& ts, const RenderSVGRoot& root, int indent) +{ + writeStandardPrefix(ts, root, indent); + ts << root << "\n"; + writeChildren(ts, root, indent); +} + +void writeSVGText(TextStream& ts, const RenderBlock& text, int indent) +{ + writeStandardPrefix(ts, text, indent); + writeRenderSVGTextBox(ts, text); + ts << "\n"; + writeResources(ts, text, indent); + writeChildren(ts, text, indent); +} + +void writeSVGInlineText(TextStream& ts, const RenderText& text, int indent) +{ + writeStandardPrefix(ts, text, indent); + + // Why not just linesBoundingBox()? + ts << " " << FloatRect(text.firstRunOrigin(), text.linesBoundingBox().size()) << "\n"; + writeResources(ts, text, indent); + writeSVGInlineTextBoxes(ts, text, indent); +} + +void writeSVGImage(TextStream& ts, const RenderSVGImage& image, int indent) +{ + writeStandardPrefix(ts, image, indent); + writePositionAndStyle(ts, image); + ts << "\n"; + writeResources(ts, image, indent); +} + +void write(TextStream& ts, const RenderSVGPath& path, int indent) +{ + writeStandardPrefix(ts, path, indent); + ts << path << "\n"; + writeResources(ts, path, indent); +} + +void writeSVGGradientStop(TextStream& ts, const RenderSVGGradientStop& stop, int indent) +{ + writeStandardPrefix(ts, stop, indent); + + SVGStopElement* stopElement = static_cast<SVGStopElement*>(stop.node()); + ASSERT(stopElement); + + RenderStyle* style = stop.style(); + if (!style) + return; + + ts << " [offset=" << stopElement->offset() << "] [color=" << stopElement->stopColorIncludingOpacity() << "]\n"; +} + +void writeResources(TextStream& ts, const RenderObject& object, int indent) +{ + const RenderStyle* style = object.style(); + const SVGRenderStyle* svgStyle = style->svgStyle(); + + // FIXME: We want to use SVGResourcesCache to determine which resources are present, instead of quering the resource <-> id cache. + // For now leave the DRT output as is, but later on we should change this so cycles are properly ignored in the DRT output. + RenderObject& renderer = const_cast<RenderObject&>(object); + if (!svgStyle->maskerResource().isEmpty()) { + if (RenderSVGResourceMasker* masker = getRenderSVGResourceById<RenderSVGResourceMasker>(object.document(), svgStyle->maskerResource())) { + writeIndent(ts, indent); + ts << " "; + writeNameAndQuotedValue(ts, "masker", svgStyle->maskerResource()); + ts << " "; + writeStandardPrefix(ts, *masker, 0); + ts << " " << masker->resourceBoundingBox(&renderer) << "\n"; + } + } + if (!svgStyle->clipperResource().isEmpty()) { + if (RenderSVGResourceClipper* clipper = getRenderSVGResourceById<RenderSVGResourceClipper>(object.document(), svgStyle->clipperResource())) { + writeIndent(ts, indent); + ts << " "; + writeNameAndQuotedValue(ts, "clipPath", svgStyle->clipperResource()); + ts << " "; + writeStandardPrefix(ts, *clipper, 0); + ts << " " << clipper->resourceBoundingBox(&renderer) << "\n"; + } + } +#if ENABLE(FILTERS) + if (!svgStyle->filterResource().isEmpty()) { + if (RenderSVGResourceFilter* filter = getRenderSVGResourceById<RenderSVGResourceFilter>(object.document(), svgStyle->filterResource())) { + writeIndent(ts, indent); + ts << " "; + writeNameAndQuotedValue(ts, "filter", svgStyle->filterResource()); + ts << " "; + writeStandardPrefix(ts, *filter, 0); + ts << " " << filter->resourceBoundingBox(&renderer) << "\n"; + } + } +#endif +} + +} // namespace WebCore + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/SVGRenderTreeAsText.h b/Source/WebCore/rendering/SVGRenderTreeAsText.h new file mode 100644 index 0000000..4e9ba5d --- /dev/null +++ b/Source/WebCore/rendering/SVGRenderTreeAsText.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2004, 2005, 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SVGRenderTreeAsText_h +#define SVGRenderTreeAsText_h + +#if ENABLE(SVG) + +#include "TextStream.h" + +namespace WebCore { + + class Color; + class FloatRect; + class FloatSize; + class Node; + class RenderBlock; + class RenderImage; + class RenderObject; + class RenderSVGGradientStop; + class RenderSVGImage; + class RenderSVGPath; + class RenderSVGRoot; + class RenderText; + class AffineTransform; + class SVGUnitTypes; + +// functions used by the main RenderTreeAsText code +void write(TextStream&, const RenderSVGPath&, int indent); +void write(TextStream&, const RenderSVGRoot&, int indent); +void writeSVGGradientStop(TextStream&, const RenderSVGGradientStop&, int indent); +void writeSVGResourceContainer(TextStream&, const RenderObject&, int indent); +void writeSVGContainer(TextStream&, const RenderObject&, int indent); +void writeSVGImage(TextStream&, const RenderSVGImage&, int indent); +void writeSVGInlineText(TextStream&, const RenderText&, int indent); +void writeSVGText(TextStream&, const RenderBlock&, int indent); +void writeResources(TextStream&, const RenderObject&, int indent); + +// helper operators defined used in various classes to dump the render tree. +TextStream& operator<<(TextStream&, const AffineTransform&); +TextStream& operator<<(TextStream&, const Color&); +TextStream& operator<<(TextStream&, const FloatRect&); + +// helper operators specific to dumping the render tree. these are used in various classes to dump the render tree +// these could be defined in separate namespace to avoid matching these generic signatures unintentionally. + +template<typename Item> +TextStream& operator<<(TextStream& ts, const Vector<Item*>& v) +{ + ts << "["; + + for (unsigned i = 0; i < v.size(); i++) { + ts << *v[i]; + if (i < v.size() - 1) + ts << ", "; + } + + ts << "]"; + return ts; +} + +template<typename Pointer> +TextStream& operator<<(TextStream& ts, Pointer* t) +{ + ts << reinterpret_cast<intptr_t>(t); + return ts; +} + +} // namespace WebCore + +#endif // ENABLE(SVG) + +#endif // SVGRenderTreeAsText_h diff --git a/Source/WebCore/rendering/SVGResources.cpp b/Source/WebCore/rendering/SVGResources.cpp new file mode 100644 index 0000000..e162f83 --- /dev/null +++ b/Source/WebCore/rendering/SVGResources.cpp @@ -0,0 +1,684 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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 + aint 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 "SVGResources.h" + +#if ENABLE(SVG) +#include "RenderSVGResourceClipper.h" +#include "RenderSVGResourceFilter.h" +#include "RenderSVGResourceMarker.h" +#include "RenderSVGResourceMasker.h" +#include "SVGFilterElement.h" +#include "SVGGradientElement.h" +#include "SVGNames.h" +#include "SVGPaint.h" +#include "SVGPatternElement.h" +#include "SVGRenderStyle.h" +#include "SVGURIReference.h" + +namespace WebCore { + +SVGResources::SVGResources() + : m_clipperFilterMaskerData(0) + , m_markerData(0) + , m_fillStrokeData(0) + , m_linkedResource(0) +{ +} + +static HashSet<AtomicStringImpl*>& clipperFilterMaskerTags() +{ + DEFINE_STATIC_LOCAL(HashSet<AtomicStringImpl*>, s_tagList, ()); + if (s_tagList.isEmpty()) { + // "container elements": http://www.w3.org/TR/SVG11/intro.html#TermContainerElement + // "graphics elements" : http://www.w3.org/TR/SVG11/intro.html#TermGraphicsElement + s_tagList.add(SVGNames::aTag.localName().impl()); + s_tagList.add(SVGNames::circleTag.localName().impl()); + s_tagList.add(SVGNames::ellipseTag.localName().impl()); + s_tagList.add(SVGNames::glyphTag.localName().impl()); + s_tagList.add(SVGNames::gTag.localName().impl()); + s_tagList.add(SVGNames::imageTag.localName().impl()); + s_tagList.add(SVGNames::lineTag.localName().impl()); + s_tagList.add(SVGNames::markerTag.localName().impl()); + s_tagList.add(SVGNames::maskTag.localName().impl()); + s_tagList.add(SVGNames::missing_glyphTag.localName().impl()); + s_tagList.add(SVGNames::pathTag.localName().impl()); + s_tagList.add(SVGNames::polygonTag.localName().impl()); + s_tagList.add(SVGNames::polylineTag.localName().impl()); + s_tagList.add(SVGNames::rectTag.localName().impl()); + s_tagList.add(SVGNames::svgTag.localName().impl()); + s_tagList.add(SVGNames::textTag.localName().impl()); + s_tagList.add(SVGNames::useTag.localName().impl()); + + // Not listed in the definitions is the clipPath element, the SVG spec says though: + // The "clipPath" element or any of its children can specify property "clip-path". + // So we have to add clipPathTag here, otherwhise clip-path on clipPath will fail. + // (Already mailed SVG WG, waiting for a solution) + s_tagList.add(SVGNames::clipPathTag.localName().impl()); + + // Not listed in the definitions are the text content elements, though filter/clipper/masker on tspan/text/.. is allowed. + // (Already mailed SVG WG, waiting for a solution) + s_tagList.add(SVGNames::altGlyphTag.localName().impl()); + s_tagList.add(SVGNames::textPathTag.localName().impl()); + s_tagList.add(SVGNames::trefTag.localName().impl()); + s_tagList.add(SVGNames::tspanTag.localName().impl()); + + // Elements that we ignore, as it doesn't make any sense. + // defs, pattern, switch (FIXME: Mail SVG WG about these) + // symbol (is converted to a svg element, when referenced by use, we can safely ignore it.) + } + + return s_tagList; +} + +static HashSet<AtomicStringImpl*>& markerTags() +{ + DEFINE_STATIC_LOCAL(HashSet<AtomicStringImpl*>, s_tagList, ()); + if (s_tagList.isEmpty()) { + s_tagList.add(SVGNames::lineTag.localName().impl()); + s_tagList.add(SVGNames::pathTag.localName().impl()); + s_tagList.add(SVGNames::polygonTag.localName().impl()); + s_tagList.add(SVGNames::polylineTag.localName().impl()); + } + + return s_tagList; +} + +static HashSet<AtomicStringImpl*>& fillAndStrokeTags() +{ + DEFINE_STATIC_LOCAL(HashSet<AtomicStringImpl*>, s_tagList, ()); + if (s_tagList.isEmpty()) { + s_tagList.add(SVGNames::altGlyphTag.localName().impl()); + s_tagList.add(SVGNames::circleTag.localName().impl()); + s_tagList.add(SVGNames::ellipseTag.localName().impl()); + s_tagList.add(SVGNames::lineTag.localName().impl()); + s_tagList.add(SVGNames::pathTag.localName().impl()); + s_tagList.add(SVGNames::polygonTag.localName().impl()); + s_tagList.add(SVGNames::polylineTag.localName().impl()); + s_tagList.add(SVGNames::rectTag.localName().impl()); + s_tagList.add(SVGNames::textTag.localName().impl()); + s_tagList.add(SVGNames::textPathTag.localName().impl()); + s_tagList.add(SVGNames::trefTag.localName().impl()); + s_tagList.add(SVGNames::tspanTag.localName().impl()); + } + + return s_tagList; +} + +static HashSet<AtomicStringImpl*>& chainableResourceTags() +{ + DEFINE_STATIC_LOCAL(HashSet<AtomicStringImpl*>, s_tagList, ()); + if (s_tagList.isEmpty()) { + s_tagList.add(SVGNames::linearGradientTag.localName().impl()); + s_tagList.add(SVGNames::filterTag.localName().impl()); + s_tagList.add(SVGNames::patternTag.localName().impl()); + s_tagList.add(SVGNames::radialGradientTag.localName().impl()); + } + + return s_tagList; +} + +static inline String targetReferenceFromResource(SVGElement* element) +{ + String target; + if (element->hasTagName(SVGNames::patternTag)) + target = static_cast<SVGPatternElement*>(element)->href(); + else if (element->hasTagName(SVGNames::linearGradientTag) || element->hasTagName(SVGNames::radialGradientTag)) + target = static_cast<SVGGradientElement*>(element)->href(); +#if ENABLE(FILTERS) + else if (element->hasTagName(SVGNames::filterTag)) + target = static_cast<SVGFilterElement*>(element)->href(); +#endif + else + ASSERT_NOT_REACHED(); + + return SVGURIReference::getTarget(target); +} + +static inline RenderSVGResourceContainer* paintingResourceFromSVGPaint(Document* document, SVGPaint* paint, AtomicString& id, bool& hasPendingResource) +{ + ASSERT(paint); + + SVGPaint::SVGPaintType paintType = paint->paintType(); + if (paintType != SVGPaint::SVG_PAINTTYPE_URI && paintType != SVGPaint::SVG_PAINTTYPE_URI_RGBCOLOR) + return 0; + + id = SVGURIReference::getTarget(paint->uri()); + RenderSVGResourceContainer* container = getRenderSVGResourceContainerById(document, id); + if (!container) { + hasPendingResource = true; + return 0; + } + + RenderSVGResourceType resourceType = container->resourceType(); + if (resourceType != PatternResourceType && resourceType != LinearGradientResourceType && resourceType != RadialGradientResourceType) + return 0; + + return container; +} + +static inline void registerPendingResource(SVGDocumentExtensions* extensions, const AtomicString& id, SVGElement* element) +{ + ASSERT(element); + if (!element->isStyled()) + return; + + extensions->addPendingResource(id, static_cast<SVGStyledElement*>(element)); +} + +bool SVGResources::buildCachedResources(const RenderObject* object, const SVGRenderStyle* style) +{ + ASSERT(object); + ASSERT(style); + + Node* node = object->node(); + ASSERT(node); + ASSERT(node->isSVGElement()); + + SVGElement* element = static_cast<SVGElement*>(node); + if (!element) + return false; + + Document* document = object->document(); + ASSERT(document); + + SVGDocumentExtensions* extensions = document->accessSVGExtensions(); + ASSERT(extensions); + + AtomicStringImpl* tagNameImpl = element->tagQName().localName().impl(); + if (!tagNameImpl) + return false; + + bool foundResources = false; + if (clipperFilterMaskerTags().contains(tagNameImpl)) { + if (style->hasClipper()) { + AtomicString id(style->clipperResource()); + if (setClipper(getRenderSVGResourceById<RenderSVGResourceClipper>(document, id))) + foundResources = true; + else + registerPendingResource(extensions, id, element); + } + +#if ENABLE(FILTERS) + if (style->hasFilter()) { + AtomicString id(style->filterResource()); + if (setFilter(getRenderSVGResourceById<RenderSVGResourceFilter>(document, id))) + foundResources = true; + else + registerPendingResource(extensions, id, element); + } +#endif + + if (style->hasMasker()) { + AtomicString id(style->maskerResource()); + if (setMasker(getRenderSVGResourceById<RenderSVGResourceMasker>(document, id))) + foundResources = true; + else + registerPendingResource(extensions, id, element); + } + } + + if (markerTags().contains(tagNameImpl) && style->hasMarkers()) { + AtomicString markerStartId(style->markerStartResource()); + if (setMarkerStart(getRenderSVGResourceById<RenderSVGResourceMarker>(document, markerStartId))) + foundResources = true; + else + registerPendingResource(extensions, markerStartId, element); + + AtomicString markerMidId(style->markerMidResource()); + if (setMarkerMid(getRenderSVGResourceById<RenderSVGResourceMarker>(document, markerMidId))) + foundResources = true; + else + registerPendingResource(extensions, markerMidId, element); + + AtomicString markerEndId(style->markerEndResource()); + if (setMarkerEnd(getRenderSVGResourceById<RenderSVGResourceMarker>(document, markerEndId))) + foundResources = true; + else + registerPendingResource(extensions, markerEndId, element); + } + + if (fillAndStrokeTags().contains(tagNameImpl)) { + if (style->hasFill()) { + bool hasPendingResource = false; + AtomicString id; + if (setFill(paintingResourceFromSVGPaint(document, style->fillPaint(), id, hasPendingResource))) + foundResources = true; + else if (hasPendingResource) + registerPendingResource(extensions, id, element); + } + + if (style->hasStroke()) { + bool hasPendingResource = false; + AtomicString id; + if (setStroke(paintingResourceFromSVGPaint(document, style->strokePaint(), id, hasPendingResource))) + foundResources = true; + else if (hasPendingResource) + registerPendingResource(extensions, id, element); + } + } + + if (chainableResourceTags().contains(tagNameImpl)) { + AtomicString id(targetReferenceFromResource(element)); + if (setLinkedResource(getRenderSVGResourceContainerById(document, id))) + foundResources = true; + else + registerPendingResource(extensions, id, element); + } + + return foundResources; +} + +void SVGResources::removeClientFromCache(RenderObject* object, bool markForInvalidation) const +{ + if (!m_clipperFilterMaskerData && !m_markerData && !m_fillStrokeData && !m_linkedResource) + return; + + if (m_linkedResource) { + ASSERT(!m_clipperFilterMaskerData); + ASSERT(!m_markerData); + ASSERT(!m_fillStrokeData); + m_linkedResource->removeClientFromCache(object, markForInvalidation); + return; + } + + if (m_clipperFilterMaskerData) { + if (m_clipperFilterMaskerData->clipper) + m_clipperFilterMaskerData->clipper->removeClientFromCache(object, markForInvalidation); +#if ENABLE(FILTERS) + if (m_clipperFilterMaskerData->filter) + m_clipperFilterMaskerData->filter->removeClientFromCache(object, markForInvalidation); +#endif + if (m_clipperFilterMaskerData->masker) + m_clipperFilterMaskerData->masker->removeClientFromCache(object, markForInvalidation); + } + + if (m_markerData) { + if (m_markerData->markerStart) + m_markerData->markerStart->removeClientFromCache(object, markForInvalidation); + if (m_markerData->markerMid) + m_markerData->markerMid->removeClientFromCache(object, markForInvalidation); + if (m_markerData->markerEnd) + m_markerData->markerEnd->removeClientFromCache(object, markForInvalidation); + } + + if (m_fillStrokeData) { + if (m_fillStrokeData->fill) + m_fillStrokeData->fill->removeClientFromCache(object, markForInvalidation); + if (m_fillStrokeData->stroke) + m_fillStrokeData->stroke->removeClientFromCache(object, markForInvalidation); + } +} + +void SVGResources::resourceDestroyed(RenderSVGResourceContainer* resource) +{ + ASSERT(resource); + if (!m_clipperFilterMaskerData && !m_markerData && !m_fillStrokeData && !m_linkedResource) + return; + + if (m_linkedResource == resource) { + ASSERT(!m_clipperFilterMaskerData); + ASSERT(!m_markerData); + ASSERT(!m_fillStrokeData); + m_linkedResource->removeAllClientsFromCache(); + m_linkedResource = 0; + return; + } + + switch (resource->resourceType()) { + case MaskerResourceType: + if (!m_clipperFilterMaskerData) + break; + if (m_clipperFilterMaskerData->masker == resource) { + m_clipperFilterMaskerData->masker->removeAllClientsFromCache(); + m_clipperFilterMaskerData->masker = 0; + } + break; + case MarkerResourceType: + if (!m_markerData) + break; + if (m_markerData->markerStart == resource) { + m_markerData->markerStart->removeAllClientsFromCache(); + m_markerData->markerStart = 0; + } + if (m_markerData->markerMid == resource) { + m_markerData->markerMid->removeAllClientsFromCache(); + m_markerData->markerMid = 0; + } + if (m_markerData->markerEnd == resource) { + m_markerData->markerEnd->removeAllClientsFromCache(); + m_markerData->markerEnd = 0; + } + break; + case PatternResourceType: + case LinearGradientResourceType: + case RadialGradientResourceType: + if (!m_fillStrokeData) + break; + if (m_fillStrokeData->fill == resource) { + m_fillStrokeData->fill->removeAllClientsFromCache(); + m_fillStrokeData->fill = 0; + } + if (m_fillStrokeData->stroke == resource) { + m_fillStrokeData->stroke->removeAllClientsFromCache(); + m_fillStrokeData->stroke = 0; + } + break; + case FilterResourceType: +#if ENABLE(FILTERS) + if (!m_clipperFilterMaskerData) + break; + if (m_clipperFilterMaskerData->filter == resource) { + m_clipperFilterMaskerData->filter->removeAllClientsFromCache(); + m_clipperFilterMaskerData->filter = 0; + } +#else + ASSERT_NOT_REACHED(); +#endif + break; + case ClipperResourceType: + if (!m_clipperFilterMaskerData) + break; + if (m_clipperFilterMaskerData->clipper == resource) { + m_clipperFilterMaskerData->clipper->removeAllClientsFromCache(); + m_clipperFilterMaskerData->clipper = 0; + } + break; + case SolidColorResourceType: + ASSERT_NOT_REACHED(); + } +} + +void SVGResources::buildSetOfResources(HashSet<RenderSVGResourceContainer*>& set) +{ + if (!m_clipperFilterMaskerData && !m_markerData && !m_fillStrokeData && !m_linkedResource) + return; + + if (m_linkedResource) { + ASSERT(!m_clipperFilterMaskerData); + ASSERT(!m_markerData); + ASSERT(!m_fillStrokeData); + set.add(m_linkedResource); + return; + } + + if (m_clipperFilterMaskerData) { + if (m_clipperFilterMaskerData->clipper) + set.add(m_clipperFilterMaskerData->clipper); +#if ENABLE(FILTERS) + if (m_clipperFilterMaskerData->filter) + set.add(m_clipperFilterMaskerData->filter); +#endif + if (m_clipperFilterMaskerData->masker) + set.add(m_clipperFilterMaskerData->masker); + } + + if (m_markerData) { + if (m_markerData->markerStart) + set.add(m_markerData->markerStart); + if (m_markerData->markerMid) + set.add(m_markerData->markerMid); + if (m_markerData->markerEnd) + set.add(m_markerData->markerEnd); + } + + if (m_fillStrokeData) { + if (m_fillStrokeData->fill) + set.add(m_fillStrokeData->fill); + if (m_fillStrokeData->stroke) + set.add(m_fillStrokeData->stroke); + } +} + +bool SVGResources::setClipper(RenderSVGResourceClipper* clipper) +{ + if (!clipper) + return false; + + ASSERT(clipper->resourceType() == ClipperResourceType); + + if (!m_clipperFilterMaskerData) + m_clipperFilterMaskerData = ClipperFilterMaskerData::create(); + + m_clipperFilterMaskerData->clipper = clipper; + return true; +} + +void SVGResources::resetClipper() +{ + ASSERT(m_clipperFilterMaskerData); + ASSERT(m_clipperFilterMaskerData->clipper); + m_clipperFilterMaskerData->clipper = 0; +} + +#if ENABLE(FILTERS) +bool SVGResources::setFilter(RenderSVGResourceFilter* filter) +{ + if (!filter) + return false; + + ASSERT(filter->resourceType() == FilterResourceType); + + if (!m_clipperFilterMaskerData) + m_clipperFilterMaskerData = ClipperFilterMaskerData::create(); + + m_clipperFilterMaskerData->filter = filter; + return true; +} + +void SVGResources::resetFilter() +{ + ASSERT(m_clipperFilterMaskerData); + ASSERT(m_clipperFilterMaskerData->filter); + m_clipperFilterMaskerData->filter = 0; +} +#endif + +bool SVGResources::setMarkerStart(RenderSVGResourceMarker* markerStart) +{ + if (!markerStart) + return false; + + ASSERT(markerStart->resourceType() == MarkerResourceType); + + if (!m_markerData) + m_markerData = MarkerData::create(); + + m_markerData->markerStart = markerStart; + return true; +} + +void SVGResources::resetMarkerStart() +{ + ASSERT(m_markerData); + ASSERT(m_markerData->markerStart); + m_markerData->markerStart = 0; +} + +bool SVGResources::setMarkerMid(RenderSVGResourceMarker* markerMid) +{ + if (!markerMid) + return false; + + ASSERT(markerMid->resourceType() == MarkerResourceType); + + if (!m_markerData) + m_markerData = MarkerData::create(); + + m_markerData->markerMid = markerMid; + return true; +} + +void SVGResources::resetMarkerMid() +{ + ASSERT(m_markerData); + ASSERT(m_markerData->markerMid); + m_markerData->markerMid = 0; +} + +bool SVGResources::setMarkerEnd(RenderSVGResourceMarker* markerEnd) +{ + if (!markerEnd) + return false; + + ASSERT(markerEnd->resourceType() == MarkerResourceType); + + if (!m_markerData) + m_markerData = MarkerData::create(); + + m_markerData->markerEnd = markerEnd; + return true; +} + +void SVGResources::resetMarkerEnd() +{ + ASSERT(m_markerData); + ASSERT(m_markerData->markerEnd); + m_markerData->markerEnd = 0; +} + +bool SVGResources::setMasker(RenderSVGResourceMasker* masker) +{ + if (!masker) + return false; + + ASSERT(masker->resourceType() == MaskerResourceType); + + if (!m_clipperFilterMaskerData) + m_clipperFilterMaskerData = ClipperFilterMaskerData::create(); + + m_clipperFilterMaskerData->masker = masker; + return true; +} + +void SVGResources::resetMasker() +{ + ASSERT(m_clipperFilterMaskerData); + ASSERT(m_clipperFilterMaskerData->masker); + m_clipperFilterMaskerData->masker = 0; +} + +bool SVGResources::setFill(RenderSVGResourceContainer* fill) +{ + if (!fill) + return false; + + ASSERT(fill->resourceType() == PatternResourceType + || fill->resourceType() == LinearGradientResourceType + || fill->resourceType() == RadialGradientResourceType); + + if (!m_fillStrokeData) + m_fillStrokeData = FillStrokeData::create(); + + m_fillStrokeData->fill = fill; + return true; +} + +void SVGResources::resetFill() +{ + ASSERT(m_fillStrokeData); + ASSERT(m_fillStrokeData->fill); + m_fillStrokeData->fill = 0; +} + +bool SVGResources::setStroke(RenderSVGResourceContainer* stroke) +{ + if (!stroke) + return false; + + ASSERT(stroke->resourceType() == PatternResourceType + || stroke->resourceType() == LinearGradientResourceType + || stroke->resourceType() == RadialGradientResourceType); + + if (!m_fillStrokeData) + m_fillStrokeData = FillStrokeData::create(); + + m_fillStrokeData->stroke = stroke; + return true; +} + +void SVGResources::resetStroke() +{ + ASSERT(m_fillStrokeData); + ASSERT(m_fillStrokeData->stroke); + m_fillStrokeData->stroke = 0; +} + +bool SVGResources::setLinkedResource(RenderSVGResourceContainer* linkedResource) +{ + if (!linkedResource) + return false; + + m_linkedResource = linkedResource; + return true; +} + +void SVGResources::resetLinkedResource() +{ + ASSERT(m_linkedResource); + m_linkedResource = 0; +} + +#ifndef NDEBUG +void SVGResources::dump(const RenderObject* object) +{ + ASSERT(object); + ASSERT(object->node()); + + fprintf(stderr, "-> this=%p, SVGResources(renderer=%p, node=%p)\n", this, object, object->node()); + fprintf(stderr, " | DOM Tree:\n"); + object->node()->showTreeForThis(); + + fprintf(stderr, "\n | List of resources:\n"); + if (m_clipperFilterMaskerData) { + if (RenderSVGResourceClipper* clipper = m_clipperFilterMaskerData->clipper) + fprintf(stderr, " |-> Clipper : %p (node=%p)\n", clipper, clipper->node()); +#if ENABLE(FILTERS) + if (RenderSVGResourceFilter* filter = m_clipperFilterMaskerData->filter) + fprintf(stderr, " |-> Filter : %p (node=%p)\n", filter, filter->node()); +#endif + if (RenderSVGResourceMasker* masker = m_clipperFilterMaskerData->masker) + fprintf(stderr, " |-> Masker : %p (node=%p)\n", masker, masker->node()); + } + + if (m_markerData) { + if (RenderSVGResourceMarker* markerStart = m_markerData->markerStart) + fprintf(stderr, " |-> MarkerStart: %p (node=%p)\n", markerStart, markerStart->node()); + if (RenderSVGResourceMarker* markerMid = m_markerData->markerMid) + fprintf(stderr, " |-> MarkerMid : %p (node=%p)\n", markerMid, markerMid->node()); + if (RenderSVGResourceMarker* markerEnd = m_markerData->markerEnd) + fprintf(stderr, " |-> MarkerEnd : %p (node=%p)\n", markerEnd, markerEnd->node()); + } + + if (m_fillStrokeData) { + if (RenderSVGResourceContainer* fill = m_fillStrokeData->fill) + fprintf(stderr, " |-> Fill : %p (node=%p)\n", fill, fill->node()); + if (RenderSVGResourceContainer* stroke = m_fillStrokeData->stroke) + fprintf(stderr, " |-> Stroke : %p (node=%p)\n", stroke, stroke->node()); + } + + if (m_linkedResource) + fprintf(stderr, " |-> xlink:href : %p (node=%p)\n", m_linkedResource, m_linkedResource->node()); +} +#endif + +} + +#endif diff --git a/Source/WebCore/rendering/SVGResources.h b/Source/WebCore/rendering/SVGResources.h new file mode 100644 index 0000000..49591cf --- /dev/null +++ b/Source/WebCore/rendering/SVGResources.h @@ -0,0 +1,179 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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 + aint 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. +*/ + +#ifndef SVGResources_h +#define SVGResources_h + +#if ENABLE(SVG) +#include <wtf/HashSet.h> +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class Document; +class RenderObject; +class RenderSVGResourceClipper; +class RenderSVGResourceContainer; +class RenderSVGResourceFilter; +class RenderSVGResourceMarker; +class RenderSVGResourceMasker; +class SVGRenderStyle; + +// Holds a set of resources associated with a RenderObject +class SVGResources : public Noncopyable { +public: + SVGResources(); + + bool buildCachedResources(const RenderObject*, const SVGRenderStyle*); + + // Ordinary resources + RenderSVGResourceClipper* clipper() const { return m_clipperFilterMaskerData ? m_clipperFilterMaskerData->clipper : 0; } +#if ENABLE(FILTERS) + RenderSVGResourceFilter* filter() const { return m_clipperFilterMaskerData ? m_clipperFilterMaskerData->filter : 0; } +#endif + RenderSVGResourceMarker* markerStart() const { return m_markerData ? m_markerData->markerStart : 0; } + RenderSVGResourceMarker* markerMid() const { return m_markerData ? m_markerData->markerMid : 0; } + RenderSVGResourceMarker* markerEnd() const { return m_markerData ? m_markerData->markerEnd : 0; } + RenderSVGResourceMasker* masker() const { return m_clipperFilterMaskerData ? m_clipperFilterMaskerData->masker : 0; } + + // Paint servers + RenderSVGResourceContainer* fill() const { return m_fillStrokeData ? m_fillStrokeData->fill : 0; } + RenderSVGResourceContainer* stroke() const { return m_fillStrokeData ? m_fillStrokeData->stroke : 0; } + + // Chainable resources - linked through xlink:href + RenderSVGResourceContainer* linkedResource() const { return m_linkedResource; } + + void buildSetOfResources(HashSet<RenderSVGResourceContainer*>&); + + // Methods operating on all cached resources + void removeClientFromCache(RenderObject*, bool markForInvalidation = true) const; + void resourceDestroyed(RenderSVGResourceContainer*); + +#ifndef NDEBUG + void dump(const RenderObject*); +#endif + +private: + friend class SVGResourcesCycleSolver; + + // Only used by SVGResourcesCache cycle detection logic + void resetClipper(); +#if ENABLE(FILTERS) + void resetFilter(); +#endif + void resetMarkerStart(); + void resetMarkerMid(); + void resetMarkerEnd(); + void resetMasker(); + void resetFill(); + void resetStroke(); + void resetLinkedResource(); + +private: + bool setClipper(RenderSVGResourceClipper*); +#if ENABLE(FILTERS) + bool setFilter(RenderSVGResourceFilter*); +#endif + bool setMarkerStart(RenderSVGResourceMarker*); + bool setMarkerMid(RenderSVGResourceMarker*); + bool setMarkerEnd(RenderSVGResourceMarker*); + bool setMasker(RenderSVGResourceMasker*); + bool setFill(RenderSVGResourceContainer*); + bool setStroke(RenderSVGResourceContainer*); + bool setLinkedResource(RenderSVGResourceContainer*); + + // From SVG 1.1 2nd Edition + // clipper: 'container elements' and 'graphics elements' + // filter: 'container elements' and 'graphics elements' + // masker: 'container elements' and 'graphics elements' + // -> a, circle, defs, ellipse, glyph, g, image, line, marker, mask, missing-glyph, path, pattern, polygon, polyline, rect, svg, switch, symbol, text, use + struct ClipperFilterMaskerData { + ClipperFilterMaskerData() + : clipper(0) +#if ENABLE(FILTERS) + , filter(0) +#endif + , masker(0) + { + } + + static PassOwnPtr<ClipperFilterMaskerData> create() + { + return new ClipperFilterMaskerData; + } + + RenderSVGResourceClipper* clipper; +#if ENABLE(FILTERS) + RenderSVGResourceFilter* filter; +#endif + RenderSVGResourceMasker* masker; + }; + + // From SVG 1.1 2nd Edition + // marker: line, path, polygon, polyline + struct MarkerData { + MarkerData() + : markerStart(0) + , markerMid(0) + , markerEnd(0) + { + } + + static PassOwnPtr<MarkerData> create() + { + return new MarkerData; + } + + RenderSVGResourceMarker* markerStart; + RenderSVGResourceMarker* markerMid; + RenderSVGResourceMarker* markerEnd; + }; + + // From SVG 1.1 2nd Edition + // fill: 'shapes' and 'text content elements' + // stroke: 'shapes' and 'text content elements' + // -> altGlyph, circle, ellipse, line, path, polygon, polyline, rect, text, textPath, tref, tspan + struct FillStrokeData { + FillStrokeData() + : fill(0) + , stroke(0) + { + } + + static PassOwnPtr<FillStrokeData> create() + { + return new FillStrokeData; + } + + RenderSVGResourceContainer* fill; + RenderSVGResourceContainer* stroke; + }; + + OwnPtr<ClipperFilterMaskerData> m_clipperFilterMaskerData; + OwnPtr<MarkerData> m_markerData; + OwnPtr<FillStrokeData> m_fillStrokeData; + RenderSVGResourceContainer* m_linkedResource; +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/SVGResourcesCache.cpp b/Source/WebCore/rendering/SVGResourcesCache.cpp new file mode 100644 index 0000000..88cbb3a --- /dev/null +++ b/Source/WebCore/rendering/SVGResourcesCache.cpp @@ -0,0 +1,165 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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 + aint 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 "SVGResourcesCache.h" + +#if ENABLE(SVG) +#include "RenderSVGResourceContainer.h" +#include "SVGDocumentExtensions.h" +#include "SVGResources.h" +#include "SVGResourcesCycleSolver.h" + +namespace WebCore { + +SVGResourcesCache::SVGResourcesCache() +{ +} + +SVGResourcesCache::~SVGResourcesCache() +{ + deleteAllValues(m_cache); +} + +void SVGResourcesCache::addResourcesFromRenderObject(RenderObject* object, const RenderStyle* style) +{ + ASSERT(object); + ASSERT(style); + ASSERT(!m_cache.contains(object)); + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + + // Build a list of all resources associated with the passed RenderObject + SVGResources* resources = new SVGResources; + if (!resources->buildCachedResources(object, svgStyle)) { + delete resources; + return; + } + + // Put object in cache. + m_cache.set(object, resources); + + // Run cycle-detection _afterwards_, so self-references can be caught as well. + SVGResourcesCycleSolver solver(object, resources); + solver.resolveCycles(); + + // Walk resources and register the render object at each resources. + HashSet<RenderSVGResourceContainer*> resourceSet; + resources->buildSetOfResources(resourceSet); + + HashSet<RenderSVGResourceContainer*>::iterator end = resourceSet.end(); + for (HashSet<RenderSVGResourceContainer*>::iterator it = resourceSet.begin(); it != end; ++it) + (*it)->addClient(object); +} + +void SVGResourcesCache::removeResourcesFromRenderObject(RenderObject* object) +{ + if (!m_cache.contains(object)) + return; + + SVGResources* resources = m_cache.get(object); + + // Walk resources and register the render object at each resources. + HashSet<RenderSVGResourceContainer*> resourceSet; + resources->buildSetOfResources(resourceSet); + + HashSet<RenderSVGResourceContainer*>::iterator end = resourceSet.end(); + for (HashSet<RenderSVGResourceContainer*>::iterator it = resourceSet.begin(); it != end; ++it) + (*it)->removeClient(object); + + delete m_cache.take(object); +} + +static inline SVGResourcesCache* resourcesCacheFromRenderObject(RenderObject* renderer) +{ + Document* document = renderer->document(); + ASSERT(document); + + SVGDocumentExtensions* extensions = document->accessSVGExtensions(); + ASSERT(extensions); + + SVGResourcesCache* cache = extensions->resourcesCache(); + ASSERT(cache); + + return cache; +} + +SVGResources* SVGResourcesCache::cachedResourcesForRenderObject(RenderObject* renderer) +{ + ASSERT(renderer); + SVGResourcesCache* cache = resourcesCacheFromRenderObject(renderer); + if (!cache->m_cache.contains(renderer)) + return 0; + + return cache->m_cache.get(renderer); +} + +void SVGResourcesCache::clientLayoutChanged(RenderObject* object) +{ + SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(object); + if (!resources) + return; + + resources->removeClientFromCache(object); +} + +void SVGResourcesCache::clientStyleChanged(RenderObject* renderer, StyleDifference diff, const RenderStyle* newStyle) +{ + ASSERT(renderer); + if (diff == StyleDifferenceEqual) + return; + + clientUpdatedFromElement(renderer, newStyle); + RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer, false); +} + +void SVGResourcesCache::clientUpdatedFromElement(RenderObject* renderer, const RenderStyle* newStyle) +{ + ASSERT(renderer); + ASSERT(renderer->parent()); + + SVGResourcesCache* cache = resourcesCacheFromRenderObject(renderer); + cache->removeResourcesFromRenderObject(renderer); + cache->addResourcesFromRenderObject(renderer, newStyle); +} + +void SVGResourcesCache::clientDestroyed(RenderObject* renderer) +{ + ASSERT(renderer); + SVGResourcesCache* cache = resourcesCacheFromRenderObject(renderer); + cache->removeResourcesFromRenderObject(renderer); +} + +void SVGResourcesCache::resourceDestroyed(RenderSVGResourceContainer* resource) +{ + ASSERT(resource); + SVGResourcesCache* cache = resourcesCacheFromRenderObject(resource); + + // The resource itself may have clients, that need to be notified. + cache->removeResourcesFromRenderObject(resource); + + HashMap<RenderObject*, SVGResources*>::iterator end = cache->m_cache.end(); + for (HashMap<RenderObject*, SVGResources*>::iterator it = cache->m_cache.begin(); it != end; ++it) + it->second->resourceDestroyed(resource); +} + +} + +#endif diff --git a/Source/WebCore/rendering/SVGResourcesCache.h b/Source/WebCore/rendering/SVGResourcesCache.h new file mode 100644 index 0000000..4a61570 --- /dev/null +++ b/Source/WebCore/rendering/SVGResourcesCache.h @@ -0,0 +1,65 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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 + aint 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. +*/ + +#ifndef SVGResourcesCache_h +#define SVGResourcesCache_h + +#if ENABLE(SVG) +#include "RenderStyleConstants.h" +#include <wtf/HashMap.h> + +namespace WebCore { + +class RenderObject; +class RenderStyle; +class RenderSVGResourceContainer; +class SVGResources; + +class SVGResourcesCache : public Noncopyable { +public: + SVGResourcesCache(); + ~SVGResourcesCache(); + + void addResourcesFromRenderObject(RenderObject*, const RenderStyle*); + void removeResourcesFromRenderObject(RenderObject*); + static SVGResources* cachedResourcesForRenderObject(RenderObject*); + + // Called from all SVG renderers destroy() methods - except for RenderSVGResourceContainer. + static void clientDestroyed(RenderObject*); + + // Called from all SVG renderers layout() methods. + static void clientLayoutChanged(RenderObject*); + + // Called from all SVG renderers styleDidChange() methods. + static void clientStyleChanged(RenderObject*, StyleDifference, const RenderStyle* newStyle); + + // Called from all SVG renderers updateFromElement() methods. + static void clientUpdatedFromElement(RenderObject*, const RenderStyle* newStyle); + + // Called from RenderSVGResourceContainer::destroy(). + static void resourceDestroyed(RenderSVGResourceContainer*); + +private: + HashMap<RenderObject*, SVGResources*> m_cache; +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/SVGResourcesCycleSolver.cpp b/Source/WebCore/rendering/SVGResourcesCycleSolver.cpp new file mode 100644 index 0000000..8cd2e80 --- /dev/null +++ b/Source/WebCore/rendering/SVGResourcesCycleSolver.cpp @@ -0,0 +1,213 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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 + aint 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 "SVGResourcesCycleSolver.h" + +// Set to a value > 0, to debug the resource cache. +#define DEBUG_CYCLE_DETECTION 0 + +#if ENABLE(SVG) +#include "RenderSVGResourceClipper.h" +#include "RenderSVGResourceFilter.h" +#include "RenderSVGResourceMarker.h" +#include "RenderSVGResourceMasker.h" +#include "SVGFilterElement.h" +#include "SVGGradientElement.h" +#include "SVGPatternElement.h" +#include "SVGResources.h" +#include "SVGResourcesCache.h" + +namespace WebCore { + +SVGResourcesCycleSolver::SVGResourcesCycleSolver(RenderObject* renderer, SVGResources* resources) + : m_renderer(renderer) + , m_resources(resources) +{ + ASSERT(m_renderer); + ASSERT(m_resources); +} + +SVGResourcesCycleSolver::~SVGResourcesCycleSolver() +{ +} + +bool SVGResourcesCycleSolver::resourceContainsCycles(RenderObject* renderer) const +{ + ASSERT(renderer); + + // First operate on the resources of the given renderer. + // <marker id="a"> <path marker-start="url(#b)"/> ... + // <marker id="b" marker-start="url(#a)"/> + if (SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(renderer)) { + HashSet<RenderSVGResourceContainer*> resourceSet; + resources->buildSetOfResources(resourceSet); + + // Walk all resources and check wheter they reference any resource contained in the resources set. + HashSet<RenderSVGResourceContainer*>::iterator end = resourceSet.end(); + for (HashSet<RenderSVGResourceContainer*>::iterator it = resourceSet.begin(); it != end; ++it) { + if (m_allResources.contains(*it)) + return true; + } + } + + // Then operate on the child resources of the given renderer. + // <marker id="a"> <path marker-start="url(#b)"/> ... + // <marker id="b"> <path marker-start="url(#a)"/> ... + for (RenderObject* child = renderer->firstChild(); child; child = child->nextSibling()) { + SVGResources* childResources = SVGResourcesCache::cachedResourcesForRenderObject(child); + if (!childResources) + continue; + + // A child of the given 'resource' contains resources. + HashSet<RenderSVGResourceContainer*> childSet; + childResources->buildSetOfResources(childSet); + + // Walk all child resources and check wheter they reference any resource contained in the resources set. + HashSet<RenderSVGResourceContainer*>::iterator end = childSet.end(); + for (HashSet<RenderSVGResourceContainer*>::iterator it = childSet.begin(); it != end; ++it) { + if (m_allResources.contains(*it)) + return true; + } + + // Walk children recursively, stop immediately if we found a cycle + if (resourceContainsCycles(child)) + return true; + } + + return false; +} + +void SVGResourcesCycleSolver::resolveCycles() +{ + ASSERT(m_allResources.isEmpty()); + +#if DEBUG_CYCLE_DETECTION > 0 + fprintf(stderr, "\nBefore cycle detection:\n"); + m_resources->dump(m_renderer); +#endif + + // Stash all resources into a HashSet for the ease of traversing. + HashSet<RenderSVGResourceContainer*> localResources; + m_resources->buildSetOfResources(localResources); + ASSERT(!localResources.isEmpty()); + + // Add all parent resource containers to the HashSet. + HashSet<RenderSVGResourceContainer*> parentResources; + RenderObject* parent = m_renderer->parent(); + while (parent) { + if (parent->isSVGResourceContainer()) + parentResources.add(parent->toRenderSVGResourceContainer()); + parent = parent->parent(); + } + +#if DEBUG_CYCLE_DETECTION > 0 + fprintf(stderr, "\nDetecting wheter any resources references any of following objects:\n"); + { + fprintf(stderr, "Local resources:\n"); + HashSet<RenderSVGResourceContainer*>::iterator end = localResources.end(); + for (HashSet<RenderSVGResourceContainer*>::iterator it = localResources.begin(); it != end; ++it) + fprintf(stderr, "|> %s: object=%p (node=%p)\n", (*it)->renderName(), *it, (*it)->node()); + + fprintf(stderr, "Parent resources:\n"); + end = parentResources.end(); + for (HashSet<RenderSVGResourceContainer*>::iterator it = parentResources.begin(); it != end; ++it) + fprintf(stderr, "|> %s: object=%p (node=%p)\n", (*it)->renderName(), *it, (*it)->node()); + } +#endif + + // Build combined set of local and parent resources. + m_allResources = localResources; + HashSet<RenderSVGResourceContainer*>::iterator end = parentResources.end(); + for (HashSet<RenderSVGResourceContainer*>::iterator it = parentResources.begin(); it != end; ++it) + m_allResources.add(*it); + + // If we're a resource, add ourselves to the HashSet. + if (m_renderer->isSVGResourceContainer()) + m_allResources.add(m_renderer->toRenderSVGResourceContainer()); + + ASSERT(!m_allResources.isEmpty()); + + // The job of this function is to determine wheter any of the 'resources' associated with the given 'renderer' + // references us (or wheter any of its kids references us) -> that's a cycle, we need to find and break it. + end = localResources.end(); + for (HashSet<RenderSVGResourceContainer*>::iterator it = localResources.begin(); it != end; ++it) { + RenderSVGResourceContainer* resource = *it; + if (parentResources.contains(resource) || resourceContainsCycles(resource)) + breakCycle(resource); + } + +#if DEBUG_CYCLE_DETECTION > 0 + fprintf(stderr, "\nAfter cycle detection:\n"); + m_resources->dump(m_renderer); +#endif + + m_allResources.clear(); +} + +void SVGResourcesCycleSolver::breakCycle(RenderSVGResourceContainer* resourceLeadingToCycle) +{ + ASSERT(resourceLeadingToCycle); + if (resourceLeadingToCycle == m_resources->linkedResource()) { + m_resources->resetLinkedResource(); + return; + } + + switch (resourceLeadingToCycle->resourceType()) { + case MaskerResourceType: + ASSERT(resourceLeadingToCycle == m_resources->masker()); + m_resources->resetMasker(); + break; + case MarkerResourceType: + ASSERT(resourceLeadingToCycle == m_resources->markerStart() || resourceLeadingToCycle == m_resources->markerMid() || resourceLeadingToCycle == m_resources->markerEnd()); + if (m_resources->markerStart() == resourceLeadingToCycle) + m_resources->resetMarkerStart(); + if (m_resources->markerMid() == resourceLeadingToCycle) + m_resources->resetMarkerMid(); + if (m_resources->markerEnd() == resourceLeadingToCycle) + m_resources->resetMarkerEnd(); + break; + case PatternResourceType: + case LinearGradientResourceType: + case RadialGradientResourceType: + ASSERT(resourceLeadingToCycle == m_resources->fill() || resourceLeadingToCycle == m_resources->stroke()); + if (m_resources->fill() == resourceLeadingToCycle) + m_resources->resetFill(); + if (m_resources->stroke() == resourceLeadingToCycle) + m_resources->resetStroke(); + break; + case FilterResourceType: +#if ENABLE(FILTERS) + ASSERT(resourceLeadingToCycle == m_resources->filter()); + m_resources->resetFilter(); +#endif + break; + case ClipperResourceType: + ASSERT(resourceLeadingToCycle == m_resources->clipper()); + m_resources->resetClipper(); + break; + case SolidColorResourceType: + ASSERT_NOT_REACHED(); + break; + } +} + +} + +#endif diff --git a/Source/WebCore/rendering/SVGResourcesCycleSolver.h b/Source/WebCore/rendering/SVGResourcesCycleSolver.h new file mode 100644 index 0000000..e63ee63 --- /dev/null +++ b/Source/WebCore/rendering/SVGResourcesCycleSolver.h @@ -0,0 +1,51 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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 + aint 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. +*/ + +#ifndef SVGResourcesCycleSolver_h +#define SVGResourcesCycleSolver_h + +#if ENABLE(SVG) +#include <wtf/HashSet.h> + +namespace WebCore { + +class RenderObject; +class RenderSVGResourceContainer; +class SVGResources; + +class SVGResourcesCycleSolver : public Noncopyable { +public: + SVGResourcesCycleSolver(RenderObject*, SVGResources*); + ~SVGResourcesCycleSolver(); + + void resolveCycles(); + +private: + bool resourceContainsCycles(RenderObject*) const; + void breakCycle(RenderSVGResourceContainer*); + + RenderObject* m_renderer; + SVGResources* m_resources; + HashSet<RenderSVGResourceContainer*> m_allResources; +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/SVGShadowTreeElements.cpp b/Source/WebCore/rendering/SVGShadowTreeElements.cpp new file mode 100644 index 0000000..7c5e1a9 --- /dev/null +++ b/Source/WebCore/rendering/SVGShadowTreeElements.cpp @@ -0,0 +1,90 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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 + aint 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" + +#if ENABLE(SVG) +#include "SVGShadowTreeElements.h" + +#include "Document.h" +#include "FloatSize.h" +#include "RenderObject.h" +#include "SVGNames.h" +#include "SVGUseElement.h" + +namespace WebCore { + +// SVGShadowTreeContainerElement + +SVGShadowTreeContainerElement::SVGShadowTreeContainerElement(Document* document) + : SVGGElement(SVGNames::gTag, document) +{ +} + +PassRefPtr<SVGShadowTreeContainerElement> SVGShadowTreeContainerElement::create(Document* document) +{ + return adoptRef(new SVGShadowTreeContainerElement(document)); +} + +FloatSize SVGShadowTreeContainerElement::containerTranslation() const +{ + return FloatSize(m_xOffset.value(this), m_yOffset.value(this)); +} + +// SVGShadowTreeRootElement + +inline SVGShadowTreeRootElement::SVGShadowTreeRootElement(Document* document, SVGUseElement* shadowParent) + : SVGShadowTreeContainerElement(document) +{ + setShadowHost(shadowParent); + setInDocument(); +} + +PassRefPtr<SVGShadowTreeRootElement> SVGShadowTreeRootElement::create(Document* document, SVGUseElement* shadowParent) +{ + return adoptRef(new SVGShadowTreeRootElement(document, shadowParent)); +} + +void SVGShadowTreeRootElement::attachElement(PassRefPtr<RenderStyle> style, RenderArena* arena) +{ + ASSERT(shadowHost()); + + // Create the renderer with the specified style + RenderObject* renderer = createRenderer(arena, style.get()); + if (renderer) { + setRenderer(renderer); + renderer->setStyle(style); + } + + // Set these explicitly since this normally happens during an attach() + setAttached(); + + // Add the renderer to the render tree + if (renderer) + shadowHost()->renderer()->addChild(renderer); +} + +void SVGShadowTreeRootElement::clearShadowHost() +{ + setShadowHost(0); +} + +} + +#endif diff --git a/Source/WebCore/rendering/SVGShadowTreeElements.h b/Source/WebCore/rendering/SVGShadowTreeElements.h new file mode 100644 index 0000000..fe25678 --- /dev/null +++ b/Source/WebCore/rendering/SVGShadowTreeElements.h @@ -0,0 +1,67 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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 + aint 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. +*/ + +#ifndef SVGShadowTreeElements_h +#define SVGShadowTreeElements_h + +#if ENABLE(SVG) +#include "SVGGElement.h" +#include "SVGLength.h" + +namespace WebCore { + +class FloatSize; +class SVGUseElement; + +class SVGShadowTreeContainerElement : public SVGGElement { +public: + static PassRefPtr<SVGShadowTreeContainerElement> create(Document*); + + FloatSize containerTranslation() const; + void setContainerOffset(const SVGLength& x, const SVGLength& y) + { + m_xOffset = x; + m_yOffset = y; + } + +protected: + SVGShadowTreeContainerElement(Document*); + +private: + virtual bool isShadowTreeContainerElement() const { return true; } + + SVGLength m_xOffset; + SVGLength m_yOffset; +}; + +class SVGShadowTreeRootElement : public SVGShadowTreeContainerElement { +public: + static PassRefPtr<SVGShadowTreeRootElement> create(Document*, SVGUseElement* shadowParent); + + void attachElement(PassRefPtr<RenderStyle>, RenderArena*); + void clearShadowHost(); + +private: + SVGShadowTreeRootElement(Document*, SVGUseElement* shadowParent); +}; + +} + +#endif +#endif diff --git a/Source/WebCore/rendering/ScrollBehavior.cpp b/Source/WebCore/rendering/ScrollBehavior.cpp new file mode 100644 index 0000000..232ea19 --- /dev/null +++ b/Source/WebCore/rendering/ScrollBehavior.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Portions are Copyright (C) 1998 Netscape Communications Corporation. + * + * Other contributors: + * Robert O'Callahan <roc+@cs.cmu.edu> + * David Baron <dbaron@fas.harvard.edu> + * Christian Biesinger <cbiesinger@web.de> + * Randall Jesup <rjesup@wgate.com> + * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> + * Josh Soref <timeless@mac.com> + * Boris Zbarsky <bzbarsky@mit.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Alternatively, the contents of this file may be used under the terms + * of either the Mozilla Public License Version 1.1, found at + * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public + * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html + * (the "GPL"), in which case the provisions of the MPL or the GPL are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of one of those two + * licenses (the MPL or the GPL) and not to allow others to use your + * version of this file under the LGPL, indicate your decision by + * deletingthe provisions above and replace them with the notice and + * other provisions required by the MPL or the GPL, as the case may be. + * If you do not delete the provisions above, a recipient may use your + * version of this file under any of the LGPL, the MPL or the GPL. + */ + +#include "config.h" +#include "ScrollBehavior.h" + +namespace WebCore { + +const ScrollAlignment ScrollAlignment::alignCenterIfNeeded = { noScroll, alignCenter, alignToClosestEdge }; +const ScrollAlignment ScrollAlignment::alignToEdgeIfNeeded = { noScroll, alignToClosestEdge, alignToClosestEdge }; +const ScrollAlignment ScrollAlignment::alignCenterAlways = { alignCenter, alignCenter, alignCenter }; +const ScrollAlignment ScrollAlignment::alignTopAlways = { alignTop, alignTop, alignTop }; +const ScrollAlignment ScrollAlignment::alignBottomAlways = { alignBottom, alignBottom, alignBottom }; + +}; // namespace WebCore diff --git a/Source/WebCore/rendering/ScrollBehavior.h b/Source/WebCore/rendering/ScrollBehavior.h new file mode 100644 index 0000000..390c68a --- /dev/null +++ b/Source/WebCore/rendering/ScrollBehavior.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003, 2009 Apple Inc. All rights reserved. + * + * Portions are Copyright (C) 1998 Netscape Communications Corporation. + * + * Other contributors: + * Robert O'Callahan <roc+@cs.cmu.edu> + * David Baron <dbaron@fas.harvard.edu> + * Christian Biesinger <cbiesinger@web.de> + * Randall Jesup <rjesup@wgate.com> + * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> + * Josh Soref <timeless@mac.com> + * Boris Zbarsky <bzbarsky@mit.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Alternatively, the contents of this file may be used under the terms + * of either the Mozilla Public License Version 1.1, found at + * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public + * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html + * (the "GPL"), in which case the provisions of the MPL or the GPL are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of one of those two + * licenses (the MPL or the GPL) and not to allow others to use your + * version of this file under the LGPL, indicate your decision by + * deletingthe provisions above and replace them with the notice and + * other provisions required by the MPL or the GPL, as the case may be. + * If you do not delete the provisions above, a recipient may use your + * version of this file under any of the LGPL, the MPL or the GPL. + */ + +#ifndef ScrollBehavior_h +#define ScrollBehavior_h + +namespace WebCore { + +enum ScrollBehavior { + noScroll, + alignCenter, + alignTop, + alignBottom, + alignLeft, + alignRight, + alignToClosestEdge +}; + +struct ScrollAlignment { + static ScrollBehavior getVisibleBehavior(const ScrollAlignment& s) { return s.m_rectVisible; } + static ScrollBehavior getPartialBehavior(const ScrollAlignment& s) { return s.m_rectPartial; } + static ScrollBehavior getHiddenBehavior(const ScrollAlignment& s) { return s.m_rectHidden; } + + static const ScrollAlignment alignCenterIfNeeded; + static const ScrollAlignment alignToEdgeIfNeeded; + static const ScrollAlignment alignCenterAlways; + static const ScrollAlignment alignTopAlways; + static const ScrollAlignment alignBottomAlways; + + ScrollBehavior m_rectVisible; + ScrollBehavior m_rectHidden; + ScrollBehavior m_rectPartial; +}; + + +}; // namespace WebCore + +#endif // ScrollBehavior_h diff --git a/Source/WebCore/rendering/ShadowElement.cpp b/Source/WebCore/rendering/ShadowElement.cpp new file mode 100644 index 0000000..e1b247c --- /dev/null +++ b/Source/WebCore/rendering/ShadowElement.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * 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 "ShadowElement.h" + +#include "HTMLNames.h" +#include "RenderTheme.h" +#include "RenderView.h" + +namespace WebCore { + +using namespace HTMLNames; + +PassRefPtr<ShadowBlockElement> ShadowBlockElement::create(HTMLElement* shadowParent) +{ + return adoptRef(new ShadowBlockElement(shadowParent)); +} + +ShadowBlockElement::ShadowBlockElement(HTMLElement* shadowParent) + : ShadowElement<HTMLDivElement>(divTag, shadowParent) +{ +} + +void ShadowBlockElement::layoutAsPart(const IntRect& partRect) +{ + RenderBox* parentRenderer = toRenderBox(renderer()->parent()); + RenderBox* selfRenderer = toRenderBox(renderer()); + IntRect oldRect = selfRenderer->frameRect(); + + LayoutStateMaintainer statePusher(parentRenderer->view(), parentRenderer, parentRenderer->size(), parentRenderer->style()->isFlippedBlocksWritingMode()); + + if (oldRect.size() != partRect.size()) + selfRenderer->setChildNeedsLayout(true, false); + + selfRenderer->layoutIfNeeded(); + selfRenderer->setFrameRect(partRect); + + if (selfRenderer->checkForRepaintDuringLayout()) + selfRenderer->repaintDuringLayoutIfMoved(oldRect); + + statePusher.pop(); + parentRenderer->addOverflowFromChild(selfRenderer); +} + +void ShadowBlockElement::updateStyleForPart(PseudoId pseudoId) +{ + if (renderer()->style()->styleType() != pseudoId) + renderer()->setStyle(createStyleForPart(renderer()->parent(), pseudoId)); +} + +PassRefPtr<ShadowBlockElement> ShadowBlockElement::createForPart(HTMLElement* shadowParent, PseudoId pseudoId) +{ + RefPtr<ShadowBlockElement> part = create(shadowParent); + part->initAsPart(pseudoId); + return part.release(); +} + +void ShadowBlockElement::initAsPart(PseudoId pseudoId) +{ + RenderObject* parentRenderer = shadowHost()->renderer(); + RefPtr<RenderStyle> styleForPart = createStyleForPart(parentRenderer, pseudoId); + setRenderer(createRenderer(parentRenderer->renderArena(), styleForPart.get())); + renderer()->setStyle(styleForPart.release()); + setAttached(); + setInDocument(); +} + +PassRefPtr<RenderStyle> ShadowBlockElement::createStyleForPart(RenderObject* parentRenderer, PseudoId pseudoId) +{ + RefPtr<RenderStyle> styleForPart; + RenderStyle* pseudoStyle = parentRenderer->getCachedPseudoStyle(pseudoId); + if (pseudoStyle) + styleForPart = RenderStyle::clone(pseudoStyle); + else + styleForPart = RenderStyle::create(); + + styleForPart->inheritFrom(parentRenderer->style()); + styleForPart->setDisplay(BLOCK); + styleForPart->setAppearance(NoControlPart); + return styleForPart.release(); +} + +bool ShadowBlockElement::partShouldHaveStyle(const RenderObject* parentRenderer, PseudoId pseudoId) +{ + // We have some -webkit-appearance values for default styles of parts and + // that appearance get turned off during RenderStyle creation + // if they have background styles specified. + // So !hasAppearance() implies that there are something to be styled. + RenderStyle* pseudoStyle = parentRenderer->getCachedPseudoStyle(pseudoId); + return !(pseudoStyle && pseudoStyle->hasAppearance()); +} + +PassRefPtr<ShadowInputElement> ShadowInputElement::create(HTMLElement* shadowParent) +{ + return adoptRef(new ShadowInputElement(shadowParent)); +} + +ShadowInputElement::ShadowInputElement(HTMLElement* shadowParent) + : ShadowElement<HTMLInputElement>(inputTag, shadowParent) +{ +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/ShadowElement.h b/Source/WebCore/rendering/ShadowElement.h new file mode 100644 index 0000000..2c1a69e --- /dev/null +++ b/Source/WebCore/rendering/ShadowElement.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ShadowElement_h +#define ShadowElement_h + +#include "HTMLDivElement.h" +#include "HTMLInputElement.h" + +namespace WebCore { + +template<class BaseElement> +class ShadowElement : public BaseElement { +protected: + ShadowElement(const QualifiedName& name, HTMLElement* shadowParent) + : BaseElement(name, shadowParent->document()) + , m_shadowParent(shadowParent) + { + BaseElement::setShadowHost(shadowParent); + } + +public: + virtual void detach(); + +private: + RefPtr<HTMLElement> m_shadowParent; +}; + +template<class BaseElement> +void ShadowElement<BaseElement>::detach() +{ + BaseElement::detach(); + // FIXME: Remove once shadow DOM uses Element::setShadowRoot(). + BaseElement::setShadowHost(0); +} + +class ShadowBlockElement : public ShadowElement<HTMLDivElement> { +public: + static PassRefPtr<ShadowBlockElement> create(HTMLElement*); + static PassRefPtr<ShadowBlockElement> createForPart(HTMLElement*, PseudoId); + static bool partShouldHaveStyle(const RenderObject* parentRenderer, PseudoId pseudoId); + void layoutAsPart(const IntRect& partRect); + virtual void updateStyleForPart(PseudoId); + +protected: + ShadowBlockElement(HTMLElement*); + void initAsPart(PseudoId pasuedId); +private: + static PassRefPtr<RenderStyle> createStyleForPart(RenderObject*, PseudoId); +}; + +class ShadowInputElement : public ShadowElement<HTMLInputElement> { +public: + static PassRefPtr<ShadowInputElement> create(HTMLElement*); +protected: + ShadowInputElement(HTMLElement*); +}; + +} // namespace WebCore + +#endif // ShadowElement_h diff --git a/Source/WebCore/rendering/TableLayout.h b/Source/WebCore/rendering/TableLayout.h new file mode 100644 index 0000000..e0fa8ee --- /dev/null +++ b/Source/WebCore/rendering/TableLayout.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2002 Lars Knoll (knoll@kde.org) + * (C) 2002 Dirk Mueller (mueller@kde.org) + * + * 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. + * + * 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. + */ + +#ifndef TableLayout_h +#define TableLayout_h + +#include <wtf/Noncopyable.h> + +namespace WebCore { + +class RenderTable; + +class TableLayout : public Noncopyable { +public: + TableLayout(RenderTable* table) + : m_table(table) + { + } + + virtual ~TableLayout() { } + + virtual void computePreferredLogicalWidths(int& minWidth, int& maxWidth) = 0; + virtual void layout() = 0; + +protected: + RenderTable* m_table; +}; + +} // namespace WebCore + +#endif // TableLayout_h diff --git a/Source/WebCore/rendering/TextControlInnerElements.cpp b/Source/WebCore/rendering/TextControlInnerElements.cpp new file mode 100644 index 0000000..d6fc7aa --- /dev/null +++ b/Source/WebCore/rendering/TextControlInnerElements.cpp @@ -0,0 +1,505 @@ +/* + * Copyright (C) 2006, 2008, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "TextControlInnerElements.h" + +#include "BeforeTextInsertedEvent.h" +#include "Document.h" +#include "EventHandler.h" +#include "EventNames.h" +#include "Frame.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "HTMLTextAreaElement.h" +#include "MouseEvent.h" +#include "Page.h" +#include "RenderLayer.h" +#include "RenderTextControlSingleLine.h" +#include "ScrollbarTheme.h" +#include "SpeechInput.h" +#include "SpeechInputEvent.h" + +namespace WebCore { + +using namespace HTMLNames; + +class RenderTextControlInnerBlock : public RenderBlock { +public: + RenderTextControlInnerBlock(Node* node, bool isMultiLine) : RenderBlock(node), m_multiLine(isMultiLine) { } + +private: + virtual bool hasLineIfEmpty() const { return true; } + virtual VisiblePosition positionForPoint(const IntPoint&); + + bool m_multiLine; +}; + +VisiblePosition RenderTextControlInnerBlock::positionForPoint(const IntPoint& point) +{ + IntPoint contentsPoint(point); + + // Multiline text controls have the scroll on shadowAncestorNode, so we need to take that + // into account here. + if (m_multiLine) { + RenderTextControl* renderer = toRenderTextControl(node()->shadowAncestorNode()->renderer()); + if (renderer->hasOverflowClip()) + contentsPoint += renderer->layer()->scrolledContentOffset(); + } + + return RenderBlock::positionForPoint(contentsPoint); +} + +// ---------------------------- + +TextControlInnerElement::TextControlInnerElement(Document* document, HTMLElement* shadowParent) + : HTMLDivElement(divTag, document) + , m_shadowParent(shadowParent) +{ + setShadowHost(shadowParent); +} + +PassRefPtr<TextControlInnerElement> TextControlInnerElement::create(HTMLElement* shadowParent) +{ + return adoptRef(new TextControlInnerElement(shadowParent->document(), shadowParent)); +} + +void TextControlInnerElement::attachInnerElement(Node* parent, PassRefPtr<RenderStyle> style, RenderArena* arena) +{ + // When adding these elements, create the renderer & style first before adding to the DOM. + // Otherwise, the render tree will create some anonymous blocks that will mess up our layout. + + // Create the renderer with the specified style + RenderObject* renderer = createRenderer(arena, style.get()); + if (renderer) { + setRenderer(renderer); + renderer->setStyle(style); + } + + // Set these explicitly since this normally happens during an attach() + setAttached(); + setInDocument(); + + // For elements not yet in shadow DOM, add the node to the DOM normally. + if (!isShadowRoot()) { + // FIXME: This code seems very wrong. Why are we magically adding |this| to the DOM here? + // We shouldn't be calling parser API methods outside of the parser! + parent->deprecatedParserAddChild(this); + } + + // Add the renderer to the render tree + if (renderer) + parent->renderer()->addChild(renderer); +} + +void TextControlInnerElement::detach() +{ + HTMLDivElement::detach(); + // FIXME: Remove once shadow DOM uses Element::setShadowRoot(). + setShadowHost(0); +} + +// ---------------------------- + +inline TextControlInnerTextElement::TextControlInnerTextElement(Document* document, HTMLElement* shadowParent) + : TextControlInnerElement(document, shadowParent) +{ +} + +PassRefPtr<TextControlInnerTextElement> TextControlInnerTextElement::create(Document* document, HTMLElement* shadowParent) +{ + return adoptRef(new TextControlInnerTextElement(document, shadowParent)); +} + +void TextControlInnerTextElement::defaultEventHandler(Event* event) +{ + // FIXME: In the future, we should add a way to have default event listeners. + // Then we would add one to the text field's inner div, and we wouldn't need this subclass. + // Or possibly we could just use a normal event listener. + if (event->isBeforeTextInsertedEvent() || event->type() == eventNames().webkitEditableContentChangedEvent) { + Node* shadowAncestor = shadowAncestorNode(); + // A TextControlInnerTextElement can be its own shadow ancestor if its been detached, but kept alive by an EditCommand. + // In this case, an undo/redo can cause events to be sent to the TextControlInnerTextElement. + // To prevent an infinite loop, we must check for this case before sending the event up the chain. + if (shadowAncestor && shadowAncestor != this) + shadowAncestor->defaultEventHandler(event); + } + if (!event->defaultHandled()) + HTMLDivElement::defaultEventHandler(event); +} + +RenderObject* TextControlInnerTextElement::createRenderer(RenderArena* arena, RenderStyle*) +{ + bool multiLine = false; + Node* shadowAncestor = shadowAncestorNode(); + if (shadowAncestor && shadowAncestor->renderer()) { + ASSERT(shadowAncestor->renderer()->isTextField() || shadowAncestor->renderer()->isTextArea()); + multiLine = shadowAncestor->renderer()->isTextArea(); + } + return new (arena) RenderTextControlInnerBlock(this, multiLine); +} + +// ---------------------------- + +inline SearchFieldResultsButtonElement::SearchFieldResultsButtonElement(Document* document) + : TextControlInnerElement(document) +{ +} + +PassRefPtr<SearchFieldResultsButtonElement> SearchFieldResultsButtonElement::create(Document* document) +{ + return adoptRef(new SearchFieldResultsButtonElement(document)); +} + +void SearchFieldResultsButtonElement::defaultEventHandler(Event* event) +{ + // On mousedown, bring up a menu, if needed + HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode()); + if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { + input->focus(); + input->select(); + RenderTextControlSingleLine* renderer = toRenderTextControlSingleLine(input->renderer()); + if (renderer->popupIsVisible()) + renderer->hidePopup(); + else if (input->maxResults() > 0) + renderer->showPopup(); + event->setDefaultHandled(); + } + + if (!event->defaultHandled()) + HTMLDivElement::defaultEventHandler(event); +} + +// ---------------------------- + +inline SearchFieldCancelButtonElement::SearchFieldCancelButtonElement(Document* document) + : TextControlInnerElement(document) + , m_capturing(false) +{ +} + +PassRefPtr<SearchFieldCancelButtonElement> SearchFieldCancelButtonElement::create(Document* document) +{ + return adoptRef(new SearchFieldCancelButtonElement(document)); +} + +void SearchFieldCancelButtonElement::detach() +{ + if (m_capturing) { + if (Frame* frame = document()->frame()) + frame->eventHandler()->setCapturingMouseEventsNode(0); + } + TextControlInnerElement::detach(); +} + + +void SearchFieldCancelButtonElement::defaultEventHandler(Event* event) +{ + // If the element is visible, on mouseup, clear the value, and set selection + HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode()); + if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { + if (renderer() && renderer()->visibleToHitTesting()) { + if (Frame* frame = document()->frame()) { + frame->eventHandler()->setCapturingMouseEventsNode(this); + m_capturing = true; + } + } + input->focus(); + input->select(); + event->setDefaultHandled(); + } + if (event->type() == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { + if (m_capturing && renderer() && renderer()->visibleToHitTesting()) { + if (Frame* frame = document()->frame()) { + frame->eventHandler()->setCapturingMouseEventsNode(0); + m_capturing = false; + } + if (hovered()) { + RefPtr<HTMLInputElement> protector(input); + String oldValue = input->value(); + input->setValue(""); + if (!oldValue.isEmpty()) { + toRenderTextControl(input->renderer())->setChangedSinceLastChangeEvent(true); + input->dispatchEvent(Event::create(eventNames().inputEvent, true, false)); + } + input->onSearch(); + event->setDefaultHandled(); + } + } + } + + if (!event->defaultHandled()) + HTMLDivElement::defaultEventHandler(event); +} + +// ---------------------------- + +inline SpinButtonElement::SpinButtonElement(HTMLElement* shadowParent) + : TextControlInnerElement(shadowParent->document(), shadowParent) + , m_capturing(false) + , m_upDownState(Indeterminate) + , m_pressStartingState(Indeterminate) + , m_repeatingTimer(this, &SpinButtonElement::repeatingTimerFired) +{ +} + +PassRefPtr<SpinButtonElement> SpinButtonElement::create(HTMLElement* shadowParent) +{ + return adoptRef(new SpinButtonElement(shadowParent)); +} + +void SpinButtonElement::defaultEventHandler(Event* event) +{ + if (!event->isMouseEvent()) { + if (!event->defaultHandled()) + HTMLDivElement::defaultEventHandler(event); + return; + } + + RenderBox* box = renderBox(); + if (!box) { + if (!event->defaultHandled()) + HTMLDivElement::defaultEventHandler(event); + return; + } + + HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode()); + if (input->disabled() || input->isReadOnlyFormControl()) { + if (!event->defaultHandled()) + HTMLDivElement::defaultEventHandler(event); + return; + } + + MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); + IntPoint local = roundedIntPoint(box->absoluteToLocal(mouseEvent->absoluteLocation(), false, true)); + if (mouseEvent->type() == eventNames().mousedownEvent && mouseEvent->button() == LeftButton) { + if (box->borderBoxRect().contains(local)) { + RefPtr<Node> protector(input); + input->focus(); + input->select(); + input->stepUpFromRenderer(m_upDownState == Up ? 1 : -1); + event->setDefaultHandled(); + startRepeatingTimer(); + } + } else if (mouseEvent->type() == eventNames().mouseupEvent && mouseEvent->button() == LeftButton) + stopRepeatingTimer(); + else if (event->type() == eventNames().mousemoveEvent) { + if (box->borderBoxRect().contains(local)) { + if (!m_capturing) { + if (Frame* frame = document()->frame()) { + frame->eventHandler()->setCapturingMouseEventsNode(this); + m_capturing = true; + } + } + UpDownState oldUpDownState = m_upDownState; + m_upDownState = local.y() < box->height() / 2 ? Up : Down; + if (m_upDownState != oldUpDownState) + renderer()->repaint(); + } else { + if (m_capturing) { + stopRepeatingTimer(); + if (Frame* frame = document()->frame()) { + frame->eventHandler()->setCapturingMouseEventsNode(0); + m_capturing = false; + } + } + } + } + + if (!event->defaultHandled()) + HTMLDivElement::defaultEventHandler(event); +} + +void SpinButtonElement::startRepeatingTimer() +{ + m_pressStartingState = m_upDownState; + ScrollbarTheme* theme = ScrollbarTheme::nativeTheme(); + m_repeatingTimer.start(theme->initialAutoscrollTimerDelay(), theme->autoscrollTimerDelay()); +} + +void SpinButtonElement::stopRepeatingTimer() +{ + m_repeatingTimer.stop(); +} + +void SpinButtonElement::repeatingTimerFired(Timer<SpinButtonElement>*) +{ + HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode()); + if (input->disabled() || input->isReadOnlyFormControl()) + return; + // On Mac OS, NSStepper updates the value for the button under the mouse + // cursor regardless of the button pressed at the beginning. So the + // following check is not needed for Mac OS. +#if !OS(MAC_OS_X) + if (m_upDownState != m_pressStartingState) + return; +#endif + input->stepUpFromRenderer(m_upDownState == Up ? 1 : -1); +} + +void SpinButtonElement::setHovered(bool flag) +{ + if (!hovered() && flag) + m_upDownState = Indeterminate; + TextControlInnerElement::setHovered(flag); +} + + +// ---------------------------- + +#if ENABLE(INPUT_SPEECH) + +inline InputFieldSpeechButtonElement::InputFieldSpeechButtonElement(HTMLElement* shadowParent) + : TextControlInnerElement(shadowParent->document(), shadowParent) + , m_capturing(false) + , m_state(Idle) + , m_listenerId(document()->page()->speechInput()->registerListener(this)) +{ +} + +InputFieldSpeechButtonElement::~InputFieldSpeechButtonElement() +{ + SpeechInput* speech = speechInput(); + if (speech) { // Could be null when page is unloading. + if (m_state != Idle) + speech->cancelRecognition(m_listenerId); + speech->unregisterListener(m_listenerId); + } +} + +PassRefPtr<InputFieldSpeechButtonElement> InputFieldSpeechButtonElement::create(HTMLElement* shadowParent) +{ + return adoptRef(new InputFieldSpeechButtonElement(shadowParent)); +} + +void InputFieldSpeechButtonElement::defaultEventHandler(Event* event) +{ + // For privacy reasons, only allow clicks directly coming from the user. + if (!event->fromUserGesture()) { + HTMLDivElement::defaultEventHandler(event); + return; + } + + // On mouse down, select the text and set focus. + HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode()); + if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { + if (renderer() && renderer()->visibleToHitTesting()) { + if (Frame* frame = document()->frame()) { + frame->eventHandler()->setCapturingMouseEventsNode(this); + m_capturing = true; + } + } + // The call to focus() below dispatches a focus event, and an event handler in the page might + // remove the input element from DOM. To make sure it remains valid until we finish our work + // here, we take a temporary reference. + RefPtr<HTMLInputElement> holdRef(input); + input->focus(); + input->select(); + event->setDefaultHandled(); + } + // On mouse up, release capture cleanly. + if (event->type() == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { + if (m_capturing && renderer() && renderer()->visibleToHitTesting()) { + if (Frame* frame = document()->frame()) { + frame->eventHandler()->setCapturingMouseEventsNode(0); + m_capturing = false; + } + } + } + + if (event->type() == eventNames().clickEvent) { + switch (m_state) { + case Idle: + if (speechInput()->startRecognition(m_listenerId, input->renderer()->absoluteBoundingBoxRect(), input->computeInheritedLanguage(), input->getAttribute(webkitgrammarAttr))) + setState(Recording); + break; + case Recording: + speechInput()->stopRecording(m_listenerId); + break; + case Recognizing: + // Nothing to do here, we will continue to wait for results. + break; + } + event->setDefaultHandled(); + } + + if (!event->defaultHandled()) + HTMLDivElement::defaultEventHandler(event); +} + +void InputFieldSpeechButtonElement::setState(SpeechInputState state) +{ + if (m_state != state) { + m_state = state; + shadowAncestorNode()->renderer()->repaint(); + } +} + +SpeechInput* InputFieldSpeechButtonElement::speechInput() +{ + return document()->page() ? document()->page()->speechInput() : 0; +} + +void InputFieldSpeechButtonElement::didCompleteRecording(int) +{ + setState(Recognizing); +} + +void InputFieldSpeechButtonElement::didCompleteRecognition(int) +{ + setState(Idle); +} + +void InputFieldSpeechButtonElement::setRecognitionResult(int, const SpeechInputResultArray& results) +{ + m_results = results; + + HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode()); + // The call to setValue() below dispatches an event, and an event handler in the page might + // remove the input element from DOM. To make sure it remains valid until we finish our work + // here, we take a temporary reference. + RefPtr<HTMLInputElement> holdRef(input); + input->setValue(results.isEmpty() ? "" : results[0]->utterance()); + input->dispatchEvent(SpeechInputEvent::create(eventNames().webkitspeechchangeEvent, results)); + renderer()->repaint(); +} + +void InputFieldSpeechButtonElement::detach() +{ + if (m_capturing) { + if (Frame* frame = document()->frame()) + frame->eventHandler()->setCapturingMouseEventsNode(0); + } + + if (m_state != Idle) + speechInput()->cancelRecognition(m_listenerId); + + TextControlInnerElement::detach(); +} + +#endif // ENABLE(INPUT_SPEECH) + +} diff --git a/Source/WebCore/rendering/TextControlInnerElements.h b/Source/WebCore/rendering/TextControlInnerElements.h new file mode 100644 index 0000000..bb77dcd --- /dev/null +++ b/Source/WebCore/rendering/TextControlInnerElements.h @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2006, 2008, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TextControlInnerElements_h +#define TextControlInnerElements_h + +#include "HTMLDivElement.h" +#include "SpeechInputListener.h" +#include "Timer.h" +#include <wtf/Forward.h> + +namespace WebCore { + +class SpeechInput; + +class TextControlInnerElement : public HTMLDivElement { +public: + static PassRefPtr<TextControlInnerElement> create(HTMLElement* shadowParent); + virtual void detach(); + + void attachInnerElement(Node*, PassRefPtr<RenderStyle>, RenderArena*); + +protected: + TextControlInnerElement(Document*, HTMLElement* shadowParent = 0); + +private: + virtual bool isMouseFocusable() const { return false; } + + RefPtr<HTMLElement> m_shadowParent; +}; + +class TextControlInnerTextElement : public TextControlInnerElement { +public: + static PassRefPtr<TextControlInnerTextElement> create(Document*, HTMLElement* shadowParent); + + virtual void defaultEventHandler(Event*); + +private: + TextControlInnerTextElement(Document*, HTMLElement* shadowParent); + virtual RenderObject* createRenderer(RenderArena*, RenderStyle*); +}; + +class SearchFieldResultsButtonElement : public TextControlInnerElement { +public: + static PassRefPtr<SearchFieldResultsButtonElement> create(Document*); + + virtual void defaultEventHandler(Event*); + +private: + SearchFieldResultsButtonElement(Document*); +}; + +class SearchFieldCancelButtonElement : public TextControlInnerElement { +public: + static PassRefPtr<SearchFieldCancelButtonElement> create(Document*); + + virtual void defaultEventHandler(Event*); + +private: + SearchFieldCancelButtonElement(Document*); + + virtual void detach(); + + bool m_capturing; +}; + +class SpinButtonElement : public TextControlInnerElement { +public: + enum UpDownState { + Indeterminate, // Hovered, but the event is not handled. + Down, + Up, + }; + + static PassRefPtr<SpinButtonElement> create(HTMLElement*); + UpDownState upDownState() const { return m_upDownState; } + +private: + SpinButtonElement(HTMLElement*); + + virtual bool isSpinButtonElement() const { return true; } + // FIXME: shadowAncestorNode() should be const. + virtual bool isEnabledFormControl() const { return static_cast<Element*>(const_cast<SpinButtonElement*>(this)->shadowAncestorNode())->isEnabledFormControl(); } + virtual bool isReadOnlyFormControl() const { return static_cast<Element*>(const_cast<SpinButtonElement*>(this)->shadowAncestorNode())->isReadOnlyFormControl(); } + virtual void defaultEventHandler(Event*); + void startRepeatingTimer(); + void stopRepeatingTimer(); + void repeatingTimerFired(Timer<SpinButtonElement>*); + virtual void setHovered(bool = true); + + bool m_capturing; + UpDownState m_upDownState; + UpDownState m_pressStartingState; + Timer<SpinButtonElement> m_repeatingTimer; +}; + +#if ENABLE(INPUT_SPEECH) + +class InputFieldSpeechButtonElement + : public TextControlInnerElement, + public SpeechInputListener { +public: + enum SpeechInputState { + Idle, + Recording, + Recognizing, + }; + + static PassRefPtr<InputFieldSpeechButtonElement> create(HTMLElement*); + virtual ~InputFieldSpeechButtonElement(); + + virtual void detach(); + virtual void defaultEventHandler(Event*); + SpeechInputState state() const { return m_state; } + + // SpeechInputListener methods. + void didCompleteRecording(int); + void didCompleteRecognition(int); + void setRecognitionResult(int, const SpeechInputResultArray&); + +private: + InputFieldSpeechButtonElement(HTMLElement*); + SpeechInput* speechInput(); + void setState(SpeechInputState state); + + bool m_capturing; + SpeechInputState m_state; + int m_listenerId; + SpeechInputResultArray m_results; +}; + +#endif // ENABLE(INPUT_SPEECH) + +} // namespace + +#endif diff --git a/Source/WebCore/rendering/TrailingFloatsRootInlineBox.h b/Source/WebCore/rendering/TrailingFloatsRootInlineBox.h new file mode 100644 index 0000000..6629857 --- /dev/null +++ b/Source/WebCore/rendering/TrailingFloatsRootInlineBox.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TrailingFloatsRootInlineBox_h +#define TrailingFloatsRootInlineBox_h + +#include "RootInlineBox.h" + +namespace WebCore { + +class TrailingFloatsRootInlineBox : public RootInlineBox { +public: + TrailingFloatsRootInlineBox(RenderBlock* block) + : RootInlineBox(block) + { + setHasVirtualLogicalHeight(); + } + +private: + virtual int virtualLogicalHeight() const { return 0; } +}; + +} // namespace WebCore + +#endif // TrailingFloatsRootInlineBox_h diff --git a/Source/WebCore/rendering/TransformState.cpp b/Source/WebCore/rendering/TransformState.cpp new file mode 100644 index 0000000..ecc614e --- /dev/null +++ b/Source/WebCore/rendering/TransformState.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "TransformState.h" + +namespace WebCore { + +void TransformState::move(int x, int y, TransformAccumulation accumulate) +{ + if (m_accumulatingTransform && m_accumulatedTransform) { + // If we're accumulating into an existing transform, apply the translation. + if (m_direction == ApplyTransformDirection) + m_accumulatedTransform->translateRight(x, y); + else + m_accumulatedTransform->translate(-x, -y); // We're unapplying, so negate + + // Then flatten if necessary. + if (accumulate == FlattenTransform) + flatten(); + } else { + // Just move the point and, optionally, the quad. + m_lastPlanarPoint.move(x, y); + if (m_mapQuad) + m_lastPlanarQuad.move(x, y); + } + m_accumulatingTransform = accumulate == AccumulateTransform; +} + +// FIXME: We transform AffineTransform to TransformationMatrix. This is rather inefficient. +void TransformState::applyTransform(const AffineTransform& transformFromContainer, TransformAccumulation accumulate) +{ + applyTransform(transformFromContainer.toTransformationMatrix(), accumulate); +} + +void TransformState::applyTransform(const TransformationMatrix& transformFromContainer, TransformAccumulation accumulate) +{ + // If we have an accumulated transform from last time, multiply in this transform + if (m_accumulatedTransform) { + if (m_direction == ApplyTransformDirection) + m_accumulatedTransform->multiply(transformFromContainer); + else + m_accumulatedTransform->multLeft(transformFromContainer); + } else if (accumulate == AccumulateTransform) { + // Make one if we started to accumulate + m_accumulatedTransform.set(new TransformationMatrix(transformFromContainer)); + } + + if (accumulate == FlattenTransform) { + const TransformationMatrix* finalTransform = m_accumulatedTransform ? m_accumulatedTransform.get() : &transformFromContainer; + flattenWithTransform(*finalTransform); + } + m_accumulatingTransform = accumulate == AccumulateTransform; +} + +void TransformState::flatten() +{ + if (!m_accumulatedTransform) { + m_accumulatingTransform = false; + return; + } + + flattenWithTransform(*m_accumulatedTransform); +} + +FloatPoint TransformState::mappedPoint() const +{ + if (!m_accumulatedTransform) + return m_lastPlanarPoint; + + if (m_direction == ApplyTransformDirection) + return m_accumulatedTransform->mapPoint(m_lastPlanarPoint); + + return m_accumulatedTransform->inverse().projectPoint(m_lastPlanarPoint); +} + +FloatQuad TransformState::mappedQuad() const +{ + if (!m_accumulatedTransform) + return m_lastPlanarQuad; + + if (m_direction == ApplyTransformDirection) + return m_accumulatedTransform->mapQuad(m_lastPlanarQuad); + + return m_accumulatedTransform->inverse().projectQuad(m_lastPlanarQuad); +} + +void TransformState::flattenWithTransform(const TransformationMatrix& t) +{ + if (m_direction == ApplyTransformDirection) { + m_lastPlanarPoint = t.mapPoint(m_lastPlanarPoint); + if (m_mapQuad) + m_lastPlanarQuad = t.mapQuad(m_lastPlanarQuad); + } else { + TransformationMatrix inverseTransform = t.inverse(); + m_lastPlanarPoint = inverseTransform.projectPoint(m_lastPlanarPoint); + if (m_mapQuad) + m_lastPlanarQuad = inverseTransform.projectQuad(m_lastPlanarQuad); + } + + // We could throw away m_accumulatedTransform if we wanted to here, but that + // would cause thrash when traversing hierarchies with alternating + // preserve-3d and flat elements. + if (m_accumulatedTransform) + m_accumulatedTransform->makeIdentity(); + m_accumulatingTransform = false; +} + +// HitTestingTransformState methods +void HitTestingTransformState::translate(int x, int y, TransformAccumulation accumulate) +{ + m_accumulatedTransform.translate(x, y); + if (accumulate == FlattenTransform) + flattenWithTransform(m_accumulatedTransform); + + m_accumulatingTransform = accumulate == AccumulateTransform; +} + +void HitTestingTransformState::applyTransform(const TransformationMatrix& transformFromContainer, TransformAccumulation accumulate) +{ + m_accumulatedTransform.multLeft(transformFromContainer); + if (accumulate == FlattenTransform) + flattenWithTransform(m_accumulatedTransform); + + m_accumulatingTransform = accumulate == AccumulateTransform; +} + +void HitTestingTransformState::flatten() +{ + flattenWithTransform(m_accumulatedTransform); +} + +void HitTestingTransformState::flattenWithTransform(const TransformationMatrix& t) +{ + TransformationMatrix inverseTransform = t.inverse(); + m_lastPlanarPoint = inverseTransform.projectPoint(m_lastPlanarPoint); + m_lastPlanarQuad = inverseTransform.projectQuad(m_lastPlanarQuad); + + m_accumulatedTransform.makeIdentity(); + m_accumulatingTransform = false; +} + +FloatPoint HitTestingTransformState::mappedPoint() const +{ + return m_accumulatedTransform.inverse().projectPoint(m_lastPlanarPoint); +} + +FloatQuad HitTestingTransformState::mappedQuad() const +{ + return m_accumulatedTransform.inverse().projectQuad(m_lastPlanarQuad); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/TransformState.h b/Source/WebCore/rendering/TransformState.h new file mode 100644 index 0000000..0b4ca46 --- /dev/null +++ b/Source/WebCore/rendering/TransformState.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TransformState_h +#define TransformState_h + +#include "AffineTransform.h" +#include "FloatPoint.h" +#include "FloatQuad.h" +#include "IntSize.h" +#include "TransformationMatrix.h" +#include <wtf/Noncopyable.h> +#include <wtf/PassRefPtr.h> +#include <wtf/OwnPtr.h> +#include <wtf/RefCounted.h> + +namespace WebCore { + +class TransformState : public Noncopyable { +public: + enum TransformDirection { ApplyTransformDirection, UnapplyInverseTransformDirection }; + enum TransformAccumulation { FlattenTransform, AccumulateTransform }; + + // If quad is non-null, it will be mapped + TransformState(TransformDirection mappingDirection, const FloatPoint& p, const FloatQuad* quad = 0) + : m_lastPlanarPoint(p) + , m_accumulatingTransform(false) + , m_mapQuad(quad != 0) + , m_direction(mappingDirection) + { + if (quad) + m_lastPlanarQuad = *quad; + } + + void move(const IntSize& s, TransformAccumulation accumulate = FlattenTransform) + { + move(s.width(), s.height(), accumulate); + } + + void move(int x, int y, TransformAccumulation = FlattenTransform); + void applyTransform(const AffineTransform& transformFromContainer, TransformAccumulation = FlattenTransform); + void applyTransform(const TransformationMatrix& transformFromContainer, TransformAccumulation = FlattenTransform); + void flatten(); + + // Return the coords of the point or quad in the last flattened layer + FloatPoint lastPlanarPoint() const { return m_lastPlanarPoint; } + FloatQuad lastPlanarQuad() const { return m_lastPlanarQuad; } + + // Return the point or quad mapped through the current transform + FloatPoint mappedPoint() const; + FloatQuad mappedQuad() const; + +private: + void flattenWithTransform(const TransformationMatrix&); + + FloatPoint m_lastPlanarPoint; + FloatQuad m_lastPlanarQuad; + + // We only allocate the transform if we need to + OwnPtr<TransformationMatrix> m_accumulatedTransform; + bool m_accumulatingTransform; + bool m_mapQuad; + TransformDirection m_direction; +}; + +class HitTestingTransformState : public RefCounted<HitTestingTransformState> { +public: + static PassRefPtr<HitTestingTransformState> create(const FloatPoint& p, const FloatQuad& quad) + { + return adoptRef(new HitTestingTransformState(p, quad)); + } + + static PassRefPtr<HitTestingTransformState> create(const HitTestingTransformState& other) + { + return adoptRef(new HitTestingTransformState(other)); + } + + enum TransformAccumulation { FlattenTransform, AccumulateTransform }; + void translate(int x, int y, TransformAccumulation); + void applyTransform(const TransformationMatrix& transformFromContainer, TransformAccumulation); + + FloatPoint mappedPoint() const; + FloatQuad mappedQuad() const; + void flatten(); + + FloatPoint m_lastPlanarPoint; + FloatQuad m_lastPlanarQuad; + TransformationMatrix m_accumulatedTransform; + bool m_accumulatingTransform; + +private: + HitTestingTransformState(const FloatPoint& p, const FloatQuad& quad) + : m_lastPlanarPoint(p) + , m_lastPlanarQuad(quad) + , m_accumulatingTransform(false) + { + } + + HitTestingTransformState(const HitTestingTransformState& other) + : RefCounted<HitTestingTransformState>() + , m_lastPlanarPoint(other.m_lastPlanarPoint) + , m_lastPlanarQuad(other.m_lastPlanarQuad) + , m_accumulatedTransform(other.m_accumulatedTransform) + , m_accumulatingTransform(other.m_accumulatingTransform) + { + } + + void flattenWithTransform(const TransformationMatrix&); +}; + +} // namespace WebCore + +#endif // TransformState_h diff --git a/Source/WebCore/rendering/VerticalPositionCache.h b/Source/WebCore/rendering/VerticalPositionCache.h new file mode 100644 index 0000000..4deaef5 --- /dev/null +++ b/Source/WebCore/rendering/VerticalPositionCache.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef VerticalPositionCache_h +#define VerticalPositionCache_h + +#include "FontBaseline.h" +#include <wtf/HashMap.h> + +namespace WebCore { + +class RenderObject; + +// Values for vertical alignment. +const int PositionTop = -0x7fffffff; +const int PositionBottom = 0x7fffffff; +const int PositionUndefined = 0x80000000; + +class VerticalPositionCache : public Noncopyable { +public: + VerticalPositionCache() + { } + + int get(RenderObject* renderer, FontBaseline baselineType) const + { + const HashMap<RenderObject*, int>& mapToCheck = baselineType == AlphabeticBaseline ? m_alphabeticPositions : m_ideographicPositions; + const HashMap<RenderObject*, int>::const_iterator it = mapToCheck.find(renderer); + if (it == mapToCheck.end()) + return PositionUndefined; + return it->second; + } + + void set(RenderObject* renderer, FontBaseline baselineType, int position) + { + if (baselineType == AlphabeticBaseline) + m_alphabeticPositions.set(renderer, position); + else + m_ideographicPositions.set(renderer, position); + } + +private: + HashMap<RenderObject*, int> m_alphabeticPositions; + HashMap<RenderObject*, int> m_ideographicPositions; +}; + +} // namespace WebCore + +#endif // VerticalPositionCache_h diff --git a/Source/WebCore/rendering/break_lines.cpp b/Source/WebCore/rendering/break_lines.cpp new file mode 100644 index 0000000..16bfcc2 --- /dev/null +++ b/Source/WebCore/rendering/break_lines.cpp @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2005, 2007, 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "break_lines.h" + +#include "CharacterNames.h" +#include "TextBreakIterator.h" +#include <wtf/StdLibExtras.h> + +#if PLATFORM(MAC) +#include <CoreServices/CoreServices.h> +#endif + +namespace WebCore { + +static inline bool isBreakableSpace(UChar ch, bool treatNoBreakSpaceAsBreak) +{ + switch (ch) { + case ' ': + case '\n': + case '\t': + return true; + case noBreakSpace: + return treatNoBreakSpaceAsBreak; + default: + return false; + } +} + +static const UChar asciiLineBreakTableFirstChar = '!'; +static const UChar asciiLineBreakTableLastChar = 127; + +// Pack 8 bits into one byte +#define B(a, b, c, d, e, f, g, h) \ + ((a) | ((b) << 1) | ((c) << 2) | ((d) << 3) | ((e) << 4) | ((f) << 5) | ((g) << 6) | ((h) << 7)) + +// Line breaking table row for each digit (0-9) +#define DI { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } + +// Line breaking table row for ascii letters (a-z A-Z) +#define AL { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } + +#define F 0xFF + +// Line breaking table for printable ASCII characters. Line breaking opportunities in this table are as below: +// - before openning punctuations such as '(', '<', '[', '{' after certain characters (compatible with Firefox 3.6); +// - after '-' and '?' (backward-compatible, and compatible with Internet Explorer). +// Please refer to <https://bugs.webkit.org/show_bug.cgi?id=37698> for line breaking matrixes of different browsers +// and the ICU standard. +static const unsigned char asciiLineBreakTable[][(asciiLineBreakTableLastChar - asciiLineBreakTableFirstChar) / 8 + 1] = { + // ! " # $ % & ' ( ) * + , - . / 0 1-8 9 : ; < = > ? @ A-X Y Z [ \ ] ^ _ ` a-x y z { | } ~ DEL + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // ! + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // " + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // # + { B(0, 0, 0, 0, 0, 0, 0, 0), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0) }, // $ + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // % + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // & + { B(0, 0, 0, 0, 0, 0, 0, 0), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0) }, // ' + { B(0, 0, 0, 0, 0, 0, 0, 0), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0) }, // ( + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // ) + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // * + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // + + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // , + { B(1, 1, 1, 1, 1, 1, 1, 1), B(1, 1, 1, 1, 1, 1, 1, 1), F, B(1, 1, 1, 1, 1, 1, 1, 1), F, F, F, B(1, 1, 1, 1, 1, 1, 1, 1), F, F, F, B(1, 1, 1, 1, 1, 1, 1, 1) }, // - + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // . + { B(0, 0, 0, 0, 0, 0, 0, 0), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0) }, // / + DI, DI, DI, DI, DI, DI, DI, DI, DI, DI, // 0-9 + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // : + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // ; + { B(0, 0, 0, 0, 0, 0, 0, 0), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0) }, // < + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // = + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // > + { B(0, 0, 1, 1, 1, 1, 0, 1), B(0, 1, 1, 0, 1, 0, 0, 1), F, B(1, 0, 0, 1, 1, 1, 0, 1), F, F, F, B(1, 1, 1, 1, 0, 1, 1, 1), F, F, F, B(1, 1, 1, 1, 0, 1, 1, 0) }, // ? + { B(0, 0, 0, 0, 0, 0, 0, 0), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0) }, // @ + AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, // A-Z + { B(0, 0, 0, 0, 0, 0, 0, 0), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0) }, // [ + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // '\' + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // ] + { B(0, 0, 0, 0, 0, 0, 0, 0), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0) }, // ^ + { B(0, 0, 0, 0, 0, 0, 0, 0), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0) }, // _ + { B(0, 0, 0, 0, 0, 0, 0, 0), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0) }, // ` + AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, // a-z + { B(0, 0, 0, 0, 0, 0, 0, 0), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0) }, // { + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // | + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // } + { B(0, 0, 0, 0, 0, 0, 0, 1), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 1, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 1, 0, 0, 0, 0, 0) }, // ~ + { B(0, 0, 0, 0, 0, 0, 0, 0), B(0, 0, 0, 0, 0, 0, 0, 0), 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0, B(0, 0, 0, 0, 0, 0, 0, 0) }, // DEL +}; + +#undef B +#undef F +#undef DI +#undef AL + +COMPILE_ASSERT(WTF_ARRAY_LENGTH(asciiLineBreakTable) == asciiLineBreakTableLastChar - asciiLineBreakTableFirstChar + 1, TestLineBreakTableConsistency); + +static inline bool shouldBreakAfter(UChar ch, UChar nextCh) +{ + switch (ch) { + case ideographicComma: + case ideographicFullStop: + // FIXME: cases for ideographicComma and ideographicFullStop are a workaround for an issue in Unicode 5.0 + // which is likely to be resolved in Unicode 5.1 <http://bugs.webkit.org/show_bug.cgi?id=17411>. + // We may want to remove or conditionalize this workaround at some point. +#ifdef ANDROID_LAYOUT + // as '/' is used in uri which is always long, we would like to break it + case '/': +#endif + return true; + default: + // If both ch and nextCh are ASCII characters, use a lookup table for enhanced speed and for compatibility + // with other browsers (see comments for asciiLineBreakTable for details). + if (ch >= asciiLineBreakTableFirstChar && ch <= asciiLineBreakTableLastChar + && nextCh >= asciiLineBreakTableFirstChar && nextCh <= asciiLineBreakTableLastChar) { + const unsigned char* tableRow = asciiLineBreakTable[ch - asciiLineBreakTableFirstChar]; + int nextChIndex = nextCh - asciiLineBreakTableFirstChar; + return tableRow[nextChIndex / 8] & (1 << (nextChIndex % 8)); + } + // Otherwise defer to the Unicode algorithm by returning false. + return false; + } +} + +static inline bool needsLineBreakIterator(UChar ch) +{ + return ch > asciiLineBreakTableLastChar && ch != noBreakSpace; +} + +#if PLATFORM(MAC) && defined(BUILDING_ON_TIGER) +static inline TextBreakLocatorRef lineBreakLocator() +{ + TextBreakLocatorRef locator = 0; + UCCreateTextBreakLocator(0, 0, kUCTextBreakLineMask, &locator); + return locator; +} +#endif + +int nextBreakablePosition(const UChar* str, int pos, int len, bool treatNoBreakSpaceAsBreak) +{ +#if !PLATFORM(MAC) || !defined(BUILDING_ON_TIGER) + TextBreakIterator* breakIterator = 0; +#endif + int nextBreak = -1; + + UChar lastCh = pos > 0 ? str[pos - 1] : 0; + for (int i = pos; i < len; i++) { + UChar ch = str[i]; + + if (isBreakableSpace(ch, treatNoBreakSpaceAsBreak) || shouldBreakAfter(lastCh, ch)) + return i; + + if (needsLineBreakIterator(ch) || needsLineBreakIterator(lastCh)) { + if (nextBreak < i && i) { +#if !PLATFORM(MAC) || !defined(BUILDING_ON_TIGER) + if (!breakIterator) + breakIterator = lineBreakIterator(str, len); + if (breakIterator) + nextBreak = textBreakFollowing(breakIterator, i - 1); +#else + static TextBreakLocatorRef breakLocator = lineBreakLocator(); + if (breakLocator) { + UniCharArrayOffset nextUCBreak; + if (UCFindTextBreak(breakLocator, kUCTextBreakLineMask, 0, str, len, i, &nextUCBreak) == 0) + nextBreak = nextUCBreak; + } +#endif + } + if (i == nextBreak && !isBreakableSpace(lastCh, treatNoBreakSpaceAsBreak)) + return i; + } + + lastCh = ch; + } + + return len; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/break_lines.h b/Source/WebCore/rendering/break_lines.h new file mode 100644 index 0000000..4d6b8dc --- /dev/null +++ b/Source/WebCore/rendering/break_lines.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2005 Apple Computer, Inc. + * + * 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. + * + */ + +#ifndef break_lines_h +#define break_lines_h + +#include <wtf/unicode/Unicode.h> + +namespace WebCore { + + int nextBreakablePosition(const UChar*, int pos, int len, bool breakNBSP = false); + + inline bool isBreakable(const UChar* str, int pos, int len, int& nextBreakable, bool breakNBSP = false) + { + if (pos > nextBreakable) + nextBreakable = nextBreakablePosition(str, pos, len, breakNBSP); + return pos == nextBreakable; + } + +} // namespace WebCore + +#endif // break_lines_h diff --git a/Source/WebCore/rendering/style/BorderData.h b/Source/WebCore/rendering/style/BorderData.h new file mode 100644 index 0000000..03635d9 --- /dev/null +++ b/Source/WebCore/rendering/style/BorderData.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef BorderData_h +#define BorderData_h + +#include "BorderValue.h" +#include "LengthSize.h" +#include "NinePieceImage.h" + +namespace WebCore { + +class BorderData { +friend class RenderStyle; +public: + BorderData() : m_topLeft(Length(0, Fixed), Length(0, Fixed)) + , m_topRight(Length(0, Fixed), Length(0, Fixed)) + , m_bottomLeft(Length(0, Fixed), Length(0, Fixed)) + , m_bottomRight(Length(0, Fixed), Length(0, Fixed)) + { + } + bool hasBorder() const + { + bool haveImage = m_image.hasImage(); + return m_left.nonZero(!haveImage) || m_right.nonZero(!haveImage) || m_top.nonZero(!haveImage) || m_bottom.nonZero(!haveImage); + } + + bool hasBorderRadius() const + { + if (m_topLeft.width().rawValue() > 0) + return true; + if (m_topRight.width().rawValue() > 0) + return true; + if (m_bottomLeft.width().rawValue() > 0) + return true; + if (m_bottomRight.width().rawValue() > 0) + return true; + return false; + } + + unsigned short borderLeftWidth() const + { + if (!m_image.hasImage() && (m_left.style() == BNONE || m_left.style() == BHIDDEN)) + return 0; + return m_left.width(); + } + + unsigned short borderRightWidth() const + { + if (!m_image.hasImage() && (m_right.style() == BNONE || m_right.style() == BHIDDEN)) + return 0; + return m_right.width(); + } + + unsigned short borderTopWidth() const + { + if (!m_image.hasImage() && (m_top.style() == BNONE || m_top.style() == BHIDDEN)) + return 0; + return m_top.width(); + } + + unsigned short borderBottomWidth() const + { + if (!m_image.hasImage() && (m_bottom.style() == BNONE || m_bottom.style() == BHIDDEN)) + return 0; + return m_bottom.width(); + } + + bool operator==(const BorderData& o) const + { + return m_left == o.m_left && m_right == o.m_right && m_top == o.m_top && m_bottom == o.m_bottom && m_image == o.m_image + && m_topLeft == o.m_topLeft && m_topRight == o.m_topRight && m_bottomLeft == o.m_bottomLeft && m_bottomRight == o.m_bottomRight; + } + + bool operator!=(const BorderData& o) const + { + return !(*this == o); + } + + const BorderValue& left() const { return m_left; } + const BorderValue& right() const { return m_right; } + const BorderValue& top() const { return m_top; } + const BorderValue& bottom() const { return m_bottom; } + + const NinePieceImage& image() const { return m_image; } + + const LengthSize& topLeft() const { return m_topLeft; } + const LengthSize& topRight() const { return m_topRight; } + const LengthSize& bottomLeft() const { return m_bottomLeft; } + const LengthSize& bottomRight() const { return m_bottomRight; } + +private: + BorderValue m_left; + BorderValue m_right; + BorderValue m_top; + BorderValue m_bottom; + + NinePieceImage m_image; + + LengthSize m_topLeft; + LengthSize m_topRight; + LengthSize m_bottomLeft; + LengthSize m_bottomRight; +}; + +} // namespace WebCore + +#endif // BorderData_h diff --git a/Source/WebCore/rendering/style/BorderValue.h b/Source/WebCore/rendering/style/BorderValue.h new file mode 100644 index 0000000..3e6fd5d --- /dev/null +++ b/Source/WebCore/rendering/style/BorderValue.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef BorderValue_h +#define BorderValue_h + +#include "Color.h" +#include "RenderStyleConstants.h" + +namespace WebCore { + +class BorderValue { +friend class RenderStyle; +public: + BorderValue() + : m_width(3) + , m_style(BNONE) + { + } + + bool nonZero(bool checkStyle = true) const + { + return width() && (!checkStyle || m_style != BNONE); + } + + bool isTransparent() const + { + return m_color.isValid() && !m_color.alpha(); + } + + bool isVisible(bool checkStyle = true) const + { + return nonZero(checkStyle) && !isTransparent() && (!checkStyle || m_style != BHIDDEN); + } + + bool operator==(const BorderValue& o) const + { + return m_width == o.m_width && m_style == o.m_style && m_color == o.m_color; + } + + bool operator!=(const BorderValue& o) const + { + return !(*this == o); + } + + const Color& color() const { return m_color; } + unsigned short width() const { return m_width; } + EBorderStyle style() const { return static_cast<EBorderStyle>(m_style); } + +protected: + Color m_color; + unsigned m_width : 12; + unsigned m_style : 4; // EBorderStyle +}; + +} // namespace WebCore + +#endif // BorderValue_h diff --git a/Source/WebCore/rendering/style/CollapsedBorderValue.h b/Source/WebCore/rendering/style/CollapsedBorderValue.h new file mode 100644 index 0000000..6207231 --- /dev/null +++ b/Source/WebCore/rendering/style/CollapsedBorderValue.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef CollapsedBorderValue_h +#define CollapsedBorderValue_h + +#include "BorderValue.h" + +namespace WebCore { + +class CollapsedBorderValue { +friend class RenderStyle; +public: + CollapsedBorderValue() + : m_border(0) + , m_precedence(BOFF) + { + } + + CollapsedBorderValue(const BorderValue* b, Color c, EBorderPrecedence p) + : m_border(b) + , m_borderColor(c) + , m_precedence(p) + { + } + + int width() const { return m_border && m_border->nonZero() ? m_border->width() : 0; } + EBorderStyle style() const { return m_border ? m_border->style() : BHIDDEN; } + bool exists() const { return m_border; } + const Color& color() const { return m_borderColor; } + bool isTransparent() const { return m_border ? m_border->isTransparent() : true; } + EBorderPrecedence precedence() const { return m_precedence; } + + bool operator==(const CollapsedBorderValue& o) const + { + if (!m_border) + return !o.m_border; + if (!o.m_border) + return false; + return *m_border == *o.m_border && m_borderColor == o.m_borderColor && m_precedence == o.m_precedence; + } + +private: + const BorderValue* m_border; + Color m_borderColor; + EBorderPrecedence m_precedence; +}; + +} // namespace WebCore + +#endif // CollapsedBorderValue_h diff --git a/Source/WebCore/rendering/style/ContentData.cpp b/Source/WebCore/rendering/style/ContentData.cpp new file mode 100644 index 0000000..d150f77 --- /dev/null +++ b/Source/WebCore/rendering/style/ContentData.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * 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 "ContentData.h" + +#include "StyleImage.h" +#include <wtf/text/StringImpl.h> + +namespace WebCore { + +void ContentData::clear() +{ + deleteContent(); + + // Delete the singly-linked list without recursing. + for (OwnPtr<ContentData> next = m_next.release(); next; next = next->m_next.release()) { } +} + +// FIXME: Why isn't this just operator==? +// FIXME: This is not a good name for a boolean-returning function. +bool ContentData::dataEquivalent(const ContentData& other) const +{ + if (type() != other.type()) + return false; + + switch (type()) { + case CONTENT_NONE: + return true; + case CONTENT_TEXT: + return equal(text(), other.text()); + case CONTENT_OBJECT: + return StyleImage::imagesEquivalent(image(), other.image()); + case CONTENT_COUNTER: + return *counter() == *other.counter(); + } + + ASSERT_NOT_REACHED(); + return false; +} + +void ContentData::deleteContent() +{ + switch (m_type) { + case CONTENT_NONE: + break; + case CONTENT_OBJECT: + m_content.m_image->deref(); + break; + case CONTENT_TEXT: + m_content.m_text->deref(); + break; + case CONTENT_COUNTER: + delete m_content.m_counter; + break; + } + + m_type = CONTENT_NONE; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/ContentData.h b/Source/WebCore/rendering/style/ContentData.h new file mode 100644 index 0000000..4f964a2 --- /dev/null +++ b/Source/WebCore/rendering/style/ContentData.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef ContentData_h +#define ContentData_h + +#include "CounterContent.h" +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class StyleImage; + +struct ContentData : Noncopyable { +public: + ContentData() + : m_type(CONTENT_NONE) + { + } + + ~ContentData() + { + clear(); + } + + void clear(); + + bool isCounter() const { return m_type == CONTENT_COUNTER; } + bool isImage() const { return m_type == CONTENT_OBJECT; } + bool isNone() const { return m_type == CONTENT_NONE; } + bool isText() const { return m_type == CONTENT_TEXT; } + + StyleContentType type() const { return m_type; } + + bool dataEquivalent(const ContentData&) const; + + StyleImage* image() const + { + ASSERT(isImage()); + return m_content.m_image; + } + void setImage(PassRefPtr<StyleImage> image) + { + deleteContent(); + m_type = CONTENT_OBJECT; + m_content.m_image = image.leakRef(); + } + + StringImpl* text() const + { + ASSERT(isText()); + return m_content.m_text; + } + void setText(PassRefPtr<StringImpl> text) + { + deleteContent(); + m_type = CONTENT_TEXT; + m_content.m_text = text.leakRef(); + } + + CounterContent* counter() const + { + ASSERT(isCounter()); + return m_content.m_counter; + } + void setCounter(PassOwnPtr<CounterContent> counter) + { + deleteContent(); + m_type = CONTENT_COUNTER; + m_content.m_counter = counter.leakPtr(); + } + + ContentData* next() const { return m_next.get(); } + void setNext(PassOwnPtr<ContentData> next) { m_next = next; } + +private: + void deleteContent(); + + StyleContentType m_type; + union { + StyleImage* m_image; + StringImpl* m_text; + CounterContent* m_counter; + } m_content; + OwnPtr<ContentData> m_next; +}; + +} // namespace WebCore + +#endif // ContentData_h diff --git a/Source/WebCore/rendering/style/CounterContent.h b/Source/WebCore/rendering/style/CounterContent.h new file mode 100644 index 0000000..52757ad --- /dev/null +++ b/Source/WebCore/rendering/style/CounterContent.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef CounterContent_h +#define CounterContent_h + +#include "RenderStyleConstants.h" +#include <wtf/text/AtomicString.h> + +namespace WebCore { + +class CounterContent : public FastAllocBase { +public: + CounterContent(const AtomicString& identifier, EListStyleType style, const AtomicString& separator) + : m_identifier(identifier) + , m_listStyle(style) + , m_separator(separator) + { + } + + const AtomicString& identifier() const { return m_identifier; } + EListStyleType listStyle() const { return m_listStyle; } + const AtomicString& separator() const { return m_separator; } + +private: + AtomicString m_identifier; + EListStyleType m_listStyle; + AtomicString m_separator; +}; + +static inline bool operator==(const CounterContent& a, const CounterContent& b) +{ + return a.identifier() == b.identifier() + && a.listStyle() == b.listStyle() + && a.separator() == b.separator(); +} + + +} // namespace WebCore + +#endif // CounterContent_h diff --git a/Source/WebCore/rendering/style/CounterDirectives.cpp b/Source/WebCore/rendering/style/CounterDirectives.cpp new file mode 100644 index 0000000..a0ff52f --- /dev/null +++ b/Source/WebCore/rendering/style/CounterDirectives.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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 "CounterDirectives.h" + +namespace WebCore { + +bool operator==(const CounterDirectives& a, const CounterDirectives& b) +{ + if (a.m_reset != b.m_reset || a.m_increment != b.m_increment) + return false; + if (a.m_reset && a.m_resetValue != b.m_resetValue) + return false; + if (a.m_increment && a.m_incrementValue != b.m_incrementValue) + return false; + return true; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/CounterDirectives.h b/Source/WebCore/rendering/style/CounterDirectives.h new file mode 100644 index 0000000..e54028e --- /dev/null +++ b/Source/WebCore/rendering/style/CounterDirectives.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef CounterDirectives_h +#define CounterDirectives_h + +#include <wtf/HashMap.h> +#include <wtf/RefPtr.h> +#include <wtf/text/AtomicStringImpl.h> + +namespace WebCore { + +struct CounterDirectives { + CounterDirectives() + : m_reset(false) + , m_increment(false) + { + } + + bool m_reset; + int m_resetValue; + bool m_increment; + int m_incrementValue; +}; + +bool operator==(const CounterDirectives&, const CounterDirectives&); +inline bool operator!=(const CounterDirectives& a, const CounterDirectives& b) { return !(a == b); } + +typedef HashMap<RefPtr<AtomicStringImpl>, CounterDirectives> CounterDirectiveMap; + +} // namespace WebCore + +#endif // CounterDirectives_h diff --git a/Source/WebCore/rendering/style/CursorData.h b/Source/WebCore/rendering/style/CursorData.h new file mode 100644 index 0000000..6d0a273 --- /dev/null +++ b/Source/WebCore/rendering/style/CursorData.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef CursorData_h +#define CursorData_h + +#include "IntPoint.h" +#include "StyleImage.h" + +namespace WebCore { + +class CursorData { +public: + CursorData(PassRefPtr<StyleImage> image, const IntPoint& hotSpot) + : m_image(image) + , m_hotSpot(hotSpot) + { + } + + bool operator==(const CursorData& o) const + { + return m_hotSpot == o.m_hotSpot && m_image == o.m_image; + } + + bool operator!=(const CursorData& o) const + { + return !(*this == o); + } + + StyleImage* image() const { return m_image.get(); } + void setImage(PassRefPtr<StyleImage> image) { m_image = image; } + + const IntPoint& hotSpot() const { return m_hotSpot; } + +private: + RefPtr<StyleImage> m_image; + IntPoint m_hotSpot; // for CSS3 support +}; + +} // namespace WebCore + +#endif // CursorData_h diff --git a/Source/WebCore/rendering/style/CursorList.h b/Source/WebCore/rendering/style/CursorList.h new file mode 100644 index 0000000..a1d1fe7 --- /dev/null +++ b/Source/WebCore/rendering/style/CursorList.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef CursorList_h +#define CursorList_h + +#include "CursorData.h" +#include <wtf/RefCounted.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class CursorList : public RefCounted<CursorList> { +public: + static PassRefPtr<CursorList> create() + { + return adoptRef(new CursorList); + } + + const CursorData& operator[](int i) const { return m_vector[i]; } + CursorData& operator[](int i) { return m_vector[i]; } + const CursorData& at(size_t i) const { return m_vector.at(i); } + CursorData& at(size_t i) { return m_vector.at(i); } + + bool operator==(const CursorList& o) const { return m_vector == o.m_vector; } + bool operator!=(const CursorList& o) const { return m_vector != o.m_vector; } + + size_t size() const { return m_vector.size(); } + void append(const CursorData& cursorData) { m_vector.append(cursorData); } + +private: + CursorList() + { + } + + Vector<CursorData> m_vector; +}; + +} // namespace WebCore + +#endif // CursorList_h diff --git a/Source/WebCore/rendering/style/DataRef.h b/Source/WebCore/rendering/style/DataRef.h new file mode 100644 index 0000000..c8d8072 --- /dev/null +++ b/Source/WebCore/rendering/style/DataRef.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2008 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef DataRef_h +#define DataRef_h + +#include <wtf/RefPtr.h> + +namespace WebCore { + +template <typename T> class DataRef { +public: + const T* get() const { return m_data.get(); } + + const T& operator*() const { return *get(); } + const T* operator->() const { return get(); } + + T* access() + { + if (!m_data->hasOneRef()) + m_data = m_data->copy(); + return m_data.get(); + } + + void init() + { + ASSERT(!m_data); + m_data = T::create(); + } + + bool operator==(const DataRef<T>& o) const + { + ASSERT(m_data); + ASSERT(o.m_data); + return m_data == o.m_data || *m_data == *o.m_data; + } + + bool operator!=(const DataRef<T>& o) const + { + ASSERT(m_data); + ASSERT(o.m_data); + return m_data != o.m_data && *m_data != *o.m_data; + } + +private: + RefPtr<T> m_data; +}; + +} // namespace WebCore + +#endif // DataRef_h diff --git a/Source/WebCore/rendering/style/FillLayer.cpp b/Source/WebCore/rendering/style/FillLayer.cpp new file mode 100644 index 0000000..59f3bb2 --- /dev/null +++ b/Source/WebCore/rendering/style/FillLayer.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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 "FillLayer.h" + +namespace WebCore { + +FillLayer::FillLayer(EFillLayerType type) + : m_next(0) + , m_image(FillLayer::initialFillImage(type)) + , m_xPosition(FillLayer::initialFillXPosition(type)) + , m_yPosition(FillLayer::initialFillYPosition(type)) + , m_attachment(FillLayer::initialFillAttachment(type)) + , m_clip(FillLayer::initialFillClip(type)) + , m_origin(FillLayer::initialFillOrigin(type)) + , m_repeatX(FillLayer::initialFillRepeatX(type)) + , m_repeatY(FillLayer::initialFillRepeatY(type)) + , m_composite(FillLayer::initialFillComposite(type)) + , m_sizeType(SizeNone) + , m_sizeLength(FillLayer::initialFillSizeLength(type)) + , m_imageSet(false) + , m_attachmentSet(false) + , m_clipSet(false) + , m_originSet(false) + , m_repeatXSet(false) + , m_repeatYSet(false) + , m_xPosSet(false) + , m_yPosSet(false) + , m_compositeSet(type == MaskFillLayer) + , m_type(type) +{ +} + +FillLayer::FillLayer(const FillLayer& o) + : m_next(o.m_next ? new FillLayer(*o.m_next) : 0) + , m_image(o.m_image) + , m_xPosition(o.m_xPosition) + , m_yPosition(o.m_yPosition) + , m_attachment(o.m_attachment) + , m_clip(o.m_clip) + , m_origin(o.m_origin) + , m_repeatX(o.m_repeatX) + , m_repeatY(o.m_repeatY) + , m_composite(o.m_composite) + , m_sizeType(o.m_sizeType) + , m_sizeLength(o.m_sizeLength) + , m_imageSet(o.m_imageSet) + , m_attachmentSet(o.m_attachmentSet) + , m_clipSet(o.m_clipSet) + , m_originSet(o.m_originSet) + , m_repeatXSet(o.m_repeatXSet) + , m_repeatYSet(o.m_repeatYSet) + , m_xPosSet(o.m_xPosSet) + , m_yPosSet(o.m_yPosSet) + , m_compositeSet(o.m_compositeSet) + , m_type(o.m_type) +{ +} + +FillLayer::~FillLayer() +{ + delete m_next; +} + +FillLayer& FillLayer::operator=(const FillLayer& o) +{ + if (m_next != o.m_next) { + delete m_next; + m_next = o.m_next ? new FillLayer(*o.m_next) : 0; + } + + m_image = o.m_image; + m_xPosition = o.m_xPosition; + m_yPosition = o.m_yPosition; + m_attachment = o.m_attachment; + m_clip = o.m_clip; + m_composite = o.m_composite; + m_origin = o.m_origin; + m_repeatX = o.m_repeatX; + m_repeatY = o.m_repeatY; + m_sizeType = o.m_sizeType; + m_sizeLength = o.m_sizeLength; + + m_imageSet = o.m_imageSet; + m_attachmentSet = o.m_attachmentSet; + m_clipSet = o.m_clipSet; + m_compositeSet = o.m_compositeSet; + m_originSet = o.m_originSet; + m_repeatXSet = o.m_repeatXSet; + m_repeatYSet = o.m_repeatYSet; + m_xPosSet = o.m_xPosSet; + m_yPosSet = o.m_yPosSet; + + m_type = o.m_type; + + return *this; +} + +bool FillLayer::operator==(const FillLayer& o) const +{ + // We do not check the "isSet" booleans for each property, since those are only used during initial construction + // to propagate patterns into layers. All layer comparisons happen after values have all been filled in anyway. + return StyleImage::imagesEquivalent(m_image.get(), o.m_image.get()) && m_xPosition == o.m_xPosition && m_yPosition == o.m_yPosition && + m_attachment == o.m_attachment && m_clip == o.m_clip && + m_composite == o.m_composite && m_origin == o.m_origin && m_repeatX == o.m_repeatX && + m_repeatY == o.m_repeatY && m_sizeType == o.m_sizeType && m_sizeLength == o.m_sizeLength && + m_type == o.m_type && ((m_next && o.m_next) ? *m_next == *o.m_next : m_next == o.m_next); +} + +void FillLayer::fillUnsetProperties() +{ + FillLayer* curr; + for (curr = this; curr && curr->isImageSet(); curr = curr->next()) { } + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (FillLayer* pattern = this; curr; curr = curr->next()) { + curr->m_image = pattern->m_image; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } + + for (curr = this; curr && curr->isXPositionSet(); curr = curr->next()) { } + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (FillLayer* pattern = this; curr; curr = curr->next()) { + curr->m_xPosition = pattern->m_xPosition; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } + + for (curr = this; curr && curr->isYPositionSet(); curr = curr->next()) { } + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (FillLayer* pattern = this; curr; curr = curr->next()) { + curr->m_yPosition = pattern->m_yPosition; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } + + for (curr = this; curr && curr->isAttachmentSet(); curr = curr->next()) { } + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (FillLayer* pattern = this; curr; curr = curr->next()) { + curr->m_attachment = pattern->m_attachment; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } + + for (curr = this; curr && curr->isClipSet(); curr = curr->next()) { } + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (FillLayer* pattern = this; curr; curr = curr->next()) { + curr->m_clip = pattern->m_clip; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } + + for (curr = this; curr && curr->isCompositeSet(); curr = curr->next()) { } + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (FillLayer* pattern = this; curr; curr = curr->next()) { + curr->m_composite = pattern->m_composite; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } + + for (curr = this; curr && curr->isOriginSet(); curr = curr->next()) { } + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (FillLayer* pattern = this; curr; curr = curr->next()) { + curr->m_origin = pattern->m_origin; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } + + for (curr = this; curr && curr->isRepeatXSet(); curr = curr->next()) { } + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (FillLayer* pattern = this; curr; curr = curr->next()) { + curr->m_repeatX = pattern->m_repeatX; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } + + for (curr = this; curr && curr->isRepeatYSet(); curr = curr->next()) { } + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (FillLayer* pattern = this; curr; curr = curr->next()) { + curr->m_repeatY = pattern->m_repeatY; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } + + for (curr = this; curr && curr->isSizeSet(); curr = curr->next()) { } + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (FillLayer* pattern = this; curr; curr = curr->next()) { + curr->m_sizeType = pattern->m_sizeType; + curr->m_sizeLength = pattern->m_sizeLength; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } +} + +void FillLayer::cullEmptyLayers() +{ + FillLayer* next; + for (FillLayer* p = this; p; p = next) { + next = p->m_next; + if (next && !next->isImageSet() && + !next->isXPositionSet() && !next->isYPositionSet() && + !next->isAttachmentSet() && !next->isClipSet() && + !next->isCompositeSet() && !next->isOriginSet() && + !next->isRepeatXSet() && !next->isRepeatYSet() + && !next->isSizeSet()) { + delete next; + p->m_next = 0; + break; + } + } +} + +bool FillLayer::containsImage(StyleImage* s) const +{ + if (!s) + return false; + if (m_image && *s == *m_image) + return true; + if (m_next) + return m_next->containsImage(s); + return false; +} + +bool FillLayer::imagesAreLoaded() const +{ + const FillLayer* curr; + for (curr = this; curr; curr = curr->next()) { + if (curr->m_image && !curr->m_image->isLoaded()) + return false; + } + + return true; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/FillLayer.h b/Source/WebCore/rendering/style/FillLayer.h new file mode 100644 index 0000000..49fb294 --- /dev/null +++ b/Source/WebCore/rendering/style/FillLayer.h @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef FillLayer_h +#define FillLayer_h + +#include "GraphicsTypes.h" +#include "Length.h" +#include "LengthSize.h" +#include "RenderStyleConstants.h" +#include "StyleImage.h" +#include <wtf/RefPtr.h> + +namespace WebCore { + +struct FillSize { + FillSize() + : type(SizeLength) + { + } + + FillSize(EFillSizeType t, LengthSize l) + : type(t) + , size(l) + { + } + + bool operator==(const FillSize& o) const + { + return type == o.type && size == o.size; + } + bool operator!=(const FillSize& o) const + { + return !(*this == o); + } + + EFillSizeType type; + LengthSize size; +}; + +class FillLayer : public FastAllocBase { +public: + FillLayer(EFillLayerType); + ~FillLayer(); + + StyleImage* image() const { return m_image.get(); } + Length xPosition() const { return m_xPosition; } + Length yPosition() const { return m_yPosition; } + EFillAttachment attachment() const { return static_cast<EFillAttachment>(m_attachment); } + EFillBox clip() const { return static_cast<EFillBox>(m_clip); } + EFillBox origin() const { return static_cast<EFillBox>(m_origin); } + EFillRepeat repeatX() const { return static_cast<EFillRepeat>(m_repeatX); } + EFillRepeat repeatY() const { return static_cast<EFillRepeat>(m_repeatY); } + CompositeOperator composite() const { return static_cast<CompositeOperator>(m_composite); } + LengthSize sizeLength() const { return m_sizeLength; } + EFillSizeType sizeType() const { return static_cast<EFillSizeType>(m_sizeType); } + FillSize size() const { return FillSize(static_cast<EFillSizeType>(m_sizeType), m_sizeLength); } + + const FillLayer* next() const { return m_next; } + FillLayer* next() { return m_next; } + + bool isImageSet() const { return m_imageSet; } + bool isXPositionSet() const { return m_xPosSet; } + bool isYPositionSet() const { return m_yPosSet; } + bool isAttachmentSet() const { return m_attachmentSet; } + bool isClipSet() const { return m_clipSet; } + bool isOriginSet() const { return m_originSet; } + bool isRepeatXSet() const { return m_repeatXSet; } + bool isRepeatYSet() const { return m_repeatYSet; } + bool isCompositeSet() const { return m_compositeSet; } + bool isSizeSet() const { return m_sizeType != SizeNone; } + + void setImage(StyleImage* i) { m_image = i; m_imageSet = true; } + void setXPosition(Length l) { m_xPosition = l; m_xPosSet = true; } + void setYPosition(Length l) { m_yPosition = l; m_yPosSet = true; } + void setAttachment(EFillAttachment attachment) { m_attachment = attachment; m_attachmentSet = true; } + void setClip(EFillBox b) { m_clip = b; m_clipSet = true; } + void setOrigin(EFillBox b) { m_origin = b; m_originSet = true; } + void setRepeatX(EFillRepeat r) { m_repeatX = r; m_repeatXSet = true; } + void setRepeatY(EFillRepeat r) { m_repeatY = r; m_repeatYSet = true; } + void setComposite(CompositeOperator c) { m_composite = c; m_compositeSet = true; } + void setSizeType(EFillSizeType b) { m_sizeType = b; } + void setSizeLength(LengthSize l) { m_sizeLength = l; } + void setSize(FillSize f) { m_sizeType = f.type; m_sizeLength = f.size; } + + void clearImage() { m_imageSet = false; } + void clearXPosition() { m_xPosSet = false; } + void clearYPosition() { m_yPosSet = false; } + void clearAttachment() { m_attachmentSet = false; } + void clearClip() { m_clipSet = false; } + void clearOrigin() { m_originSet = false; } + void clearRepeatX() { m_repeatXSet = false; } + void clearRepeatY() { m_repeatYSet = false; } + void clearComposite() { m_compositeSet = false; } + void clearSize() { m_sizeType = SizeNone; } + + void setNext(FillLayer* n) { if (m_next != n) { delete m_next; m_next = n; } } + + FillLayer& operator=(const FillLayer& o); + FillLayer(const FillLayer& o); + + bool operator==(const FillLayer& o) const; + bool operator!=(const FillLayer& o) const + { + return !(*this == o); + } + + bool containsImage(StyleImage*) const; + bool imagesAreLoaded() const; + + bool hasImage() const + { + if (m_image) + return true; + return m_next ? m_next->hasImage() : false; + } + + bool hasFixedImage() const + { + if (m_image && m_attachment == FixedBackgroundAttachment) + return true; + return m_next ? m_next->hasFixedImage() : false; + } + + EFillLayerType type() const { return static_cast<EFillLayerType>(m_type); } + + void fillUnsetProperties(); + void cullEmptyLayers(); + + static EFillAttachment initialFillAttachment(EFillLayerType) { return ScrollBackgroundAttachment; } + static EFillBox initialFillClip(EFillLayerType) { return BorderFillBox; } + static EFillBox initialFillOrigin(EFillLayerType type) { return type == BackgroundFillLayer ? PaddingFillBox : BorderFillBox; } + static EFillRepeat initialFillRepeatX(EFillLayerType) { return RepeatFill; } + static EFillRepeat initialFillRepeatY(EFillLayerType) { return RepeatFill; } + static CompositeOperator initialFillComposite(EFillLayerType) { return CompositeSourceOver; } + static EFillSizeType initialFillSizeType(EFillLayerType) { return SizeLength; } + static LengthSize initialFillSizeLength(EFillLayerType) { return LengthSize(); } + static FillSize initialFillSize(EFillLayerType) { return FillSize(); } + static Length initialFillXPosition(EFillLayerType) { return Length(0.0, Percent); } + static Length initialFillYPosition(EFillLayerType) { return Length(0.0, Percent); } + static StyleImage* initialFillImage(EFillLayerType) { return 0; } + +private: + friend class RenderStyle; + + FillLayer() { } + + FillLayer* m_next; + + RefPtr<StyleImage> m_image; + + Length m_xPosition; + Length m_yPosition; + + unsigned m_attachment : 2; // EFillAttachment + unsigned m_clip : 2; // EFillBox + unsigned m_origin : 2; // EFillBox + unsigned m_repeatX : 3; // EFillRepeat + unsigned m_repeatY : 3; // EFillRepeat + unsigned m_composite : 4; // CompositeOperator + unsigned m_sizeType : 2; // EFillSizeType + + LengthSize m_sizeLength; + + bool m_imageSet : 1; + bool m_attachmentSet : 1; + bool m_clipSet : 1; + bool m_originSet : 1; + bool m_repeatXSet : 1; + bool m_repeatYSet : 1; + bool m_xPosSet : 1; + bool m_yPosSet : 1; + bool m_compositeSet : 1; + + unsigned m_type : 1; // EFillLayerType +}; + +} // namespace WebCore + +#endif // FillLayer_h diff --git a/Source/WebCore/rendering/style/KeyframeList.cpp b/Source/WebCore/rendering/style/KeyframeList.cpp new file mode 100644 index 0000000..bafa426 --- /dev/null +++ b/Source/WebCore/rendering/style/KeyframeList.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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 "KeyframeList.h" +#include "RenderObject.h" + +namespace WebCore { + +KeyframeList::~KeyframeList() +{ + clear(); +} + +void KeyframeList::clear() +{ + m_keyframes.clear(); + m_properties.clear(); +} + +bool KeyframeList::operator==(const KeyframeList& o) const +{ + if (m_keyframes.size() != o.m_keyframes.size()) + return false; + + Vector<KeyframeValue>::const_iterator it2 = o.m_keyframes.begin(); + for (Vector<KeyframeValue>::const_iterator it1 = m_keyframes.begin(); it1 != m_keyframes.end(); ++it1) { + if (it1->key() != it2->key()) + return false; + const RenderStyle& style1 = *it1->style(); + const RenderStyle& style2 = *it2->style(); + if (style1 != style2) + return false; + ++it2; + } + + return true; +} + +void KeyframeList::insert(const KeyframeValue& keyframe) +{ + if (keyframe.key() < 0 || keyframe.key() > 1) + return; + + bool inserted = false; + bool replaced = false; + for (size_t i = 0; i < m_keyframes.size(); ++i) { + if (m_keyframes[i].key() == keyframe.key()) { + m_keyframes[i] = keyframe; + replaced = true; + break; + } + + if (m_keyframes[i].key() > keyframe.key()) { + // insert before + m_keyframes.insert(i, keyframe); + inserted = true; + break; + } + } + + if (!replaced && !inserted) + m_keyframes.append(keyframe); + + if (replaced) { + // We have to rebuild the properties list from scratch. + m_properties.clear(); + for (Vector<KeyframeValue>::const_iterator it = m_keyframes.begin(); it != m_keyframes.end(); ++it) { + const KeyframeValue& currKeyframe = *it; + for (HashSet<int>::const_iterator it = currKeyframe.properties().begin(); it != currKeyframe.properties().end(); ++it) + m_properties.add(*it); + } + } else { + for (HashSet<int>::const_iterator it = keyframe.properties().begin(); it != keyframe.properties().end(); ++it) + m_properties.add(*it); + } +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/KeyframeList.h b/Source/WebCore/rendering/style/KeyframeList.h new file mode 100644 index 0000000..64170ce --- /dev/null +++ b/Source/WebCore/rendering/style/KeyframeList.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef KeyframeList_h +#define KeyframeList_h + +#include <wtf/Vector.h> +#include <wtf/HashSet.h> +#include <wtf/RefPtr.h> +#include <wtf/text/AtomicString.h> + +namespace WebCore { + +class RenderObject; +class RenderStyle; + +class KeyframeValue { +public: + KeyframeValue(float key, PassRefPtr<RenderStyle> style) + : m_key(key) + , m_style(style) + { + } + + void addProperty(int prop) { m_properties.add(prop); } + bool containsProperty(int prop) const { return m_properties.contains(prop); } + const HashSet<int>& properties() const { return m_properties; } + + float key() const { return m_key; } + void setKey(float key) { m_key = key; } + + const RenderStyle* style() const { return m_style.get(); } + void setStyle(PassRefPtr<RenderStyle> style) { m_style = style; } + +private: + float m_key; + HashSet<int> m_properties; // The properties specified in this keyframe. + RefPtr<RenderStyle> m_style; +}; + +class KeyframeList { +public: + KeyframeList(RenderObject* renderer, const AtomicString& animationName) + : m_animationName(animationName) + , m_renderer(renderer) + { + insert(KeyframeValue(0, 0)); + insert(KeyframeValue(1, 0)); + } + ~KeyframeList(); + + bool operator==(const KeyframeList& o) const; + bool operator!=(const KeyframeList& o) const { return !(*this == o); } + + const AtomicString& animationName() const { return m_animationName; } + + void insert(const KeyframeValue& keyframe); + + void addProperty(int prop) { m_properties.add(prop); } + bool containsProperty(int prop) const { return m_properties.contains(prop); } + HashSet<int>::const_iterator beginProperties() const { return m_properties.begin(); } + HashSet<int>::const_iterator endProperties() const { return m_properties.end(); } + + void clear(); + bool isEmpty() const { return m_keyframes.isEmpty(); } + size_t size() const { return m_keyframes.size(); } + const KeyframeValue& operator[](size_t index) const { return m_keyframes[index]; } + +private: + AtomicString m_animationName; + Vector<KeyframeValue> m_keyframes; // kept sorted by key + HashSet<int> m_properties; // the properties being animated + RenderObject* m_renderer; +}; + +} // namespace WebCore + +#endif // KeyframeList_h diff --git a/Source/WebCore/rendering/style/LineClampValue.h b/Source/WebCore/rendering/style/LineClampValue.h new file mode 100644 index 0000000..2119ca2 --- /dev/null +++ b/Source/WebCore/rendering/style/LineClampValue.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LineClampValue_h +#define LineClampValue_h + +#include "RenderStyleConstants.h" + +namespace WebCore { + +class LineClampValue { +public: + LineClampValue() + : m_type(LineClampLineCount) + , m_value(-1) + { + } + + LineClampValue(int value, ELineClampType type) + : m_type(type) + , m_value(value) + { + } + + int value() const { return m_value; } + + bool isPercentage() const { return m_type == LineClampPercentage; } + + bool isNone() const { return m_value == -1; } + + bool operator==(const LineClampValue& o) const + { + return value() == o.value() && isPercentage() == o.isPercentage(); + } + + bool operator!=(const LineClampValue& o) const + { + return !(*this == o); + } + +private: + ELineClampType m_type; + int m_value; +}; + +} // namespace WebCore + +#endif // LineClampValue_h diff --git a/Source/WebCore/rendering/style/NinePieceImage.cpp b/Source/WebCore/rendering/style/NinePieceImage.cpp new file mode 100644 index 0000000..d585e8f --- /dev/null +++ b/Source/WebCore/rendering/style/NinePieceImage.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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 "NinePieceImage.h" + +namespace WebCore { + +bool NinePieceImage::operator==(const NinePieceImage& o) const +{ + return StyleImage::imagesEquivalent(m_image.get(), o.m_image.get()) && m_slices == o.m_slices && m_horizontalRule == o.m_horizontalRule && + m_verticalRule == o.m_verticalRule; +} + +} diff --git a/Source/WebCore/rendering/style/NinePieceImage.h b/Source/WebCore/rendering/style/NinePieceImage.h new file mode 100644 index 0000000..c400551 --- /dev/null +++ b/Source/WebCore/rendering/style/NinePieceImage.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef NinePieceImage_h +#define NinePieceImage_h + +#include "LengthBox.h" +#include "StyleImage.h" + +namespace WebCore { + +enum ENinePieceImageRule { + StretchImageRule, RoundImageRule, RepeatImageRule +}; + +class NinePieceImage { +public: + NinePieceImage() + : m_image(0) + , m_horizontalRule(StretchImageRule) + , m_verticalRule(StretchImageRule) + { + } + + NinePieceImage(StyleImage* image, LengthBox slices, ENinePieceImageRule h, ENinePieceImageRule v) + : m_image(image) + , m_slices(slices) + , m_horizontalRule(h) + , m_verticalRule(v) + { + } + + bool operator==(const NinePieceImage& o) const; + bool operator!=(const NinePieceImage& o) const { return !(*this == o); } + + bool hasImage() const { return m_image != 0; } + StyleImage* image() const { return m_image.get(); } + void setImage(StyleImage* image) { m_image = image; } + + const LengthBox& slices() const { return m_slices; } + void setSlices(const LengthBox& l) { m_slices = l; } + + ENinePieceImageRule horizontalRule() const { return static_cast<ENinePieceImageRule>(m_horizontalRule); } + void setHorizontalRule(ENinePieceImageRule rule) { m_horizontalRule = rule; } + + ENinePieceImageRule verticalRule() const { return static_cast<ENinePieceImageRule>(m_verticalRule); } + void setVerticalRule(ENinePieceImageRule rule) { m_verticalRule = rule; } + +private: + RefPtr<StyleImage> m_image; + LengthBox m_slices; + unsigned m_horizontalRule : 2; // ENinePieceImageRule + unsigned m_verticalRule : 2; // ENinePieceImageRule +}; + +} // namespace WebCore + +#endif // NinePieceImage_h diff --git a/Source/WebCore/rendering/style/OutlineValue.h b/Source/WebCore/rendering/style/OutlineValue.h new file mode 100644 index 0000000..19c17a7 --- /dev/null +++ b/Source/WebCore/rendering/style/OutlineValue.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef OutlineValue_h +#define OutlineValue_h + +#include "BorderValue.h" + +namespace WebCore { + +class OutlineValue : public BorderValue { +friend class RenderStyle; +public: + OutlineValue() + : m_offset(0) + , m_isAuto(false) + { + } + + bool operator==(const OutlineValue& o) const + { + return m_width == o.m_width && m_style == o.m_style && m_color == o.m_color && m_offset == o.m_offset && m_isAuto == o.m_isAuto; + } + + bool operator!=(const OutlineValue& o) const + { + return !(*this == o); + } + + int offset() const { return m_offset; } + bool isAuto() const { return m_isAuto; } + +private: + int m_offset; + bool m_isAuto; +}; + +} // namespace WebCore + +#endif // OutlineValue_h diff --git a/Source/WebCore/rendering/style/RenderStyle.cpp b/Source/WebCore/rendering/style/RenderStyle.cpp new file mode 100644 index 0000000..881818c --- /dev/null +++ b/Source/WebCore/rendering/style/RenderStyle.cpp @@ -0,0 +1,1473 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * 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 "RenderStyle.h" + +#include "CSSPropertyNames.h" +#include "CSSStyleSelector.h" +#include "FontSelector.h" +#include "RenderArena.h" +#include "RenderObject.h" +#include "ScaleTransformOperation.h" +#include "StyleImage.h" +#include <wtf/StdLibExtras.h> +#include <algorithm> + +using namespace std; + +namespace WebCore { + +inline RenderStyle* defaultStyle() +{ + static RenderStyle* s_defaultStyle = RenderStyle::createDefaultStyle().releaseRef(); + return s_defaultStyle; +} + +PassRefPtr<RenderStyle> RenderStyle::create() +{ + return adoptRef(new RenderStyle()); +} + +PassRefPtr<RenderStyle> RenderStyle::createDefaultStyle() +{ + return adoptRef(new RenderStyle(true)); +} + +PassRefPtr<RenderStyle> RenderStyle::clone(const RenderStyle* other) +{ + return adoptRef(new RenderStyle(*other)); +} + +ALWAYS_INLINE RenderStyle::RenderStyle() + : m_affectedByAttributeSelectors(false) + , m_unique(false) + , m_affectedByEmpty(false) + , m_emptyState(false) + , m_childrenAffectedByFirstChildRules(false) + , m_childrenAffectedByLastChildRules(false) + , m_childrenAffectedByDirectAdjacentRules(false) + , m_childrenAffectedByForwardPositionalRules(false) + , m_childrenAffectedByBackwardPositionalRules(false) + , m_firstChildState(false) + , m_lastChildState(false) + , m_childIndex(0) + , m_box(defaultStyle()->m_box) + , visual(defaultStyle()->visual) + , m_background(defaultStyle()->m_background) + , surround(defaultStyle()->surround) + , rareNonInheritedData(defaultStyle()->rareNonInheritedData) + , rareInheritedData(defaultStyle()->rareInheritedData) + , inherited(defaultStyle()->inherited) +#if ENABLE(SVG) + , m_svgStyle(defaultStyle()->m_svgStyle) +#endif +{ + setBitDefaults(); // Would it be faster to copy this from the default style? +} + +ALWAYS_INLINE RenderStyle::RenderStyle(bool) + : m_affectedByAttributeSelectors(false) + , m_unique(false) + , m_affectedByEmpty(false) + , m_emptyState(false) + , m_childrenAffectedByFirstChildRules(false) + , m_childrenAffectedByLastChildRules(false) + , m_childrenAffectedByDirectAdjacentRules(false) + , m_childrenAffectedByForwardPositionalRules(false) + , m_childrenAffectedByBackwardPositionalRules(false) + , m_firstChildState(false) + , m_lastChildState(false) + , m_childIndex(0) +{ + setBitDefaults(); + + m_box.init(); + visual.init(); + m_background.init(); + surround.init(); + rareNonInheritedData.init(); + rareNonInheritedData.access()->flexibleBox.init(); + rareNonInheritedData.access()->marquee.init(); + rareNonInheritedData.access()->m_multiCol.init(); + rareNonInheritedData.access()->m_transform.init(); + rareInheritedData.init(); + inherited.init(); + +#if ENABLE(SVG) + m_svgStyle.init(); +#endif +} + +ALWAYS_INLINE RenderStyle::RenderStyle(const RenderStyle& o) + : RefCounted<RenderStyle>() + , m_affectedByAttributeSelectors(false) + , m_unique(false) + , m_affectedByEmpty(false) + , m_emptyState(false) + , m_childrenAffectedByFirstChildRules(false) + , m_childrenAffectedByLastChildRules(false) + , m_childrenAffectedByDirectAdjacentRules(false) + , m_childrenAffectedByForwardPositionalRules(false) + , m_childrenAffectedByBackwardPositionalRules(false) + , m_firstChildState(false) + , m_lastChildState(false) + , m_childIndex(0) + , m_box(o.m_box) + , visual(o.visual) + , m_background(o.m_background) + , surround(o.surround) + , rareNonInheritedData(o.rareNonInheritedData) + , rareInheritedData(o.rareInheritedData) + , inherited(o.inherited) +#if ENABLE(SVG) + , m_svgStyle(o.m_svgStyle) +#endif + , inherited_flags(o.inherited_flags) + , noninherited_flags(o.noninherited_flags) +{ +} + +void RenderStyle::inheritFrom(const RenderStyle* inheritParent) +{ + rareInheritedData = inheritParent->rareInheritedData; + inherited = inheritParent->inherited; + inherited_flags = inheritParent->inherited_flags; +#if ENABLE(SVG) + if (m_svgStyle != inheritParent->m_svgStyle) + m_svgStyle.access()->inheritFrom(inheritParent->m_svgStyle.get()); +#endif +} + +RenderStyle::~RenderStyle() +{ +} + +bool RenderStyle::operator==(const RenderStyle& o) const +{ + // compare everything except the pseudoStyle pointer + return inherited_flags == o.inherited_flags && + noninherited_flags == o.noninherited_flags && + m_box == o.m_box && + visual == o.visual && + m_background == o.m_background && + surround == o.surround && + rareNonInheritedData == o.rareNonInheritedData && + rareInheritedData == o.rareInheritedData && + inherited == o.inherited +#if ENABLE(SVG) + && m_svgStyle == o.m_svgStyle +#endif + ; +} + +bool RenderStyle::isStyleAvailable() const +{ + return this != CSSStyleSelector::styleNotYetAvailable(); +} + +static inline int pseudoBit(PseudoId pseudo) +{ + return 1 << (pseudo - 1); +} + +bool RenderStyle::hasAnyPublicPseudoStyles() const +{ + return PUBLIC_PSEUDOID_MASK & noninherited_flags._pseudoBits; +} + +bool RenderStyle::hasPseudoStyle(PseudoId pseudo) const +{ + ASSERT(pseudo > NOPSEUDO); + ASSERT(pseudo < FIRST_INTERNAL_PSEUDOID); + return pseudoBit(pseudo) & noninherited_flags._pseudoBits; +} + +void RenderStyle::setHasPseudoStyle(PseudoId pseudo) +{ + ASSERT(pseudo > NOPSEUDO); + ASSERT(pseudo < FIRST_INTERNAL_PSEUDOID); + noninherited_flags._pseudoBits |= pseudoBit(pseudo); +} + +RenderStyle* RenderStyle::getCachedPseudoStyle(PseudoId pid) const +{ + ASSERT(styleType() != VISITED_LINK); + + if (!m_cachedPseudoStyles || !m_cachedPseudoStyles->size()) + return 0; + + if (styleType() != NOPSEUDO) { + if (pid == VISITED_LINK) + return m_cachedPseudoStyles->at(0)->styleType() == VISITED_LINK ? m_cachedPseudoStyles->at(0).get() : 0; + return 0; + } + + for (size_t i = 0; i < m_cachedPseudoStyles->size(); ++i) { + RenderStyle* pseudoStyle = m_cachedPseudoStyles->at(i).get(); + if (pseudoStyle->styleType() == pid) + return pseudoStyle; + } + + return 0; +} + +RenderStyle* RenderStyle::addCachedPseudoStyle(PassRefPtr<RenderStyle> pseudo) +{ + if (!pseudo) + return 0; + + RenderStyle* result = pseudo.get(); + + if (!m_cachedPseudoStyles) + m_cachedPseudoStyles.set(new PseudoStyleCache); + + m_cachedPseudoStyles->append(pseudo); + + return result; +} + +void RenderStyle::removeCachedPseudoStyle(PseudoId pid) +{ + if (!m_cachedPseudoStyles) + return; + for (size_t i = 0; i < m_cachedPseudoStyles->size(); ++i) { + RenderStyle* pseudoStyle = m_cachedPseudoStyles->at(i).get(); + if (pseudoStyle->styleType() == pid) { + m_cachedPseudoStyles->remove(i); + return; + } + } +} + +bool RenderStyle::inheritedNotEqual(const RenderStyle* other) const +{ + return inherited_flags != other->inherited_flags || + inherited != other->inherited || +#if ENABLE(SVG) + m_svgStyle->inheritedNotEqual(other->m_svgStyle.get()) || +#endif + rareInheritedData != other->rareInheritedData; +} + +static bool positionedObjectMoved(const LengthBox& a, const LengthBox& b) +{ + // If any unit types are different, then we can't guarantee + // that this was just a movement. + if (a.left().type() != b.left().type() || + a.right().type() != b.right().type() || + a.top().type() != b.top().type() || + a.bottom().type() != b.bottom().type()) + return false; + + // Only one unit can be non-auto in the horizontal direction and + // in the vertical direction. Otherwise the adjustment of values + // is changing the size of the box. + if (!a.left().isIntrinsicOrAuto() && !a.right().isIntrinsicOrAuto()) + return false; + if (!a.top().isIntrinsicOrAuto() && !a.bottom().isIntrinsicOrAuto()) + return false; + + // One of the units is fixed or percent in both directions and stayed + // that way in the new style. Therefore all we are doing is moving. + return true; +} + +/* + compares two styles. The result gives an idea of the action that + needs to be taken when replacing the old style with a new one. + + CbLayout: The containing block of the object needs a relayout. + Layout: the RenderObject needs a relayout after the style change + Visible: The change is visible, but no relayout is needed + NonVisible: The object does need neither repaint nor relayout after + the change. + + ### TODO: + A lot can be optimised here based on the display type, lots of + optimisations are unimplemented, and currently result in the + worst case result causing a relayout of the containing block. +*/ +StyleDifference RenderStyle::diff(const RenderStyle* other, unsigned& changedContextSensitiveProperties) const +{ + changedContextSensitiveProperties = ContextSensitivePropertyNone; + +#if ENABLE(SVG) + StyleDifference svgChange = StyleDifferenceEqual; + if (m_svgStyle != other->m_svgStyle) { + svgChange = m_svgStyle->diff(other->m_svgStyle.get()); + if (svgChange == StyleDifferenceLayout) + return svgChange; + } +#endif + + if (m_box->width() != other->m_box->width() || + m_box->minWidth() != other->m_box->minWidth() || + m_box->maxWidth() != other->m_box->maxWidth() || + m_box->height() != other->m_box->height() || + m_box->minHeight() != other->m_box->minHeight() || + m_box->maxHeight() != other->m_box->maxHeight()) + return StyleDifferenceLayout; + + if (m_box->verticalAlign() != other->m_box->verticalAlign() || noninherited_flags._vertical_align != other->noninherited_flags._vertical_align) + return StyleDifferenceLayout; + + if (m_box->boxSizing() != other->m_box->boxSizing()) + return StyleDifferenceLayout; + + if (surround->margin != other->surround->margin) + return StyleDifferenceLayout; + + if (surround->padding != other->surround->padding) + return StyleDifferenceLayout; + + if (rareNonInheritedData.get() != other->rareNonInheritedData.get()) { + if (rareNonInheritedData->m_appearance != other->rareNonInheritedData->m_appearance || + rareNonInheritedData->marginBeforeCollapse != other->rareNonInheritedData->marginBeforeCollapse || + rareNonInheritedData->marginAfterCollapse != other->rareNonInheritedData->marginAfterCollapse || + rareNonInheritedData->lineClamp != other->rareNonInheritedData->lineClamp || + rareNonInheritedData->textOverflow != other->rareNonInheritedData->textOverflow) + return StyleDifferenceLayout; + + if (rareNonInheritedData->flexibleBox.get() != other->rareNonInheritedData->flexibleBox.get() && + *rareNonInheritedData->flexibleBox.get() != *other->rareNonInheritedData->flexibleBox.get()) + return StyleDifferenceLayout; + + // FIXME: We should add an optimized form of layout that just recomputes visual overflow. + if (!rareNonInheritedData->shadowDataEquivalent(*other->rareNonInheritedData.get())) + return StyleDifferenceLayout; + + if (!rareNonInheritedData->reflectionDataEquivalent(*other->rareNonInheritedData.get())) + return StyleDifferenceLayout; + + if (rareNonInheritedData->m_multiCol.get() != other->rareNonInheritedData->m_multiCol.get() && + *rareNonInheritedData->m_multiCol.get() != *other->rareNonInheritedData->m_multiCol.get()) + return StyleDifferenceLayout; + + if (rareNonInheritedData->m_transform.get() != other->rareNonInheritedData->m_transform.get() && + *rareNonInheritedData->m_transform.get() != *other->rareNonInheritedData->m_transform.get()) { +#if USE(ACCELERATED_COMPOSITING) + changedContextSensitiveProperties |= ContextSensitivePropertyTransform; + // Don't return; keep looking for another change +#else + return StyleDifferenceLayout; +#endif + } + +#if !USE(ACCELERATED_COMPOSITING) + if (rareNonInheritedData.get() != other->rareNonInheritedData.get()) { + if (rareNonInheritedData->m_transformStyle3D != other->rareNonInheritedData->m_transformStyle3D || + rareNonInheritedData->m_backfaceVisibility != other->rareNonInheritedData->m_backfaceVisibility || + rareNonInheritedData->m_perspective != other->rareNonInheritedData->m_perspective || + rareNonInheritedData->m_perspectiveOriginX != other->rareNonInheritedData->m_perspectiveOriginX || + rareNonInheritedData->m_perspectiveOriginY != other->rareNonInheritedData->m_perspectiveOriginY) + return StyleDifferenceLayout; + } +#endif + +#if ENABLE(DASHBOARD_SUPPORT) + // If regions change, trigger a relayout to re-calc regions. + if (rareNonInheritedData->m_dashboardRegions != other->rareNonInheritedData->m_dashboardRegions) + return StyleDifferenceLayout; +#endif + } + + if (rareInheritedData.get() != other->rareInheritedData.get()) { + if (rareInheritedData->highlight != other->rareInheritedData->highlight || + rareInheritedData->indent != other->rareInheritedData->indent || + rareInheritedData->m_effectiveZoom != other->rareInheritedData->m_effectiveZoom || + rareInheritedData->textSizeAdjust != other->rareInheritedData->textSizeAdjust || + rareInheritedData->wordBreak != other->rareInheritedData->wordBreak || + rareInheritedData->wordWrap != other->rareInheritedData->wordWrap || + rareInheritedData->nbspMode != other->rareInheritedData->nbspMode || + rareInheritedData->khtmlLineBreak != other->rareInheritedData->khtmlLineBreak || + rareInheritedData->textSecurity != other->rareInheritedData->textSecurity || + rareInheritedData->hyphens != other->rareInheritedData->hyphens || + rareInheritedData->hyphenationString != other->rareInheritedData->hyphenationString || + rareInheritedData->hyphenationLocale != other->rareInheritedData->hyphenationLocale || + rareInheritedData->textEmphasisMark != other->rareInheritedData->textEmphasisMark || + rareInheritedData->textEmphasisPosition != other->rareInheritedData->textEmphasisPosition || + rareInheritedData->textEmphasisCustomMark != other->rareInheritedData->textEmphasisCustomMark) + return StyleDifferenceLayout; + + if (!rareInheritedData->shadowDataEquivalent(*other->rareInheritedData.get())) + return StyleDifferenceLayout; + + if (textStrokeWidth() != other->textStrokeWidth()) + return StyleDifferenceLayout; + } + + if (inherited->line_height != other->inherited->line_height || + inherited->list_style_image != other->inherited->list_style_image || + inherited->font != other->inherited->font || + inherited->horizontal_border_spacing != other->inherited->horizontal_border_spacing || + inherited->vertical_border_spacing != other->inherited->vertical_border_spacing || + inherited_flags._box_direction != other->inherited_flags._box_direction || + inherited_flags._visuallyOrdered != other->inherited_flags._visuallyOrdered || + noninherited_flags._position != other->noninherited_flags._position || + noninherited_flags._floating != other->noninherited_flags._floating || + noninherited_flags._originalDisplay != other->noninherited_flags._originalDisplay) + return StyleDifferenceLayout; + + + if (((int)noninherited_flags._effectiveDisplay) >= TABLE) { + if (inherited_flags._border_collapse != other->inherited_flags._border_collapse || + inherited_flags._empty_cells != other->inherited_flags._empty_cells || + inherited_flags._caption_side != other->inherited_flags._caption_side || + noninherited_flags._table_layout != other->noninherited_flags._table_layout) + return StyleDifferenceLayout; + + // In the collapsing border model, 'hidden' suppresses other borders, while 'none' + // does not, so these style differences can be width differences. + if (inherited_flags._border_collapse && + ((borderTopStyle() == BHIDDEN && other->borderTopStyle() == BNONE) || + (borderTopStyle() == BNONE && other->borderTopStyle() == BHIDDEN) || + (borderBottomStyle() == BHIDDEN && other->borderBottomStyle() == BNONE) || + (borderBottomStyle() == BNONE && other->borderBottomStyle() == BHIDDEN) || + (borderLeftStyle() == BHIDDEN && other->borderLeftStyle() == BNONE) || + (borderLeftStyle() == BNONE && other->borderLeftStyle() == BHIDDEN) || + (borderRightStyle() == BHIDDEN && other->borderRightStyle() == BNONE) || + (borderRightStyle() == BNONE && other->borderRightStyle() == BHIDDEN))) + return StyleDifferenceLayout; + } + + if (noninherited_flags._effectiveDisplay == LIST_ITEM) { + if (inherited_flags._list_style_type != other->inherited_flags._list_style_type || + inherited_flags._list_style_position != other->inherited_flags._list_style_position) + return StyleDifferenceLayout; + } + + if (inherited_flags._text_align != other->inherited_flags._text_align || + inherited_flags._text_transform != other->inherited_flags._text_transform || + inherited_flags._direction != other->inherited_flags._direction || + inherited_flags._white_space != other->inherited_flags._white_space || + noninherited_flags._clear != other->noninherited_flags._clear) + return StyleDifferenceLayout; + + // Check block flow direction. + if (inherited_flags.m_writingMode != other->inherited_flags.m_writingMode) + return StyleDifferenceLayout; + + // Check text combine mode. + if (rareNonInheritedData->m_textCombine != other->rareNonInheritedData->m_textCombine) + return StyleDifferenceLayout; + + // Overflow returns a layout hint. + if (noninherited_flags._overflowX != other->noninherited_flags._overflowX || + noninherited_flags._overflowY != other->noninherited_flags._overflowY) + return StyleDifferenceLayout; + + // If our border widths change, then we need to layout. Other changes to borders + // only necessitate a repaint. + if (borderLeftWidth() != other->borderLeftWidth() || + borderTopWidth() != other->borderTopWidth() || + borderBottomWidth() != other->borderBottomWidth() || + borderRightWidth() != other->borderRightWidth()) + return StyleDifferenceLayout; + + // If the counter directives change, trigger a relayout to re-calculate counter values and rebuild the counter node tree. + const CounterDirectiveMap* mapA = rareNonInheritedData->m_counterDirectives.get(); + const CounterDirectiveMap* mapB = other->rareNonInheritedData->m_counterDirectives.get(); + if (!(mapA == mapB || (mapA && mapB && *mapA == *mapB))) + return StyleDifferenceLayout; + if (rareNonInheritedData->m_counterIncrement != other->rareNonInheritedData->m_counterIncrement || + rareNonInheritedData->m_counterReset != other->rareNonInheritedData->m_counterReset) + return StyleDifferenceLayout; + + if ((rareNonInheritedData->opacity == 1 && other->rareNonInheritedData->opacity < 1) || + (rareNonInheritedData->opacity < 1 && other->rareNonInheritedData->opacity == 1)) { + // FIXME: We should add an optimized form of layout that just recomputes visual overflow. + return StyleDifferenceLayout; + } + + if ((visibility() == COLLAPSE) != (other->visibility() == COLLAPSE)) + return StyleDifferenceLayout; + +#if ENABLE(SVG) + // SVGRenderStyle::diff() might have returned StyleDifferenceRepaint, eg. if fill changes. + // If eg. the font-size changed at the same time, we're not allowed to return StyleDifferenceRepaint, + // but have to return StyleDifferenceLayout, that's why this if branch comes after all branches + // that are relevant for SVG and might return StyleDifferenceLayout. + if (svgChange != StyleDifferenceEqual) + return svgChange; +#endif + + // Make sure these left/top/right/bottom checks stay below all layout checks and above + // all visible checks. + if (position() != StaticPosition) { + if (surround->offset != other->surround->offset) { + // Optimize for the case where a positioned layer is moving but not changing size. + if (position() == AbsolutePosition && positionedObjectMoved(surround->offset, other->surround->offset)) + return StyleDifferenceLayoutPositionedMovementOnly; + + // FIXME: We will need to do a bit of work in RenderObject/Box::setStyle before we + // can stop doing a layout when relative positioned objects move. In particular, we'll need + // to update scrolling positions and figure out how to do a repaint properly of the updated layer. + //if (other->position() == RelativePosition) + // return RepaintLayer; + //else + return StyleDifferenceLayout; + } else if (m_box->zIndex() != other->m_box->zIndex() || m_box->hasAutoZIndex() != other->m_box->hasAutoZIndex() || + visual->clip != other->visual->clip || visual->hasClip != other->visual->hasClip) + return StyleDifferenceRepaintLayer; + } + + if (rareNonInheritedData->opacity != other->rareNonInheritedData->opacity) { +#if USE(ACCELERATED_COMPOSITING) + changedContextSensitiveProperties |= ContextSensitivePropertyOpacity; + // Don't return; keep looking for another change. +#else + return StyleDifferenceRepaintLayer; +#endif + } + + if (rareNonInheritedData->m_mask != other->rareNonInheritedData->m_mask || + rareNonInheritedData->m_maskBoxImage != other->rareNonInheritedData->m_maskBoxImage) + return StyleDifferenceRepaintLayer; + + if (inherited->color != other->inherited->color || + inherited_flags._visibility != other->inherited_flags._visibility || + inherited_flags._text_decorations != other->inherited_flags._text_decorations || + inherited_flags._force_backgrounds_to_white != other->inherited_flags._force_backgrounds_to_white || + inherited_flags._insideLink != other->inherited_flags._insideLink || + surround->border != other->surround->border || + *m_background.get() != *other->m_background.get() || + visual->textDecoration != other->visual->textDecoration || + rareInheritedData->userModify != other->rareInheritedData->userModify || + rareInheritedData->userSelect != other->rareInheritedData->userSelect || + rareNonInheritedData->userDrag != other->rareNonInheritedData->userDrag || + rareNonInheritedData->m_borderFit != other->rareNonInheritedData->m_borderFit || + rareInheritedData->textFillColor != other->rareInheritedData->textFillColor || + rareInheritedData->textStrokeColor != other->rareInheritedData->textStrokeColor || + rareInheritedData->textEmphasisColor != other->rareInheritedData->textEmphasisColor || + rareInheritedData->textEmphasisFill != other->rareInheritedData->textEmphasisFill) + return StyleDifferenceRepaint; + +#if USE(ACCELERATED_COMPOSITING) + if (rareNonInheritedData.get() != other->rareNonInheritedData.get()) { + if (rareNonInheritedData->m_transformStyle3D != other->rareNonInheritedData->m_transformStyle3D || + rareNonInheritedData->m_backfaceVisibility != other->rareNonInheritedData->m_backfaceVisibility || + rareNonInheritedData->m_perspective != other->rareNonInheritedData->m_perspective || + rareNonInheritedData->m_perspectiveOriginX != other->rareNonInheritedData->m_perspectiveOriginX || + rareNonInheritedData->m_perspectiveOriginY != other->rareNonInheritedData->m_perspectiveOriginY) + return StyleDifferenceRecompositeLayer; + } +#endif + + // Cursors are not checked, since they will be set appropriately in response to mouse events, + // so they don't need to cause any repaint or layout. + + // Animations don't need to be checked either. We always set the new style on the RenderObject, so we will get a chance to fire off + // the resulting transition properly. + return StyleDifferenceEqual; +} + +void RenderStyle::setClip(Length top, Length right, Length bottom, Length left) +{ + StyleVisualData* data = visual.access(); + data->clip.m_top = top; + data->clip.m_right = right; + data->clip.m_bottom = bottom; + data->clip.m_left = left; +} + +void RenderStyle::addCursor(PassRefPtr<StyleImage> image, const IntPoint& hotSpot) +{ + if (!rareInheritedData.access()->cursorData) + rareInheritedData.access()->cursorData = CursorList::create(); + rareInheritedData.access()->cursorData->append(CursorData(image, hotSpot)); +} + +void RenderStyle::setCursorList(PassRefPtr<CursorList> other) +{ + rareInheritedData.access()->cursorData = other; +} + +void RenderStyle::clearCursorList() +{ + if (rareInheritedData->cursorData) + rareInheritedData.access()->cursorData = 0; +} + +void RenderStyle::clearContent() +{ + if (rareNonInheritedData->m_content) + rareNonInheritedData->m_content->clear(); +} + +ContentData* RenderStyle::prepareToSetContent(StringImpl* string, bool add) +{ + OwnPtr<ContentData>& content = rareNonInheritedData.access()->m_content; + ContentData* lastContent = content.get(); + while (lastContent && lastContent->next()) + lastContent = lastContent->next(); + + if (string && add && lastContent && lastContent->isText()) { + // Augment the existing string and share the existing ContentData node. + String newText = lastContent->text(); + newText.append(string); + lastContent->setText(newText.impl()); + return 0; + } + + bool reuseContent = !add; + OwnPtr<ContentData> newContentData; + if (reuseContent && content) { + content->clear(); + newContentData = content.release(); + } else + newContentData = adoptPtr(new ContentData); + + ContentData* result = newContentData.get(); + + if (lastContent && !reuseContent) + lastContent->setNext(newContentData.release()); + else + content = newContentData.release(); + + return result; +} + +void RenderStyle::setContent(PassRefPtr<StyleImage> image, bool add) +{ + if (!image) + return; + prepareToSetContent(0, add)->setImage(image); +} + +void RenderStyle::setContent(PassRefPtr<StringImpl> string, bool add) +{ + if (!string) + return; + if (ContentData* data = prepareToSetContent(string.get(), add)) + data->setText(string); +} + +void RenderStyle::setContent(PassOwnPtr<CounterContent> counter, bool add) +{ + if (!counter) + return; + prepareToSetContent(0, add)->setCounter(counter); +} + +void RenderStyle::applyTransform(TransformationMatrix& transform, const IntSize& borderBoxSize, ApplyTransformOrigin applyOrigin) const +{ + // transform-origin brackets the transform with translate operations. + // Optimize for the case where the only transform is a translation, since the transform-origin is irrelevant + // in that case. + bool applyTransformOrigin = false; + unsigned s = rareNonInheritedData->m_transform->m_operations.operations().size(); + unsigned i; + if (applyOrigin == IncludeTransformOrigin) { + for (i = 0; i < s; i++) { + TransformOperation::OperationType type = rareNonInheritedData->m_transform->m_operations.operations()[i]->getOperationType(); + if (type != TransformOperation::TRANSLATE_X && + type != TransformOperation::TRANSLATE_Y && + type != TransformOperation::TRANSLATE && + type != TransformOperation::TRANSLATE_Z && + type != TransformOperation::TRANSLATE_3D + ) { + applyTransformOrigin = true; + break; + } + } + } + + if (applyTransformOrigin) { + transform.translate3d(transformOriginX().calcFloatValue(borderBoxSize.width()), transformOriginY().calcFloatValue(borderBoxSize.height()), transformOriginZ()); + } + + for (i = 0; i < s; i++) + rareNonInheritedData->m_transform->m_operations.operations()[i]->apply(transform, borderBoxSize); + + if (applyTransformOrigin) { + transform.translate3d(-transformOriginX().calcFloatValue(borderBoxSize.width()), -transformOriginY().calcFloatValue(borderBoxSize.height()), -transformOriginZ()); + } +} + +void RenderStyle::setPageScaleTransform(float scale) +{ + if (scale == 1) + return; + TransformOperations transform; + transform.operations().append(ScaleTransformOperation::create(scale, scale, ScaleTransformOperation::SCALE)); + setTransform(transform); + setTransformOriginX(Length(0, Fixed)); + setTransformOriginY(Length(0, Fixed)); +} + +void RenderStyle::setTextShadow(ShadowData* val, bool add) +{ + ASSERT(!val || (!val->spread() && val->style() == Normal)); + + StyleRareInheritedData* rareData = rareInheritedData.access(); + if (!add) { + delete rareData->textShadow; + rareData->textShadow = val; + return; + } + + val->setNext(rareData->textShadow); + rareData->textShadow = val; +} + +void RenderStyle::setBoxShadow(ShadowData* shadowData, bool add) +{ + StyleRareNonInheritedData* rareData = rareNonInheritedData.access(); + if (!add) { + rareData->m_boxShadow.set(shadowData); + return; + } + + shadowData->setNext(rareData->m_boxShadow.leakPtr()); + rareData->m_boxShadow.set(shadowData); +} + +static void constrainCornerRadiiForRect(const IntRect& r, IntSize& topLeft, IntSize& topRight, IntSize& bottomLeft, IntSize& bottomRight) +{ + // Constrain corner radii using CSS3 rules: + // http://www.w3.org/TR/css3-background/#the-border-radius + + float factor = 1; + unsigned radiiSum; + + // top + radiiSum = static_cast<unsigned>(topLeft.width()) + static_cast<unsigned>(topRight.width()); // Casts to avoid integer overflow. + if (radiiSum > static_cast<unsigned>(r.width())) + factor = min(static_cast<float>(r.width()) / radiiSum, factor); + + // bottom + radiiSum = static_cast<unsigned>(bottomLeft.width()) + static_cast<unsigned>(bottomRight.width()); + if (radiiSum > static_cast<unsigned>(r.width())) + factor = min(static_cast<float>(r.width()) / radiiSum, factor); + + // left + radiiSum = static_cast<unsigned>(topLeft.height()) + static_cast<unsigned>(bottomLeft.height()); + if (radiiSum > static_cast<unsigned>(r.height())) + factor = min(static_cast<float>(r.height()) / radiiSum, factor); + + // right + radiiSum = static_cast<unsigned>(topRight.height()) + static_cast<unsigned>(bottomRight.height()); + if (radiiSum > static_cast<unsigned>(r.height())) + factor = min(static_cast<float>(r.height()) / radiiSum, factor); + + // Scale all radii by f if necessary. + if (factor < 1) { + // If either radius on a corner becomes zero, reset both radii on that corner. + topLeft.scale(factor); + if (!topLeft.width() || !topLeft.height()) + topLeft = IntSize(); + topRight.scale(factor); + if (!topRight.width() || !topRight.height()) + topRight = IntSize(); + bottomLeft.scale(factor); + if (!bottomLeft.width() || !bottomLeft.height()) + bottomLeft = IntSize(); + bottomRight.scale(factor); + if (!bottomRight.width() || !bottomRight.height()) + bottomRight = IntSize(); + } +} + +void RenderStyle::getBorderRadiiForRect(const IntRect& r, IntSize& topLeft, IntSize& topRight, IntSize& bottomLeft, IntSize& bottomRight) const +{ + topLeft = IntSize(surround->border.topLeft().width().calcValue(r.width()), surround->border.topLeft().height().calcValue(r.height())); + topRight = IntSize(surround->border.topRight().width().calcValue(r.width()), surround->border.topRight().height().calcValue(r.height())); + + bottomLeft = IntSize(surround->border.bottomLeft().width().calcValue(r.width()), surround->border.bottomLeft().height().calcValue(r.height())); + bottomRight = IntSize(surround->border.bottomRight().width().calcValue(r.width()), surround->border.bottomRight().height().calcValue(r.height())); + + constrainCornerRadiiForRect(r, topLeft, topRight, bottomLeft, bottomRight); +} + +void RenderStyle::getInnerBorderRadiiForRectWithBorderWidths(const IntRect& innerRect, unsigned short topWidth, unsigned short bottomWidth, unsigned short leftWidth, unsigned short rightWidth, IntSize& innerTopLeft, IntSize& innerTopRight, IntSize& innerBottomLeft, IntSize& innerBottomRight) const +{ + innerTopLeft = IntSize(surround->border.topLeft().width().calcValue(innerRect.width()), surround->border.topLeft().height().calcValue(innerRect.height())); + innerTopRight = IntSize(surround->border.topRight().width().calcValue(innerRect.width()), surround->border.topRight().height().calcValue(innerRect.height())); + innerBottomLeft = IntSize(surround->border.bottomLeft().width().calcValue(innerRect.width()), surround->border.bottomLeft().height().calcValue(innerRect.height())); + innerBottomRight = IntSize(surround->border.bottomRight().width().calcValue(innerRect.width()), surround->border.bottomRight().height().calcValue(innerRect.height())); + + + innerTopLeft.setWidth(max(0, innerTopLeft.width() - leftWidth)); + innerTopLeft.setHeight(max(0, innerTopLeft.height() - topWidth)); + + innerTopRight.setWidth(max(0, innerTopRight.width() - rightWidth)); + innerTopRight.setHeight(max(0, innerTopRight.height() - topWidth)); + + innerBottomLeft.setWidth(max(0, innerBottomLeft.width() - leftWidth)); + innerBottomLeft.setHeight(max(0, innerBottomLeft.height() - bottomWidth)); + + innerBottomRight.setWidth(max(0, innerBottomRight.width() - rightWidth)); + innerBottomRight.setHeight(max(0, innerBottomRight.height() - bottomWidth)); + + constrainCornerRadiiForRect(innerRect, innerTopLeft, innerTopRight, innerBottomLeft, innerBottomRight); +} + +const CounterDirectiveMap* RenderStyle::counterDirectives() const +{ + return rareNonInheritedData->m_counterDirectives.get(); +} + +CounterDirectiveMap& RenderStyle::accessCounterDirectives() +{ + OwnPtr<CounterDirectiveMap>& map = rareNonInheritedData.access()->m_counterDirectives; + if (!map) + map.set(new CounterDirectiveMap); + return *map.get(); +} + +const AtomicString& RenderStyle::hyphenString() const +{ + ASSERT(hyphens() != HyphensNone); + + const AtomicString& hyphenationString = rareInheritedData.get()->hyphenationString; + if (!hyphenationString.isNull()) + return hyphenationString; + + // FIXME: This should depend on locale. + DEFINE_STATIC_LOCAL(AtomicString, hyphenMinusString, (&hyphenMinus, 1)); + DEFINE_STATIC_LOCAL(AtomicString, hyphenString, (&hyphen, 1)); + return font().primaryFontHasGlyphForCharacter(hyphen) ? hyphenString : hyphenMinusString; +} + +const AtomicString& RenderStyle::textEmphasisMarkString() const +{ + switch (textEmphasisMark()) { + case TextEmphasisMarkNone: + return nullAtom; + case TextEmphasisMarkCustom: + return textEmphasisCustomMark(); + case TextEmphasisMarkDot: { + DEFINE_STATIC_LOCAL(AtomicString, filledDotString, (&bullet, 1)); + DEFINE_STATIC_LOCAL(AtomicString, openDotString, (&whiteBullet, 1)); + return textEmphasisFill() == TextEmphasisFillFilled ? filledDotString : openDotString; + } + case TextEmphasisMarkCircle: { + DEFINE_STATIC_LOCAL(AtomicString, filledCircleString, (&blackCircle, 1)); + DEFINE_STATIC_LOCAL(AtomicString, openCircleString, (&whiteCircle, 1)); + return textEmphasisFill() == TextEmphasisFillFilled ? filledCircleString : openCircleString; + } + case TextEmphasisMarkDoubleCircle: { + DEFINE_STATIC_LOCAL(AtomicString, filledDoubleCircleString, (&fisheye, 1)); + DEFINE_STATIC_LOCAL(AtomicString, openDoubleCircleString, (&bullseye, 1)); + return textEmphasisFill() == TextEmphasisFillFilled ? filledDoubleCircleString : openDoubleCircleString; + } + case TextEmphasisMarkTriangle: { + DEFINE_STATIC_LOCAL(AtomicString, filledTriangleString, (&blackUpPointingTriangle, 1)); + DEFINE_STATIC_LOCAL(AtomicString, openTriangleString, (&whiteUpPointingTriangle, 1)); + return textEmphasisFill() == TextEmphasisFillFilled ? filledTriangleString : openTriangleString; + } + case TextEmphasisMarkSesame: { + DEFINE_STATIC_LOCAL(AtomicString, filledSesameString, (&sesameDot, 1)); + DEFINE_STATIC_LOCAL(AtomicString, openSesameString, (&whiteSesameDot, 1)); + return textEmphasisFill() == TextEmphasisFillFilled ? filledSesameString : openSesameString; + } + case TextEmphasisMarkAuto: + ASSERT_NOT_REACHED(); + return nullAtom; + } + + ASSERT_NOT_REACHED(); + return nullAtom; +} + +#if ENABLE(DASHBOARD_SUPPORT) +const Vector<StyleDashboardRegion>& RenderStyle::initialDashboardRegions() +{ + DEFINE_STATIC_LOCAL(Vector<StyleDashboardRegion>, emptyList, ()); + return emptyList; +} + +const Vector<StyleDashboardRegion>& RenderStyle::noneDashboardRegions() +{ + DEFINE_STATIC_LOCAL(Vector<StyleDashboardRegion>, noneList, ()); + static bool noneListInitialized = false; + + if (!noneListInitialized) { + StyleDashboardRegion region; + region.label = ""; + region.offset.m_top = Length(); + region.offset.m_right = Length(); + region.offset.m_bottom = Length(); + region.offset.m_left = Length(); + region.type = StyleDashboardRegion::None; + noneList.append(region); + noneListInitialized = true; + } + return noneList; +} +#endif + +void RenderStyle::adjustAnimations() +{ + AnimationList* animationList = rareNonInheritedData->m_animations.get(); + if (!animationList) + return; + + // Get rid of empty animations and anything beyond them + for (size_t i = 0; i < animationList->size(); ++i) { + if (animationList->animation(i)->isEmpty()) { + animationList->resize(i); + break; + } + } + + if (animationList->isEmpty()) { + clearAnimations(); + return; + } + + // Repeat patterns into layers that don't have some properties set. + animationList->fillUnsetProperties(); +} + +void RenderStyle::adjustTransitions() +{ + AnimationList* transitionList = rareNonInheritedData->m_transitions.get(); + if (!transitionList) + return; + + // Get rid of empty transitions and anything beyond them + for (size_t i = 0; i < transitionList->size(); ++i) { + if (transitionList->animation(i)->isEmpty()) { + transitionList->resize(i); + break; + } + } + + if (transitionList->isEmpty()) { + clearTransitions(); + return; + } + + // Repeat patterns into layers that don't have some properties set. + transitionList->fillUnsetProperties(); + + // Make sure there are no duplicate properties. This is an O(n^2) algorithm + // but the lists tend to be very short, so it is probably ok + for (size_t i = 0; i < transitionList->size(); ++i) { + for (size_t j = i+1; j < transitionList->size(); ++j) { + if (transitionList->animation(i)->property() == transitionList->animation(j)->property()) { + // toss i + transitionList->remove(i); + j = i; + } + } + } +} + +AnimationList* RenderStyle::accessAnimations() +{ + if (!rareNonInheritedData.access()->m_animations) + rareNonInheritedData.access()->m_animations.set(new AnimationList()); + return rareNonInheritedData->m_animations.get(); +} + +AnimationList* RenderStyle::accessTransitions() +{ + if (!rareNonInheritedData.access()->m_transitions) + rareNonInheritedData.access()->m_transitions.set(new AnimationList()); + return rareNonInheritedData->m_transitions.get(); +} + +const Animation* RenderStyle::transitionForProperty(int property) const +{ + if (transitions()) { + for (size_t i = 0; i < transitions()->size(); ++i) { + const Animation* p = transitions()->animation(i); + if (p->property() == cAnimateAll || p->property() == property) { + return p; + } + } + } + return 0; +} + +void RenderStyle::setBlendedFontSize(int size) +{ + FontSelector* currentFontSelector = font().fontSelector(); + FontDescription desc(fontDescription()); + desc.setSpecifiedSize(size); + desc.setComputedSize(size); + setFontDescription(desc); + font().update(currentFontSelector); +} + +void RenderStyle::getShadowExtent(const ShadowData* shadow, int &top, int &right, int &bottom, int &left) const +{ + top = 0; + right = 0; + bottom = 0; + left = 0; + + for ( ; shadow; shadow = shadow->next()) { + if (shadow->style() == Inset) + continue; + int blurAndSpread = shadow->blur() + shadow->spread(); + + top = min(top, shadow->y() - blurAndSpread); + right = max(right, shadow->x() + blurAndSpread); + bottom = max(bottom, shadow->y() + blurAndSpread); + left = min(left, shadow->x() - blurAndSpread); + } +} + +void RenderStyle::getShadowHorizontalExtent(const ShadowData* shadow, int &left, int &right) const +{ + left = 0; + right = 0; + + for ( ; shadow; shadow = shadow->next()) { + if (shadow->style() == Inset) + continue; + int blurAndSpread = shadow->blur() + shadow->spread(); + + left = min(left, shadow->x() - blurAndSpread); + right = max(right, shadow->x() + blurAndSpread); + } +} + +void RenderStyle::getShadowVerticalExtent(const ShadowData* shadow, int &top, int &bottom) const +{ + top = 0; + bottom = 0; + + for ( ; shadow; shadow = shadow->next()) { + if (shadow->style() == Inset) + continue; + int blurAndSpread = shadow->blur() + shadow->spread(); + + top = min(top, shadow->y() - blurAndSpread); + bottom = max(bottom, shadow->y() + blurAndSpread); + } +} + +static EBorderStyle borderStyleForColorProperty(const RenderStyle* style, int colorProperty) +{ + EBorderStyle borderStyle; + switch (colorProperty) { + case CSSPropertyBorderLeftColor: + borderStyle = style->borderLeftStyle(); + break; + case CSSPropertyBorderRightColor: + borderStyle = style->borderRightStyle(); + break; + case CSSPropertyBorderTopColor: + borderStyle = style->borderTopStyle(); + break; + case CSSPropertyBorderBottomColor: + borderStyle = style->borderBottomStyle(); + break; + default: + borderStyle = BNONE; + break; + } + return borderStyle; +} + +const Color RenderStyle::colorIncludingFallback(int colorProperty, EBorderStyle borderStyle) const +{ + Color result; + switch (colorProperty) { + case CSSPropertyBackgroundColor: + return backgroundColor(); // Background color doesn't fall back. + case CSSPropertyBorderLeftColor: + result = borderLeftColor(); + borderStyle = borderLeftStyle(); + break; + case CSSPropertyBorderRightColor: + result = borderRightColor(); + borderStyle = borderRightStyle(); + break; + case CSSPropertyBorderTopColor: + result = borderTopColor(); + borderStyle = borderTopStyle(); + break; + case CSSPropertyBorderBottomColor: + result = borderBottomColor(); + borderStyle = borderBottomStyle(); + break; + case CSSPropertyColor: + result = color(); + break; + case CSSPropertyOutlineColor: + result = outlineColor(); + break; + case CSSPropertyWebkitColumnRuleColor: + result = columnRuleColor(); + break; + case CSSPropertyWebkitTextEmphasisColor: + result = textEmphasisColor(); + break; + case CSSPropertyWebkitTextFillColor: + result = textFillColor(); + break; + case CSSPropertyWebkitTextStrokeColor: + result = textStrokeColor(); + break; + default: + ASSERT_NOT_REACHED(); + break; + } + + if (!result.isValid()) { + if ((colorProperty == CSSPropertyBorderLeftColor || colorProperty == CSSPropertyBorderRightColor + || colorProperty == CSSPropertyBorderTopColor || colorProperty == CSSPropertyBorderBottomColor) + && (borderStyle == INSET || borderStyle == OUTSET || borderStyle == RIDGE || borderStyle == GROOVE)) + result.setRGB(238, 238, 238); + else + result = color(); + } + + return result; +} + +const Color RenderStyle::visitedDependentColor(int colorProperty) const +{ + EBorderStyle borderStyle = borderStyleForColorProperty(this, colorProperty); + Color unvisitedColor = colorIncludingFallback(colorProperty, borderStyle); + if (insideLink() != InsideVisitedLink) + return unvisitedColor; + + RenderStyle* visitedStyle = getCachedPseudoStyle(VISITED_LINK); + if (!visitedStyle) + return unvisitedColor; + Color visitedColor = visitedStyle->colorIncludingFallback(colorProperty, borderStyle); + + // Take the alpha from the unvisited color, but get the RGB values from the visited color. + return Color(visitedColor.red(), visitedColor.green(), visitedColor.blue(), unvisitedColor.alpha()); +} + +Length RenderStyle::logicalWidth() const +{ + if (isHorizontalWritingMode()) + return width(); + return height(); +} + +Length RenderStyle::logicalHeight() const +{ + if (isHorizontalWritingMode()) + return height(); + return width(); +} + +Length RenderStyle::logicalMinWidth() const +{ + if (isHorizontalWritingMode()) + return minWidth(); + return minHeight(); +} + +Length RenderStyle::logicalMaxWidth() const +{ + if (isHorizontalWritingMode()) + return maxWidth(); + return maxHeight(); +} + +Length RenderStyle::logicalMinHeight() const +{ + if (isHorizontalWritingMode()) + return minHeight(); + return minWidth(); +} + +Length RenderStyle::logicalMaxHeight() const +{ + if (isHorizontalWritingMode()) + return maxHeight(); + return maxWidth(); +} + +const BorderValue& RenderStyle::borderBefore() const +{ + switch (writingMode()) { + case TopToBottomWritingMode: + return borderTop(); + case BottomToTopWritingMode: + return borderBottom(); + case LeftToRightWritingMode: + return borderLeft(); + case RightToLeftWritingMode: + return borderRight(); + } + ASSERT_NOT_REACHED(); + return borderTop(); +} + +const BorderValue& RenderStyle::borderAfter() const +{ + switch (writingMode()) { + case TopToBottomWritingMode: + return borderBottom(); + case BottomToTopWritingMode: + return borderTop(); + case LeftToRightWritingMode: + return borderRight(); + case RightToLeftWritingMode: + return borderLeft(); + } + ASSERT_NOT_REACHED(); + return borderBottom(); +} + +const BorderValue& RenderStyle::borderStart() const +{ + if (isHorizontalWritingMode()) + return isLeftToRightDirection() ? borderLeft() : borderRight(); + return isLeftToRightDirection() ? borderTop() : borderBottom(); +} + +const BorderValue& RenderStyle::borderEnd() const +{ + if (isHorizontalWritingMode()) + return isLeftToRightDirection() ? borderRight() : borderLeft(); + return isLeftToRightDirection() ? borderBottom() : borderTop(); +} + +unsigned short RenderStyle::borderBeforeWidth() const +{ + switch (writingMode()) { + case TopToBottomWritingMode: + return borderTopWidth(); + case BottomToTopWritingMode: + return borderBottomWidth(); + case LeftToRightWritingMode: + return borderLeftWidth(); + case RightToLeftWritingMode: + return borderRightWidth(); + } + ASSERT_NOT_REACHED(); + return borderTopWidth(); +} + +unsigned short RenderStyle::borderAfterWidth() const +{ + switch (writingMode()) { + case TopToBottomWritingMode: + return borderBottomWidth(); + case BottomToTopWritingMode: + return borderTopWidth(); + case LeftToRightWritingMode: + return borderRightWidth(); + case RightToLeftWritingMode: + return borderLeftWidth(); + } + ASSERT_NOT_REACHED(); + return borderBottomWidth(); +} + +unsigned short RenderStyle::borderStartWidth() const +{ + if (isHorizontalWritingMode()) + return isLeftToRightDirection() ? borderLeftWidth() : borderRightWidth(); + return isLeftToRightDirection() ? borderTopWidth() : borderBottomWidth(); +} + +unsigned short RenderStyle::borderEndWidth() const +{ + if (isHorizontalWritingMode()) + return isLeftToRightDirection() ? borderRightWidth() : borderLeftWidth(); + return isLeftToRightDirection() ? borderBottomWidth() : borderTopWidth(); +} + +Length RenderStyle::marginBefore() const +{ + switch (writingMode()) { + case TopToBottomWritingMode: + return marginTop(); + case BottomToTopWritingMode: + return marginBottom(); + case LeftToRightWritingMode: + return marginLeft(); + case RightToLeftWritingMode: + return marginRight(); + } + ASSERT_NOT_REACHED(); + return marginTop(); +} + +Length RenderStyle::marginAfter() const +{ + switch (writingMode()) { + case TopToBottomWritingMode: + return marginBottom(); + case BottomToTopWritingMode: + return marginTop(); + case LeftToRightWritingMode: + return marginRight(); + case RightToLeftWritingMode: + return marginLeft(); + } + ASSERT_NOT_REACHED(); + return marginBottom(); +} + +Length RenderStyle::marginBeforeUsing(const RenderStyle* otherStyle) const +{ + switch (otherStyle->writingMode()) { + case TopToBottomWritingMode: + return marginTop(); + case BottomToTopWritingMode: + return marginBottom(); + case LeftToRightWritingMode: + return marginLeft(); + case RightToLeftWritingMode: + return marginRight(); + } + ASSERT_NOT_REACHED(); + return marginTop(); +} + +Length RenderStyle::marginAfterUsing(const RenderStyle* otherStyle) const +{ + switch (otherStyle->writingMode()) { + case TopToBottomWritingMode: + return marginBottom(); + case BottomToTopWritingMode: + return marginTop(); + case LeftToRightWritingMode: + return marginRight(); + case RightToLeftWritingMode: + return marginLeft(); + } + ASSERT_NOT_REACHED(); + return marginBottom(); +} + +Length RenderStyle::marginStart() const +{ + if (isHorizontalWritingMode()) + return isLeftToRightDirection() ? marginLeft() : marginRight(); + return isLeftToRightDirection() ? marginTop() : marginBottom(); +} + +Length RenderStyle::marginEnd() const +{ + if (isHorizontalWritingMode()) + return isLeftToRightDirection() ? marginRight() : marginLeft(); + return isLeftToRightDirection() ? marginBottom() : marginTop(); +} + +Length RenderStyle::marginStartUsing(const RenderStyle* otherStyle) const +{ + if (otherStyle->isHorizontalWritingMode()) + return otherStyle->isLeftToRightDirection() ? marginLeft() : marginRight(); + return otherStyle->isLeftToRightDirection() ? marginTop() : marginBottom(); +} + +Length RenderStyle::marginEndUsing(const RenderStyle* otherStyle) const +{ + if (otherStyle->isHorizontalWritingMode()) + return otherStyle->isLeftToRightDirection() ? marginRight() : marginLeft(); + return otherStyle->isLeftToRightDirection() ? marginBottom() : marginTop(); +} + +void RenderStyle::setMarginStart(Length margin) +{ + if (isHorizontalWritingMode()) { + if (isLeftToRightDirection()) + setMarginLeft(margin); + else + setMarginRight(margin); + } else { + if (isLeftToRightDirection()) + setMarginTop(margin); + else + setMarginBottom(margin); + } +} + +void RenderStyle::setMarginEnd(Length margin) +{ + if (isHorizontalWritingMode()) { + if (isLeftToRightDirection()) + setMarginRight(margin); + else + setMarginLeft(margin); + } else { + if (isLeftToRightDirection()) + setMarginBottom(margin); + else + setMarginTop(margin); + } +} + +Length RenderStyle::paddingBefore() const +{ + switch (writingMode()) { + case TopToBottomWritingMode: + return paddingTop(); + case BottomToTopWritingMode: + return paddingBottom(); + case LeftToRightWritingMode: + return paddingLeft(); + case RightToLeftWritingMode: + return paddingRight(); + } + ASSERT_NOT_REACHED(); + return paddingTop(); +} + +Length RenderStyle::paddingAfter() const +{ + switch (writingMode()) { + case TopToBottomWritingMode: + return paddingBottom(); + case BottomToTopWritingMode: + return paddingTop(); + case LeftToRightWritingMode: + return paddingRight(); + case RightToLeftWritingMode: + return paddingLeft(); + } + ASSERT_NOT_REACHED(); + return paddingBottom(); +} + +Length RenderStyle::paddingStart() const +{ + if (isHorizontalWritingMode()) + return isLeftToRightDirection() ? paddingLeft() : paddingRight(); + return isLeftToRightDirection() ? paddingTop() : paddingBottom(); +} + +Length RenderStyle::paddingEnd() const +{ + if (isHorizontalWritingMode()) + return isLeftToRightDirection() ? paddingRight() : paddingLeft(); + return isLeftToRightDirection() ? paddingBottom() : paddingTop(); +} + +TextEmphasisMark RenderStyle::textEmphasisMark() const +{ + TextEmphasisMark mark = static_cast<TextEmphasisMark>(rareInheritedData->textEmphasisMark); + if (mark != TextEmphasisMarkAuto) + return mark; + + if (isHorizontalWritingMode()) + return TextEmphasisMarkDot; + + return TextEmphasisMarkSesame; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/RenderStyle.h b/Source/WebCore/rendering/style/RenderStyle.h new file mode 100644 index 0000000..7e61e46 --- /dev/null +++ b/Source/WebCore/rendering/style/RenderStyle.h @@ -0,0 +1,1409 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef RenderStyle_h +#define RenderStyle_h + +#include "TransformationMatrix.h" +#include "AnimationList.h" +#include "BorderData.h" +#include "BorderValue.h" +#include "CSSImageGeneratorValue.h" +#include "CSSPrimitiveValue.h" +#include "CSSPropertyNames.h" +#include "CSSReflectionDirection.h" +#include "CSSValueList.h" +#include "CollapsedBorderValue.h" +#include "Color.h" +#include "ColorSpace.h" +#include "ContentData.h" +#include "CounterDirectives.h" +#include "CursorList.h" +#include "DataRef.h" +#include "FillLayer.h" +#include "FloatPoint.h" +#include "Font.h" +#include "GraphicsTypes.h" +#include "IntRect.h" +#include "Length.h" +#include "LengthBox.h" +#include "LengthSize.h" +#include "LineClampValue.h" +#include "NinePieceImage.h" +#include "OutlineValue.h" +#include "Pair.h" +#include "RenderStyleConstants.h" +#include "ShadowData.h" +#include "StyleBackgroundData.h" +#include "StyleBoxData.h" +#include "StyleFlexibleBoxData.h" +#include "StyleInheritedData.h" +#include "StyleMarqueeData.h" +#include "StyleMultiColData.h" +#include "StyleRareInheritedData.h" +#include "StyleRareNonInheritedData.h" +#include "StyleReflection.h" +#include "StyleSurroundData.h" +#include "StyleTransformData.h" +#include "StyleVisualData.h" +#include "TextDirection.h" +#include "ThemeTypes.h" +#include "TimingFunction.h" +#include "TransformOperations.h" +#include <wtf/Forward.h> +#include <wtf/OwnPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/StdLibExtras.h> +#include <wtf/Vector.h> + +#if ENABLE(DASHBOARD_SUPPORT) +#include "StyleDashboardRegion.h" +#endif + +#if ENABLE(SVG) +#include "SVGRenderStyle.h" +#endif + +#if COMPILER(WINSCW) +#define compareEqual(t, u) ((t) == (u)) +#else +template<typename T, typename U> inline bool compareEqual(const T& t, const U& u) { return t == static_cast<T>(u); } +#endif + +#define SET_VAR(group, variable, value) \ + if (!compareEqual(group->variable, value)) \ + group.access()->variable = value; + +namespace WebCore { + +using std::max; + +class CSSStyleSelector; +class CSSValueList; +class Pair; +class StyleImage; + +typedef Vector<RefPtr<RenderStyle>, 4> PseudoStyleCache; + +class RenderStyle: public RefCounted<RenderStyle> { + friend class AnimationBase; // Used by CSS animations. We can't allow them to animate based off visited colors. + friend class ApplyStyleCommand; // Editing has to only reveal unvisited info. + friend class EditingStyle; // Editing has to only reveal unvisited info. + friend class CSSStyleSelector; // Sets members directly. + friend class CSSComputedStyleDeclaration; // Ignores visited styles, so needs to be able to see unvisited info. + friend class PropertyWrapperMaybeInvalidColor; // Used by CSS animations. We can't allow them to animate based off visited colors. + friend class RenderSVGResource; // FIXME: Needs to alter the visited state by hand. Should clean the SVG code up and move it into RenderStyle perhaps. + friend class RenderTreeAsText; // FIXME: Only needed so the render tree can keep lying and dump the wrong colors. Rebaselining would allow this to be yanked. +protected: + + // The following bitfield is 32-bits long, which optimizes padding with the + // int refCount in the base class. Beware when adding more bits. + bool m_affectedByAttributeSelectors : 1; + bool m_unique : 1; + + // Bits for dynamic child matching. + bool m_affectedByEmpty : 1; + bool m_emptyState : 1; + + // We optimize for :first-child and :last-child. The other positional child selectors like nth-child or + // *-child-of-type, we will just give up and re-evaluate whenever children change at all. + bool m_childrenAffectedByFirstChildRules : 1; + bool m_childrenAffectedByLastChildRules : 1; + bool m_childrenAffectedByDirectAdjacentRules : 1; + bool m_childrenAffectedByForwardPositionalRules : 1; + bool m_childrenAffectedByBackwardPositionalRules : 1; + bool m_firstChildState : 1; + bool m_lastChildState : 1; + unsigned m_childIndex : 21; // Plenty of bits to cache an index. + + // non-inherited attributes + DataRef<StyleBoxData> m_box; + DataRef<StyleVisualData> visual; + DataRef<StyleBackgroundData> m_background; + DataRef<StyleSurroundData> surround; + DataRef<StyleRareNonInheritedData> rareNonInheritedData; + + // inherited attributes + DataRef<StyleRareInheritedData> rareInheritedData; + DataRef<StyleInheritedData> inherited; + + // list of associated pseudo styles + OwnPtr<PseudoStyleCache> m_cachedPseudoStyles; + +#if ENABLE(SVG) + DataRef<SVGRenderStyle> m_svgStyle; +#endif + +// !START SYNC!: Keep this in sync with the copy constructor in RenderStyle.cpp + + // inherit + struct InheritedFlags { + bool operator==(const InheritedFlags& other) const + { + return (_empty_cells == other._empty_cells) && + (_caption_side == other._caption_side) && + (_list_style_type == other._list_style_type) && + (_list_style_position == other._list_style_position) && + (_visibility == other._visibility) && + (_text_align == other._text_align) && + (_text_transform == other._text_transform) && + (_text_decorations == other._text_decorations) && + (_text_transform == other._text_transform) && + (_cursor_style == other._cursor_style) && + (_direction == other._direction) && + (_border_collapse == other._border_collapse) && + (_white_space == other._white_space) && + (_box_direction == other._box_direction) && + (_visuallyOrdered == other._visuallyOrdered) && + (_force_backgrounds_to_white == other._force_backgrounds_to_white) && + (_pointerEvents == other._pointerEvents) && + (_insideLink == other._insideLink) && + (m_writingMode == other.m_writingMode); + } + + bool operator!=(const InheritedFlags& other) const { return !(*this == other); } + + unsigned _empty_cells : 1; // EEmptyCell + unsigned _caption_side : 2; // ECaptionSide + unsigned _list_style_type : 7; // EListStyleType + unsigned _list_style_position : 1; // EListStylePosition + unsigned _visibility : 2; // EVisibility + unsigned _text_align : 3; // ETextAlign + unsigned _text_transform : 2; // ETextTransform + unsigned _text_decorations : 4; + unsigned _cursor_style : 6; // ECursor + unsigned _direction : 1; // TextDirection + bool _border_collapse : 1 ; + unsigned _white_space : 3; // EWhiteSpace + unsigned _box_direction : 1; // EBoxDirection (CSS3 box_direction property, flexible box layout module) + // 34 bits + + // non CSS2 inherited + bool _visuallyOrdered : 1; + bool _force_backgrounds_to_white : 1; + unsigned _pointerEvents : 4; // EPointerEvents + unsigned _insideLink : 2; // EInsideLink + // 43 bits + + // CSS Text Layout Module Level 3: Vertical writing support + unsigned m_writingMode : 2; // WritingMode + // 45 bits + } inherited_flags; + +// don't inherit + struct NonInheritedFlags { + bool operator==(const NonInheritedFlags& other) const + { + return _effectiveDisplay == other._effectiveDisplay + && _originalDisplay == other._originalDisplay + && _overflowX == other._overflowX + && _overflowY == other._overflowY + && _vertical_align == other._vertical_align + && _clear == other._clear + && _position == other._position + && _floating == other._floating + && _table_layout == other._table_layout + && _page_break_before == other._page_break_before + && _page_break_after == other._page_break_after + && _page_break_inside == other._page_break_inside + && _styleType == other._styleType + && _affectedByHover == other._affectedByHover + && _affectedByActive == other._affectedByActive + && _affectedByDrag == other._affectedByDrag + && _pseudoBits == other._pseudoBits + && _unicodeBidi == other._unicodeBidi + && _isLink == other._isLink; + } + + bool operator!=(const NonInheritedFlags& other) const { return !(*this == other); } + + unsigned _effectiveDisplay : 5; // EDisplay + unsigned _originalDisplay : 5; // EDisplay + unsigned _overflowX : 3; // EOverflow + unsigned _overflowY : 3; // EOverflow + unsigned _vertical_align : 4; // EVerticalAlign + unsigned _clear : 2; // EClear + unsigned _position : 2; // EPosition + unsigned _floating : 2; // EFloat + unsigned _table_layout : 1; // ETableLayout + + unsigned _page_break_before : 2; // EPageBreak + unsigned _page_break_after : 2; // EPageBreak + unsigned _page_break_inside : 2; // EPageBreak + + unsigned _styleType : 6; // PseudoId + bool _affectedByHover : 1; + bool _affectedByActive : 1; + bool _affectedByDrag : 1; + unsigned _pseudoBits : 7; + unsigned _unicodeBidi : 2; // EUnicodeBidi + bool _isLink : 1; + // 50 bits + } noninherited_flags; + +// !END SYNC! + +protected: + void setBitDefaults() + { + inherited_flags._empty_cells = initialEmptyCells(); + inherited_flags._caption_side = initialCaptionSide(); + inherited_flags._list_style_type = initialListStyleType(); + inherited_flags._list_style_position = initialListStylePosition(); + inherited_flags._visibility = initialVisibility(); + inherited_flags._text_align = initialTextAlign(); + inherited_flags._text_transform = initialTextTransform(); + inherited_flags._text_decorations = initialTextDecoration(); + inherited_flags._cursor_style = initialCursor(); + inherited_flags._direction = initialDirection(); + inherited_flags._border_collapse = initialBorderCollapse(); + inherited_flags._white_space = initialWhiteSpace(); + inherited_flags._visuallyOrdered = initialVisuallyOrdered(); + inherited_flags._box_direction = initialBoxDirection(); + inherited_flags._force_backgrounds_to_white = false; + inherited_flags._pointerEvents = initialPointerEvents(); + inherited_flags._insideLink = NotInsideLink; + inherited_flags.m_writingMode = initialWritingMode(); + + noninherited_flags._effectiveDisplay = noninherited_flags._originalDisplay = initialDisplay(); + noninherited_flags._overflowX = initialOverflowX(); + noninherited_flags._overflowY = initialOverflowY(); + noninherited_flags._vertical_align = initialVerticalAlign(); + noninherited_flags._clear = initialClear(); + noninherited_flags._position = initialPosition(); + noninherited_flags._floating = initialFloating(); + noninherited_flags._table_layout = initialTableLayout(); + noninherited_flags._page_break_before = initialPageBreak(); + noninherited_flags._page_break_after = initialPageBreak(); + noninherited_flags._page_break_inside = initialPageBreak(); + noninherited_flags._styleType = NOPSEUDO; + noninherited_flags._affectedByHover = false; + noninherited_flags._affectedByActive = false; + noninherited_flags._affectedByDrag = false; + noninherited_flags._pseudoBits = 0; + noninherited_flags._unicodeBidi = initialUnicodeBidi(); + noninherited_flags._isLink = false; + } + +private: + ALWAYS_INLINE RenderStyle(); + // used to create the default style. + ALWAYS_INLINE RenderStyle(bool); + ALWAYS_INLINE RenderStyle(const RenderStyle&); + +public: + static PassRefPtr<RenderStyle> create(); + static PassRefPtr<RenderStyle> createDefaultStyle(); + static PassRefPtr<RenderStyle> clone(const RenderStyle*); + + ~RenderStyle(); + + void inheritFrom(const RenderStyle* inheritParent); + + PseudoId styleType() const { return static_cast<PseudoId>(noninherited_flags._styleType); } + void setStyleType(PseudoId styleType) { noninherited_flags._styleType = styleType; } + + RenderStyle* getCachedPseudoStyle(PseudoId) const; + RenderStyle* addCachedPseudoStyle(PassRefPtr<RenderStyle>); + void removeCachedPseudoStyle(PseudoId); + + const PseudoStyleCache* cachedPseudoStyles() const { return m_cachedPseudoStyles.get(); } + + bool affectedByHoverRules() const { return noninherited_flags._affectedByHover; } + bool affectedByActiveRules() const { return noninherited_flags._affectedByActive; } + bool affectedByDragRules() const { return noninherited_flags._affectedByDrag; } + + void setAffectedByHoverRules(bool b) { noninherited_flags._affectedByHover = b; } + void setAffectedByActiveRules(bool b) { noninherited_flags._affectedByActive = b; } + void setAffectedByDragRules(bool b) { noninherited_flags._affectedByDrag = b; } + + bool operator==(const RenderStyle& other) const; + bool operator!=(const RenderStyle& other) const { return !(*this == other); } + bool isFloating() const { return !(noninherited_flags._floating == FNONE); } + bool hasMargin() const { return surround->margin.nonZero(); } + bool hasBorder() const { return surround->border.hasBorder(); } + bool hasPadding() const { return surround->padding.nonZero(); } + bool hasOffset() const { return surround->offset.nonZero(); } + + bool hasBackgroundImage() const { return m_background->background().hasImage(); } + bool hasFixedBackgroundImage() const { return m_background->background().hasFixedImage(); } + bool hasAppearance() const { return appearance() != NoControlPart; } + + bool hasBackground() const + { + Color color = visitedDependentColor(CSSPropertyBackgroundColor); + if (color.isValid() && color.alpha() > 0) + return true; + return hasBackgroundImage(); + } + + bool visuallyOrdered() const { return inherited_flags._visuallyOrdered; } + void setVisuallyOrdered(bool b) { inherited_flags._visuallyOrdered = b; } + + bool isStyleAvailable() const; + + bool hasAnyPublicPseudoStyles() const; + bool hasPseudoStyle(PseudoId pseudo) const; + void setHasPseudoStyle(PseudoId pseudo); + + // attribute getter methods + + EDisplay display() const { return static_cast<EDisplay>(noninherited_flags._effectiveDisplay); } + EDisplay originalDisplay() const { return static_cast<EDisplay>(noninherited_flags._originalDisplay); } + + Length left() const { return surround->offset.left(); } + Length right() const { return surround->offset.right(); } + Length top() const { return surround->offset.top(); } + Length bottom() const { return surround->offset.bottom(); } + + // Whether or not a positioned element requires normal flow x/y to be computed + // to determine its position. + bool hasStaticX() const { return (left().isAuto() && right().isAuto()) || left().isStatic() || right().isStatic(); } + bool hasStaticY() const { return (top().isAuto() && bottom().isAuto()) || top().isStatic(); } + + EPosition position() const { return static_cast<EPosition>(noninherited_flags._position); } + EFloat floating() const { return static_cast<EFloat>(noninherited_flags._floating); } + + Length width() const { return m_box->width(); } + Length height() const { return m_box->height(); } + Length minWidth() const { return m_box->minWidth(); } + Length maxWidth() const { return m_box->maxWidth(); } + Length minHeight() const { return m_box->minHeight(); } + Length maxHeight() const { return m_box->maxHeight(); } + + Length logicalWidth() const; + Length logicalHeight() const; + Length logicalMinWidth() const; + Length logicalMaxWidth() const; + Length logicalMinHeight() const; + Length logicalMaxHeight() const; + + const BorderData& border() const { return surround->border; } + 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(); } + + const BorderValue& borderBefore() const; + const BorderValue& borderAfter() const; + const BorderValue& borderStart() const; + const BorderValue& borderEnd() const; + + const NinePieceImage& borderImage() const { return surround->border.image(); } + + const LengthSize& borderTopLeftRadius() const { return surround->border.topLeft(); } + const LengthSize& borderTopRightRadius() const { return surround->border.topRight(); } + const LengthSize& borderBottomLeftRadius() const { return surround->border.bottomLeft(); } + const LengthSize& borderBottomRightRadius() const { return surround->border.bottomRight(); } + bool hasBorderRadius() const { return surround->border.hasBorderRadius(); } + + unsigned short borderLeftWidth() const { return surround->border.borderLeftWidth(); } + EBorderStyle borderLeftStyle() const { return surround->border.left().style(); } + bool borderLeftIsTransparent() const { return surround->border.left().isTransparent(); } + unsigned short borderRightWidth() const { return surround->border.borderRightWidth(); } + EBorderStyle borderRightStyle() const { return surround->border.right().style(); } + bool borderRightIsTransparent() const { return surround->border.right().isTransparent(); } + unsigned short borderTopWidth() const { return surround->border.borderTopWidth(); } + EBorderStyle borderTopStyle() const { return surround->border.top().style(); } + bool borderTopIsTransparent() const { return surround->border.top().isTransparent(); } + unsigned short borderBottomWidth() const { return surround->border.borderBottomWidth(); } + EBorderStyle borderBottomStyle() const { return surround->border.bottom().style(); } + bool borderBottomIsTransparent() const { return surround->border.bottom().isTransparent(); } + + unsigned short borderBeforeWidth() const; + unsigned short borderAfterWidth() const; + unsigned short borderStartWidth() const; + unsigned short borderEndWidth() const; + + unsigned short outlineSize() const { return max(0, outlineWidth() + outlineOffset()); } + unsigned short outlineWidth() const + { + if (m_background->outline().style() == BNONE) + return 0; + return m_background->outline().width(); + } + bool hasOutline() const { return outlineWidth() > 0 && outlineStyle() > BHIDDEN; } + EBorderStyle outlineStyle() const { return m_background->outline().style(); } + bool outlineStyleIsAuto() const { return m_background->outline().isAuto(); } + + EOverflow overflowX() const { return static_cast<EOverflow>(noninherited_flags._overflowX); } + EOverflow overflowY() const { return static_cast<EOverflow>(noninherited_flags._overflowY); } + + EVisibility visibility() const { return static_cast<EVisibility>(inherited_flags._visibility); } + EVerticalAlign verticalAlign() const { return static_cast<EVerticalAlign>(noninherited_flags._vertical_align); } + Length verticalAlignLength() const { return m_box->verticalAlign(); } + + Length clipLeft() const { return visual->clip.left(); } + Length clipRight() const { return visual->clip.right(); } + Length clipTop() const { return visual->clip.top(); } + Length clipBottom() const { return visual->clip.bottom(); } + LengthBox clip() const { return visual->clip; } + bool hasClip() const { return visual->hasClip; } + + EUnicodeBidi unicodeBidi() const { return static_cast<EUnicodeBidi>(noninherited_flags._unicodeBidi); } + + EClear clear() const { return static_cast<EClear>(noninherited_flags._clear); } + ETableLayout tableLayout() const { return static_cast<ETableLayout>(noninherited_flags._table_layout); } + + const Font& font() const { return inherited->font; } + const FontDescription& fontDescription() const { return inherited->font.fontDescription(); } + int fontSize() const { return inherited->font.pixelSize(); } + + Length textIndent() const { return rareInheritedData->indent; } + ETextAlign textAlign() const { return static_cast<ETextAlign>(inherited_flags._text_align); } + ETextTransform textTransform() const { return static_cast<ETextTransform>(inherited_flags._text_transform); } + int textDecorationsInEffect() const { return inherited_flags._text_decorations; } + int textDecoration() const { return visual->textDecoration; } + int wordSpacing() const { return inherited->font.wordSpacing(); } + int letterSpacing() const { return inherited->font.letterSpacing(); } + + float zoom() const { return visual->m_zoom; } + float effectiveZoom() const { return rareInheritedData->m_effectiveZoom; } + + TextDirection direction() const { return static_cast<TextDirection>(inherited_flags._direction); } + bool isLeftToRightDirection() const { return direction() == LTR; } + + Length lineHeight() const { return inherited->line_height; } + int computedLineHeight() const + { + Length lh = lineHeight(); + + // Negative value means the line height is not set. Use the font's built-in spacing. + if (lh.isNegative()) + return font().lineSpacing(); + + if (lh.isPercent()) + return lh.calcMinValue(fontSize()); + + return lh.value(); + } + + EWhiteSpace whiteSpace() const { return static_cast<EWhiteSpace>(inherited_flags._white_space); } + static bool autoWrap(EWhiteSpace ws) + { + // Nowrap and pre don't automatically wrap. + return ws != NOWRAP && ws != PRE; + } + + bool autoWrap() const + { + return autoWrap(whiteSpace()); + } + + static bool preserveNewline(EWhiteSpace ws) + { + // Normal and nowrap do not preserve newlines. + return ws != NORMAL && ws != NOWRAP; + } + + bool preserveNewline() const + { + return preserveNewline(whiteSpace()); + } + + static bool collapseWhiteSpace(EWhiteSpace ws) + { + // Pre and prewrap do not collapse whitespace. + return ws != PRE && ws != PRE_WRAP; + } + + bool collapseWhiteSpace() const + { + return collapseWhiteSpace(whiteSpace()); + } + + bool isCollapsibleWhiteSpace(UChar c) const + { + switch (c) { + case ' ': + case '\t': + return collapseWhiteSpace(); + case '\n': + return !preserveNewline(); + } + return false; + } + + bool breakOnlyAfterWhiteSpace() const + { + return whiteSpace() == PRE_WRAP || khtmlLineBreak() == AFTER_WHITE_SPACE; + } + + bool breakWords() const + { + return wordBreak() == BreakWordBreak || wordWrap() == BreakWordWrap; + } + + StyleImage* backgroundImage() const { return m_background->background().image(); } + EFillRepeat backgroundRepeatX() const { return static_cast<EFillRepeat>(m_background->background().repeatX()); } + EFillRepeat backgroundRepeatY() const { return static_cast<EFillRepeat>(m_background->background().repeatY()); } + CompositeOperator backgroundComposite() const { return static_cast<CompositeOperator>(m_background->background().composite()); } + EFillAttachment backgroundAttachment() const { return static_cast<EFillAttachment>(m_background->background().attachment()); } + EFillBox backgroundClip() const { return static_cast<EFillBox>(m_background->background().clip()); } + EFillBox backgroundOrigin() const { return static_cast<EFillBox>(m_background->background().origin()); } + Length backgroundXPosition() const { return m_background->background().xPosition(); } + Length backgroundYPosition() const { return m_background->background().yPosition(); } + EFillSizeType backgroundSizeType() const { return m_background->background().sizeType(); } + LengthSize backgroundSizeLength() const { return m_background->background().sizeLength(); } + FillLayer* accessBackgroundLayers() { return &(m_background.access()->m_background); } + const FillLayer* backgroundLayers() const { return &(m_background->background()); } + + StyleImage* maskImage() const { return rareNonInheritedData->m_mask.image(); } + EFillRepeat maskRepeatX() const { return static_cast<EFillRepeat>(rareNonInheritedData->m_mask.repeatX()); } + EFillRepeat maskRepeatY() const { return static_cast<EFillRepeat>(rareNonInheritedData->m_mask.repeatY()); } + CompositeOperator maskComposite() const { return static_cast<CompositeOperator>(rareNonInheritedData->m_mask.composite()); } + EFillAttachment maskAttachment() const { return static_cast<EFillAttachment>(rareNonInheritedData->m_mask.attachment()); } + EFillBox maskClip() const { return static_cast<EFillBox>(rareNonInheritedData->m_mask.clip()); } + EFillBox maskOrigin() const { return static_cast<EFillBox>(rareNonInheritedData->m_mask.origin()); } + Length maskXPosition() const { return rareNonInheritedData->m_mask.xPosition(); } + Length maskYPosition() const { return rareNonInheritedData->m_mask.yPosition(); } + EFillSizeType maskSizeType() const { return rareNonInheritedData->m_mask.sizeType(); } + LengthSize maskSizeLength() const { return rareNonInheritedData->m_mask.sizeLength(); } + FillLayer* accessMaskLayers() { return &(rareNonInheritedData.access()->m_mask); } + const FillLayer* maskLayers() const { return &(rareNonInheritedData->m_mask); } + const NinePieceImage& maskBoxImage() const { return rareNonInheritedData->m_maskBoxImage; } + + // returns true for collapsing borders, false for separate borders + bool borderCollapse() const { return inherited_flags._border_collapse; } + short horizontalBorderSpacing() const { return inherited->horizontal_border_spacing; } + short verticalBorderSpacing() const { return inherited->vertical_border_spacing; } + EEmptyCell emptyCells() const { return static_cast<EEmptyCell>(inherited_flags._empty_cells); } + ECaptionSide captionSide() const { return static_cast<ECaptionSide>(inherited_flags._caption_side); } + + short counterIncrement() const { return rareNonInheritedData->m_counterIncrement; } + short counterReset() const { return rareNonInheritedData->m_counterReset; } + + EListStyleType listStyleType() const { return static_cast<EListStyleType>(inherited_flags._list_style_type); } + StyleImage* listStyleImage() const { return inherited->list_style_image.get(); } + EListStylePosition listStylePosition() const { return static_cast<EListStylePosition>(inherited_flags._list_style_position); } + + Length marginTop() const { return surround->margin.top(); } + Length marginBottom() const { return surround->margin.bottom(); } + Length marginLeft() const { return surround->margin.left(); } + Length marginRight() const { return surround->margin.right(); } + Length marginBefore() const; + Length marginAfter() const; + Length marginStart() const; + Length marginEnd() const; + Length marginStartUsing(const RenderStyle* otherStyle) const; + Length marginEndUsing(const RenderStyle* otherStyle) const; + Length marginBeforeUsing(const RenderStyle* otherStyle) const; + Length marginAfterUsing(const RenderStyle* otherStyle) const; + + LengthBox paddingBox() const { return surround->padding; } + Length paddingTop() const { return surround->padding.top(); } + Length paddingBottom() const { return surround->padding.bottom(); } + Length paddingLeft() const { return surround->padding.left(); } + Length paddingRight() const { return surround->padding.right(); } + Length paddingBefore() const; + Length paddingAfter() const; + Length paddingStart() const; + Length paddingEnd() const; + + ECursor cursor() const { return static_cast<ECursor>(inherited_flags._cursor_style); } + + CursorList* cursors() const { return rareInheritedData->cursorData.get(); } + + EInsideLink insideLink() const { return static_cast<EInsideLink>(inherited_flags._insideLink); } + bool isLink() const { return noninherited_flags._isLink; } + + short widows() const { return rareInheritedData->widows; } + short orphans() const { return rareInheritedData->orphans; } + EPageBreak pageBreakInside() const { return static_cast<EPageBreak>(noninherited_flags._page_break_inside); } + EPageBreak pageBreakBefore() const { return static_cast<EPageBreak>(noninherited_flags._page_break_before); } + EPageBreak pageBreakAfter() const { return static_cast<EPageBreak>(noninherited_flags._page_break_after); } + + // CSS3 Getter Methods + + int outlineOffset() const + { + if (m_background->outline().style() == BNONE) + return 0; + return m_background->outline().offset(); + } + + const ShadowData* textShadow() const { return rareInheritedData->textShadow; } + void getTextShadowExtent(int& top, int& right, int& bottom, int& left) const { getShadowExtent(textShadow(), top, right, bottom, left); } + void getTextShadowHorizontalExtent(int& left, int& right) const { getShadowHorizontalExtent(textShadow(), left, right); } + void getTextShadowVerticalExtent(int& top, int& bottom) const { getShadowVerticalExtent(textShadow(), top, bottom); } + void getTextShadowInlineDirectionExtent(int& logicalLeft, int& logicalRight) { getShadowInlineDirectionExtent(textShadow(), logicalLeft, logicalRight); } + void getTextShadowBlockDirectionExtent(int& logicalTop, int& logicalBottom) { getShadowBlockDirectionExtent(textShadow(), logicalTop, logicalBottom); } + + float textStrokeWidth() const { return rareInheritedData->textStrokeWidth; } + ColorSpace colorSpace() const { return static_cast<ColorSpace>(rareInheritedData->colorSpace); } + float opacity() const { return rareNonInheritedData->opacity; } + ControlPart appearance() const { return static_cast<ControlPart>(rareNonInheritedData->m_appearance); } + EBoxAlignment boxAlign() const { return static_cast<EBoxAlignment>(rareNonInheritedData->flexibleBox->align); } + EBoxDirection boxDirection() const { return static_cast<EBoxDirection>(inherited_flags._box_direction); } + float boxFlex() { return rareNonInheritedData->flexibleBox->flex; } + unsigned int boxFlexGroup() const { return rareNonInheritedData->flexibleBox->flex_group; } + EBoxLines boxLines() { return static_cast<EBoxLines>(rareNonInheritedData->flexibleBox->lines); } + unsigned int boxOrdinalGroup() const { return rareNonInheritedData->flexibleBox->ordinal_group; } + EBoxOrient boxOrient() const { return static_cast<EBoxOrient>(rareNonInheritedData->flexibleBox->orient); } + EBoxAlignment boxPack() const { return static_cast<EBoxAlignment>(rareNonInheritedData->flexibleBox->pack); } + + const ShadowData* boxShadow() const { return rareNonInheritedData->m_boxShadow.get(); } + void getBoxShadowExtent(int& top, int& right, int& bottom, int& left) const { getShadowExtent(boxShadow(), top, right, bottom, left); } + void getBoxShadowHorizontalExtent(int& left, int& right) const { getShadowHorizontalExtent(boxShadow(), left, right); } + void getBoxShadowVerticalExtent(int& top, int& bottom) const { getShadowVerticalExtent(boxShadow(), top, bottom); } + void getBoxShadowInlineDirectionExtent(int& logicalLeft, int& logicalRight) { getShadowInlineDirectionExtent(boxShadow(), logicalLeft, logicalRight); } + void getBoxShadowBlockDirectionExtent(int& logicalTop, int& logicalBottom) { getShadowBlockDirectionExtent(boxShadow(), logicalTop, logicalBottom); } + + StyleReflection* boxReflect() const { return rareNonInheritedData->m_boxReflect.get(); } + EBoxSizing boxSizing() const { return m_box->boxSizing(); } + Length marqueeIncrement() const { return rareNonInheritedData->marquee->increment; } + int marqueeSpeed() const { return rareNonInheritedData->marquee->speed; } + int marqueeLoopCount() const { return rareNonInheritedData->marquee->loops; } + EMarqueeBehavior marqueeBehavior() const { return static_cast<EMarqueeBehavior>(rareNonInheritedData->marquee->behavior); } + EMarqueeDirection marqueeDirection() const { return static_cast<EMarqueeDirection>(rareNonInheritedData->marquee->direction); } + EUserModify userModify() const { return static_cast<EUserModify>(rareInheritedData->userModify); } + EUserDrag userDrag() const { return static_cast<EUserDrag>(rareNonInheritedData->userDrag); } + EUserSelect userSelect() const { return static_cast<EUserSelect>(rareInheritedData->userSelect); } + bool textOverflow() const { return rareNonInheritedData->textOverflow; } + EMarginCollapse marginBeforeCollapse() const { return static_cast<EMarginCollapse>(rareNonInheritedData->marginBeforeCollapse); } + EMarginCollapse marginAfterCollapse() const { return static_cast<EMarginCollapse>(rareNonInheritedData->marginAfterCollapse); } + EWordBreak wordBreak() const { return static_cast<EWordBreak>(rareInheritedData->wordBreak); } + EWordWrap wordWrap() const { return static_cast<EWordWrap>(rareInheritedData->wordWrap); } + ENBSPMode nbspMode() const { return static_cast<ENBSPMode>(rareInheritedData->nbspMode); } + EKHTMLLineBreak khtmlLineBreak() const { return static_cast<EKHTMLLineBreak>(rareInheritedData->khtmlLineBreak); } + EMatchNearestMailBlockquoteColor matchNearestMailBlockquoteColor() const { return static_cast<EMatchNearestMailBlockquoteColor>(rareNonInheritedData->matchNearestMailBlockquoteColor); } + const AtomicString& highlight() const { return rareInheritedData->highlight; } + Hyphens hyphens() const { return static_cast<Hyphens>(rareInheritedData->hyphens); } + const AtomicString& hyphenationString() const { return rareInheritedData->hyphenationString; } + const AtomicString& hyphenationLocale() const { return rareInheritedData->hyphenationLocale; } + EBorderFit borderFit() const { return static_cast<EBorderFit>(rareNonInheritedData->m_borderFit); } + EResize resize() const { return static_cast<EResize>(rareInheritedData->resize); } + float columnWidth() const { return rareNonInheritedData->m_multiCol->m_width; } + bool hasAutoColumnWidth() const { return rareNonInheritedData->m_multiCol->m_autoWidth; } + unsigned short columnCount() const { return rareNonInheritedData->m_multiCol->m_count; } + bool hasAutoColumnCount() const { return rareNonInheritedData->m_multiCol->m_autoCount; } + bool specifiesColumns() const { return !hasAutoColumnCount() || !hasAutoColumnWidth(); } + float columnGap() const { return rareNonInheritedData->m_multiCol->m_gap; } + bool hasNormalColumnGap() const { return rareNonInheritedData->m_multiCol->m_normalGap; } + EBorderStyle columnRuleStyle() const { return rareNonInheritedData->m_multiCol->m_rule.style(); } + unsigned short columnRuleWidth() const { return rareNonInheritedData->m_multiCol->ruleWidth(); } + bool columnRuleIsTransparent() const { return rareNonInheritedData->m_multiCol->m_rule.isTransparent(); } + bool columnSpan() const { return rareNonInheritedData->m_multiCol->m_columnSpan; } + EPageBreak columnBreakBefore() const { return static_cast<EPageBreak>(rareNonInheritedData->m_multiCol->m_breakBefore); } + EPageBreak columnBreakInside() const { return static_cast<EPageBreak>(rareNonInheritedData->m_multiCol->m_breakInside); } + EPageBreak columnBreakAfter() const { return static_cast<EPageBreak>(rareNonInheritedData->m_multiCol->m_breakAfter); } + const TransformOperations& transform() const { return rareNonInheritedData->m_transform->m_operations; } + Length transformOriginX() const { return rareNonInheritedData->m_transform->m_x; } + Length transformOriginY() const { return rareNonInheritedData->m_transform->m_y; } + float transformOriginZ() const { return rareNonInheritedData->m_transform->m_z; } + bool hasTransform() const { return !rareNonInheritedData->m_transform->m_operations.operations().isEmpty(); } + + TextEmphasisFill textEmphasisFill() const { return static_cast<TextEmphasisFill>(rareInheritedData->textEmphasisFill); } + TextEmphasisMark textEmphasisMark() const; + const AtomicString& textEmphasisCustomMark() const { return rareInheritedData->textEmphasisCustomMark; } + TextEmphasisPosition textEmphasisPosition() const { return static_cast<TextEmphasisPosition>(rareInheritedData->textEmphasisPosition); } + const AtomicString& textEmphasisMarkString() const; + + // Return true if any transform related property (currently transform, transformStyle3D or perspective) + // indicates that we are transforming + bool hasTransformRelatedProperty() const { return hasTransform() || preserves3D() || hasPerspective(); } + + enum ApplyTransformOrigin { IncludeTransformOrigin, ExcludeTransformOrigin }; + void applyTransform(TransformationMatrix&, const IntSize& borderBoxSize, ApplyTransformOrigin = IncludeTransformOrigin) const; + void setPageScaleTransform(float); + + bool hasMask() const { return rareNonInheritedData->m_mask.hasImage() || rareNonInheritedData->m_maskBoxImage.hasImage(); } + + TextCombine textCombine() const { return static_cast<TextCombine>(rareNonInheritedData->m_textCombine); } + // End CSS3 Getters + + // Apple-specific property getter methods + EPointerEvents pointerEvents() const { return static_cast<EPointerEvents>(inherited_flags._pointerEvents); } + const AnimationList* animations() const { return rareNonInheritedData->m_animations.get(); } + const AnimationList* transitions() const { return rareNonInheritedData->m_transitions.get(); } + + AnimationList* accessAnimations(); + AnimationList* accessTransitions(); + + bool hasAnimations() const { return rareNonInheritedData->m_animations && rareNonInheritedData->m_animations->size() > 0; } + bool hasTransitions() const { return rareNonInheritedData->m_transitions && rareNonInheritedData->m_transitions->size() > 0; } + + // return the first found Animation (including 'all' transitions) + const Animation* transitionForProperty(int property) const; + + ETransformStyle3D transformStyle3D() const { return rareNonInheritedData->m_transformStyle3D; } + bool preserves3D() const { return rareNonInheritedData->m_transformStyle3D == TransformStyle3DPreserve3D; } + + EBackfaceVisibility backfaceVisibility() const { return rareNonInheritedData->m_backfaceVisibility; } + float perspective() const { return rareNonInheritedData->m_perspective; } + bool hasPerspective() const { return rareNonInheritedData->m_perspective > 0; } + Length perspectiveOriginX() const { return rareNonInheritedData->m_perspectiveOriginX; } + Length perspectiveOriginY() const { return rareNonInheritedData->m_perspectiveOriginY; } + LengthSize pageSize() const { return rareNonInheritedData->m_pageSize; } + PageSizeType pageSizeType() const { return rareNonInheritedData->m_pageSizeType; } + +#if USE(ACCELERATED_COMPOSITING) + // When set, this ensures that styles compare as different. Used during accelerated animations. + bool isRunningAcceleratedAnimation() const { return rareNonInheritedData->m_runningAcceleratedAnimation; } +#endif + + const LineClampValue& lineClamp() const { return rareNonInheritedData->lineClamp; } + bool textSizeAdjust() const { return rareInheritedData->textSizeAdjust; } + ETextSecurity textSecurity() const { return static_cast<ETextSecurity>(rareInheritedData->textSecurity); } + + WritingMode writingMode() const { return static_cast<WritingMode>(inherited_flags.m_writingMode); } + bool isHorizontalWritingMode() const { return writingMode() == TopToBottomWritingMode || writingMode() == BottomToTopWritingMode; } + bool isFlippedLinesWritingMode() const { return writingMode() == LeftToRightWritingMode || writingMode() == BottomToTopWritingMode; } + bool isFlippedBlocksWritingMode() const { return writingMode() == RightToLeftWritingMode || writingMode() == BottomToTopWritingMode; } + + ESpeak speak() { return static_cast<ESpeak>(rareInheritedData->speak); } + +#ifdef ANDROID_CSS_RING + // called when building nav cache to determine if the ring data is unchanged + const void* ringData() const { return reinterpret_cast<const void*>(rareInheritedData.get()); } + Color ringFillColor() const { return rareInheritedData->ringFillColor; } + Length ringInnerWidth() const { return rareInheritedData->ringInnerWidth; } + Length ringOuterWidth() const { return rareInheritedData->ringOuterWidth; } + Length ringOutset() const { return rareInheritedData->ringOutset; } + Color ringPressedInnerColor() const { return rareInheritedData->ringPressedInnerColor; } + Color ringPressedOuterColor() const { return rareInheritedData->ringPressedOuterColor; } + Length ringRadius() const { return rareInheritedData->ringRadius; } + Color ringSelectedInnerColor() const { return rareInheritedData->ringSelectedInnerColor; } + Color ringSelectedOuterColor() const { return rareInheritedData->ringSelectedOuterColor; } +#endif +#ifdef ANDROID_CSS_TAP_HIGHLIGHT_COLOR + Color tapHighlightColor() const { return rareInheritedData->tapHighlightColor; } +#endif +// attribute setter methods + + void setDisplay(EDisplay v) { noninherited_flags._effectiveDisplay = v; } + void setOriginalDisplay(EDisplay v) { noninherited_flags._originalDisplay = v; } + void setPosition(EPosition v) { noninherited_flags._position = v; } + void setFloating(EFloat v) { noninherited_flags._floating = v; } + + void setLeft(Length v) { SET_VAR(surround, offset.m_left, v) } + void setRight(Length v) { SET_VAR(surround, offset.m_right, v) } + void setTop(Length v) { SET_VAR(surround, offset.m_top, v) } + void setBottom(Length v) { SET_VAR(surround, offset.m_bottom, v) } + + void setWidth(Length v) { SET_VAR(m_box, m_width, v) } + void setHeight(Length v) { SET_VAR(m_box, m_height, v) } + + void setMinWidth(Length v) { SET_VAR(m_box, m_minWidth, v) } + void setMaxWidth(Length v) { SET_VAR(m_box, m_maxWidth, v) } + void setMinHeight(Length v) { SET_VAR(m_box, m_minHeight, v) } + void setMaxHeight(Length v) { SET_VAR(m_box, m_maxHeight, v) } + +#if ENABLE(DASHBOARD_SUPPORT) + Vector<StyleDashboardRegion> dashboardRegions() const { return rareNonInheritedData->m_dashboardRegions; } + void setDashboardRegions(Vector<StyleDashboardRegion> regions) { SET_VAR(rareNonInheritedData, m_dashboardRegions, regions); } + + void setDashboardRegion(int type, const String& label, Length t, Length r, Length b, Length l, bool append) + { + StyleDashboardRegion region; + region.label = label; + region.offset.m_top = t; + region.offset.m_right = r; + region.offset.m_bottom = b; + region.offset.m_left = l; + region.type = type; + if (!append) + rareNonInheritedData.access()->m_dashboardRegions.clear(); + rareNonInheritedData.access()->m_dashboardRegions.append(region); + } +#endif + + void resetBorder() { resetBorderImage(); resetBorderTop(); resetBorderRight(); resetBorderBottom(); resetBorderLeft(); resetBorderRadius(); } + void resetBorderTop() { SET_VAR(surround, border.m_top, BorderValue()) } + void resetBorderRight() { SET_VAR(surround, border.m_right, BorderValue()) } + void resetBorderBottom() { SET_VAR(surround, border.m_bottom, BorderValue()) } + void resetBorderLeft() { SET_VAR(surround, border.m_left, BorderValue()) } + void resetBorderImage() { SET_VAR(surround, border.m_image, NinePieceImage()) } + void resetBorderRadius() { resetBorderTopLeftRadius(); resetBorderTopRightRadius(); resetBorderBottomLeftRadius(); resetBorderBottomRightRadius(); } + void resetBorderTopLeftRadius() { SET_VAR(surround, border.m_topLeft, initialBorderRadius()) } + void resetBorderTopRightRadius() { SET_VAR(surround, border.m_topRight, initialBorderRadius()) } + void resetBorderBottomLeftRadius() { SET_VAR(surround, border.m_bottomLeft, initialBorderRadius()) } + void resetBorderBottomRightRadius() { SET_VAR(surround, border.m_bottomRight, initialBorderRadius()) } + + void resetOutline() { SET_VAR(m_background, m_outline, OutlineValue()) } + + void setBackgroundColor(const Color& v) { SET_VAR(m_background, m_color, v) } + + void setBackgroundXPosition(Length l) { SET_VAR(m_background, m_background.m_xPosition, l) } + void setBackgroundYPosition(Length l) { SET_VAR(m_background, m_background.m_yPosition, l) } + void setBackgroundSize(EFillSizeType b) { SET_VAR(m_background, m_background.m_sizeType, b) } + void setBackgroundSizeLength(LengthSize l) { SET_VAR(m_background, m_background.m_sizeLength, l) } + + void setBorderImage(const NinePieceImage& b) { SET_VAR(surround, border.m_image, b) } + + void setBorderTopLeftRadius(const LengthSize& s) { SET_VAR(surround, border.m_topLeft, s) } + void setBorderTopRightRadius(const LengthSize& s) { SET_VAR(surround, border.m_topRight, s) } + void setBorderBottomLeftRadius(const LengthSize& s) { SET_VAR(surround, border.m_bottomLeft, s) } + void setBorderBottomRightRadius(const LengthSize& s) { SET_VAR(surround, border.m_bottomRight, s) } + + void setBorderRadius(const LengthSize& s) + { + setBorderTopLeftRadius(s); + setBorderTopRightRadius(s); + setBorderBottomLeftRadius(s); + setBorderBottomRightRadius(s); + } + void setBorderRadius(const IntSize& s) + { + setBorderRadius(LengthSize(Length(s.width(), Fixed), Length(s.height(), Fixed))); + } + + + void getBorderRadiiForRect(const IntRect&, IntSize& topLeft, IntSize& topRight, IntSize& bottomLeft, IntSize& bottomRight) const; + void getInnerBorderRadiiForRectWithBorderWidths(const IntRect&, unsigned short topWidth, + unsigned short bottomWidth, unsigned short leftWidth, unsigned short rightWidth, + IntSize& innerTopLeft, IntSize& innerTopRight, IntSize& innerBottomLeft, + IntSize& innerBottomRight) const; + + void setBorderLeftWidth(unsigned short v) { SET_VAR(surround, border.m_left.m_width, v) } + void setBorderLeftStyle(EBorderStyle v) { SET_VAR(surround, border.m_left.m_style, v) } + void setBorderLeftColor(const Color& v) { SET_VAR(surround, border.m_left.m_color, v) } + void setBorderRightWidth(unsigned short v) { SET_VAR(surround, border.m_right.m_width, v) } + void setBorderRightStyle(EBorderStyle v) { SET_VAR(surround, border.m_right.m_style, v) } + void setBorderRightColor(const Color& v) { SET_VAR(surround, border.m_right.m_color, v) } + void setBorderTopWidth(unsigned short v) { SET_VAR(surround, border.m_top.m_width, v) } + void setBorderTopStyle(EBorderStyle v) { SET_VAR(surround, border.m_top.m_style, v) } + void setBorderTopColor(const Color& v) { SET_VAR(surround, border.m_top.m_color, v) } + void setBorderBottomWidth(unsigned short v) { SET_VAR(surround, border.m_bottom.m_width, v) } + void setBorderBottomStyle(EBorderStyle v) { SET_VAR(surround, border.m_bottom.m_style, v) } + void setBorderBottomColor(const Color& v) { SET_VAR(surround, border.m_bottom.m_color, v) } + void setOutlineWidth(unsigned short v) { SET_VAR(m_background, m_outline.m_width, v) } + + void setOutlineStyle(EBorderStyle v, bool isAuto = false) + { + SET_VAR(m_background, m_outline.m_style, v) + SET_VAR(m_background, m_outline.m_isAuto, isAuto) + } + + void setOutlineColor(const Color& v) { SET_VAR(m_background, m_outline.m_color, v) } + + void setOverflowX(EOverflow v) { noninherited_flags._overflowX = v; } + void setOverflowY(EOverflow v) { noninherited_flags._overflowY = v; } + void setVisibility(EVisibility v) { inherited_flags._visibility = v; } + void setVerticalAlign(EVerticalAlign v) { noninherited_flags._vertical_align = v; } + void setVerticalAlignLength(Length l) { SET_VAR(m_box, m_verticalAlign, l) } + + void setHasClip(bool b = true) { SET_VAR(visual, hasClip, b) } + void setClipLeft(Length v) { SET_VAR(visual, clip.m_left, v) } + void setClipRight(Length v) { SET_VAR(visual, clip.m_right, v) } + void setClipTop(Length v) { SET_VAR(visual, clip.m_top, v) } + void setClipBottom(Length v) { SET_VAR(visual, clip.m_bottom, v) } + void setClip(Length top, Length right, Length bottom, Length left); + void setClip(LengthBox box) { SET_VAR(visual, clip, box) } + + void setUnicodeBidi(EUnicodeBidi b) { noninherited_flags._unicodeBidi = b; } + + void setClear(EClear v) { noninherited_flags._clear = v; } + void setTableLayout(ETableLayout v) { noninherited_flags._table_layout = v; } + + bool setFontDescription(const FontDescription& v) + { + if (inherited->font.fontDescription() != v) { + inherited.access()->font = Font(v, inherited->font.letterSpacing(), inherited->font.wordSpacing()); + return true; + } + return false; + } + + // Only used for blending font sizes when animating. + void setBlendedFontSize(int); + + void setColor(const Color& v) { SET_VAR(inherited, color, v) } + void setTextIndent(Length v) { SET_VAR(rareInheritedData, indent, v) } + void setTextAlign(ETextAlign v) { inherited_flags._text_align = v; } + void setTextTransform(ETextTransform v) { inherited_flags._text_transform = v; } + void addToTextDecorationsInEffect(int v) { inherited_flags._text_decorations |= v; } + void setTextDecorationsInEffect(int v) { inherited_flags._text_decorations = v; } + void setTextDecoration(int v) { SET_VAR(visual, textDecoration, v); } + void setDirection(TextDirection v) { inherited_flags._direction = v; } + void setLineHeight(Length v) { SET_VAR(inherited, line_height, v) } + void setZoom(float f) { SET_VAR(visual, m_zoom, f); setEffectiveZoom(effectiveZoom() * zoom()); } + void setEffectiveZoom(float f) { SET_VAR(rareInheritedData, m_effectiveZoom, f) } + + void setWhiteSpace(EWhiteSpace v) { inherited_flags._white_space = v; } + + void setWordSpacing(int v) { inherited.access()->font.setWordSpacing(v); } + void setLetterSpacing(int v) { inherited.access()->font.setLetterSpacing(v); } + + void clearBackgroundLayers() { m_background.access()->m_background = FillLayer(BackgroundFillLayer); } + void inheritBackgroundLayers(const FillLayer& parent) { m_background.access()->m_background = parent; } + + void adjustBackgroundLayers() + { + if (backgroundLayers()->next()) { + accessBackgroundLayers()->cullEmptyLayers(); + accessBackgroundLayers()->fillUnsetProperties(); + } + } + + void clearMaskLayers() { rareNonInheritedData.access()->m_mask = FillLayer(MaskFillLayer); } + void inheritMaskLayers(const FillLayer& parent) { rareNonInheritedData.access()->m_mask = parent; } + + void adjustMaskLayers() + { + if (maskLayers()->next()) { + accessMaskLayers()->cullEmptyLayers(); + accessMaskLayers()->fillUnsetProperties(); + } + } + + void setMaskBoxImage(const NinePieceImage& b) { SET_VAR(rareNonInheritedData, m_maskBoxImage, b) } + void setMaskXPosition(Length l) { SET_VAR(rareNonInheritedData, m_mask.m_xPosition, l) } + void setMaskYPosition(Length l) { SET_VAR(rareNonInheritedData, m_mask.m_yPosition, l) } + void setMaskSize(LengthSize l) { SET_VAR(rareNonInheritedData, m_mask.m_sizeLength, l) } + + void setBorderCollapse(bool collapse) { inherited_flags._border_collapse = collapse; } + void setHorizontalBorderSpacing(short v) { SET_VAR(inherited, horizontal_border_spacing, v) } + void setVerticalBorderSpacing(short v) { SET_VAR(inherited, vertical_border_spacing, v) } + void setEmptyCells(EEmptyCell v) { inherited_flags._empty_cells = v; } + void setCaptionSide(ECaptionSide v) { inherited_flags._caption_side = v; } + + void setCounterIncrement(short v) { SET_VAR(rareNonInheritedData, m_counterIncrement, v) } + void setCounterReset(short v) { SET_VAR(rareNonInheritedData, m_counterReset, v) } + + void setListStyleType(EListStyleType v) { inherited_flags._list_style_type = v; } + void setListStyleImage(PassRefPtr<StyleImage> v) { if (inherited->list_style_image != v) inherited.access()->list_style_image = v; } + void setListStylePosition(EListStylePosition v) { inherited_flags._list_style_position = v; } + + void resetMargin() { SET_VAR(surround, margin, LengthBox(Fixed)) } + void setMarginTop(Length v) { SET_VAR(surround, margin.m_top, v) } + void setMarginBottom(Length v) { SET_VAR(surround, margin.m_bottom, v) } + void setMarginLeft(Length v) { SET_VAR(surround, margin.m_left, v) } + void setMarginRight(Length v) { SET_VAR(surround, margin.m_right, v) } + void setMarginStart(Length); + void setMarginEnd(Length); + + void resetPadding() { SET_VAR(surround, padding, LengthBox(Auto)) } + void setPaddingBox(const LengthBox& b) { SET_VAR(surround, padding, b) } + void setPaddingTop(Length v) { SET_VAR(surround, padding.m_top, v) } + void setPaddingBottom(Length v) { SET_VAR(surround, padding.m_bottom, v) } + void setPaddingLeft(Length v) { SET_VAR(surround, padding.m_left, v) } + void setPaddingRight(Length v) { SET_VAR(surround, padding.m_right, v) } + + void setCursor(ECursor c) { inherited_flags._cursor_style = c; } + void addCursor(PassRefPtr<StyleImage>, const IntPoint& hotSpot = IntPoint()); + void setCursorList(PassRefPtr<CursorList>); + void clearCursorList(); + + void setInsideLink(EInsideLink insideLink) { inherited_flags._insideLink = insideLink; } + void setIsLink(bool b) { noninherited_flags._isLink = b; } + + bool forceBackgroundsToWhite() const { return inherited_flags._force_backgrounds_to_white; } + void setForceBackgroundsToWhite(bool b=true) { inherited_flags._force_backgrounds_to_white = b; } + + bool hasAutoZIndex() const { return m_box->hasAutoZIndex(); } + void setHasAutoZIndex() { SET_VAR(m_box, m_hasAutoZIndex, true); SET_VAR(m_box, m_zIndex, 0) } + int zIndex() const { return m_box->zIndex(); } + void setZIndex(int v) { SET_VAR(m_box, m_hasAutoZIndex, false); SET_VAR(m_box, m_zIndex, v) } + + void setWidows(short w) { SET_VAR(rareInheritedData, widows, w); } + void setOrphans(short o) { SET_VAR(rareInheritedData, orphans, o); } + void setPageBreakInside(EPageBreak b) { noninherited_flags._page_break_inside = b; } + void setPageBreakBefore(EPageBreak b) { noninherited_flags._page_break_before = b; } + void setPageBreakAfter(EPageBreak b) { noninherited_flags._page_break_after = b; } + + // CSS3 Setters + void setOutlineOffset(int v) { SET_VAR(m_background, m_outline.m_offset, v) } + void setTextShadow(ShadowData* val, bool add=false); + void setTextStrokeColor(const Color& c) { SET_VAR(rareInheritedData, textStrokeColor, c) } + void setTextStrokeWidth(float w) { SET_VAR(rareInheritedData, textStrokeWidth, w) } + void setTextFillColor(const Color& c) { SET_VAR(rareInheritedData, textFillColor, c) } + void setColorSpace(ColorSpace space) { SET_VAR(rareInheritedData, colorSpace, space) } + void setOpacity(float f) { SET_VAR(rareNonInheritedData, opacity, f); } + void setAppearance(ControlPart a) { SET_VAR(rareNonInheritedData, m_appearance, a); } + void setBoxAlign(EBoxAlignment a) { SET_VAR(rareNonInheritedData.access()->flexibleBox, align, a); } + void setBoxDirection(EBoxDirection d) { inherited_flags._box_direction = d; } + void setBoxFlex(float f) { SET_VAR(rareNonInheritedData.access()->flexibleBox, flex, f); } + void setBoxFlexGroup(unsigned int fg) { SET_VAR(rareNonInheritedData.access()->flexibleBox, flex_group, fg); } + void setBoxLines(EBoxLines l) { SET_VAR(rareNonInheritedData.access()->flexibleBox, lines, l); } + void setBoxOrdinalGroup(unsigned int og) { SET_VAR(rareNonInheritedData.access()->flexibleBox, ordinal_group, og); } + void setBoxOrient(EBoxOrient o) { SET_VAR(rareNonInheritedData.access()->flexibleBox, orient, o); } + void setBoxPack(EBoxAlignment p) { SET_VAR(rareNonInheritedData.access()->flexibleBox, pack, p); } + void setBoxShadow(ShadowData* val, bool add=false); + void setBoxReflect(PassRefPtr<StyleReflection> reflect) { if (rareNonInheritedData->m_boxReflect != reflect) rareNonInheritedData.access()->m_boxReflect = reflect; } + void setBoxSizing(EBoxSizing s) { SET_VAR(m_box, m_boxSizing, s); } + void setMarqueeIncrement(const Length& f) { SET_VAR(rareNonInheritedData.access()->marquee, increment, f); } + void setMarqueeSpeed(int f) { SET_VAR(rareNonInheritedData.access()->marquee, speed, f); } + void setMarqueeDirection(EMarqueeDirection d) { SET_VAR(rareNonInheritedData.access()->marquee, direction, d); } + void setMarqueeBehavior(EMarqueeBehavior b) { SET_VAR(rareNonInheritedData.access()->marquee, behavior, b); } + void setMarqueeLoopCount(int i) { SET_VAR(rareNonInheritedData.access()->marquee, loops, i); } + void setUserModify(EUserModify u) { SET_VAR(rareInheritedData, userModify, u); } + void setUserDrag(EUserDrag d) { SET_VAR(rareNonInheritedData, userDrag, d); } + void setUserSelect(EUserSelect s) { SET_VAR(rareInheritedData, userSelect, s); } + void setTextOverflow(bool b) { SET_VAR(rareNonInheritedData, textOverflow, b); } + void setMarginBeforeCollapse(EMarginCollapse c) { SET_VAR(rareNonInheritedData, marginBeforeCollapse, c); } + void setMarginAfterCollapse(EMarginCollapse c) { SET_VAR(rareNonInheritedData, marginAfterCollapse, c); } + void setWordBreak(EWordBreak b) { SET_VAR(rareInheritedData, wordBreak, b); } + void setWordWrap(EWordWrap b) { SET_VAR(rareInheritedData, wordWrap, b); } + void setNBSPMode(ENBSPMode b) { SET_VAR(rareInheritedData, nbspMode, b); } + void setKHTMLLineBreak(EKHTMLLineBreak b) { SET_VAR(rareInheritedData, khtmlLineBreak, b); } + void setMatchNearestMailBlockquoteColor(EMatchNearestMailBlockquoteColor c) { SET_VAR(rareNonInheritedData, matchNearestMailBlockquoteColor, c); } + void setHighlight(const AtomicString& h) { SET_VAR(rareInheritedData, highlight, h); } + void setHyphens(Hyphens h) { SET_VAR(rareInheritedData, hyphens, h); } + void setHyphenationString(const AtomicString& h) { SET_VAR(rareInheritedData, hyphenationString, h); } + void setHyphenationLocale(const AtomicString& h) { SET_VAR(rareInheritedData, hyphenationLocale, h); } + void setBorderFit(EBorderFit b) { SET_VAR(rareNonInheritedData, m_borderFit, b); } + void setResize(EResize r) { SET_VAR(rareInheritedData, resize, r); } + void setColumnWidth(float f) { SET_VAR(rareNonInheritedData.access()->m_multiCol, m_autoWidth, false); SET_VAR(rareNonInheritedData.access()->m_multiCol, m_width, f); } + void setHasAutoColumnWidth() { SET_VAR(rareNonInheritedData.access()->m_multiCol, m_autoWidth, true); SET_VAR(rareNonInheritedData.access()->m_multiCol, m_width, 0); } + void setColumnCount(unsigned short c) { SET_VAR(rareNonInheritedData.access()->m_multiCol, m_autoCount, false); SET_VAR(rareNonInheritedData.access()->m_multiCol, m_count, c); } + void setHasAutoColumnCount() { SET_VAR(rareNonInheritedData.access()->m_multiCol, m_autoCount, true); SET_VAR(rareNonInheritedData.access()->m_multiCol, m_count, 0); } + void setColumnGap(float f) { SET_VAR(rareNonInheritedData.access()->m_multiCol, m_normalGap, false); SET_VAR(rareNonInheritedData.access()->m_multiCol, m_gap, f); } + void setHasNormalColumnGap() { SET_VAR(rareNonInheritedData.access()->m_multiCol, m_normalGap, true); SET_VAR(rareNonInheritedData.access()->m_multiCol, m_gap, 0); } + void setColumnRuleColor(const Color& c) { SET_VAR(rareNonInheritedData.access()->m_multiCol, m_rule.m_color, c); } + void setColumnRuleStyle(EBorderStyle b) { SET_VAR(rareNonInheritedData.access()->m_multiCol, m_rule.m_style, b); } + void setColumnRuleWidth(unsigned short w) { SET_VAR(rareNonInheritedData.access()->m_multiCol, m_rule.m_width, w); } + void resetColumnRule() { SET_VAR(rareNonInheritedData.access()->m_multiCol, m_rule, BorderValue()) } + void setColumnSpan(bool b) { SET_VAR(rareNonInheritedData.access()->m_multiCol, m_columnSpan, b); } + void setColumnBreakBefore(EPageBreak p) { SET_VAR(rareNonInheritedData.access()->m_multiCol, m_breakBefore, p); } + void setColumnBreakInside(EPageBreak p) { SET_VAR(rareNonInheritedData.access()->m_multiCol, m_breakInside, p); } + void setColumnBreakAfter(EPageBreak p) { SET_VAR(rareNonInheritedData.access()->m_multiCol, m_breakAfter, p); } + void inheritColumnPropertiesFrom(RenderStyle* parent) { rareNonInheritedData.access()->m_multiCol = parent->rareNonInheritedData->m_multiCol; } + void setTransform(const TransformOperations& ops) { SET_VAR(rareNonInheritedData.access()->m_transform, m_operations, ops); } + void setTransformOriginX(Length l) { SET_VAR(rareNonInheritedData.access()->m_transform, m_x, l); } + void setTransformOriginY(Length l) { SET_VAR(rareNonInheritedData.access()->m_transform, m_y, l); } + void setTransformOriginZ(float f) { SET_VAR(rareNonInheritedData.access()->m_transform, m_z, f); } + void setSpeak(ESpeak s) { SET_VAR(rareInheritedData, speak, s); } + void setTextCombine(TextCombine v) { SET_VAR(rareNonInheritedData, m_textCombine, v); } + void setTextEmphasisColor(const Color& c) { SET_VAR(rareInheritedData, textEmphasisColor, c) } + void setTextEmphasisFill(TextEmphasisFill fill) { SET_VAR(rareInheritedData, textEmphasisFill, fill); } + void setTextEmphasisMark(TextEmphasisMark mark) { SET_VAR(rareInheritedData, textEmphasisMark, mark); } + void setTextEmphasisCustomMark(const AtomicString& mark) { SET_VAR(rareInheritedData, textEmphasisCustomMark, mark); } + void setTextEmphasisPosition(TextEmphasisPosition position) { SET_VAR(rareInheritedData, textEmphasisPosition, position); } + // End CSS3 Setters + + // Apple-specific property setters + void setPointerEvents(EPointerEvents p) { inherited_flags._pointerEvents = p; } + + void clearAnimations() + { + rareNonInheritedData.access()->m_animations.clear(); + } + + void clearTransitions() + { + rareNonInheritedData.access()->m_transitions.clear(); + } + + void inheritAnimations(const AnimationList* parent) { rareNonInheritedData.access()->m_animations = parent ? adoptPtr(new AnimationList(*parent)) : PassOwnPtr<AnimationList>(); } + void inheritTransitions(const AnimationList* parent) { rareNonInheritedData.access()->m_transitions = parent ? adoptPtr(new AnimationList(*parent)) : PassOwnPtr<AnimationList>(); } + void adjustAnimations(); + void adjustTransitions(); + + void setTransformStyle3D(ETransformStyle3D b) { SET_VAR(rareNonInheritedData, m_transformStyle3D, b); } + void setBackfaceVisibility(EBackfaceVisibility b) { SET_VAR(rareNonInheritedData, m_backfaceVisibility, b); } + void setPerspective(float p) { SET_VAR(rareNonInheritedData, m_perspective, p); } + void setPerspectiveOriginX(Length l) { SET_VAR(rareNonInheritedData, m_perspectiveOriginX, l); } + void setPerspectiveOriginY(Length l) { SET_VAR(rareNonInheritedData, m_perspectiveOriginY, l); } + void setPageSize(LengthSize s) { SET_VAR(rareNonInheritedData, m_pageSize, s); } + void setPageSizeType(PageSizeType t) { SET_VAR(rareNonInheritedData, m_pageSizeType, t); } + void resetPageSizeType() { SET_VAR(rareNonInheritedData, m_pageSizeType, PAGE_SIZE_AUTO); } + +#if USE(ACCELERATED_COMPOSITING) + void setIsRunningAcceleratedAnimation(bool b = true) { SET_VAR(rareNonInheritedData, m_runningAcceleratedAnimation, b); } +#endif + + void setLineClamp(LineClampValue c) { SET_VAR(rareNonInheritedData, lineClamp, c); } + void setTextSizeAdjust(bool b) { SET_VAR(rareInheritedData, textSizeAdjust, b); } + void setTextSecurity(ETextSecurity aTextSecurity) { SET_VAR(rareInheritedData, textSecurity, aTextSecurity); } + +#ifdef ANDROID_CSS_RING + void setRingFillColor(const Color& v) { SET_VAR(rareInheritedData, ringFillColor, v); } + void setRingInnerWidth(Length v) { SET_VAR(rareInheritedData, ringInnerWidth, v); } + void setRingOuterWidth(Length v) { SET_VAR(rareInheritedData, ringOuterWidth, v); } + void setRingOutset(Length v) { SET_VAR(rareInheritedData, ringOutset, v); } + void setRingPressedInnerColor(const Color& v) { + SET_VAR(rareInheritedData, ringPressedInnerColor, v); } + void setRingPressedOuterColor(const Color& v) { + SET_VAR(rareInheritedData, ringPressedOuterColor, v); } + void setRingRadius(Length v) { SET_VAR(rareInheritedData, ringRadius, v); } + void setRingSelectedInnerColor(const Color& v) { + SET_VAR(rareInheritedData, ringSelectedInnerColor, v); } + void setRingSelectedOuterColor(const Color& v) { + SET_VAR(rareInheritedData, ringSelectedOuterColor, v); } +#endif +#ifdef ANDROID_CSS_TAP_HIGHLIGHT_COLOR + void setTapHighlightColor(const Color& v) { SET_VAR(rareInheritedData, tapHighlightColor, v); } +#endif + +#if ENABLE(SVG) + const SVGRenderStyle* svgStyle() const { return m_svgStyle.get(); } + SVGRenderStyle* accessSVGStyle() { return m_svgStyle.access(); } + + float fillOpacity() const { return svgStyle()->fillOpacity(); } + void setFillOpacity(float f) { accessSVGStyle()->setFillOpacity(f); } + + float strokeOpacity() const { return svgStyle()->strokeOpacity(); } + void setStrokeOpacity(float f) { accessSVGStyle()->setStrokeOpacity(f); } + + float floodOpacity() const { return svgStyle()->floodOpacity(); } + void setFloodOpacity(float f) { accessSVGStyle()->setFloodOpacity(f); } +#endif + + const ContentData* contentData() const { return rareNonInheritedData->m_content.get(); } + bool contentDataEquivalent(const RenderStyle* otherStyle) const { return const_cast<RenderStyle*>(this)->rareNonInheritedData->contentDataEquivalent(*const_cast<RenderStyle*>(otherStyle)->rareNonInheritedData); } + void clearContent(); + void setContent(PassRefPtr<StringImpl>, bool add = false); + void setContent(PassRefPtr<StyleImage>, bool add = false); + void setContent(PassOwnPtr<CounterContent>, bool add = false); + + const CounterDirectiveMap* counterDirectives() const; + CounterDirectiveMap& accessCounterDirectives(); + + const AtomicString& hyphenString() const; + + bool inheritedNotEqual(const RenderStyle*) const; + + StyleDifference diff(const RenderStyle*, unsigned& changedContextSensitiveProperties) const; + + bool isDisplayReplacedType() const + { + return display() == INLINE_BLOCK || display() == INLINE_BOX || display() == INLINE_TABLE; + } + + bool isDisplayInlineType() const + { + return display() == INLINE || isDisplayReplacedType(); + } + + bool isOriginalDisplayInlineType() const + { + return originalDisplay() == INLINE || originalDisplay() == INLINE_BLOCK || + originalDisplay() == INLINE_BOX || originalDisplay() == INLINE_TABLE; + } + + void setWritingMode(WritingMode v) { inherited_flags.m_writingMode = v; } + + // To tell if this style matched attribute selectors. This makes it impossible to share. + bool affectedByAttributeSelectors() const { return m_affectedByAttributeSelectors; } + void setAffectedByAttributeSelectors() { m_affectedByAttributeSelectors = true; } + + bool unique() const { return m_unique; } + void setUnique() { m_unique = true; } + + // Methods for indicating the style is affected by dynamic updates (e.g., children changing, our position changing in our sibling list, etc.) + bool affectedByEmpty() const { return m_affectedByEmpty; } + bool emptyState() const { return m_emptyState; } + void setEmptyState(bool b) { m_affectedByEmpty = true; m_unique = true; m_emptyState = b; } + bool childrenAffectedByPositionalRules() const { return childrenAffectedByForwardPositionalRules() || childrenAffectedByBackwardPositionalRules(); } + bool childrenAffectedByFirstChildRules() const { return m_childrenAffectedByFirstChildRules; } + void setChildrenAffectedByFirstChildRules() { m_childrenAffectedByFirstChildRules = true; } + bool childrenAffectedByLastChildRules() const { return m_childrenAffectedByLastChildRules; } + void setChildrenAffectedByLastChildRules() { m_childrenAffectedByLastChildRules = true; } + bool childrenAffectedByDirectAdjacentRules() const { return m_childrenAffectedByDirectAdjacentRules; } + void setChildrenAffectedByDirectAdjacentRules() { m_childrenAffectedByDirectAdjacentRules = true; } + bool childrenAffectedByForwardPositionalRules() const { return m_childrenAffectedByForwardPositionalRules; } + void setChildrenAffectedByForwardPositionalRules() { m_childrenAffectedByForwardPositionalRules = true; } + bool childrenAffectedByBackwardPositionalRules() const { return m_childrenAffectedByBackwardPositionalRules; } + void setChildrenAffectedByBackwardPositionalRules() { m_childrenAffectedByBackwardPositionalRules = true; } + bool firstChildState() const { return m_firstChildState; } + void setFirstChildState() { m_firstChildState = true; } + bool lastChildState() const { return m_lastChildState; } + void setLastChildState() { m_lastChildState = true; } + unsigned childIndex() const { return m_childIndex; } + void setChildIndex(unsigned index) { m_childIndex = index; } + + const Color visitedDependentColor(int colorProperty) const; + + // Initial values for all the properties + static bool initialBorderCollapse() { return false; } + static EBorderStyle initialBorderStyle() { return BNONE; } + static NinePieceImage initialNinePieceImage() { return NinePieceImage(); } + static LengthSize initialBorderRadius() { return LengthSize(Length(0, Fixed), Length(0, Fixed)); } + static ECaptionSide initialCaptionSide() { return CAPTOP; } + static EClear initialClear() { return CNONE; } + static TextDirection initialDirection() { return LTR; } + static WritingMode initialWritingMode() { return TopToBottomWritingMode; } + static TextCombine initialTextCombine() { return TextCombineNone; } + static EDisplay initialDisplay() { return INLINE; } + static EEmptyCell initialEmptyCells() { return SHOW; } + static EFloat initialFloating() { return FNONE; } + static EListStylePosition initialListStylePosition() { return OUTSIDE; } + static EListStyleType initialListStyleType() { return Disc; } + static EOverflow initialOverflowX() { return OVISIBLE; } + static EOverflow initialOverflowY() { return OVISIBLE; } + static EPageBreak initialPageBreak() { return PBAUTO; } + static EPosition initialPosition() { return StaticPosition; } + static ETableLayout initialTableLayout() { return TAUTO; } + static EUnicodeBidi initialUnicodeBidi() { return UBNormal; } + static ETextTransform initialTextTransform() { return TTNONE; } + static EVisibility initialVisibility() { return VISIBLE; } + static EWhiteSpace initialWhiteSpace() { return NORMAL; } + static short initialHorizontalBorderSpacing() { return 0; } + static short initialVerticalBorderSpacing() { return 0; } + static ECursor initialCursor() { return CURSOR_AUTO; } + static Color initialColor() { return Color::black; } + static StyleImage* initialListStyleImage() { return 0; } + static unsigned short initialBorderWidth() { return 3; } + static int initialLetterWordSpacing() { return 0; } + static Length initialSize() { return Length(); } + static Length initialMinSize() { return Length(0, Fixed); } + static Length initialMaxSize() { return Length(undefinedLength, Fixed); } + static Length initialOffset() { return Length(); } + static Length initialMargin() { return Length(Fixed); } + static Length initialPadding() { return Length(Fixed); } + static Length initialTextIndent() { return Length(Fixed); } + static EVerticalAlign initialVerticalAlign() { return BASELINE; } + static int initialWidows() { return 2; } + static int initialOrphans() { return 2; } + static Length initialLineHeight() { return Length(-100.0, Percent); } + static ETextAlign initialTextAlign() { return TAAUTO; } + static ETextDecoration initialTextDecoration() { return TDNONE; } + static float initialZoom() { return 1.0f; } + static int initialOutlineOffset() { return 0; } + static float initialOpacity() { return 1.0f; } + static EBoxAlignment initialBoxAlign() { return BSTRETCH; } + static EBoxDirection initialBoxDirection() { return BNORMAL; } + static EBoxLines initialBoxLines() { return SINGLE; } + static EBoxOrient initialBoxOrient() { return HORIZONTAL; } + static EBoxAlignment initialBoxPack() { return BSTART; } + static float initialBoxFlex() { return 0.0f; } + static int initialBoxFlexGroup() { return 1; } + static int initialBoxOrdinalGroup() { return 1; } + static EBoxSizing initialBoxSizing() { return CONTENT_BOX; } + static StyleReflection* initialBoxReflect() { return 0; } + static int initialMarqueeLoopCount() { return -1; } + static int initialMarqueeSpeed() { return 85; } + static Length initialMarqueeIncrement() { return Length(6, Fixed); } + static EMarqueeBehavior initialMarqueeBehavior() { return MSCROLL; } + static EMarqueeDirection initialMarqueeDirection() { return MAUTO; } + static EUserModify initialUserModify() { return READ_ONLY; } + static EUserDrag initialUserDrag() { return DRAG_AUTO; } + static EUserSelect initialUserSelect() { return SELECT_TEXT; } + static bool initialTextOverflow() { return false; } + static EMarginCollapse initialMarginBeforeCollapse() { return MCOLLAPSE; } + static EMarginCollapse initialMarginAfterCollapse() { return MCOLLAPSE; } + static EWordBreak initialWordBreak() { return NormalWordBreak; } + static EWordWrap initialWordWrap() { return NormalWordWrap; } + static ENBSPMode initialNBSPMode() { return NBNORMAL; } + static EKHTMLLineBreak initialKHTMLLineBreak() { return LBNORMAL; } + static EMatchNearestMailBlockquoteColor initialMatchNearestMailBlockquoteColor() { return BCNORMAL; } + static const AtomicString& initialHighlight() { return nullAtom; } + static ESpeak initialSpeak() { return SpeakNormal; } + static Hyphens initialHyphens() { return HyphensManual; } + static const AtomicString& initialHyphenationString() { return nullAtom; } + static const AtomicString& initialHyphenationLocale() { return nullAtom; } + static EBorderFit initialBorderFit() { return BorderFitBorder; } + static EResize initialResize() { return RESIZE_NONE; } + static ControlPart initialAppearance() { return NoControlPart; } + static bool initialVisuallyOrdered() { return false; } + static float initialTextStrokeWidth() { return 0; } + static unsigned short initialColumnCount() { return 1; } + static bool initialColumnSpan() { return false; } + static const TransformOperations& initialTransform() { DEFINE_STATIC_LOCAL(TransformOperations, ops, ()); return ops; } + static Length initialTransformOriginX() { return Length(50.0, Percent); } + static Length initialTransformOriginY() { return Length(50.0, Percent); } + static EPointerEvents initialPointerEvents() { return PE_AUTO; } + static float initialTransformOriginZ() { return 0; } + static ETransformStyle3D initialTransformStyle3D() { return TransformStyle3DFlat; } + static EBackfaceVisibility initialBackfaceVisibility() { return BackfaceVisibilityVisible; } + static float initialPerspective() { return 0; } + static Length initialPerspectiveOriginX() { return Length(50.0, Percent); } + static Length initialPerspectiveOriginY() { return Length(50.0, Percent); } + static Color initialBackgroundColor() { return Color::transparent; } + static Color initialTextEmphasisColor() { return TextEmphasisFillFilled; } + static TextEmphasisFill initialTextEmphasisFill() { return TextEmphasisFillFilled; } + static TextEmphasisMark initialTextEmphasisMark() { return TextEmphasisMarkNone; } + static const AtomicString& initialTextEmphasisCustomMark() { return nullAtom; } + static TextEmphasisPosition initialTextEmphasisPosition() { return TextEmphasisPositionOver; } + + // Keep these at the end. + static LineClampValue initialLineClamp() { return LineClampValue(); } + static bool initialTextSizeAdjust() { return true; } + static ETextSecurity initialTextSecurity() { return TSNONE; } +#if ENABLE(DASHBOARD_SUPPORT) + static const Vector<StyleDashboardRegion>& initialDashboardRegions(); + static const Vector<StyleDashboardRegion>& noneDashboardRegions(); +#endif + +#ifdef ANDROID_CSS_RING + static Color initialRingFillColor() { return Color::ringFill; } + static Length initialRingInnerWidth() { return Length(16, Fixed); } // 1.0 + static Length initialRingOuterWidth() { return Length(40, Fixed); } // 2.5 + static Length initialRingOutset() { return Length(3, Fixed); } + static Color initialRingSelectedInnerColor() { return Color::ringSelectedInner; } + static Color initialRingSelectedOuterColor() { return Color::ringSelectedOuter; } + static Color initialRingPressedInnerColor() { return Color::ringPressedInner; } + static Color initialRingPressedOuterColor() { return Color::ringPressedOuter; } + static Length initialRingRadius() { return Length(1, Fixed); } +#endif +#ifdef ANDROID_CSS_TAP_HIGHLIGHT_COLOR + static Color initialTapHighlightColor() { return Color::tap; } +#endif + +private: + void getShadowExtent(const ShadowData*, int& top, int& right, int& bottom, int& left) const; + void getShadowHorizontalExtent(const ShadowData*, int& left, int& right) const; + void getShadowVerticalExtent(const ShadowData*, int& top, int& bottom) const; + void getShadowInlineDirectionExtent(const ShadowData* shadow, int& logicalLeft, int& logicalRight) const + { + return isHorizontalWritingMode() ? getShadowHorizontalExtent(shadow, logicalLeft, logicalRight) : getShadowVerticalExtent(shadow, logicalLeft, logicalRight); + } + void getShadowBlockDirectionExtent(const ShadowData* shadow, int& logicalTop, int& logicalBottom) const + { + return isHorizontalWritingMode() ? getShadowVerticalExtent(shadow, logicalTop, logicalBottom) : getShadowHorizontalExtent(shadow, logicalTop, logicalBottom); + } + + // Color accessors are all private to make sure callers use visitedDependentColor instead to access them. + const Color& borderLeftColor() const { return surround->border.left().color(); } + const Color& borderRightColor() const { return surround->border.right().color(); } + const Color& borderTopColor() const { return surround->border.top().color(); } + const Color& borderBottomColor() const { return surround->border.bottom().color(); } + const Color& backgroundColor() const { return m_background->color(); } + const Color& color() const { return inherited->color; } + const Color& columnRuleColor() const { return rareNonInheritedData->m_multiCol->m_rule.color(); } + const Color& outlineColor() const { return m_background->outline().color(); } + const Color& textEmphasisColor() const { return rareInheritedData->textEmphasisColor; } + const Color& textFillColor() const { return rareInheritedData->textFillColor; } + const Color& textStrokeColor() const { return rareInheritedData->textStrokeColor; } + + const Color colorIncludingFallback(int colorProperty, EBorderStyle borderStyle) const; + + ContentData* prepareToSetContent(StringImpl*, bool add); +}; + +inline int adjustForAbsoluteZoom(int value, const RenderStyle* style) +{ + double zoomFactor = style->effectiveZoom(); + if (zoomFactor == 1) + return value; + // Needed because computeLengthInt truncates (rather than rounds) when scaling up. + if (zoomFactor > 1) { + if (value < 0) + value--; + else + value++; + } + + return roundForImpreciseConversion<int, INT_MAX, INT_MIN>(value / zoomFactor); +} + +inline float adjustFloatForAbsoluteZoom(float value, const RenderStyle* style) +{ + return value / style->effectiveZoom(); +} + +} // namespace WebCore + +#endif // RenderStyle_h diff --git a/Source/WebCore/rendering/style/RenderStyleConstants.h b/Source/WebCore/rendering/style/RenderStyleConstants.h new file mode 100644 index 0000000..0839864 --- /dev/null +++ b/Source/WebCore/rendering/style/RenderStyleConstants.h @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.com) + * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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. + * + */ + +#ifndef RenderStyleConstants_h +#define RenderStyleConstants_h + +namespace WebCore { + +/* + * WARNING: + * -------- + * + * The order of the values in the enums have to agree with the order specified + * in CSSValueKeywords.in, otherwise some optimizations in the parser will fail, + * and produce invalid results. + */ + +// The difference between two styles. The following values are used: +// (1) StyleDifferenceEqual - The two styles are identical +// (2) StyleDifferenceRecompositeLayer - The layer needs its position and transform updated, but no repaint +// (3) StyleDifferenceRepaint - The object just needs to be repainted. +// (4) StyleDifferenceRepaintLayer - The layer and its descendant layers needs to be repainted. +// (5) StyleDifferenceLayout - A layout is required. +enum StyleDifference { + StyleDifferenceEqual, +#if USE(ACCELERATED_COMPOSITING) + StyleDifferenceRecompositeLayer, +#endif + StyleDifferenceRepaint, + StyleDifferenceRepaintLayer, + StyleDifferenceLayoutPositionedMovementOnly, + StyleDifferenceLayout +}; + +// When some style properties change, different amounts of work have to be done depending on +// context (e.g. whether the property is changing on an element which has a compositing layer). +// A simple StyleDifference does not provide enough information so we return a bit mask of +// StyleDifferenceContextSensitiveProperties from RenderStyle::diff() too. +enum StyleDifferenceContextSensitiveProperty { + ContextSensitivePropertyNone = 0, + ContextSensitivePropertyTransform = (1 << 0), + ContextSensitivePropertyOpacity = (1 << 1) +}; + +// Static pseudo styles. Dynamic ones are produced on the fly. +enum PseudoId { + // The order must be NOP ID, public IDs, and then internal IDs. + NOPSEUDO, FIRST_LINE, FIRST_LETTER, BEFORE, AFTER, SELECTION, FIRST_LINE_INHERITED, SCROLLBAR, FILE_UPLOAD_BUTTON, INPUT_PLACEHOLDER, + SLIDER_THUMB, SEARCH_CANCEL_BUTTON, SEARCH_DECORATION, SEARCH_RESULTS_DECORATION, SEARCH_RESULTS_BUTTON, MEDIA_CONTROLS_PANEL, + MEDIA_CONTROLS_PLAY_BUTTON, MEDIA_CONTROLS_MUTE_BUTTON, MEDIA_CONTROLS_TIMELINE, MEDIA_CONTROLS_TIMELINE_CONTAINER, + MEDIA_CONTROLS_VOLUME_SLIDER, MEDIA_CONTROLS_VOLUME_SLIDER_CONTAINER, MEDIA_CONTROLS_VOLUME_SLIDER_MUTE_BUTTON, + MEDIA_CONTROLS_CURRENT_TIME_DISPLAY, MEDIA_CONTROLS_TIME_REMAINING_DISPLAY, + MEDIA_CONTROLS_SEEK_BACK_BUTTON, MEDIA_CONTROLS_SEEK_FORWARD_BUTTON, MEDIA_CONTROLS_FULLSCREEN_BUTTON, MEDIA_CONTROLS_REWIND_BUTTON, + MEDIA_CONTROLS_RETURN_TO_REALTIME_BUTTON, MEDIA_CONTROLS_TOGGLE_CLOSED_CAPTIONS_BUTTON, + MEDIA_CONTROLS_STATUS_DISPLAY, SCROLLBAR_THUMB, SCROLLBAR_BUTTON, SCROLLBAR_TRACK, SCROLLBAR_TRACK_PIECE, SCROLLBAR_CORNER, RESIZER, + INPUT_LIST_BUTTON, INPUT_SPEECH_BUTTON, INNER_SPIN_BUTTON, OUTER_SPIN_BUTTON, VISITED_LINK, PROGRESS_BAR_VALUE, + METER_HORIZONTAL_BAR, METER_HORIZONTAL_OPTIMUM, METER_HORIZONTAL_SUBOPTIMAL, METER_HORIZONTAL_EVEN_LESS_GOOD, + METER_VERTICAL_BAR, METER_VERTICAL_OPTIMUM, METER_VERTICAL_SUBOPTIMAL, METER_VERTICAL_EVEN_LESS_GOOD, + AFTER_LAST_INTERNAL_PSEUDOID, + FULL_SCREEN, FULL_SCREEN_DOCUMENT, + FIRST_PUBLIC_PSEUDOID = FIRST_LINE, + FIRST_INTERNAL_PSEUDOID = FILE_UPLOAD_BUTTON, + PUBLIC_PSEUDOID_MASK = ((1 << FIRST_INTERNAL_PSEUDOID) - 1) & ~((1 << FIRST_PUBLIC_PSEUDOID) - 1) +}; + +// These have been defined in the order of their precedence for border-collapsing. Do +// not change this order! +enum EBorderStyle { BNONE, BHIDDEN, INSET, GROOVE, RIDGE, OUTSET, DOTTED, DASHED, SOLID, DOUBLE }; + +enum EBorderPrecedence { BOFF, BTABLE, BCOLGROUP, BCOL, BROWGROUP, BROW, BCELL }; + +enum EPosition { + StaticPosition, RelativePosition, AbsolutePosition, FixedPosition +}; + +enum EFloat { + FNONE = 0, FLEFT, FRIGHT +}; + +enum EMarginCollapse { MCOLLAPSE, MSEPARATE, MDISCARD }; + +// Box attributes. Not inherited. + +enum EBoxSizing { CONTENT_BOX, BORDER_BOX }; + +// Random visual rendering model attributes. Not inherited. + +enum EOverflow { + OVISIBLE, OHIDDEN, OSCROLL, OAUTO, OOVERLAY, OMARQUEE +}; + +enum EVerticalAlign { + BASELINE, MIDDLE, SUB, SUPER, TEXT_TOP, + TEXT_BOTTOM, TOP, BOTTOM, BASELINE_MIDDLE, LENGTH +}; + +enum EClear { + CNONE = 0, CLEFT = 1, CRIGHT = 2, CBOTH = 3 +}; + +enum ETableLayout { + TAUTO, TFIXED +}; + +enum EUnicodeBidi { + UBNormal, Embed, Override +}; + +// CSS Text Layout Module Level 3: Vertical writing support +enum WritingMode { + TopToBottomWritingMode, RightToLeftWritingMode, LeftToRightWritingMode, BottomToTopWritingMode +}; + +enum TextCombine { + TextCombineNone, TextCombineHorizontal +}; + +enum EFillAttachment { + ScrollBackgroundAttachment, LocalBackgroundAttachment, FixedBackgroundAttachment +}; + +enum EFillBox { + BorderFillBox, PaddingFillBox, ContentFillBox, TextFillBox +}; + +enum EFillRepeat { + RepeatFill, NoRepeatFill, RoundFill, SpaceFill +}; + +enum EFillLayerType { + BackgroundFillLayer, MaskFillLayer +}; + +// CSS3 Background Values +enum EFillSizeType { Contain, Cover, SizeLength, SizeNone }; + +// CSS3 Marquee Properties + +enum EMarqueeBehavior { MNONE, MSCROLL, MSLIDE, MALTERNATE }; +enum EMarqueeDirection { MAUTO = 0, MLEFT = 1, MRIGHT = -1, MUP = 2, MDOWN = -2, MFORWARD = 3, MBACKWARD = -3 }; + +// CSS3 Flexible Box Properties + +enum EBoxAlignment { BSTRETCH, BSTART, BCENTER, BEND, BJUSTIFY, BBASELINE }; +enum EBoxOrient { HORIZONTAL, VERTICAL }; +enum EBoxLines { SINGLE, MULTIPLE }; +enum EBoxDirection { BNORMAL, BREVERSE }; + +enum ETextSecurity { + TSNONE, TSDISC, TSCIRCLE, TSSQUARE +}; + +// CSS3 User Modify Properties + +enum EUserModify { + READ_ONLY, READ_WRITE, READ_WRITE_PLAINTEXT_ONLY +}; + +// CSS3 User Drag Values + +enum EUserDrag { + DRAG_AUTO, DRAG_NONE, DRAG_ELEMENT +}; + +// CSS3 User Select Values + +enum EUserSelect { + SELECT_NONE, SELECT_TEXT +}; + +// Word Break Values. Matches WinIE, rather than CSS3 + +enum EWordBreak { + NormalWordBreak, BreakAllWordBreak, BreakWordBreak +}; + +enum EWordWrap { + NormalWordWrap, BreakWordWrap +}; + +enum ENBSPMode { + NBNORMAL, SPACE +}; + +enum EKHTMLLineBreak { + LBNORMAL, AFTER_WHITE_SPACE +}; + +enum EMatchNearestMailBlockquoteColor { + BCNORMAL, MATCH +}; + +enum EResize { + RESIZE_NONE, RESIZE_BOTH, RESIZE_HORIZONTAL, RESIZE_VERTICAL +}; + +// The order of this enum must match the order of the list style types in CSSValueKeywords.in. +enum EListStyleType { + Disc, + Circle, + Square, + DecimalListStyle, + DecimalLeadingZero, + ArabicIndic, + BinaryListStyle, + Bengali, + Cambodian, + Khmer, + Devanagari, + Gujarati, + Gurmukhi, + Kannada, + LowerHexadecimal, + Lao, + Malayalam, + Mongolian, + Myanmar, + Octal, + Oriya, + Persian, + Urdu, + Telugu, + Tibetan, + Thai, + UpperHexadecimal, + LowerRoman, + UpperRoman, + LowerGreek, + LowerAlpha, + LowerLatin, + UpperAlpha, + UpperLatin, + Afar, + EthiopicHalehameAaEt, + EthiopicHalehameAaEr, + Amharic, + EthiopicHalehameAmEt, + AmharicAbegede, + EthiopicAbegedeAmEt, + CjkEarthlyBranch, + CjkHeavenlyStem, + Ethiopic, + EthiopicHalehameGez, + EthiopicAbegede, + EthiopicAbegedeGez, + HangulConsonant, + Hangul, + LowerNorwegian, + Oromo, + EthiopicHalehameOmEt, + Sidama, + EthiopicHalehameSidEt, + Somali, + EthiopicHalehameSoEt, + Tigre, + EthiopicHalehameTig, + TigrinyaEr, + EthiopicHalehameTiEr, + TigrinyaErAbegede, + EthiopicAbegedeTiEr, + TigrinyaEt, + EthiopicHalehameTiEt, + TigrinyaEtAbegede, + EthiopicAbegedeTiEt, + UpperGreek, + UpperNorwegian, + Asterisks, + Footnotes, + Hebrew, + Armenian, + LowerArmenian, + UpperArmenian, + Georgian, + CJKIdeographic, + Hiragana, + Katakana, + HiraganaIroha, + KatakanaIroha, + NoneListStyle +}; + +enum StyleContentType { + CONTENT_NONE, CONTENT_OBJECT, CONTENT_TEXT, CONTENT_COUNTER +}; + +enum EBorderFit { BorderFitBorder, BorderFitLines }; + +enum EAnimationFillMode { AnimationFillModeNone, AnimationFillModeForwards, AnimationFillModeBackwards, AnimationFillModeBoth }; + +enum EAnimPlayState { + AnimPlayStatePlaying = 0x0, + AnimPlayStatePaused = 0x1 +}; + +enum EWhiteSpace { + NORMAL, PRE, PRE_WRAP, PRE_LINE, NOWRAP, KHTML_NOWRAP +}; + +enum ETextAlign { + TAAUTO, LEFT, RIGHT, CENTER, JUSTIFY, WEBKIT_LEFT, WEBKIT_RIGHT, WEBKIT_CENTER +}; + +enum ETextTransform { + CAPITALIZE, UPPERCASE, LOWERCASE, TTNONE +}; + +enum ETextDecoration { + TDNONE = 0x0 , UNDERLINE = 0x1, OVERLINE = 0x2, LINE_THROUGH= 0x4, BLINK = 0x8 +}; + +enum EPageBreak { + PBAUTO, PBALWAYS, PBAVOID +}; + +enum EEmptyCell { + SHOW, HIDE +}; + +enum ECaptionSide { + CAPTOP, CAPBOTTOM, CAPLEFT, CAPRIGHT +}; + +enum EListStylePosition { OUTSIDE, INSIDE }; + +enum EVisibility { VISIBLE, HIDDEN, COLLAPSE }; + +enum ECursor { + // The following must match the order in CSSValueKeywords.in. + CURSOR_AUTO, + CURSOR_CROSS, + CURSOR_DEFAULT, + CURSOR_POINTER, + CURSOR_MOVE, + CURSOR_VERTICAL_TEXT, + CURSOR_CELL, + CURSOR_CONTEXT_MENU, + CURSOR_ALIAS, + CURSOR_PROGRESS, + CURSOR_NO_DROP, + CURSOR_NOT_ALLOWED, + CURSOR_WEBKIT_ZOOM_IN, + CURSOR_WEBKIT_ZOOM_OUT, + CURSOR_E_RESIZE, + CURSOR_NE_RESIZE, + CURSOR_NW_RESIZE, + CURSOR_N_RESIZE, + CURSOR_SE_RESIZE, + CURSOR_SW_RESIZE, + CURSOR_S_RESIZE, + CURSOR_W_RESIZE, + CURSOR_EW_RESIZE, + CURSOR_NS_RESIZE, + CURSOR_NESW_RESIZE, + CURSOR_NWSE_RESIZE, + CURSOR_COL_RESIZE, + CURSOR_ROW_RESIZE, + CURSOR_TEXT, + CURSOR_WAIT, + CURSOR_HELP, + CURSOR_ALL_SCROLL, + CURSOR_WEBKIT_GRAB, + CURSOR_WEBKIT_GRABBING, + + // The following are handled as exceptions so don't need to match. + CURSOR_COPY, + CURSOR_NONE +}; + +enum EDisplay { + INLINE, BLOCK, LIST_ITEM, RUN_IN, COMPACT, INLINE_BLOCK, + TABLE, INLINE_TABLE, TABLE_ROW_GROUP, + TABLE_HEADER_GROUP, TABLE_FOOTER_GROUP, TABLE_ROW, + TABLE_COLUMN_GROUP, TABLE_COLUMN, TABLE_CELL, + TABLE_CAPTION, BOX, INLINE_BOX, +#if ENABLE(WCSS) + WAP_MARQUEE, +#endif + NONE +}; + +enum EInsideLink { + NotInsideLink, InsideUnvisitedLink, InsideVisitedLink +}; + +enum EPointerEvents { + PE_NONE, PE_AUTO, PE_STROKE, PE_FILL, PE_PAINTED, PE_VISIBLE, + PE_VISIBLE_STROKE, PE_VISIBLE_FILL, PE_VISIBLE_PAINTED, PE_ALL +}; + +enum ETransformStyle3D { + TransformStyle3DFlat, TransformStyle3DPreserve3D +}; + +enum EBackfaceVisibility { + BackfaceVisibilityVisible, BackfaceVisibilityHidden +}; + +enum ELineClampType { LineClampLineCount, LineClampPercentage }; + +enum Hyphens { HyphensNone, HyphensManual, HyphensAuto }; + +enum ESpeak { SpeakNone, SpeakNormal, SpeakSpellOut, SpeakDigits, SpeakLiteralPunctuation, SpeakNoPunctuation }; + +enum TextEmphasisFill { TextEmphasisFillFilled, TextEmphasisFillOpen }; + +enum TextEmphasisMark { TextEmphasisMarkNone, TextEmphasisMarkAuto, TextEmphasisMarkDot, TextEmphasisMarkCircle, TextEmphasisMarkDoubleCircle, TextEmphasisMarkTriangle, TextEmphasisMarkSesame, TextEmphasisMarkCustom }; + +enum TextEmphasisPosition { TextEmphasisPositionOver, TextEmphasisPositionUnder }; + +} // namespace WebCore + +#endif // RenderStyleConstants_h diff --git a/Source/WebCore/rendering/style/SVGRenderStyle.cpp b/Source/WebCore/rendering/style/SVGRenderStyle.cpp new file mode 100644 index 0000000..28f80f2 --- /dev/null +++ b/Source/WebCore/rendering/style/SVGRenderStyle.cpp @@ -0,0 +1,220 @@ +/* + Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> + 2004, 2005, 2010 Rob Buis <buis@kde.org> + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + Based on khtml code by: + Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + Copyright (C) 2002-2003 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Apple Computer, Inc. + + 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" + +#if ENABLE(SVG) +#include "SVGRenderStyle.h" + +#include "CSSPrimitiveValue.h" +#include "CSSValueList.h" +#include "IntRect.h" +#include "NodeRenderStyle.h" +#include "SVGStyledElement.h" + +using namespace std; + +namespace WebCore { + +SVGRenderStyle::SVGRenderStyle() +{ + static SVGRenderStyle* defaultStyle = new SVGRenderStyle(CreateDefault); + + fill = defaultStyle->fill; + stroke = defaultStyle->stroke; + text = defaultStyle->text; + stops = defaultStyle->stops; + misc = defaultStyle->misc; + shadowSVG = defaultStyle->shadowSVG; + inheritedResources = defaultStyle->inheritedResources; + resources = defaultStyle->resources; + + setBitDefaults(); +} + +SVGRenderStyle::SVGRenderStyle(CreateDefaultType) +{ + setBitDefaults(); + + fill.init(); + stroke.init(); + text.init(); + stops.init(); + misc.init(); + shadowSVG.init(); + inheritedResources.init(); + resources.init(); +} + +SVGRenderStyle::SVGRenderStyle(const SVGRenderStyle& other) + : RefCounted<SVGRenderStyle>() +{ + fill = other.fill; + stroke = other.stroke; + text = other.text; + stops = other.stops; + misc = other.misc; + shadowSVG = other.shadowSVG; + inheritedResources = other.inheritedResources; + resources = other.resources; + + svg_inherited_flags = other.svg_inherited_flags; + svg_noninherited_flags = other.svg_noninherited_flags; +} + +SVGRenderStyle::~SVGRenderStyle() +{ +} + +bool SVGRenderStyle::operator==(const SVGRenderStyle& other) const +{ + return fill == other.fill + && stroke == other.stroke + && text == other.text + && stops == other.stops + && misc == other.misc + && shadowSVG == other.shadowSVG + && inheritedResources == other.inheritedResources + && resources == other.resources + && svg_inherited_flags == other.svg_inherited_flags + && svg_noninherited_flags == other.svg_noninherited_flags; +} + +bool SVGRenderStyle::inheritedNotEqual(const SVGRenderStyle* other) const +{ + return fill != other->fill + || stroke != other->stroke + || text != other->text + || inheritedResources != other->inheritedResources + || svg_inherited_flags != other->svg_inherited_flags; +} + +void SVGRenderStyle::inheritFrom(const SVGRenderStyle* svgInheritParent) +{ + if (!svgInheritParent) + return; + + fill = svgInheritParent->fill; + stroke = svgInheritParent->stroke; + text = svgInheritParent->text; + inheritedResources = svgInheritParent->inheritedResources; + + svg_inherited_flags = svgInheritParent->svg_inherited_flags; +} + +StyleDifference SVGRenderStyle::diff(const SVGRenderStyle* other) const +{ + // NOTE: All comparisions that may return StyleDifferenceLayout have to go before those who return StyleDifferenceRepaint + + // If kerning changes, we need a relayout, to force SVGCharacterData to be recalculated in the SVGRootInlineBox. + if (text != other->text) + return StyleDifferenceLayout; + + // If resources change, we need a relayout, as the presence of resources influences the repaint rect. + if (resources != other->resources) + return StyleDifferenceLayout; + + // If markers change, we need a relayout, as marker boundaries are cached in RenderSVGPath. + if (inheritedResources != other->inheritedResources) + return StyleDifferenceLayout; + + // All text related properties influence layout. + if (svg_inherited_flags._textAnchor != other->svg_inherited_flags._textAnchor + || svg_inherited_flags._writingMode != other->svg_inherited_flags._writingMode + || svg_inherited_flags._glyphOrientationHorizontal != other->svg_inherited_flags._glyphOrientationHorizontal + || svg_inherited_flags._glyphOrientationVertical != other->svg_inherited_flags._glyphOrientationVertical + || svg_noninherited_flags.f._alignmentBaseline != other->svg_noninherited_flags.f._alignmentBaseline + || svg_noninherited_flags.f._dominantBaseline != other->svg_noninherited_flags.f._dominantBaseline + || svg_noninherited_flags.f._baselineShift != other->svg_noninherited_flags.f._baselineShift) + return StyleDifferenceLayout; + + // Text related properties influence layout. + bool miscNotEqual = misc != other->misc; + if (miscNotEqual && misc->baselineShiftValue != other->misc->baselineShiftValue) + return StyleDifferenceLayout; + + // These properties affect the cached stroke bounding box rects. + if (svg_inherited_flags._capStyle != other->svg_inherited_flags._capStyle + || svg_inherited_flags._joinStyle != other->svg_inherited_flags._joinStyle) + return StyleDifferenceLayout; + + // Shadow changes require relayouts, as they affect the repaint rects. + if (shadowSVG != other->shadowSVG) + return StyleDifferenceLayout; + + // Some stroke properties, requires relayouts, as the cached stroke boundaries need to be recalculated. + if (stroke != other->stroke) { + if (stroke->width != other->stroke->width + || stroke->paint != other->stroke->paint + || stroke->miterLimit != other->stroke->miterLimit + || stroke->dashArray != other->stroke->dashArray + || stroke->dashOffset != other->stroke->dashOffset) + return StyleDifferenceLayout; + + // Only the stroke-opacity case remains, where we only need a repaint. + ASSERT(stroke->opacity != other->stroke->opacity); + return StyleDifferenceRepaint; + } + + // NOTE: All comparisions below may only return StyleDifferenceRepaint + + // Painting related properties only need repaints. + if (miscNotEqual) { + if (misc->floodColor != other->misc->floodColor + || misc->floodOpacity != other->misc->floodOpacity + || misc->lightingColor != other->misc->lightingColor) + return StyleDifferenceRepaint; + } + + // If fill changes, we just need to repaint. Fill boundaries are not influenced by this, only by the Path, that RenderSVGPath contains. + if (fill != other->fill) + return StyleDifferenceRepaint; + + // If gradient stops change, we just need to repaint. Style updates are already handled through RenderSVGGradientSTop. + if (stops != other->stops) + return StyleDifferenceRepaint; + + // Changes of these flags only cause repaints. + if (svg_inherited_flags._colorRendering != other->svg_inherited_flags._colorRendering + || svg_inherited_flags._imageRendering != other->svg_inherited_flags._imageRendering + || svg_inherited_flags._shapeRendering != other->svg_inherited_flags._shapeRendering + || svg_inherited_flags._clipRule != other->svg_inherited_flags._clipRule + || svg_inherited_flags._fillRule != other->svg_inherited_flags._fillRule + || svg_inherited_flags._colorInterpolation != other->svg_inherited_flags._colorInterpolation + || svg_inherited_flags._colorInterpolationFilters != other->svg_inherited_flags._colorInterpolationFilters) + return StyleDifferenceRepaint; + + // FIXME: vector-effect is not taken into account in the layout-phase. Once this is fixed, we should relayout here. + if (svg_noninherited_flags.f._vectorEffect != other->svg_noninherited_flags.f._vectorEffect) + return StyleDifferenceRepaint; + + return StyleDifferenceEqual; +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/style/SVGRenderStyle.h b/Source/WebCore/rendering/style/SVGRenderStyle.h new file mode 100644 index 0000000..7f032e7 --- /dev/null +++ b/Source/WebCore/rendering/style/SVGRenderStyle.h @@ -0,0 +1,431 @@ +/* + Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> + 2004, 2005 Rob Buis <buis@kde.org> + Copyright (C) 2005, 2006 Apple Computer, Inc. + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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. +*/ + +#ifndef SVGRenderStyle_h +#define SVGRenderStyle_h + +#if ENABLE(SVG) +#include "CSSValueList.h" +#include "DataRef.h" +#include "GraphicsTypes.h" +#include "Path.h" +#include "RenderStyleConstants.h" +#include "SVGPaint.h" +#include "SVGRenderStyleDefs.h" + +namespace WebCore { + +class FloatRect; +class IntRect; +class RenderObject; + +class SVGRenderStyle : public RefCounted<SVGRenderStyle> { +public: + static PassRefPtr<SVGRenderStyle> create() { return adoptRef(new SVGRenderStyle); } + PassRefPtr<SVGRenderStyle> copy() const { return adoptRef(new SVGRenderStyle(*this));} + ~SVGRenderStyle(); + + bool inheritedNotEqual(const SVGRenderStyle*) const; + void inheritFrom(const SVGRenderStyle*); + + StyleDifference diff(const SVGRenderStyle*) const; + + bool operator==(const SVGRenderStyle&) const; + bool operator!=(const SVGRenderStyle& o) const { return !(*this == o); } + + // Initial values for all the properties + static EAlignmentBaseline initialAlignmentBaseline() { return AB_AUTO; } + static EDominantBaseline initialDominantBaseline() { return DB_AUTO; } + static EBaselineShift initialBaselineShift() { return BS_BASELINE; } + static EVectorEffect initialVectorEffect() { return VE_NONE; } + static LineCap initialCapStyle() { return ButtCap; } + static WindRule initialClipRule() { return RULE_NONZERO; } + static EColorInterpolation initialColorInterpolation() { return CI_SRGB; } + static EColorInterpolation initialColorInterpolationFilters() { return CI_LINEARRGB; } + static EColorRendering initialColorRendering() { return CR_AUTO; } + static WindRule initialFillRule() { return RULE_NONZERO; } + static EImageRendering initialImageRendering() { return IR_AUTO; } + static LineJoin initialJoinStyle() { return MiterJoin; } + static EShapeRendering initialShapeRendering() { return SR_AUTO; } + static ETextAnchor initialTextAnchor() { return TA_START; } + static SVGWritingMode initialWritingMode() { return WM_LRTB; } + static EGlyphOrientation initialGlyphOrientationHorizontal() { return GO_0DEG; } + static EGlyphOrientation initialGlyphOrientationVertical() { return GO_AUTO; } + static float initialFillOpacity() { return 1.0f; } + static SVGPaint* initialFillPaint() { return SVGPaint::defaultFill(); } + static float initialStrokeOpacity() { return 1.0f; } + static SVGPaint* initialStrokePaint() { return SVGPaint::defaultStroke(); } + static Vector<SVGLength> initialStrokeDashArray() { return Vector<SVGLength>(); } + static float initialStrokeMiterLimit() { return 4.0f; } + static float initialStopOpacity() { return 1.0f; } + static Color initialStopColor() { return Color(0, 0, 0); } + static float initialFloodOpacity() { return 1.0f; } + static Color initialFloodColor() { return Color(0, 0, 0); } + static Color initialLightingColor() { return Color(255, 255, 255); } + static ShadowData* initialShadow() { return 0; } + static String initialClipperResource() { return String(); } + static String initialFilterResource() { return String(); } + static String initialMaskerResource() { return String(); } + static String initialMarkerStartResource() { return String(); } + static String initialMarkerMidResource() { return String(); } + static String initialMarkerEndResource() { return String(); } + + static SVGLength initialBaselineShiftValue() + { + SVGLength length; + ExceptionCode ec = 0; + length.newValueSpecifiedUnits(LengthTypeNumber, 0, ec); + ASSERT(!ec); + return length; + } + + static SVGLength initialKerning() + { + SVGLength length; + ExceptionCode ec = 0; + length.newValueSpecifiedUnits(LengthTypeNumber, 0, ec); + ASSERT(!ec); + return length; + } + + static SVGLength initialStrokeDashOffset() + { + SVGLength length; + ExceptionCode ec = 0; + length.newValueSpecifiedUnits(LengthTypeNumber, 0, ec); + ASSERT(!ec); + return length; + } + + static SVGLength initialStrokeWidth() + { + SVGLength length; + ExceptionCode ec = 0; + length.newValueSpecifiedUnits(LengthTypeNumber, 1, ec); + ASSERT(!ec); + return length; + } + + // SVG CSS Property setters + void setAlignmentBaseline(EAlignmentBaseline val) { svg_noninherited_flags.f._alignmentBaseline = val; } + void setDominantBaseline(EDominantBaseline val) { svg_noninherited_flags.f._dominantBaseline = val; } + void setBaselineShift(EBaselineShift val) { svg_noninherited_flags.f._baselineShift = val; } + void setVectorEffect(EVectorEffect val) { svg_noninherited_flags.f._vectorEffect = val; } + void setCapStyle(LineCap val) { svg_inherited_flags._capStyle = val; } + void setClipRule(WindRule val) { svg_inherited_flags._clipRule = val; } + void setColorInterpolation(EColorInterpolation val) { svg_inherited_flags._colorInterpolation = val; } + void setColorInterpolationFilters(EColorInterpolation val) { svg_inherited_flags._colorInterpolationFilters = val; } + void setColorRendering(EColorRendering val) { svg_inherited_flags._colorRendering = val; } + void setFillRule(WindRule val) { svg_inherited_flags._fillRule = val; } + void setImageRendering(EImageRendering val) { svg_inherited_flags._imageRendering = val; } + void setJoinStyle(LineJoin val) { svg_inherited_flags._joinStyle = val; } + void setShapeRendering(EShapeRendering val) { svg_inherited_flags._shapeRendering = val; } + void setTextAnchor(ETextAnchor val) { svg_inherited_flags._textAnchor = val; } + void setWritingMode(SVGWritingMode val) { svg_inherited_flags._writingMode = val; } + void setGlyphOrientationHorizontal(EGlyphOrientation val) { svg_inherited_flags._glyphOrientationHorizontal = val; } + void setGlyphOrientationVertical(EGlyphOrientation val) { svg_inherited_flags._glyphOrientationVertical = val; } + + void setFillOpacity(float obj) + { + if (!(fill->opacity == obj)) + fill.access()->opacity = obj; + } + + void setFillPaint(PassRefPtr<SVGPaint> obj) + { + if (!(fill->paint == obj)) + fill.access()->paint = obj; + } + + void setStrokeOpacity(float obj) + { + if (!(stroke->opacity == obj)) + stroke.access()->opacity = obj; + } + + void setStrokePaint(PassRefPtr<SVGPaint> obj) + { + if (!(stroke->paint == obj)) + stroke.access()->paint = obj; + } + + void setStrokeDashArray(const Vector<SVGLength>& obj) + { + if (!(stroke->dashArray == obj)) + stroke.access()->dashArray = obj; + } + + void setStrokeMiterLimit(float obj) + { + if (!(stroke->miterLimit == obj)) + stroke.access()->miterLimit = obj; + } + + void setStrokeWidth(const SVGLength& obj) + { + if (!(stroke->width == obj)) + stroke.access()->width = obj; + } + + void setStrokeDashOffset(const SVGLength& obj) + { + if (!(stroke->dashOffset == obj)) + stroke.access()->dashOffset = obj; + } + + void setKerning(const SVGLength& obj) + { + if (!(text->kerning == obj)) + text.access()->kerning = obj; + } + + void setStopOpacity(float obj) + { + if (!(stops->opacity == obj)) + stops.access()->opacity = obj; + } + + void setStopColor(const Color& obj) + { + if (!(stops->color == obj)) + stops.access()->color = obj; + } + + void setFloodOpacity(float obj) + { + if (!(misc->floodOpacity == obj)) + misc.access()->floodOpacity = obj; + } + + void setFloodColor(const Color& obj) + { + if (!(misc->floodColor == obj)) + misc.access()->floodColor = obj; + } + + void setLightingColor(const Color& obj) + { + if (!(misc->lightingColor == obj)) + misc.access()->lightingColor = obj; + } + + void setBaselineShiftValue(const SVGLength& obj) + { + if (!(misc->baselineShiftValue == obj)) + misc.access()->baselineShiftValue = obj; + } + + void setShadow(PassOwnPtr<ShadowData> obj) { shadowSVG.access()->shadow = obj; } + + // Setters for non-inherited resources + void setClipperResource(const String& obj) + { + if (!(resources->clipper == obj)) + resources.access()->clipper = obj; + } + + void setFilterResource(const String& obj) + { + if (!(resources->filter == obj)) + resources.access()->filter = obj; + } + + void setMaskerResource(const String& obj) + { + if (!(resources->masker == obj)) + resources.access()->masker = obj; + } + + // Setters for inherited resources + void setMarkerStartResource(const String& obj) + { + if (!(inheritedResources->markerStart == obj)) + inheritedResources.access()->markerStart = obj; + } + + void setMarkerMidResource(const String& obj) + { + if (!(inheritedResources->markerMid == obj)) + inheritedResources.access()->markerMid = obj; + } + + void setMarkerEndResource(const String& obj) + { + if (!(inheritedResources->markerEnd == obj)) + inheritedResources.access()->markerEnd = obj; + } + + // Read accessors for all the properties + EAlignmentBaseline alignmentBaseline() const { return (EAlignmentBaseline) svg_noninherited_flags.f._alignmentBaseline; } + EDominantBaseline dominantBaseline() const { return (EDominantBaseline) svg_noninherited_flags.f._dominantBaseline; } + EBaselineShift baselineShift() const { return (EBaselineShift) svg_noninherited_flags.f._baselineShift; } + EVectorEffect vectorEffect() const { return (EVectorEffect) svg_noninherited_flags.f._vectorEffect; } + LineCap capStyle() const { return (LineCap) svg_inherited_flags._capStyle; } + WindRule clipRule() const { return (WindRule) svg_inherited_flags._clipRule; } + EColorInterpolation colorInterpolation() const { return (EColorInterpolation) svg_inherited_flags._colorInterpolation; } + EColorInterpolation colorInterpolationFilters() const { return (EColorInterpolation) svg_inherited_flags._colorInterpolationFilters; } + EColorRendering colorRendering() const { return (EColorRendering) svg_inherited_flags._colorRendering; } + WindRule fillRule() const { return (WindRule) svg_inherited_flags._fillRule; } + EImageRendering imageRendering() const { return (EImageRendering) svg_inherited_flags._imageRendering; } + LineJoin joinStyle() const { return (LineJoin) svg_inherited_flags._joinStyle; } + EShapeRendering shapeRendering() const { return (EShapeRendering) svg_inherited_flags._shapeRendering; } + ETextAnchor textAnchor() const { return (ETextAnchor) svg_inherited_flags._textAnchor; } + SVGWritingMode writingMode() const { return (SVGWritingMode) svg_inherited_flags._writingMode; } + EGlyphOrientation glyphOrientationHorizontal() const { return (EGlyphOrientation) svg_inherited_flags._glyphOrientationHorizontal; } + EGlyphOrientation glyphOrientationVertical() const { return (EGlyphOrientation) svg_inherited_flags._glyphOrientationVertical; } + float fillOpacity() const { return fill->opacity; } + SVGPaint* fillPaint() const { return fill->paint.get(); } + float strokeOpacity() const { return stroke->opacity; } + SVGPaint* strokePaint() const { return stroke->paint.get(); } + Vector<SVGLength> strokeDashArray() const { return stroke->dashArray; } + float strokeMiterLimit() const { return stroke->miterLimit; } + SVGLength strokeWidth() const { return stroke->width; } + SVGLength strokeDashOffset() const { return stroke->dashOffset; } + SVGLength kerning() const { return text->kerning; } + float stopOpacity() const { return stops->opacity; } + Color stopColor() const { return stops->color; } + float floodOpacity() const { return misc->floodOpacity; } + Color floodColor() const { return misc->floodColor; } + Color lightingColor() const { return misc->lightingColor; } + SVGLength baselineShiftValue() const { return misc->baselineShiftValue; } + ShadowData* shadow() const { return shadowSVG->shadow.get(); } + String clipperResource() const { return resources->clipper; } + String filterResource() const { return resources->filter; } + String maskerResource() const { return resources->masker; } + String markerStartResource() const { return inheritedResources->markerStart; } + String markerMidResource() const { return inheritedResources->markerMid; } + String markerEndResource() const { return inheritedResources->markerEnd; } + + // convenience + bool hasClipper() const { return !clipperResource().isEmpty(); } + bool hasMasker() const { return !maskerResource().isEmpty(); } + bool hasFilter() const { return !filterResource().isEmpty(); } + bool hasMarkers() const { return !markerStartResource().isEmpty() || !markerMidResource().isEmpty() || !markerEndResource().isEmpty(); } + bool hasStroke() const { return strokePaint()->paintType() != SVGPaint::SVG_PAINTTYPE_NONE; } + bool hasFill() const { return fillPaint()->paintType() != SVGPaint::SVG_PAINTTYPE_NONE; } + bool isVerticalWritingMode() const { return writingMode() == WM_TBRL || writingMode() == WM_TB; } + +protected: + // inherit + struct InheritedFlags { + bool operator==(const InheritedFlags& other) const + { + return (_colorRendering == other._colorRendering) + && (_imageRendering == other._imageRendering) + && (_shapeRendering == other._shapeRendering) + && (_clipRule == other._clipRule) + && (_fillRule == other._fillRule) + && (_capStyle == other._capStyle) + && (_joinStyle == other._joinStyle) + && (_textAnchor == other._textAnchor) + && (_colorInterpolation == other._colorInterpolation) + && (_colorInterpolationFilters == other._colorInterpolationFilters) + && (_writingMode == other._writingMode) + && (_glyphOrientationHorizontal == other._glyphOrientationHorizontal) + && (_glyphOrientationVertical == other._glyphOrientationVertical); + } + + bool operator!=(const InheritedFlags& other) const + { + return !(*this == other); + } + + unsigned _colorRendering : 2; // EColorRendering + unsigned _imageRendering : 2; // EImageRendering + unsigned _shapeRendering : 2; // EShapeRendering + unsigned _clipRule : 1; // WindRule + unsigned _fillRule : 1; // WindRule + unsigned _capStyle : 2; // LineCap + unsigned _joinStyle : 2; // LineJoin + unsigned _textAnchor : 2; // ETextAnchor + unsigned _colorInterpolation : 2; // EColorInterpolation + unsigned _colorInterpolationFilters : 2; // EColorInterpolation + unsigned _writingMode : 3; // SVGWritingMode + unsigned _glyphOrientationHorizontal : 3; // EGlyphOrientation + unsigned _glyphOrientationVertical : 3; // EGlyphOrientation + } svg_inherited_flags; + + // don't inherit + struct NonInheritedFlags { + // 32 bit non-inherited, don't add to the struct, or the operator will break. + bool operator==(const NonInheritedFlags &other) const { return _niflags == other._niflags; } + bool operator!=(const NonInheritedFlags &other) const { return _niflags != other._niflags; } + + union { + struct { + unsigned _alignmentBaseline : 4; // EAlignmentBaseline + unsigned _dominantBaseline : 4; // EDominantBaseline + unsigned _baselineShift : 2; // EBaselineShift + unsigned _vectorEffect: 1; // EVectorEffect + // 21 bits unused + } f; + uint32_t _niflags; + }; + } svg_noninherited_flags; + + // inherited attributes + DataRef<StyleFillData> fill; + DataRef<StyleStrokeData> stroke; + DataRef<StyleTextData> text; + DataRef<StyleInheritedResourceData> inheritedResources; + + // non-inherited attributes + DataRef<StyleStopData> stops; + DataRef<StyleMiscData> misc; + DataRef<StyleShadowSVGData> shadowSVG; + DataRef<StyleResourceData> resources; + +private: + enum CreateDefaultType { CreateDefault }; + + SVGRenderStyle(); + SVGRenderStyle(const SVGRenderStyle&); + SVGRenderStyle(CreateDefaultType); // Used to create the default style. + + void setBitDefaults() + { + svg_inherited_flags._clipRule = initialClipRule(); + svg_inherited_flags._colorRendering = initialColorRendering(); + svg_inherited_flags._fillRule = initialFillRule(); + svg_inherited_flags._imageRendering = initialImageRendering(); + svg_inherited_flags._shapeRendering = initialShapeRendering(); + svg_inherited_flags._textAnchor = initialTextAnchor(); + svg_inherited_flags._capStyle = initialCapStyle(); + svg_inherited_flags._joinStyle = initialJoinStyle(); + svg_inherited_flags._colorInterpolation = initialColorInterpolation(); + svg_inherited_flags._colorInterpolationFilters = initialColorInterpolationFilters(); + svg_inherited_flags._writingMode = initialWritingMode(); + svg_inherited_flags._glyphOrientationHorizontal = initialGlyphOrientationHorizontal(); + svg_inherited_flags._glyphOrientationVertical = initialGlyphOrientationVertical(); + + svg_noninherited_flags._niflags = 0; + svg_noninherited_flags.f._alignmentBaseline = initialAlignmentBaseline(); + svg_noninherited_flags.f._dominantBaseline = initialDominantBaseline(); + svg_noninherited_flags.f._baselineShift = initialBaselineShift(); + svg_noninherited_flags.f._vectorEffect = initialVectorEffect(); + } +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif // SVGRenderStyle_h diff --git a/Source/WebCore/rendering/style/SVGRenderStyleDefs.cpp b/Source/WebCore/rendering/style/SVGRenderStyleDefs.cpp new file mode 100644 index 0000000..c30ae6d --- /dev/null +++ b/Source/WebCore/rendering/style/SVGRenderStyleDefs.cpp @@ -0,0 +1,227 @@ +/* + Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> + 2004, 2005, 2007 Rob Buis <buis@kde.org> + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + Based on khtml code by: + Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + Copyright (C) 2002-2003 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Apple Computer, Inc. + + 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" + +#if ENABLE(SVG) +#include "SVGRenderStyleDefs.h" + +#include "RenderStyle.h" +#include "SVGRenderStyle.h" + +namespace WebCore { + +StyleFillData::StyleFillData() + : opacity(SVGRenderStyle::initialFillOpacity()) + , paint(SVGRenderStyle::initialFillPaint()) +{ +} + +StyleFillData::StyleFillData(const StyleFillData& other) + : RefCounted<StyleFillData>() + , opacity(other.opacity) + , paint(other.paint) +{ +} + +bool StyleFillData::operator==(const StyleFillData& other) const +{ + if (opacity != other.opacity) + return false; + + if (!paint || !other.paint) + return paint == other.paint; + + if (paint->paintType() != other.paint->paintType()) + return false; + + if (paint->paintType() == SVGPaint::SVG_PAINTTYPE_URI) + return paint->uri() == other.paint->uri(); + + if (paint->paintType() == SVGPaint::SVG_PAINTTYPE_RGBCOLOR) + return paint->color() == other.paint->color(); + + return paint == other.paint; +} + +StyleStrokeData::StyleStrokeData() + : opacity(SVGRenderStyle::initialStrokeOpacity()) + , miterLimit(SVGRenderStyle::initialStrokeMiterLimit()) + , width(SVGRenderStyle::initialStrokeWidth()) + , dashOffset(SVGRenderStyle::initialStrokeDashOffset()) + , dashArray(SVGRenderStyle::initialStrokeDashArray()) + , paint(SVGRenderStyle::initialStrokePaint()) +{ +} + +StyleStrokeData::StyleStrokeData(const StyleStrokeData& other) + : RefCounted<StyleStrokeData>() + , opacity(other.opacity) + , miterLimit(other.miterLimit) + , width(other.width) + , dashOffset(other.dashOffset) + , dashArray(other.dashArray) + , paint(other.paint) +{ +} + +bool StyleStrokeData::operator==(const StyleStrokeData& other) const +{ + return paint == other.paint + && width == other.width + && opacity == other.opacity + && miterLimit == other.miterLimit + && dashOffset == other.dashOffset + && dashArray == other.dashArray; +} + +StyleStopData::StyleStopData() + : opacity(SVGRenderStyle::initialStopOpacity()) + , color(SVGRenderStyle::initialStopColor()) +{ +} + +StyleStopData::StyleStopData(const StyleStopData& other) + : RefCounted<StyleStopData>() + , opacity(other.opacity) + , color(other.color) +{ +} + +bool StyleStopData::operator==(const StyleStopData& other) const +{ + return color == other.color + && opacity == other.opacity; +} + +StyleTextData::StyleTextData() + : kerning(SVGRenderStyle::initialKerning()) +{ +} + +StyleTextData::StyleTextData(const StyleTextData& other) + : RefCounted<StyleTextData>() + , kerning(other.kerning) +{ +} + +bool StyleTextData::operator==(const StyleTextData& other) const +{ + return kerning == other.kerning; +} + +StyleMiscData::StyleMiscData() + : floodColor(SVGRenderStyle::initialFloodColor()) + , floodOpacity(SVGRenderStyle::initialFloodOpacity()) + , lightingColor(SVGRenderStyle::initialLightingColor()) + , baselineShiftValue(SVGRenderStyle::initialBaselineShiftValue()) +{ +} + +StyleMiscData::StyleMiscData(const StyleMiscData& other) + : RefCounted<StyleMiscData>() + , floodColor(other.floodColor) + , floodOpacity(other.floodOpacity) + , lightingColor(other.lightingColor) + , baselineShiftValue(other.baselineShiftValue) +{ +} + +bool StyleMiscData::operator==(const StyleMiscData& other) const +{ + return floodOpacity == other.floodOpacity + && floodColor == other.floodColor + && lightingColor == other.lightingColor + && baselineShiftValue == other.baselineShiftValue; +} + +StyleShadowSVGData::StyleShadowSVGData() +{ +} + +StyleShadowSVGData::StyleShadowSVGData(const StyleShadowSVGData& other) + : RefCounted<StyleShadowSVGData>() + , shadow(other.shadow ? new ShadowData(*other.shadow) : 0) +{ +} + +bool StyleShadowSVGData::operator==(const StyleShadowSVGData& other) const +{ + if ((!shadow && other.shadow) || (shadow && !other.shadow)) + return false; + if (shadow && other.shadow && (*shadow != *other.shadow)) + return false; + return true; +} + +StyleResourceData::StyleResourceData() + : clipper(SVGRenderStyle::initialClipperResource()) + , filter(SVGRenderStyle::initialFilterResource()) + , masker(SVGRenderStyle::initialMaskerResource()) +{ +} + +StyleResourceData::StyleResourceData(const StyleResourceData& other) + : RefCounted<StyleResourceData>() + , clipper(other.clipper) + , filter(other.filter) + , masker(other.masker) +{ +} + +bool StyleResourceData::operator==(const StyleResourceData& other) const +{ + return clipper == other.clipper + && filter == other.filter + && masker == other.masker; +} + +StyleInheritedResourceData::StyleInheritedResourceData() + : markerStart(SVGRenderStyle::initialMarkerStartResource()) + , markerMid(SVGRenderStyle::initialMarkerMidResource()) + , markerEnd(SVGRenderStyle::initialMarkerEndResource()) +{ +} + +StyleInheritedResourceData::StyleInheritedResourceData(const StyleInheritedResourceData& other) + : RefCounted<StyleInheritedResourceData>() + , markerStart(other.markerStart) + , markerMid(other.markerMid) + , markerEnd(other.markerEnd) +{ +} + +bool StyleInheritedResourceData::operator==(const StyleInheritedResourceData& other) const +{ + return markerStart == other.markerStart + && markerMid == other.markerMid + && markerEnd == other.markerEnd; +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/style/SVGRenderStyleDefs.h b/Source/WebCore/rendering/style/SVGRenderStyleDefs.h new file mode 100644 index 0000000..de058a2 --- /dev/null +++ b/Source/WebCore/rendering/style/SVGRenderStyleDefs.h @@ -0,0 +1,266 @@ +/* + Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> + 2004, 2005 Rob Buis <buis@kde.org> + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + Based on khtml code by: + Copyright (C) 2000-2003 Lars Knoll (knoll@kde.org) + (C) 2000 Antti Koivisto (koivisto@kde.org) + (C) 2000-2003 Dirk Mueller (mueller@kde.org) + (C) 2002-2003 Apple Computer, Inc. + + 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. +*/ + +#ifndef SVGRenderStyleDefs_h +#define SVGRenderStyleDefs_h + +#if ENABLE(SVG) +#include "Color.h" +#include "PlatformString.h" +#include "SVGLength.h" +#include "ShadowData.h" +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + + enum EBaselineShift { + BS_BASELINE, BS_SUB, BS_SUPER, BS_LENGTH + }; + + enum ETextAnchor { + TA_START, TA_MIDDLE, TA_END + }; + + enum EColorInterpolation { + CI_AUTO, CI_SRGB, CI_LINEARRGB + }; + + enum EColorRendering { + CR_AUTO, CR_OPTIMIZESPEED, CR_OPTIMIZEQUALITY + }; + + enum EImageRendering { + IR_AUTO, IR_OPTIMIZESPEED, IR_OPTIMIZEQUALITY + }; + + enum EShapeRendering { + SR_AUTO, SR_OPTIMIZESPEED, SR_CRISPEDGES, SR_GEOMETRICPRECISION + }; + + enum SVGWritingMode { + WM_LRTB, WM_LR, WM_RLTB, WM_RL, WM_TBRL, WM_TB + }; + + enum EGlyphOrientation { + GO_0DEG, GO_90DEG, GO_180DEG, GO_270DEG, GO_AUTO + }; + + enum EAlignmentBaseline { + AB_AUTO, AB_BASELINE, AB_BEFORE_EDGE, AB_TEXT_BEFORE_EDGE, + AB_MIDDLE, AB_CENTRAL, AB_AFTER_EDGE, AB_TEXT_AFTER_EDGE, + AB_IDEOGRAPHIC, AB_ALPHABETIC, AB_HANGING, AB_MATHEMATICAL + }; + + enum EDominantBaseline { + DB_AUTO, DB_USE_SCRIPT, DB_NO_CHANGE, DB_RESET_SIZE, + DB_IDEOGRAPHIC, DB_ALPHABETIC, DB_HANGING, DB_MATHEMATICAL, + DB_CENTRAL, DB_MIDDLE, DB_TEXT_AFTER_EDGE, DB_TEXT_BEFORE_EDGE + }; + + enum EVectorEffect { + VE_NONE, + VE_NON_SCALING_STROKE + }; + + class CSSValue; + class CSSValueList; + class SVGPaint; + + // Inherited/Non-Inherited Style Datastructures + class StyleFillData : public RefCounted<StyleFillData> { + public: + static PassRefPtr<StyleFillData> create() { return adoptRef(new StyleFillData); } + PassRefPtr<StyleFillData> copy() const { return adoptRef(new StyleFillData(*this)); } + + bool operator==(const StyleFillData&) const; + bool operator!=(const StyleFillData& other) const + { + return !(*this == other); + } + + float opacity; + RefPtr<SVGPaint> paint; + + private: + StyleFillData(); + StyleFillData(const StyleFillData&); + }; + + class StyleStrokeData : public RefCounted<StyleStrokeData> { + public: + static PassRefPtr<StyleStrokeData> create() { return adoptRef(new StyleStrokeData); } + PassRefPtr<StyleStrokeData> copy() const { return adoptRef(new StyleStrokeData(*this)); } + + bool operator==(const StyleStrokeData&) const; + bool operator!=(const StyleStrokeData& other) const + { + return !(*this == other); + } + + float opacity; + float miterLimit; + + SVGLength width; + SVGLength dashOffset; + Vector<SVGLength> dashArray; + + RefPtr<SVGPaint> paint; + + private: + StyleStrokeData(); + StyleStrokeData(const StyleStrokeData&); + }; + + class StyleStopData : public RefCounted<StyleStopData> { + public: + static PassRefPtr<StyleStopData> create() { return adoptRef(new StyleStopData); } + PassRefPtr<StyleStopData> copy() const { return adoptRef(new StyleStopData(*this)); } + + bool operator==(const StyleStopData&) const; + bool operator!=(const StyleStopData& other) const + { + return !(*this == other); + } + + float opacity; + Color color; + + private: + StyleStopData(); + StyleStopData(const StyleStopData&); + }; + + class StyleTextData : public RefCounted<StyleTextData> { + public: + static PassRefPtr<StyleTextData> create() { return adoptRef(new StyleTextData); } + PassRefPtr<StyleTextData> copy() const { return adoptRef(new StyleTextData(*this)); } + + bool operator==(const StyleTextData& other) const; + bool operator!=(const StyleTextData& other) const + { + return !(*this == other); + } + + SVGLength kerning; + + private: + StyleTextData(); + StyleTextData(const StyleTextData&); + }; + + // Note: the rule for this class is, *no inheritance* of these props + class StyleMiscData : public RefCounted<StyleMiscData> { + public: + static PassRefPtr<StyleMiscData> create() { return adoptRef(new StyleMiscData); } + PassRefPtr<StyleMiscData> copy() const { return adoptRef(new StyleMiscData(*this)); } + + bool operator==(const StyleMiscData&) const; + bool operator!=(const StyleMiscData& other) const + { + return !(*this == other); + } + + Color floodColor; + float floodOpacity; + Color lightingColor; + + // non-inherited text stuff lives here not in StyleTextData. + SVGLength baselineShiftValue; + + private: + StyleMiscData(); + StyleMiscData(const StyleMiscData&); + }; + + class StyleShadowSVGData : public RefCounted<StyleShadowSVGData> { + public: + static PassRefPtr<StyleShadowSVGData> create() { return adoptRef(new StyleShadowSVGData); } + PassRefPtr<StyleShadowSVGData> copy() const { return adoptRef(new StyleShadowSVGData(*this)); } + + bool operator==(const StyleShadowSVGData&) const; + bool operator!=(const StyleShadowSVGData& other) const + { + return !(*this == other); + } + + OwnPtr<ShadowData> shadow; + + private: + StyleShadowSVGData(); + StyleShadowSVGData(const StyleShadowSVGData&); + }; + + // Non-inherited resources + class StyleResourceData : public RefCounted<StyleResourceData> { + public: + static PassRefPtr<StyleResourceData> create() { return adoptRef(new StyleResourceData); } + PassRefPtr<StyleResourceData> copy() const { return adoptRef(new StyleResourceData(*this)); } + + bool operator==(const StyleResourceData&) const; + bool operator!=(const StyleResourceData& other) const + { + return !(*this == other); + } + + String clipper; + String filter; + String masker; + + private: + StyleResourceData(); + StyleResourceData(const StyleResourceData&); + }; + + // Inherited resources + class StyleInheritedResourceData : public RefCounted<StyleInheritedResourceData> { + public: + static PassRefPtr<StyleInheritedResourceData> create() { return adoptRef(new StyleInheritedResourceData); } + PassRefPtr<StyleInheritedResourceData> copy() const { return adoptRef(new StyleInheritedResourceData(*this)); } + + bool operator==(const StyleInheritedResourceData&) const; + bool operator!=(const StyleInheritedResourceData& other) const + { + return !(*this == other); + } + + String markerStart; + String markerMid; + String markerEnd; + + private: + StyleInheritedResourceData(); + StyleInheritedResourceData(const StyleInheritedResourceData&); + }; + +} // namespace WebCore + +#endif // ENABLE(SVG) + +#endif // SVGRenderStyleDefs_h diff --git a/Source/WebCore/rendering/style/ShadowData.cpp b/Source/WebCore/rendering/style/ShadowData.cpp new file mode 100644 index 0000000..e8d381c --- /dev/null +++ b/Source/WebCore/rendering/style/ShadowData.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * + * 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 "ShadowData.h" + +#include "FloatRect.h" +#include "IntRect.h" + +using namespace std; + +namespace WebCore { + +ShadowData::ShadowData(const ShadowData& o) + : m_x(o.m_x) + , m_y(o.m_y) + , m_blur(o.m_blur) + , m_spread(o.m_spread) + , m_color(o.m_color) + , m_style(o.m_style) + , m_isWebkitBoxShadow(o.m_isWebkitBoxShadow) +{ + m_next = o.m_next ? new ShadowData(*o.m_next) : 0; +} + +bool ShadowData::operator==(const ShadowData& o) const +{ + if ((m_next && !o.m_next) || (!m_next && o.m_next) + || (m_next && o.m_next && *m_next != *o.m_next)) + return false; + + return m_x == o.m_x + && m_y == o.m_y + && m_blur == o.m_blur + && m_spread == o.m_spread + && m_style == o.m_style + && m_color == o.m_color + && m_isWebkitBoxShadow == o.m_isWebkitBoxShadow; +} + +static inline void calculateShadowExtent(const ShadowData* shadow, int additionalOutlineSize, int& shadowLeft, int& shadowRight, int& shadowTop, int& shadowBottom) +{ + do { + int blurAndSpread = shadow->blur() + shadow->spread() + additionalOutlineSize; + if (shadow->style() == Normal) { + shadowLeft = min(shadow->x() - blurAndSpread, shadowLeft); + shadowRight = max(shadow->x() + blurAndSpread, shadowRight); + shadowTop = min(shadow->y() - blurAndSpread, shadowTop); + shadowBottom = max(shadow->y() + blurAndSpread, shadowBottom); + } + + shadow = shadow->next(); + } while (shadow); +} + +void ShadowData::adjustRectForShadow(IntRect& rect, int additionalOutlineSize) const +{ + int shadowLeft = 0; + int shadowRight = 0; + int shadowTop = 0; + int shadowBottom = 0; + calculateShadowExtent(this, additionalOutlineSize, shadowLeft, shadowRight, shadowTop, shadowBottom); + + rect.move(shadowLeft, shadowTop); + rect.setWidth(rect.width() - shadowLeft + shadowRight); + rect.setHeight(rect.height() - shadowTop + shadowBottom); +} + +void ShadowData::adjustRectForShadow(FloatRect& rect, int additionalOutlineSize) const +{ + int shadowLeft = 0; + int shadowRight = 0; + int shadowTop = 0; + int shadowBottom = 0; + calculateShadowExtent(this, additionalOutlineSize, shadowLeft, shadowRight, shadowTop, shadowBottom); + + rect.move(shadowLeft, shadowTop); + rect.setWidth(rect.width() - shadowLeft + shadowRight); + rect.setHeight(rect.height() - shadowTop + shadowBottom); +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/ShadowData.h b/Source/WebCore/rendering/style/ShadowData.h new file mode 100644 index 0000000..fb5926d --- /dev/null +++ b/Source/WebCore/rendering/style/ShadowData.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef ShadowData_h +#define ShadowData_h + +#include "Color.h" +#include <wtf/FastAllocBase.h> + +namespace WebCore { + +class FloatRect; +class IntRect; + +enum ShadowStyle { Normal, Inset }; + +// This struct holds information about shadows for the text-shadow and box-shadow properties. + +class ShadowData : public FastAllocBase { +public: + ShadowData() + : m_x(0) + , m_y(0) + , m_blur(0) + , m_spread(0) + , m_style(Normal) + , m_isWebkitBoxShadow(false) + , m_next(0) + { + } + + ShadowData(int x, int y, int blur, int spread, ShadowStyle style, bool isWebkitBoxShadow, const Color& color) + : m_x(x) + , m_y(y) + , m_blur(blur) + , m_spread(spread) + , m_color(color) + , m_style(style) + , m_isWebkitBoxShadow(isWebkitBoxShadow) + , m_next(0) + { + } + + ShadowData(const ShadowData& o); + ~ShadowData() { delete m_next; } + + bool operator==(const ShadowData& o) const; + bool operator!=(const ShadowData& o) const + { + return !(*this == o); + } + + int x() const { return m_x; } + int y() const { return m_y; } + int blur() const { return m_blur; } + int spread() const { return m_spread; } + ShadowStyle style() const { return m_style; } + const Color& color() const { return m_color; } + bool isWebkitBoxShadow() const { return m_isWebkitBoxShadow; } + + const ShadowData* next() const { return m_next; } + void setNext(ShadowData* shadow) { m_next = shadow; } + + void adjustRectForShadow(IntRect&, int additionalOutlineSize = 0) const; + void adjustRectForShadow(FloatRect&, int additionalOutlineSize = 0) const; + +private: + int m_x; + int m_y; + int m_blur; + int m_spread; + Color m_color; + ShadowStyle m_style; + bool m_isWebkitBoxShadow; + ShadowData* m_next; +}; + +} // namespace WebCore + +#endif // ShadowData_h diff --git a/Source/WebCore/rendering/style/StyleAllInOne.cpp b/Source/WebCore/rendering/style/StyleAllInOne.cpp new file mode 100644 index 0000000..25b539f --- /dev/null +++ b/Source/WebCore/rendering/style/StyleAllInOne.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// This all-in-one cpp file cuts down on template bloat to allow us to build our Windows release build. + +#include "ContentData.cpp" +#include "CounterDirectives.cpp" +#include "FillLayer.cpp" +#include "KeyframeList.cpp" +#include "NinePieceImage.cpp" +#include "RenderStyle.cpp" +#include "SVGRenderStyle.cpp" +#include "SVGRenderStyleDefs.cpp" +#include "ShadowData.cpp" +#include "StyleBackgroundData.cpp" +#include "StyleBoxData.cpp" +#include "StyleCachedImage.cpp" +#include "StyleFlexibleBoxData.cpp" +#include "StyleGeneratedImage.cpp" +#include "StyleInheritedData.cpp" +#include "StyleMarqueeData.cpp" +#include "StyleMultiColData.cpp" +#include "StyleRareInheritedData.cpp" +#include "StyleRareNonInheritedData.cpp" +#include "StyleSurroundData.cpp" +#include "StyleTransformData.cpp" +#include "StyleVisualData.cpp" diff --git a/Source/WebCore/rendering/style/StyleBackgroundData.cpp b/Source/WebCore/rendering/style/StyleBackgroundData.cpp new file mode 100644 index 0000000..08f5527 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleBackgroundData.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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 "StyleBackgroundData.h" + +#include "RenderStyle.h" +#include "RenderStyleConstants.h" + +namespace WebCore { + +StyleBackgroundData::StyleBackgroundData() + : m_background(BackgroundFillLayer) + , m_color(RenderStyle::initialBackgroundColor()) +{ +} + +StyleBackgroundData::StyleBackgroundData(const StyleBackgroundData& o) + : RefCounted<StyleBackgroundData>() + , m_background(o.m_background) + , m_color(o.m_color) + , m_outline(o.m_outline) +{ +} + +bool StyleBackgroundData::operator==(const StyleBackgroundData& o) const +{ + return m_background == o.m_background && m_color == o.m_color && m_outline == o.m_outline; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/StyleBackgroundData.h b/Source/WebCore/rendering/style/StyleBackgroundData.h new file mode 100644 index 0000000..48a700e --- /dev/null +++ b/Source/WebCore/rendering/style/StyleBackgroundData.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef StyleBackgroundData_h +#define StyleBackgroundData_h + +#include "Color.h" +#include "FillLayer.h" +#include "OutlineValue.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> + +namespace WebCore { + +class StyleBackgroundData : public RefCounted<StyleBackgroundData> { +public: + static PassRefPtr<StyleBackgroundData> create() { return adoptRef(new StyleBackgroundData); } + PassRefPtr<StyleBackgroundData> copy() const { return adoptRef(new StyleBackgroundData(*this)); } + ~StyleBackgroundData() { } + + bool operator==(const StyleBackgroundData& o) const; + bool operator!=(const StyleBackgroundData& o) const + { + return !(*this == o); + } + + const FillLayer& background() const { return m_background; } + const Color& color() const { return m_color; } + const OutlineValue& outline() const { return m_outline; } + +private: + friend class RenderStyle; + + StyleBackgroundData(); + StyleBackgroundData(const StyleBackgroundData&); + + FillLayer m_background; + Color m_color; + OutlineValue m_outline; +}; + +} // namespace WebCore + +#endif // StyleBackgroundData_h diff --git a/Source/WebCore/rendering/style/StyleBoxData.cpp b/Source/WebCore/rendering/style/StyleBoxData.cpp new file mode 100644 index 0000000..2c523da --- /dev/null +++ b/Source/WebCore/rendering/style/StyleBoxData.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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 "StyleBoxData.h" + +#include "RenderStyle.h" +#include "RenderStyleConstants.h" + +namespace WebCore { + +StyleBoxData::StyleBoxData() + : m_minWidth(RenderStyle::initialMinSize()) + , m_maxWidth(RenderStyle::initialMaxSize()) + , m_minHeight(RenderStyle::initialMinSize()) + , m_maxHeight(RenderStyle::initialMaxSize()) + , m_zIndex(0) + , m_hasAutoZIndex(true) + , m_boxSizing(CONTENT_BOX) +{ +} + +StyleBoxData::StyleBoxData(const StyleBoxData& o) + : RefCounted<StyleBoxData>() + , m_width(o.m_width) + , m_height(o.m_height) + , m_minWidth(o.m_minWidth) + , m_maxWidth(o.m_maxWidth) + , m_minHeight(o.m_minHeight) + , m_maxHeight(o.m_maxHeight) + , m_zIndex(o.m_zIndex) + , m_hasAutoZIndex(o.m_hasAutoZIndex) + , m_boxSizing(o.m_boxSizing) +{ +} + +bool StyleBoxData::operator==(const StyleBoxData& o) const +{ + return m_width == o.m_width + && m_height == o.m_height + && m_minWidth == o.m_minWidth + && m_maxWidth == o.m_maxWidth + && m_minHeight == o.m_minHeight + && m_maxHeight == o.m_maxHeight + && m_zIndex == o.m_zIndex + && m_hasAutoZIndex == o.m_hasAutoZIndex + && m_boxSizing == o.m_boxSizing; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/StyleBoxData.h b/Source/WebCore/rendering/style/StyleBoxData.h new file mode 100644 index 0000000..00bce4e --- /dev/null +++ b/Source/WebCore/rendering/style/StyleBoxData.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef StyleBoxData_h +#define StyleBoxData_h + +#include "Length.h" +#include "RenderStyleConstants.h" +#include <wtf/RefCounted.h> +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class StyleBoxData : public RefCounted<StyleBoxData> { +public: + static PassRefPtr<StyleBoxData> create() { return adoptRef(new StyleBoxData); } + PassRefPtr<StyleBoxData> copy() const { return adoptRef(new StyleBoxData(*this)); } + + bool operator==(const StyleBoxData& o) const; + bool operator!=(const StyleBoxData& o) const + { + return !(*this == o); + } + + Length width() const { return m_width; } + Length height() const { return m_height; } + + Length minWidth() const { return m_minWidth; } + Length minHeight() const { return m_minHeight; } + + Length maxWidth() const { return m_maxWidth; } + Length maxHeight() const { return m_maxHeight; } + + Length verticalAlign() const { return m_verticalAlign; } + + int zIndex() const { return m_zIndex; } + bool hasAutoZIndex() const { return m_hasAutoZIndex; } + + EBoxSizing boxSizing() const { return static_cast<EBoxSizing>(m_boxSizing); } + +private: + friend class RenderStyle; + + StyleBoxData(); + StyleBoxData(const StyleBoxData&); + + Length m_width; + Length m_height; + + Length m_minWidth; + Length m_maxWidth; + + Length m_minHeight; + Length m_maxHeight; + + Length m_verticalAlign; + + int m_zIndex; + bool m_hasAutoZIndex : 1; + unsigned m_boxSizing : 1; // EBoxSizing +}; + +} // namespace WebCore + +#endif // StyleBoxData_h diff --git a/Source/WebCore/rendering/style/StyleCachedImage.cpp b/Source/WebCore/rendering/style/StyleCachedImage.cpp new file mode 100644 index 0000000..1d7aba8 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleCachedImage.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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 "StyleCachedImage.h" + +#include "CachedImage.h" +#include "RenderObject.h" + +namespace WebCore { + +PassRefPtr<CSSValue> StyleCachedImage::cssValue() const +{ + return CSSPrimitiveValue::create(m_image->url(), CSSPrimitiveValue::CSS_URI); +} + +bool StyleCachedImage::canRender(float multiplier) const +{ + return m_image->canRender(multiplier); +} + +bool StyleCachedImage::isLoaded() const +{ + return m_image->isLoaded(); +} + +bool StyleCachedImage::errorOccurred() const +{ + return m_image->errorOccurred(); +} + +IntSize StyleCachedImage::imageSize(const RenderObject* /*renderer*/, float multiplier) const +{ + return m_image->imageSize(multiplier); +} + +bool StyleCachedImage::imageHasRelativeWidth() const +{ + return m_image->imageHasRelativeWidth(); +} + +bool StyleCachedImage::imageHasRelativeHeight() const +{ + return m_image->imageHasRelativeHeight(); +} + +bool StyleCachedImage::usesImageContainerSize() const +{ + return m_image->usesImageContainerSize(); +} + +void StyleCachedImage::setImageContainerSize(const IntSize& size) +{ + return m_image->setImageContainerSize(size); +} + +void StyleCachedImage::addClient(RenderObject* renderer) +{ + return m_image->addClient(renderer); +} + +void StyleCachedImage::removeClient(RenderObject* renderer) +{ + return m_image->removeClient(renderer); +} + +Image* StyleCachedImage::image(RenderObject*, const IntSize&) const +{ + return m_image->image(); +} + +} diff --git a/Source/WebCore/rendering/style/StyleCachedImage.h b/Source/WebCore/rendering/style/StyleCachedImage.h new file mode 100644 index 0000000..3d6e1a2 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleCachedImage.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef StyleCachedImage_h +#define StyleCachedImage_h + +#include "CachedResourceHandle.h" +#include "StyleImage.h" + +namespace WebCore { + +class CachedImage; + +class StyleCachedImage : public StyleImage { +public: + static PassRefPtr<StyleCachedImage> create(CachedImage* image) { return adoptRef(new StyleCachedImage(image)); } + virtual WrappedImagePtr data() const { return m_image.get(); } + + virtual bool isCachedImage() const { return true; } + + virtual PassRefPtr<CSSValue> cssValue() const; + + CachedImage* cachedImage() const { return m_image.get(); } + + virtual bool canRender(float multiplier) const; + virtual bool isLoaded() const; + virtual bool errorOccurred() const; + virtual IntSize imageSize(const RenderObject*, float multiplier) const; + virtual bool imageHasRelativeWidth() const; + virtual bool imageHasRelativeHeight() const; + virtual bool usesImageContainerSize() const; + virtual void setImageContainerSize(const IntSize&); + virtual void addClient(RenderObject*); + virtual void removeClient(RenderObject*); + virtual Image* image(RenderObject*, const IntSize&) const; + +private: + StyleCachedImage(CachedImage* image) + : m_image(image) + { + } + + CachedResourceHandle<CachedImage> m_image; +}; + +} +#endif diff --git a/Source/WebCore/rendering/style/StyleDashboardRegion.h b/Source/WebCore/rendering/style/StyleDashboardRegion.h new file mode 100644 index 0000000..bbb0cda --- /dev/null +++ b/Source/WebCore/rendering/style/StyleDashboardRegion.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef StyleDashboardRegion_h +#define StyleDashboardRegion_h +#if ENABLE(DASHBOARD_SUPPORT) + +#include "LengthBox.h" +#include "PlatformString.h" + +namespace WebCore { + +// Dashboard region attributes. Not inherited. + +struct StyleDashboardRegion { + String label; + LengthBox offset; + int type; + + enum { + None, + Circle, + Rectangle + }; + + bool operator==(const StyleDashboardRegion& o) const + { + return type == o.type && offset == o.offset && label == o.label; + } + + bool operator!=(const StyleDashboardRegion& o) const + { + return !(*this == o); + } +}; + +} // namespace WebCore + +#endif // ENABLE(DASHBOARD_SUPPORT) +#endif // StyleDashboardRegion_h diff --git a/Source/WebCore/rendering/style/StyleFlexibleBoxData.cpp b/Source/WebCore/rendering/style/StyleFlexibleBoxData.cpp new file mode 100644 index 0000000..7c00080 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleFlexibleBoxData.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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 "StyleFlexibleBoxData.h" + +#include "RenderStyle.h" + +namespace WebCore { + +StyleFlexibleBoxData::StyleFlexibleBoxData() + : flex(RenderStyle::initialBoxFlex()) + , flex_group(RenderStyle::initialBoxFlexGroup()) + , ordinal_group(RenderStyle::initialBoxOrdinalGroup()) + , align(RenderStyle::initialBoxAlign()) + , pack(RenderStyle::initialBoxPack()) + , orient(RenderStyle::initialBoxOrient()) + , lines(RenderStyle::initialBoxLines()) +{ +} + +StyleFlexibleBoxData::StyleFlexibleBoxData(const StyleFlexibleBoxData& o) + : RefCounted<StyleFlexibleBoxData>() + , flex(o.flex) + , flex_group(o.flex_group) + , ordinal_group(o.ordinal_group) + , align(o.align) + , pack(o.pack) + , orient(o.orient) + , lines(o.lines) +{ +} + +bool StyleFlexibleBoxData::operator==(const StyleFlexibleBoxData& o) const +{ + return flex == o.flex && flex_group == o.flex_group && + ordinal_group == o.ordinal_group && align == o.align && + pack == o.pack && orient == o.orient && lines == o.lines; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/StyleFlexibleBoxData.h b/Source/WebCore/rendering/style/StyleFlexibleBoxData.h new file mode 100644 index 0000000..f5d5e74 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleFlexibleBoxData.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef StyleFlexibleBoxData_h +#define StyleFlexibleBoxData_h + +#include <wtf/RefCounted.h> +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class StyleFlexibleBoxData : public RefCounted<StyleFlexibleBoxData> { +public: + static PassRefPtr<StyleFlexibleBoxData> create() { return adoptRef(new StyleFlexibleBoxData); } + PassRefPtr<StyleFlexibleBoxData> copy() const { return adoptRef(new StyleFlexibleBoxData(*this)); } + + bool operator==(const StyleFlexibleBoxData& o) const; + bool operator!=(const StyleFlexibleBoxData& o) const + { + return !(*this == o); + } + + float flex; + unsigned int flex_group; + unsigned int ordinal_group; + + unsigned align : 3; // EBoxAlignment + unsigned pack: 3; // EBoxAlignment + unsigned orient: 1; // EBoxOrient + unsigned lines : 1; // EBoxLines + +private: + StyleFlexibleBoxData(); + StyleFlexibleBoxData(const StyleFlexibleBoxData&); +}; + +} // namespace WebCore + +#endif // StyleFlexibleBoxData_h diff --git a/Source/WebCore/rendering/style/StyleGeneratedImage.cpp b/Source/WebCore/rendering/style/StyleGeneratedImage.cpp new file mode 100644 index 0000000..2322f5f --- /dev/null +++ b/Source/WebCore/rendering/style/StyleGeneratedImage.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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 "StyleGeneratedImage.h" + +#include "CSSImageGeneratorValue.h" +#include "RenderObject.h" + +namespace WebCore { + +PassRefPtr<CSSValue> StyleGeneratedImage::cssValue() const +{ + return m_generator; +} + +IntSize StyleGeneratedImage::imageSize(const RenderObject* renderer, float multiplier) const +{ + if (m_fixedSize) { + IntSize fixedSize = m_generator->fixedSize(renderer); + if (multiplier == 1.0f) + return fixedSize; + + int width = fixedSize.width() * multiplier; + int height = fixedSize.height() * multiplier; + + // Don't let images that have a width/height >= 1 shrink below 1 when zoomed. + if (fixedSize.width() > 0) + width = max(1, width); + + if (fixedSize.height() > 0) + height = max(1, height); + + return IntSize(width, height); + } + + return m_containerSize; +} + +void StyleGeneratedImage::setImageContainerSize(const IntSize& size) +{ + m_containerSize = size; +} + +void StyleGeneratedImage::addClient(RenderObject* renderer) +{ + m_generator->addClient(renderer, IntSize()); +} + +void StyleGeneratedImage::removeClient(RenderObject* renderer) +{ + m_generator->removeClient(renderer); +} + +Image* StyleGeneratedImage::image(RenderObject* renderer, const IntSize& size) const +{ + return m_generator->image(renderer, size); +} + +} diff --git a/Source/WebCore/rendering/style/StyleGeneratedImage.h b/Source/WebCore/rendering/style/StyleGeneratedImage.h new file mode 100644 index 0000000..7be1f6a --- /dev/null +++ b/Source/WebCore/rendering/style/StyleGeneratedImage.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef StyleGeneratedImage_h +#define StyleGeneratedImage_h + +#include "StyleImage.h" + +namespace WebCore { + +class CSSValue; +class CSSImageGeneratorValue; + +class StyleGeneratedImage : public StyleImage { +public: + static PassRefPtr<StyleGeneratedImage> create(CSSImageGeneratorValue* val, bool fixedSize) + { + return adoptRef(new StyleGeneratedImage(val, fixedSize)); + } + + virtual WrappedImagePtr data() const { return m_generator; } + + virtual bool isGeneratedImage() const { return true; } + + virtual PassRefPtr<CSSValue> cssValue() const; + + virtual IntSize imageSize(const RenderObject*, float multiplier) const; + virtual bool imageHasRelativeWidth() const { return !m_fixedSize; } + virtual bool imageHasRelativeHeight() const { return !m_fixedSize; } + virtual bool usesImageContainerSize() const { return !m_fixedSize; } + virtual void setImageContainerSize(const IntSize&); + virtual void addClient(RenderObject*); + virtual void removeClient(RenderObject*); + virtual Image* image(RenderObject*, const IntSize&) const; + +private: + StyleGeneratedImage(CSSImageGeneratorValue* val, bool fixedSize) + : m_generator(val) + , m_fixedSize(fixedSize) + { + } + + CSSImageGeneratorValue* m_generator; // The generator holds a reference to us. + IntSize m_containerSize; + bool m_fixedSize; +}; + +} +#endif diff --git a/Source/WebCore/rendering/style/StyleImage.h b/Source/WebCore/rendering/style/StyleImage.h new file mode 100644 index 0000000..ead8d4a --- /dev/null +++ b/Source/WebCore/rendering/style/StyleImage.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef StyleImage_h +#define StyleImage_h + +#include "CSSValue.h" +#include "IntSize.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +class CSSValue; +class Image; +class RenderObject; + +typedef void* WrappedImagePtr; + +class StyleImage : public RefCounted<StyleImage> { +public: + virtual ~StyleImage() { } + + bool operator==(const StyleImage& other) const + { + return data() == other.data(); + } + + virtual PassRefPtr<CSSValue> cssValue() const = 0; + + virtual bool canRender(float /*multiplier*/) const { return true; } + virtual bool isLoaded() const { return true; } + virtual bool errorOccurred() const { return false; } + virtual IntSize imageSize(const RenderObject*, float multiplier) const = 0; + virtual bool imageHasRelativeWidth() const = 0; + virtual bool imageHasRelativeHeight() const = 0; + virtual bool usesImageContainerSize() const = 0; + virtual void setImageContainerSize(const IntSize&) = 0; + virtual void addClient(RenderObject*) = 0; + virtual void removeClient(RenderObject*) = 0; + virtual Image* image(RenderObject*, const IntSize&) const = 0; + virtual WrappedImagePtr data() const = 0; + + virtual bool isCachedImage() const { return false; } + virtual bool isPendingImage() const { return false; } + virtual bool isGeneratedImage() const { return false; } + + static bool imagesEquivalent(StyleImage* image1, StyleImage* image2) + { + if (image1 != image2) { + if (!image1 || !image2) + return false; + return *image1 == *image2; + } + return true; + } + +protected: + StyleImage() { } +}; + +} +#endif diff --git a/Source/WebCore/rendering/style/StyleInheritedData.cpp b/Source/WebCore/rendering/style/StyleInheritedData.cpp new file mode 100644 index 0000000..874d053 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleInheritedData.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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 "StyleRareInheritedData.h" + +#include "RenderStyle.h" +#include "StyleImage.h" + +namespace WebCore { + +StyleInheritedData::StyleInheritedData() + : line_height(RenderStyle::initialLineHeight()) + , list_style_image(RenderStyle::initialListStyleImage()) + , color(RenderStyle::initialColor()) + , horizontal_border_spacing(RenderStyle::initialHorizontalBorderSpacing()) + , vertical_border_spacing(RenderStyle::initialVerticalBorderSpacing()) +{ +} + +StyleInheritedData::~StyleInheritedData() +{ +} + +StyleInheritedData::StyleInheritedData(const StyleInheritedData& o) + : RefCounted<StyleInheritedData>() + , line_height(o.line_height) + , list_style_image(o.list_style_image) + , font(o.font) + , color(o.color) + , horizontal_border_spacing(o.horizontal_border_spacing) + , vertical_border_spacing(o.vertical_border_spacing) +{ +} + +bool StyleInheritedData::operator==(const StyleInheritedData& o) const +{ + return + line_height == o.line_height && + StyleImage::imagesEquivalent(list_style_image.get(), o.list_style_image.get()) && + font == o.font && + color == o.color && + horizontal_border_spacing == o.horizontal_border_spacing && + vertical_border_spacing == o.vertical_border_spacing; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/StyleInheritedData.h b/Source/WebCore/rendering/style/StyleInheritedData.h new file mode 100644 index 0000000..ea398db --- /dev/null +++ b/Source/WebCore/rendering/style/StyleInheritedData.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef StyleInheritedData_h +#define StyleInheritedData_h + +#include "Color.h" +#include "Font.h" +#include "Length.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +class StyleImage; + +class StyleInheritedData : public RefCounted<StyleInheritedData> { +public: + static PassRefPtr<StyleInheritedData> create() { return adoptRef(new StyleInheritedData); } + PassRefPtr<StyleInheritedData> copy() const { return adoptRef(new StyleInheritedData(*this)); } + ~StyleInheritedData(); + + bool operator==(const StyleInheritedData& o) const; + bool operator!=(const StyleInheritedData& o) const + { + return !(*this == o); + } + + // could be packed in a short but doesn't + // make a difference currently because of padding + Length line_height; + + RefPtr<StyleImage> list_style_image; + + Font font; + Color color; + + short horizontal_border_spacing; + short vertical_border_spacing; +private: + StyleInheritedData(); + StyleInheritedData(const StyleInheritedData&); +}; + +} // namespace WebCore + +#endif // StyleInheritedData_h diff --git a/Source/WebCore/rendering/style/StyleMarqueeData.cpp b/Source/WebCore/rendering/style/StyleMarqueeData.cpp new file mode 100644 index 0000000..f0e824d --- /dev/null +++ b/Source/WebCore/rendering/style/StyleMarqueeData.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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 "StyleBackgroundData.h" + +#include "RenderStyle.h" + +namespace WebCore { + +StyleMarqueeData::StyleMarqueeData() + : increment(RenderStyle::initialMarqueeIncrement()) + , speed(RenderStyle::initialMarqueeSpeed()) + , loops(RenderStyle::initialMarqueeLoopCount()) + , behavior(RenderStyle::initialMarqueeBehavior()) + , direction(RenderStyle::initialMarqueeDirection()) +{ +} + +StyleMarqueeData::StyleMarqueeData(const StyleMarqueeData& o) + : RefCounted<StyleMarqueeData>() + , increment(o.increment) + , speed(o.speed) + , loops(o.loops) + , behavior(o.behavior) + , direction(o.direction) +{ +} + +bool StyleMarqueeData::operator==(const StyleMarqueeData& o) const +{ + return increment == o.increment && speed == o.speed && direction == o.direction && + behavior == o.behavior && loops == o.loops; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/StyleMarqueeData.h b/Source/WebCore/rendering/style/StyleMarqueeData.h new file mode 100644 index 0000000..5765f5d --- /dev/null +++ b/Source/WebCore/rendering/style/StyleMarqueeData.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef StyleMarqueeData_h +#define StyleMarqueeData_h + +#include "Length.h" +#include "RenderStyleConstants.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> + +namespace WebCore { + +class StyleMarqueeData : public RefCounted<StyleMarqueeData> { +public: + static PassRefPtr<StyleMarqueeData> create() { return adoptRef(new StyleMarqueeData); } + PassRefPtr<StyleMarqueeData> copy() const { return adoptRef(new StyleMarqueeData(*this)); } + + bool operator==(const StyleMarqueeData& o) const; + bool operator!=(const StyleMarqueeData& o) const + { + return !(*this == o); + } + + Length increment; + int speed; + + int loops; // -1 means infinite. + + unsigned behavior : 2; // EMarqueeBehavior + EMarqueeDirection direction : 3; // not unsigned because EMarqueeDirection has negative values + +private: + StyleMarqueeData(); + StyleMarqueeData(const StyleMarqueeData&); +}; + +} // namespace WebCore + +#endif // StyleMarqueeData_h diff --git a/Source/WebCore/rendering/style/StyleMultiColData.cpp b/Source/WebCore/rendering/style/StyleMultiColData.cpp new file mode 100644 index 0000000..3366e9f --- /dev/null +++ b/Source/WebCore/rendering/style/StyleMultiColData.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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 "StyleMultiColData.h" + +#include "RenderStyle.h" + +namespace WebCore { + +StyleMultiColData::StyleMultiColData() + : m_width(0) + , m_count(RenderStyle::initialColumnCount()) + , m_gap(0) + , m_autoWidth(true) + , m_autoCount(true) + , m_normalGap(true) + , m_columnSpan(false) + , m_breakBefore(RenderStyle::initialPageBreak()) + , m_breakAfter(RenderStyle::initialPageBreak()) + , m_breakInside(RenderStyle::initialPageBreak()) +{ +} + +StyleMultiColData::StyleMultiColData(const StyleMultiColData& o) + : RefCounted<StyleMultiColData>() + , m_width(o.m_width) + , m_count(o.m_count) + , m_gap(o.m_gap) + , m_rule(o.m_rule) + , m_autoWidth(o.m_autoWidth) + , m_autoCount(o.m_autoCount) + , m_normalGap(o.m_normalGap) + , m_columnSpan(o.m_columnSpan) + , m_breakBefore(o.m_breakBefore) + , m_breakAfter(o.m_breakAfter) + , m_breakInside(o.m_breakInside) +{ +} + +bool StyleMultiColData::operator==(const StyleMultiColData& o) const +{ + return m_width == o.m_width && m_count == o.m_count && m_gap == o.m_gap + && m_rule == o.m_rule && m_breakBefore == o.m_breakBefore + && m_autoWidth == o.m_autoWidth && m_autoCount == o.m_autoCount && m_normalGap == o.m_normalGap + && m_columnSpan == o.m_columnSpan && m_breakAfter == o.m_breakAfter && m_breakInside == o.m_breakInside; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/StyleMultiColData.h b/Source/WebCore/rendering/style/StyleMultiColData.h new file mode 100644 index 0000000..9948846 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleMultiColData.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef StyleMultiColData_h +#define StyleMultiColData_h + +#include "BorderValue.h" +#include "Length.h" +#include "RenderStyleConstants.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> + +namespace WebCore { + +// CSS3 Multi Column Layout + +class StyleMultiColData : public RefCounted<StyleMultiColData> { +public: + static PassRefPtr<StyleMultiColData> create() { return adoptRef(new StyleMultiColData); } + PassRefPtr<StyleMultiColData> copy() const { return adoptRef(new StyleMultiColData(*this)); } + + bool operator==(const StyleMultiColData& o) const; + bool operator!=(const StyleMultiColData &o) const + { + return !(*this == o); + } + + unsigned short ruleWidth() const + { + if (m_rule.style() == BNONE || m_rule.style() == BHIDDEN) + return 0; + return m_rule.width(); + } + + float m_width; + unsigned short m_count; + float m_gap; + BorderValue m_rule; + + bool m_autoWidth : 1; + bool m_autoCount : 1; + bool m_normalGap : 1; + bool m_columnSpan : 1; + unsigned m_breakBefore : 2; // EPageBreak + unsigned m_breakAfter : 2; // EPageBreak + unsigned m_breakInside : 2; // EPageBreak + +private: + StyleMultiColData(); + StyleMultiColData(const StyleMultiColData&); +}; + +} // namespace WebCore + +#endif // StyleMultiColData_h diff --git a/Source/WebCore/rendering/style/StylePendingImage.h b/Source/WebCore/rendering/style/StylePendingImage.h new file mode 100644 index 0000000..b0c9b01 --- /dev/null +++ b/Source/WebCore/rendering/style/StylePendingImage.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef StylePendingImage_h +#define StylePendingImage_h + +#include "StyleImage.h" + +namespace WebCore { + +// StylePendingImage is a placeholder StyleImage that is entered into the RenderStyle during +// style resolution, in order to avoid loading images that are not referenced by the final style. +// They should never exist in a RenderStyle after it has been returned from the style selector. + +class StylePendingImage : public StyleImage { +public: + static PassRefPtr<StylePendingImage> create(CSSImageValue* value) { return adoptRef(new StylePendingImage(value)); } + + virtual WrappedImagePtr data() const { return m_value; } + + virtual bool isPendingImage() const { return true; } + + virtual PassRefPtr<CSSValue> cssValue() const { return m_value; } + CSSImageValue* cssImageValue() const { return m_value; } + + virtual IntSize imageSize(const RenderObject*, float /*multiplier*/) const { return IntSize(); } + virtual bool imageHasRelativeWidth() const { return false; } + virtual bool imageHasRelativeHeight() const { return false; } + virtual bool usesImageContainerSize() const { return false; } + virtual void setImageContainerSize(const IntSize&) { } + virtual void addClient(RenderObject*) { } + virtual void removeClient(RenderObject*) { } + virtual Image* image(RenderObject*, const IntSize&) const + { + ASSERT_NOT_REACHED(); + return 0; + } + +private: + StylePendingImage(CSSImageValue* value) + : m_value(value) + { + } + + CSSImageValue* m_value; // Not retained; it owns us. +}; + +} +#endif diff --git a/Source/WebCore/rendering/style/StyleRareInheritedData.cpp b/Source/WebCore/rendering/style/StyleRareInheritedData.cpp new file mode 100644 index 0000000..6138df2 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleRareInheritedData.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * 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 "StyleRareInheritedData.h" + +#include "RenderStyle.h" +#include "RenderStyleConstants.h" + +namespace WebCore { + +StyleRareInheritedData::StyleRareInheritedData() + : textStrokeWidth(RenderStyle::initialTextStrokeWidth()) +#ifdef ANDROID_CSS_RING + , ringFillColor(RenderStyle::initialRingFillColor()) + , ringInnerWidth(RenderStyle::initialRingInnerWidth()) + , ringOuterWidth(RenderStyle::initialRingOuterWidth()) + , ringOutset(RenderStyle::initialRingOutset()) + , ringPressedInnerColor(RenderStyle::initialRingPressedInnerColor()) + , ringPressedOuterColor(RenderStyle::initialRingPressedOuterColor()) + , ringRadius(RenderStyle::initialRingRadius()) + , ringSelectedInnerColor(RenderStyle::initialRingSelectedInnerColor()) + , ringSelectedOuterColor(RenderStyle::initialRingSelectedOuterColor()) +#endif +#ifdef ANDROID_CSS_TAP_HIGHLIGHT_COLOR + , tapHighlightColor(RenderStyle::initialTapHighlightColor()) +#endif + , textShadow(0) + , indent(RenderStyle::initialTextIndent()) + , m_effectiveZoom(RenderStyle::initialZoom()) + , widows(RenderStyle::initialWidows()) + , orphans(RenderStyle::initialOrphans()) + , textSecurity(RenderStyle::initialTextSecurity()) + , userModify(READ_ONLY) + , wordBreak(RenderStyle::initialWordBreak()) + , wordWrap(RenderStyle::initialWordWrap()) + , nbspMode(NBNORMAL) + , khtmlLineBreak(LBNORMAL) + , textSizeAdjust(RenderStyle::initialTextSizeAdjust()) + , resize(RenderStyle::initialResize()) + , userSelect(RenderStyle::initialUserSelect()) + , colorSpace(ColorSpaceDeviceRGB) + , speak(SpeakNormal) + , hyphens(HyphensManual) + , textEmphasisFill(TextEmphasisFillFilled) + , textEmphasisMark(TextEmphasisMarkNone) + , textEmphasisPosition(TextEmphasisPositionOver) +{ +} + +StyleRareInheritedData::StyleRareInheritedData(const StyleRareInheritedData& o) + : RefCounted<StyleRareInheritedData>() + , textStrokeColor(o.textStrokeColor) + , textStrokeWidth(o.textStrokeWidth) + , textFillColor(o.textFillColor) + , textEmphasisColor(o.textEmphasisColor) +#ifdef ANDROID_CSS_RING + , ringFillColor(o.ringFillColor) + , ringInnerWidth(o.ringInnerWidth) + , ringOuterWidth(o.ringOuterWidth) + , ringOutset(o.ringOutset) + , ringPressedInnerColor(o.ringPressedInnerColor) + , ringPressedOuterColor(o.ringPressedOuterColor) + , ringRadius(o.ringRadius) + , ringSelectedInnerColor(o.ringSelectedInnerColor) + , ringSelectedOuterColor(o.ringSelectedOuterColor) +#endif +#ifdef ANDROID_CSS_TAP_HIGHLIGHT_COLOR + , tapHighlightColor(o.tapHighlightColor) +#endif + , textShadow(o.textShadow ? new ShadowData(*o.textShadow) : 0) + , highlight(o.highlight) + , cursorData(o.cursorData) + , indent(o.indent) + , m_effectiveZoom(o.m_effectiveZoom) + , widows(o.widows) + , orphans(o.orphans) + , textSecurity(o.textSecurity) + , userModify(o.userModify) + , wordBreak(o.wordBreak) + , wordWrap(o.wordWrap) + , nbspMode(o.nbspMode) + , khtmlLineBreak(o.khtmlLineBreak) + , textSizeAdjust(o.textSizeAdjust) + , resize(o.resize) + , userSelect(o.userSelect) + , colorSpace(o.colorSpace) + , speak(o.speak) + , hyphens(o.hyphens) + , textEmphasisFill(o.textEmphasisFill) + , textEmphasisMark(o.textEmphasisMark) + , textEmphasisPosition(o.textEmphasisPosition) + , hyphenationString(o.hyphenationString) + , hyphenationLocale(o.hyphenationLocale) + , textEmphasisCustomMark(o.textEmphasisCustomMark) +{ +} + +StyleRareInheritedData::~StyleRareInheritedData() +{ + delete textShadow; +} + +static bool cursorDataEquivalent(const CursorList* c1, const CursorList* c2) +{ + if (c1 == c2) + return true; + if ((!c1 && c2) || (c1 && !c2)) + return false; + return (*c1 == *c2); +} + +bool StyleRareInheritedData::operator==(const StyleRareInheritedData& o) const +{ + return textStrokeColor == o.textStrokeColor + && textStrokeWidth == o.textStrokeWidth + && textFillColor == o.textFillColor + && textEmphasisColor == o.textEmphasisColor + && shadowDataEquivalent(o) + && highlight == o.highlight + && cursorDataEquivalent(cursorData.get(), o.cursorData.get()) + && indent == o.indent + && m_effectiveZoom == o.m_effectiveZoom + && widows == o.widows + && orphans == o.orphans + && textSecurity == o.textSecurity + && userModify == o.userModify + && wordBreak == o.wordBreak + && wordWrap == o.wordWrap + && nbspMode == o.nbspMode + && khtmlLineBreak == o.khtmlLineBreak + && textSizeAdjust == o.textSizeAdjust +#ifdef ANDROID_CSS_RING + && ringFillColor == o.ringFillColor + && ringInnerWidth == o.ringInnerWidth + && ringOuterWidth == o.ringOuterWidth + && ringOutset == o.ringOutset + && ringPressedInnerColor == o.ringPressedInnerColor + && ringPressedOuterColor == o.ringPressedOuterColor + && ringRadius == o.ringRadius + && ringSelectedInnerColor == o.ringSelectedInnerColor + && ringSelectedOuterColor == o.ringSelectedOuterColor +#endif +#ifdef ANDROID_CSS_TAP_HIGHLIGHT_COLOR + && tapHighlightColor == o.tapHighlightColor +#endif + && resize == o.resize + && userSelect == o.userSelect + && colorSpace == o.colorSpace + && speak == o.speak + && hyphens == o.hyphens + && textEmphasisFill == o.textEmphasisFill + && textEmphasisMark == o.textEmphasisMark + && textEmphasisPosition == o.textEmphasisPosition + && hyphenationString == o.hyphenationString + && hyphenationLocale == o.hyphenationLocale + && textEmphasisCustomMark == o.textEmphasisCustomMark; +} + +bool StyleRareInheritedData::shadowDataEquivalent(const StyleRareInheritedData& o) const +{ + if ((!textShadow && o.textShadow) || (textShadow && !o.textShadow)) + return false; + if (textShadow && o.textShadow && (*textShadow != *o.textShadow)) + return false; + return true; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/StyleRareInheritedData.h b/Source/WebCore/rendering/style/StyleRareInheritedData.h new file mode 100644 index 0000000..a370934 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleRareInheritedData.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef StyleRareInheritedData_h +#define StyleRareInheritedData_h + +#include "Color.h" +#include "Length.h" +#include <wtf/RefCounted.h> +#include <wtf/PassRefPtr.h> +#include <wtf/text/AtomicString.h> + +namespace WebCore { + +class CursorList; +class ShadowData; + +// This struct is for rarely used inherited CSS3, CSS2, and WebKit-specific properties. +// By grouping them together, we save space, and only allocate this object when someone +// actually uses one of these properties. +class StyleRareInheritedData : public RefCounted<StyleRareInheritedData> { +public: + static PassRefPtr<StyleRareInheritedData> create() { return adoptRef(new StyleRareInheritedData); } + PassRefPtr<StyleRareInheritedData> copy() const { return adoptRef(new StyleRareInheritedData(*this)); } + ~StyleRareInheritedData(); + + bool operator==(const StyleRareInheritedData& o) const; + bool operator!=(const StyleRareInheritedData& o) const + { + return !(*this == o); + } + bool shadowDataEquivalent(const StyleRareInheritedData&) const; + + Color textStrokeColor; + float textStrokeWidth; + Color textFillColor; + Color textEmphasisColor; + +#ifdef ANDROID_CSS_RING + Color ringFillColor; + Length ringInnerWidth; + Length ringOuterWidth; + Length ringOutset; + Color ringPressedInnerColor; + Color ringPressedOuterColor; + Length ringRadius; + Color ringSelectedInnerColor; + Color ringSelectedOuterColor; +#endif +#ifdef ANDROID_CSS_TAP_HIGHLIGHT_COLOR + Color tapHighlightColor; +#endif + + ShadowData* textShadow; // Our text shadow information for shadowed text drawing. + AtomicString highlight; // Apple-specific extension for custom highlight rendering. + + RefPtr<CursorList> cursorData; + Length indent; + float m_effectiveZoom; + + // Paged media properties. + short widows; + short orphans; + + unsigned textSecurity : 2; // ETextSecurity + unsigned userModify : 2; // EUserModify (editing) + unsigned wordBreak : 2; // EWordBreak + unsigned wordWrap : 1; // EWordWrap + unsigned nbspMode : 1; // ENBSPMode + unsigned khtmlLineBreak : 1; // EKHTMLLineBreak + bool textSizeAdjust : 1; // An Apple extension. + unsigned resize : 2; // EResize + unsigned userSelect : 1; // EUserSelect + unsigned colorSpace : 1; // ColorSpace + unsigned speak : 3; // ESpeak + unsigned hyphens : 2; // Hyphens + unsigned textEmphasisFill : 1; // TextEmphasisFill + unsigned textEmphasisMark : 3; // TextEmphasisMark + unsigned textEmphasisPosition : 1; // TextEmphasisPosition + + AtomicString hyphenationString; + AtomicString hyphenationLocale; + + AtomicString textEmphasisCustomMark; + +private: + StyleRareInheritedData(); + StyleRareInheritedData(const StyleRareInheritedData&); +}; + +} // namespace WebCore + +#endif // StyleRareInheritedData_h diff --git a/Source/WebCore/rendering/style/StyleRareNonInheritedData.cpp b/Source/WebCore/rendering/style/StyleRareNonInheritedData.cpp new file mode 100644 index 0000000..42cf966 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleRareNonInheritedData.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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 "StyleRareNonInheritedData.h" + +#include "CSSStyleSelector.h" +#include "ContentData.h" +#include "RenderCounter.h" +#include "RenderStyle.h" +#include "StyleImage.h" + +namespace WebCore { + +StyleRareNonInheritedData::StyleRareNonInheritedData() + : lineClamp(RenderStyle::initialLineClamp()) + , opacity(RenderStyle::initialOpacity()) + , m_content(0) + , m_counterDirectives(0) + , userDrag(RenderStyle::initialUserDrag()) + , textOverflow(RenderStyle::initialTextOverflow()) + , marginBeforeCollapse(MCOLLAPSE) + , marginAfterCollapse(MCOLLAPSE) + , matchNearestMailBlockquoteColor(RenderStyle::initialMatchNearestMailBlockquoteColor()) + , m_appearance(RenderStyle::initialAppearance()) + , m_borderFit(RenderStyle::initialBorderFit()) + , m_textCombine(RenderStyle::initialTextCombine()) + , m_counterIncrement(0) + , m_counterReset(0) +#if USE(ACCELERATED_COMPOSITING) + , m_runningAcceleratedAnimation(false) +#endif + , m_boxShadow(0) + , m_animations(0) + , m_transitions(0) + , m_mask(FillLayer(MaskFillLayer)) + , m_transformStyle3D(RenderStyle::initialTransformStyle3D()) + , m_backfaceVisibility(RenderStyle::initialBackfaceVisibility()) + , m_perspective(RenderStyle::initialPerspective()) + , m_perspectiveOriginX(RenderStyle::initialPerspectiveOriginX()) + , m_perspectiveOriginY(RenderStyle::initialPerspectiveOriginY()) + , m_pageSize() + , m_pageSizeType(PAGE_SIZE_AUTO) +{ +} + +StyleRareNonInheritedData::StyleRareNonInheritedData(const StyleRareNonInheritedData& o) + : RefCounted<StyleRareNonInheritedData>() + , lineClamp(o.lineClamp) + , opacity(o.opacity) + , flexibleBox(o.flexibleBox) + , marquee(o.marquee) + , m_multiCol(o.m_multiCol) + , m_transform(o.m_transform) + , m_content(0) + , m_counterDirectives(0) + , userDrag(o.userDrag) + , textOverflow(o.textOverflow) + , marginBeforeCollapse(o.marginBeforeCollapse) + , marginAfterCollapse(o.marginAfterCollapse) + , matchNearestMailBlockquoteColor(o.matchNearestMailBlockquoteColor) + , m_appearance(o.m_appearance) + , m_borderFit(o.m_borderFit) + , m_textCombine(o.m_textCombine) + , m_counterIncrement(o.m_counterIncrement) + , m_counterReset(o.m_counterReset) +#if USE(ACCELERATED_COMPOSITING) + , m_runningAcceleratedAnimation(o.m_runningAcceleratedAnimation) +#endif + , m_boxShadow(o.m_boxShadow ? new ShadowData(*o.m_boxShadow) : 0) + , m_boxReflect(o.m_boxReflect) + , m_animations(o.m_animations ? new AnimationList(*o.m_animations) : 0) + , m_transitions(o.m_transitions ? new AnimationList(*o.m_transitions) : 0) + , m_mask(o.m_mask) + , m_maskBoxImage(o.m_maskBoxImage) + , m_transformStyle3D(o.m_transformStyle3D) + , m_backfaceVisibility(o.m_backfaceVisibility) + , m_perspective(o.m_perspective) + , m_perspectiveOriginX(o.m_perspectiveOriginX) + , m_perspectiveOriginY(o.m_perspectiveOriginY) + , m_pageSize(o.m_pageSize) + , m_pageSizeType(o.m_pageSizeType) +{ +} + +StyleRareNonInheritedData::~StyleRareNonInheritedData() +{ +} + +bool StyleRareNonInheritedData::operator==(const StyleRareNonInheritedData& o) const +{ + return lineClamp == o.lineClamp +#if ENABLE(DASHBOARD_SUPPORT) + && m_dashboardRegions == o.m_dashboardRegions +#endif + && opacity == o.opacity + && flexibleBox == o.flexibleBox + && marquee == o.marquee + && m_multiCol == o.m_multiCol + && m_transform == o.m_transform + && contentDataEquivalent(o) + && m_counterDirectives == o.m_counterDirectives + && userDrag == o.userDrag + && textOverflow == o.textOverflow + && marginBeforeCollapse == o.marginBeforeCollapse + && marginAfterCollapse == o.marginAfterCollapse + && matchNearestMailBlockquoteColor == o.matchNearestMailBlockquoteColor + && m_appearance == o.m_appearance + && m_borderFit == o.m_borderFit + && m_textCombine == o.m_textCombine + && m_counterIncrement == o.m_counterIncrement + && m_counterReset == o.m_counterReset +#if USE(ACCELERATED_COMPOSITING) + && !m_runningAcceleratedAnimation && !o.m_runningAcceleratedAnimation +#endif + && shadowDataEquivalent(o) + && reflectionDataEquivalent(o) + && animationDataEquivalent(o) + && transitionDataEquivalent(o) + && m_mask == o.m_mask + && m_maskBoxImage == o.m_maskBoxImage + && (m_transformStyle3D == o.m_transformStyle3D) + && (m_backfaceVisibility == o.m_backfaceVisibility) + && (m_perspective == o.m_perspective) + && (m_perspectiveOriginX == o.m_perspectiveOriginX) + && (m_perspectiveOriginY == o.m_perspectiveOriginY) + && (m_pageSize == o.m_pageSize) + && (m_pageSizeType == o.m_pageSizeType) + ; +} + +bool StyleRareNonInheritedData::contentDataEquivalent(const StyleRareNonInheritedData& o) const +{ + ContentData* c1 = m_content.get(); + ContentData* c2 = o.m_content.get(); + + while (c1 && c2) { + if (!c1->dataEquivalent(*c2)) + return false; + c1 = c1->next(); + c2 = c2->next(); + } + + return !c1 && !c2; +} + +bool StyleRareNonInheritedData::shadowDataEquivalent(const StyleRareNonInheritedData& o) const +{ + if ((!m_boxShadow && o.m_boxShadow) || (m_boxShadow && !o.m_boxShadow)) + return false; + if (m_boxShadow && o.m_boxShadow && (*m_boxShadow != *o.m_boxShadow)) + return false; + return true; +} + +bool StyleRareNonInheritedData::reflectionDataEquivalent(const StyleRareNonInheritedData& o) const +{ + if (m_boxReflect != o.m_boxReflect) { + if (!m_boxReflect || !o.m_boxReflect) + return false; + return *m_boxReflect == *o.m_boxReflect; + } + return true; + +} + +bool StyleRareNonInheritedData::animationDataEquivalent(const StyleRareNonInheritedData& o) const +{ + if ((!m_animations && o.m_animations) || (m_animations && !o.m_animations)) + return false; + if (m_animations && o.m_animations && (*m_animations != *o.m_animations)) + return false; + return true; +} + +bool StyleRareNonInheritedData::transitionDataEquivalent(const StyleRareNonInheritedData& o) const +{ + if ((!m_transitions && o.m_transitions) || (m_transitions && !o.m_transitions)) + return false; + if (m_transitions && o.m_transitions && (*m_transitions != *o.m_transitions)) + return false; + return true; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/StyleRareNonInheritedData.h b/Source/WebCore/rendering/style/StyleRareNonInheritedData.h new file mode 100644 index 0000000..89437f6 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleRareNonInheritedData.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef StyleRareNonInheritedData_h +#define StyleRareNonInheritedData_h + +#include "CounterDirectives.h" +#include "CursorData.h" +#include "DataRef.h" +#include "FillLayer.h" +#include "LineClampValue.h" +#include "NinePieceImage.h" +#include "StyleTransformData.h" +#include <wtf/OwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class AnimationList; +class CSSStyleSelector; +class ShadowData; +class StyleFlexibleBoxData; +class StyleMarqueeData; +class StyleMultiColData; +class StyleReflection; +class StyleTransformData; + +struct ContentData; +struct LengthSize; + +#if ENABLE(DASHBOARD_SUPPORT) +struct StyleDashboardRegion; +#endif + +// Page size type. +// StyleRareNonInheritedData::m_pageSize is meaningful only when +// StyleRareNonInheritedData::m_pageSizeType is PAGE_SIZE_RESOLVED. +enum PageSizeType { + PAGE_SIZE_AUTO, // size: auto + PAGE_SIZE_AUTO_LANDSCAPE, // size: landscape + PAGE_SIZE_AUTO_PORTRAIT, // size: portrait + PAGE_SIZE_RESOLVED // Size is fully resolved. +}; + +// This struct is for rarely used non-inherited CSS3, CSS2, and WebKit-specific properties. +// By grouping them together, we save space, and only allocate this object when someone +// actually uses one of these properties. +class StyleRareNonInheritedData : public RefCounted<StyleRareNonInheritedData> { +public: + static PassRefPtr<StyleRareNonInheritedData> create() { return adoptRef(new StyleRareNonInheritedData); } + PassRefPtr<StyleRareNonInheritedData> copy() const { return adoptRef(new StyleRareNonInheritedData(*this)); } + ~StyleRareNonInheritedData(); + + bool operator==(const StyleRareNonInheritedData&) const; + bool operator!=(const StyleRareNonInheritedData& o) const { return !(*this == o); } + + bool contentDataEquivalent(const StyleRareNonInheritedData& o) const; + bool shadowDataEquivalent(const StyleRareNonInheritedData& o) const; + bool reflectionDataEquivalent(const StyleRareNonInheritedData& o) const; + bool animationDataEquivalent(const StyleRareNonInheritedData&) const; + bool transitionDataEquivalent(const StyleRareNonInheritedData&) const; + + LineClampValue lineClamp; // An Apple extension. +#if ENABLE(DASHBOARD_SUPPORT) + Vector<StyleDashboardRegion> m_dashboardRegions; +#endif + float opacity; // Whether or not we're transparent. + + DataRef<StyleFlexibleBoxData> flexibleBox; // Flexible box properties + DataRef<StyleMarqueeData> marquee; // Marquee properties + DataRef<StyleMultiColData> m_multiCol; // CSS3 multicol properties + DataRef<StyleTransformData> m_transform; // Transform properties (rotate, scale, skew, etc.) + + OwnPtr<ContentData> m_content; + OwnPtr<CounterDirectiveMap> m_counterDirectives; + + unsigned userDrag : 2; // EUserDrag + bool textOverflow : 1; // Whether or not lines that spill out should be truncated with "..." + unsigned marginBeforeCollapse : 2; // EMarginCollapse + unsigned marginAfterCollapse : 2; // EMarginCollapse + unsigned matchNearestMailBlockquoteColor : 1; // EMatchNearestMailBlockquoteColor, FIXME: This property needs to be eliminated. It should never have been added. + unsigned m_appearance : 6; // EAppearance + unsigned m_borderFit : 1; // EBorderFit + unsigned m_textCombine : 1; // CSS3 text-combine properties + + short m_counterIncrement; + short m_counterReset; + +#if USE(ACCELERATED_COMPOSITING) + bool m_runningAcceleratedAnimation : 1; +#endif + OwnPtr<ShadowData> m_boxShadow; // For box-shadow decorations. + + RefPtr<StyleReflection> m_boxReflect; + + OwnPtr<AnimationList> m_animations; + OwnPtr<AnimationList> m_transitions; + + FillLayer m_mask; + NinePieceImage m_maskBoxImage; + + ETransformStyle3D m_transformStyle3D; + EBackfaceVisibility m_backfaceVisibility; + float m_perspective; + Length m_perspectiveOriginX; + Length m_perspectiveOriginY; + + LengthSize m_pageSize; + PageSizeType m_pageSizeType; + +private: + StyleRareNonInheritedData(); + StyleRareNonInheritedData(const StyleRareNonInheritedData&); +}; + +} // namespace WebCore + +#endif // StyleRareNonInheritedData_h diff --git a/Source/WebCore/rendering/style/StyleReflection.h b/Source/WebCore/rendering/style/StyleReflection.h new file mode 100644 index 0000000..455d1b7 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleReflection.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef StyleReflection_h +#define StyleReflection_h + +#include "CSSReflectionDirection.h" +#include "Length.h" +#include "NinePieceImage.h" +#include <wtf/RefCounted.h> + +namespace WebCore { + +class StyleReflection : public RefCounted<StyleReflection> { +public: + static PassRefPtr<StyleReflection> create() + { + return adoptRef(new StyleReflection); + } + + bool operator==(const StyleReflection& o) const + { + return m_direction == o.m_direction && m_offset == o.m_offset && m_mask == o.m_mask; + } + bool operator!=(const StyleReflection& o) const { return !(*this == o); } + + CSSReflectionDirection direction() const { return m_direction; } + Length offset() const { return m_offset; } + const NinePieceImage& mask() const { return m_mask; } + + void setDirection(CSSReflectionDirection dir) { m_direction = dir; } + void setOffset(const Length& l) { m_offset = l; } + void setMask(const NinePieceImage& image) { m_mask = image; } + +private: + StyleReflection() + : m_direction(ReflectionBelow) + , m_offset(0, Fixed) + { + } + + CSSReflectionDirection m_direction; + Length m_offset; + NinePieceImage m_mask; +}; + +} // namespace WebCore + +#endif // StyleReflection_h diff --git a/Source/WebCore/rendering/style/StyleSurroundData.cpp b/Source/WebCore/rendering/style/StyleSurroundData.cpp new file mode 100644 index 0000000..8d5e79c --- /dev/null +++ b/Source/WebCore/rendering/style/StyleSurroundData.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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 "StyleSurroundData.h" + +namespace WebCore { + +StyleSurroundData::StyleSurroundData() + : margin(Fixed) + , padding(Fixed) +{ +} + +StyleSurroundData::StyleSurroundData(const StyleSurroundData& o) + : RefCounted<StyleSurroundData>() + , offset(o.offset) + , margin(o.margin) + , padding(o.padding) + , border(o.border) +{ +} + +bool StyleSurroundData::operator==(const StyleSurroundData& o) const +{ + return offset == o.offset && margin == o.margin && padding == o.padding && border == o.border; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/StyleSurroundData.h b/Source/WebCore/rendering/style/StyleSurroundData.h new file mode 100644 index 0000000..b8f21e4 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleSurroundData.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef StyleSurroundData_h +#define StyleSurroundData_h + +#include "BorderData.h" +#include "LengthBox.h" +#include <wtf/RefCounted.h> +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class StyleSurroundData : public RefCounted<StyleSurroundData> { +public: + static PassRefPtr<StyleSurroundData> create() { return adoptRef(new StyleSurroundData); } + PassRefPtr<StyleSurroundData> copy() const { return adoptRef(new StyleSurroundData(*this)); } + + bool operator==(const StyleSurroundData& o) const; + bool operator!=(const StyleSurroundData& o) const + { + return !(*this == o); + } + + LengthBox offset; + LengthBox margin; + LengthBox padding; + BorderData border; + +private: + StyleSurroundData(); + StyleSurroundData(const StyleSurroundData&); +}; + +} // namespace WebCore + +#endif // StyleSurroundData_h diff --git a/Source/WebCore/rendering/style/StyleTransformData.cpp b/Source/WebCore/rendering/style/StyleTransformData.cpp new file mode 100644 index 0000000..2baebf9 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleTransformData.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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 "StyleTransformData.h" + +#include "RenderStyle.h" + +namespace WebCore { + +StyleTransformData::StyleTransformData() + : m_operations(RenderStyle::initialTransform()) + , m_x(RenderStyle::initialTransformOriginX()) + , m_y(RenderStyle::initialTransformOriginY()) + , m_z(RenderStyle::initialTransformOriginZ()) +{ +} + +StyleTransformData::StyleTransformData(const StyleTransformData& o) + : RefCounted<StyleTransformData>() + , m_operations(o.m_operations) + , m_x(o.m_x) + , m_y(o.m_y) + , m_z(o.m_z) +{ +} + +bool StyleTransformData::operator==(const StyleTransformData& o) const +{ + return m_x == o.m_x && m_y == o.m_y && m_z == o.m_z && m_operations == o.m_operations; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/StyleTransformData.h b/Source/WebCore/rendering/style/StyleTransformData.h new file mode 100644 index 0000000..6039824 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleTransformData.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef StyleTransformData_h +#define StyleTransformData_h + +#include "Length.h" +#include "TransformOperations.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> + +namespace WebCore { + +class StyleTransformData : public RefCounted<StyleTransformData> { +public: + static PassRefPtr<StyleTransformData> create() { return adoptRef(new StyleTransformData); } + PassRefPtr<StyleTransformData> copy() const { return adoptRef(new StyleTransformData(*this)); } + + bool operator==(const StyleTransformData& o) const; + bool operator!=(const StyleTransformData& o) const + { + return !(*this == o); + } + + TransformOperations m_operations; + Length m_x; + Length m_y; + float m_z; + +private: + StyleTransformData(); + StyleTransformData(const StyleTransformData&); +}; + +} // namespace WebCore + +#endif // StyleTransformData_h diff --git a/Source/WebCore/rendering/style/StyleVisualData.cpp b/Source/WebCore/rendering/style/StyleVisualData.cpp new file mode 100644 index 0000000..14996c9 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleVisualData.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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 "StyleVisualData.h" + +#include "RenderStyle.h" + +namespace WebCore { + +StyleVisualData::StyleVisualData() + : hasClip(false) + , textDecoration(RenderStyle::initialTextDecoration()) + , m_zoom(RenderStyle::initialZoom()) +{ +} + +StyleVisualData::~StyleVisualData() +{ +} + +StyleVisualData::StyleVisualData(const StyleVisualData& o) + : RefCounted<StyleVisualData>() + , clip(o.clip) + , hasClip(o.hasClip) + , textDecoration(o.textDecoration) + , m_zoom(RenderStyle::initialZoom()) +{ +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/StyleVisualData.h b/Source/WebCore/rendering/style/StyleVisualData.h new file mode 100644 index 0000000..d1f0f83 --- /dev/null +++ b/Source/WebCore/rendering/style/StyleVisualData.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.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. + * + */ + +#ifndef StyleVisualData_h +#define StyleVisualData_h + +#include "LengthBox.h" +#include <wtf/RefCounted.h> +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class StyleVisualData : public RefCounted<StyleVisualData> { +public: + static PassRefPtr<StyleVisualData> create() { return adoptRef(new StyleVisualData); } + PassRefPtr<StyleVisualData> copy() const { return adoptRef(new StyleVisualData(*this)); } + ~StyleVisualData(); + + bool operator==(const StyleVisualData& o) const + { + return ( clip == o.clip && + hasClip == o.hasClip && + textDecoration == o.textDecoration && + m_zoom == o.m_zoom); + } + bool operator!=(const StyleVisualData& o) const { return !(*this == o); } + + LengthBox clip; + bool hasClip : 1; + unsigned textDecoration : 4; // Text decorations defined *only* by this element. + + float m_zoom; + +private: + StyleVisualData(); + StyleVisualData(const StyleVisualData&); +}; + +} // namespace WebCore + +#endif // StyleVisualData_h diff --git a/Source/WebCore/rendering/svg/RenderSVGInline.cpp b/Source/WebCore/rendering/svg/RenderSVGInline.cpp new file mode 100644 index 0000000..4d0c533 --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGInline.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * Copyright (C) 2006 Apple Inc. All rights reserved. + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGInline.h" + +#include "RenderSVGResource.h" +#include "RenderSVGText.h" +#include "SVGInlineFlowBox.h" + +namespace WebCore { + +RenderSVGInline::RenderSVGInline(Node* n) + : RenderInline(n) +{ +} + +InlineFlowBox* RenderSVGInline::createInlineFlowBox() +{ + InlineFlowBox* box = new (renderArena()) SVGInlineFlowBox(this); + box->setHasVirtualLogicalHeight(); + return box; +} + +FloatRect RenderSVGInline::objectBoundingBox() const +{ + if (const RenderObject* object = RenderSVGText::locateRenderSVGTextAncestor(this)) + return object->objectBoundingBox(); + + return FloatRect(); +} + +FloatRect RenderSVGInline::strokeBoundingBox() const +{ + if (const RenderObject* object = RenderSVGText::locateRenderSVGTextAncestor(this)) + return object->strokeBoundingBox(); + + return FloatRect(); +} + +FloatRect RenderSVGInline::repaintRectInLocalCoordinates() const +{ + if (const RenderObject* object = RenderSVGText::locateRenderSVGTextAncestor(this)) + return object->repaintRectInLocalCoordinates(); + + return FloatRect(); +} + +IntRect RenderSVGInline::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) +{ + return SVGRenderSupport::clippedOverflowRectForRepaint(this, repaintContainer); +} + +void RenderSVGInline::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& repaintRect, bool fixed) +{ + SVGRenderSupport::computeRectForRepaint(this, repaintContainer, repaintRect, fixed); +} + +void RenderSVGInline::mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool useTransforms, bool fixed, TransformState& transformState) const +{ + SVGRenderSupport::mapLocalToContainer(this, repaintContainer, useTransforms, fixed, transformState); +} + +void RenderSVGInline::absoluteQuads(Vector<FloatQuad>& quads) +{ + RenderObject* object = RenderSVGText::locateRenderSVGTextAncestor(this); + if (!object) + return; + + FloatRect textBoundingBox = object->strokeBoundingBox(); + for (InlineFlowBox* box = firstLineBox(); box; box = box->nextLineBox()) + quads.append(localToAbsoluteQuad(FloatRect(textBoundingBox.x() + box->x(), textBoundingBox.y() + box->y(), box->logicalWidth(), box->logicalHeight()))); +} + +void RenderSVGInline::destroy() +{ + SVGResourcesCache::clientDestroyed(this); + RenderInline::destroy(); +} + +void RenderSVGInline::styleWillChange(StyleDifference diff, const RenderStyle* newStyle) +{ + if (diff == StyleDifferenceLayout) + setNeedsBoundariesUpdate(); + RenderInline::styleWillChange(diff, newStyle); +} + +void RenderSVGInline::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderInline::styleDidChange(diff, oldStyle); + SVGResourcesCache::clientStyleChanged(this, diff, style()); +} + +void RenderSVGInline::updateFromElement() +{ + RenderInline::updateFromElement(); + SVGResourcesCache::clientUpdatedFromElement(this, style()); +} + + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/RenderSVGInline.h b/Source/WebCore/rendering/svg/RenderSVGInline.h new file mode 100644 index 0000000..d7b7e66 --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGInline.h @@ -0,0 +1,68 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * (C) 2006 Apple Computer Inc. + * + * 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. + * + */ + +#ifndef RenderSVGInline_h +#define RenderSVGInline_h + +#if ENABLE(SVG) +#include "RenderInline.h" + +#include "SVGRenderSupport.h" + +namespace WebCore { + +class RenderSVGInline : public RenderInline { +public: + explicit RenderSVGInline(Node*); + + virtual const char* renderName() const { return "RenderSVGInline"; } + virtual bool requiresLayer() const { return false; } + virtual bool isSVGInline() const { return true; } + + // Chapter 10.4 of the SVG Specification say that we should use the + // object bounding box of the parent text element. + // We search for the root text element and take its bounding box. + // It is also necessary to take the stroke and repaint rect of + // this element, since we need it for filters. + virtual FloatRect objectBoundingBox() const; + virtual FloatRect strokeBoundingBox() const; + virtual FloatRect repaintRectInLocalCoordinates() const; + + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer); + virtual void computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect&, bool fixed = false); + virtual void mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool useTransforms, bool fixed, TransformState&) const; + virtual void absoluteQuads(Vector<FloatQuad>&); + +private: + virtual InlineFlowBox* createInlineFlowBox(); + + virtual void destroy(); + virtual void styleWillChange(StyleDifference, const RenderStyle* newStyle); + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + virtual void updateFromElement(); +}; + +} + +#endif // ENABLE(SVG) +#endif // !RenderSVGTSpan_H diff --git a/Source/WebCore/rendering/svg/RenderSVGInlineText.cpp b/Source/WebCore/rendering/svg/RenderSVGInlineText.cpp new file mode 100644 index 0000000..b791f3e --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGInlineText.cpp @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * Copyright (C) 2006 Apple Computer Inc. + * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) 2008 Rob Buis <buis@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGInlineText.h" + +#include "FloatConversion.h" +#include "FloatQuad.h" +#include "RenderBlock.h" +#include "RenderSVGRoot.h" +#include "RenderSVGText.h" +#include "SVGInlineTextBox.h" +#include "SVGRootInlineBox.h" +#include "VisiblePosition.h" + +namespace WebCore { + +static PassRefPtr<StringImpl> applySVGWhitespaceRules(PassRefPtr<StringImpl> string, bool preserveWhiteSpace) +{ + if (preserveWhiteSpace) { + // Spec: When xml:space="preserve", the SVG user agent will do the following using a + // copy of the original character data content. It will convert all newline and tab + // characters into space characters. Then, it will draw all space characters, including + // leading, trailing and multiple contiguous space characters. + RefPtr<StringImpl> newString = string->replace('\t', ' '); + newString = newString->replace('\n', ' '); + newString = newString->replace('\r', ' '); + return newString.release(); + } + + // Spec: When xml:space="default", the SVG user agent will do the following using a + // copy of the original character data content. First, it will remove all newline + // characters. Then it will convert all tab characters into space characters. + // Then, it will strip off all leading and trailing space characters. + // Then, all contiguous space characters will be consolidated. + RefPtr<StringImpl> newString = string->replace('\n', StringImpl::empty()); + newString = newString->replace('\r', StringImpl::empty()); + newString = newString->replace('\t', ' '); + return newString.release(); +} + +RenderSVGInlineText::RenderSVGInlineText(Node* n, PassRefPtr<StringImpl> string) + : RenderText(n, applySVGWhitespaceRules(string, false)) +{ +} + +void RenderSVGInlineText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderText::styleDidChange(diff, oldStyle); + + if (diff == StyleDifferenceLayout) { + // The text metrics may be influenced by style changes. + if (RenderSVGText* textRenderer = RenderSVGText::locateRenderSVGTextAncestor(this)) + textRenderer->setNeedsPositioningValuesUpdate(); + } + + const RenderStyle* newStyle = style(); + if (!newStyle || newStyle->whiteSpace() != PRE) + return; + + if (!oldStyle || oldStyle->whiteSpace() != PRE) + setText(applySVGWhitespaceRules(originalText(), true), true); +} + +InlineTextBox* RenderSVGInlineText::createTextBox() +{ + InlineTextBox* box = new (renderArena()) SVGInlineTextBox(this); + box->setHasVirtualLogicalHeight(); + return box; +} + +IntRect RenderSVGInlineText::localCaretRect(InlineBox* box, int caretOffset, int*) +{ + if (!box->isInlineTextBox()) + return IntRect(); + + InlineTextBox* textBox = static_cast<InlineTextBox*>(box); + if (static_cast<unsigned>(caretOffset) < textBox->start() || static_cast<unsigned>(caretOffset) > textBox->start() + textBox->len()) + return IntRect(); + + // Use the edge of the selection rect to determine the caret rect. + if (static_cast<unsigned>(caretOffset) < textBox->start() + textBox->len()) { + IntRect rect = textBox->selectionRect(0, 0, caretOffset, caretOffset + 1); + int x = box->isLeftToRightDirection() ? rect.x() : rect.right(); + return IntRect(x, rect.y(), caretWidth, rect.height()); + } + + IntRect rect = textBox->selectionRect(0, 0, caretOffset - 1, caretOffset); + int x = box->isLeftToRightDirection() ? rect.right() : rect.x(); + return IntRect(x, rect.y(), caretWidth, rect.height()); +} + +IntRect RenderSVGInlineText::linesBoundingBox() const +{ + IntRect boundingBox; + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) + boundingBox.unite(box->calculateBoundaries()); + return boundingBox; +} + +bool RenderSVGInlineText::characterStartsNewTextChunk(int position) const +{ + ASSERT(m_attributes.xValues().size() == textLength()); + ASSERT(m_attributes.yValues().size() == textLength()); + ASSERT(position >= 0); + ASSERT(position < static_cast<int>(textLength())); + + // Each <textPath> element starts a new text chunk, regardless of any x/y values. + if (!position && parent()->isSVGTextPath() && !previousSibling()) + return true; + + int currentPosition = 0; + unsigned size = m_attributes.textMetricsValues().size(); + for (unsigned i = 0; i < size; ++i) { + const SVGTextMetrics& metrics = m_attributes.textMetricsValues().at(i); + + // We found the desired character. + if (currentPosition == position) { + return m_attributes.xValues().at(position) != SVGTextLayoutAttributes::emptyValue() + || m_attributes.yValues().at(position) != SVGTextLayoutAttributes::emptyValue(); + } + + currentPosition += metrics.length(); + if (currentPosition > position) + break; + } + + // The desired position is available in the x/y list, but not in the character data values list. + // That means the previous character data described a single glyph, consisting of multiple unicode characters. + // The consequence is that the desired character does not define a new absolute x/y position, even if present in the x/y test. + // This code is tested by svg/W3C-SVG-1.1/text-text-06-t.svg (and described in detail, why this influences chunk detection). + return false; +} + +VisiblePosition RenderSVGInlineText::positionForPoint(const IntPoint& point) +{ + if (!firstTextBox() || !textLength()) + return createVisiblePosition(0, DOWNSTREAM); + + RenderStyle* style = this->style(); + ASSERT(style); + int baseline = style->font().ascent(); + + RenderBlock* containingBlock = this->containingBlock(); + ASSERT(containingBlock); + + // Map local point to absolute point, as the character origins stored in the text fragments use absolute coordinates. + FloatPoint absolutePoint(point); + absolutePoint.move(containingBlock->x(), containingBlock->y()); + + float closestDistance = std::numeric_limits<float>::max(); + float closestDistancePosition = 0; + const SVGTextFragment* closestDistanceFragment = 0; + SVGInlineTextBox* closestDistanceBox = 0; + + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { + ASSERT(box->isSVGInlineTextBox()); + SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(box); + Vector<SVGTextFragment>& fragments = textBox->textFragments(); + + unsigned textFragmentsSize = fragments.size(); + for (unsigned i = 0; i < textFragmentsSize; ++i) { + const SVGTextFragment& fragment = fragments.at(i); + FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height); + if (!fragment.transform.isIdentity()) + fragmentRect = fragment.transform.mapRect(fragmentRect); + + float distance = powf(fragmentRect.x() - absolutePoint.x(), 2) + + powf(fragmentRect.y() + fragmentRect.height() / 2 - absolutePoint.y(), 2); + + if (distance < closestDistance) { + closestDistance = distance; + closestDistanceBox = textBox; + closestDistanceFragment = &fragment; + closestDistancePosition = fragmentRect.x(); + } + } + } + + if (!closestDistanceFragment) + return createVisiblePosition(0, DOWNSTREAM); + + int offset = closestDistanceBox->offsetForPositionInFragment(*closestDistanceFragment, absolutePoint.x() - closestDistancePosition, true); + return createVisiblePosition(offset + closestDistanceBox->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM); +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/RenderSVGInlineText.h b/Source/WebCore/rendering/svg/RenderSVGInlineText.h new file mode 100644 index 0000000..926ec43 --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGInlineText.h @@ -0,0 +1,84 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. + * (C) 2008 Rob Buis <buis@kde.org> + * + * 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. + * + */ + +#ifndef RenderSVGInlineText_h +#define RenderSVGInlineText_h + +#if ENABLE(SVG) +#include "RenderText.h" +#include "SVGTextLayoutAttributes.h" + +namespace WebCore { + +class SVGInlineTextBox; + +class RenderSVGInlineText : public RenderText { +public: + RenderSVGInlineText(Node*, PassRefPtr<StringImpl>); + + bool characterStartsNewTextChunk(int position) const; + + SVGTextLayoutAttributes& layoutAttributes() { return m_attributes; } + const SVGTextLayoutAttributes& layoutAttributes() const { return m_attributes; } + void storeLayoutAttributes(const SVGTextLayoutAttributes& attributes) { m_attributes = attributes; } + +private: + virtual const char* renderName() const { return "RenderSVGInlineText"; } + + virtual void styleDidChange(StyleDifference, const RenderStyle*); + + // FIXME: We need objectBoundingBox for DRT results and filters at the moment. + // This should be fixed to give back the objectBoundingBox of the text root. + virtual FloatRect objectBoundingBox() const { return FloatRect(); } + + virtual bool requiresLayer() const { return false; } + virtual bool isSVGInlineText() const { return true; } + + virtual VisiblePosition positionForPoint(const IntPoint&); + virtual IntRect localCaretRect(InlineBox*, int caretOffset, int* extraWidthToEndOfLine = 0); + virtual IntRect linesBoundingBox() const; + virtual InlineTextBox* createTextBox(); + + SVGTextLayoutAttributes m_attributes; +}; + +inline RenderSVGInlineText* toRenderSVGInlineText(RenderObject* object) +{ + ASSERT(!object || object->isSVGInlineText()); + return static_cast<RenderSVGInlineText*>(object); +} + +inline const RenderSVGInlineText* toRenderSVGInlineText(const RenderObject* object) +{ + ASSERT(!object || object->isSVGInlineText()); + return static_cast<const RenderSVGInlineText*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderSVGInlineText(const RenderSVGInlineText*); + +} + +#endif // ENABLE(SVG) +#endif // RenderSVGInlineText_h diff --git a/Source/WebCore/rendering/svg/RenderSVGPath.cpp b/Source/WebCore/rendering/svg/RenderSVGPath.cpp new file mode 100644 index 0000000..0c8ac0c --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGPath.cpp @@ -0,0 +1,338 @@ +/* + Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> + 2004, 2005, 2008 Rob Buis <buis@kde.org> + 2005, 2007 Eric Seidel <eric@webkit.org> + 2009 Google, Inc. + 2009 Dirk Schulze <krit@webkit.org> + Copyright (C) Research In Motion Limited 2010. All rights reserved. + 2009 Jeff Schiller <codedread@gmail.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 + aint 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" + +#if ENABLE(SVG) +#include "RenderSVGPath.h" + +#include "FloatPoint.h" +#include "FloatQuad.h" +#include "GraphicsContext.h" +#include "HitTestRequest.h" +#include "PointerEventsHitRules.h" +#include "RenderSVGContainer.h" +#include "RenderSVGResourceMarker.h" +#include "RenderSVGResourceSolidColor.h" +#include "SVGRenderSupport.h" +#include "SVGResources.h" +#include "SVGStyledTransformableElement.h" +#include "SVGTransformList.h" +#include "SVGURIReference.h" +#include "StrokeStyleApplier.h" +#include <wtf/MathExtras.h> + +namespace WebCore { + +class BoundingRectStrokeStyleApplier : public StrokeStyleApplier { +public: + BoundingRectStrokeStyleApplier(const RenderObject* object, RenderStyle* style) + : m_object(object) + , m_style(style) + { + ASSERT(style); + ASSERT(object); + } + + void strokeStyle(GraphicsContext* gc) + { + SVGRenderSupport::applyStrokeStyleToContext(gc, m_style, m_object); + } + +private: + const RenderObject* m_object; + RenderStyle* m_style; +}; + +RenderSVGPath::RenderSVGPath(SVGStyledTransformableElement* node) + : RenderSVGModelObject(node) + , m_needsBoundariesUpdate(false) // default is false, the cached rects are empty from the beginning + , m_needsPathUpdate(true) // default is true, so we grab a Path object once from SVGStyledTransformableElement + , m_needsTransformUpdate(true) // default is true, so we grab a AffineTransform object once from SVGStyledTransformableElement +{ +} + +RenderSVGPath::~RenderSVGPath() +{ +} + +bool RenderSVGPath::fillContains(const FloatPoint& point, bool requiresFill, WindRule fillRule) +{ + if (!m_fillBoundingBox.contains(point)) + return false; + + Color fallbackColor; + if (requiresFill && !RenderSVGResource::fillPaintingResource(this, style(), fallbackColor)) + return false; + + return m_path.contains(point, fillRule); +} + +bool RenderSVGPath::strokeContains(const FloatPoint& point, bool requiresStroke) +{ + if (!m_strokeAndMarkerBoundingBox.contains(point)) + return false; + + Color fallbackColor; + if (requiresStroke && !RenderSVGResource::strokePaintingResource(this, style(), fallbackColor)) + return false; + + BoundingRectStrokeStyleApplier strokeStyle(this, style()); + return m_path.strokeContains(&strokeStyle, point); +} + +void RenderSVGPath::layout() +{ + LayoutRepainter repainter(*this, checkForRepaintDuringLayout() && selfNeedsLayout()); + SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node()); + + bool updateCachedBoundariesInParents = false; + + bool needsPathUpdate = m_needsPathUpdate; + if (needsPathUpdate) { + m_path.clear(); + element->toPathData(m_path); + m_needsPathUpdate = false; + updateCachedBoundariesInParents = true; + } + + if (m_needsTransformUpdate) { + m_localTransform = element->animatedLocalTransform(); + m_needsTransformUpdate = false; + updateCachedBoundariesInParents = true; + } + + if (m_needsBoundariesUpdate) + updateCachedBoundariesInParents = true; + + // Invalidate all resources of this client if our layout changed. + if (m_everHadLayout && selfNeedsLayout()) + SVGResourcesCache::clientLayoutChanged(this); + + // At this point LayoutRepainter already grabbed the old bounds, + // recalculate them now so repaintAfterLayout() uses the new bounds. + if (needsPathUpdate || m_needsBoundariesUpdate) { + updateCachedBoundaries(); + m_needsBoundariesUpdate = false; + } + + // If our bounds changed, notify the parents. + if (updateCachedBoundariesInParents) + RenderSVGModelObject::setNeedsBoundariesUpdate(); + + repainter.repaintAfterLayout(); + setNeedsLayout(false); +} + +void RenderSVGPath::fillAndStrokePath(GraphicsContext* context) +{ + RenderStyle* style = this->style(); + + Color fallbackColor; + if (RenderSVGResource* fillPaintingResource = RenderSVGResource::fillPaintingResource(this, style, fallbackColor)) { + if (fillPaintingResource->applyResource(this, style, context, ApplyToFillMode)) + fillPaintingResource->postApplyResource(this, context, ApplyToFillMode, &m_path); + else if (fallbackColor.isValid()) { + RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource(); + fallbackResource->setColor(fallbackColor); + if (fallbackResource->applyResource(this, style, context, ApplyToFillMode)) + fallbackResource->postApplyResource(this, context, ApplyToFillMode, &m_path); + } + } + + fallbackColor = Color(); + RenderSVGResource* strokePaintingResource = RenderSVGResource::strokePaintingResource(this, style, fallbackColor); + if (!strokePaintingResource) + return; + + Path path; + + bool nonScalingStroke = style->svgStyle()->vectorEffect() == VE_NON_SCALING_STROKE; + bool restoreContext = false; + if (nonScalingStroke) { + SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node()); + AffineTransform nonScalingStrokeTransform = element->getScreenCTM(SVGLocatable::DisallowStyleUpdate); + if (!nonScalingStrokeTransform.isInvertible()) + return; + + path = m_path; + path.transform(nonScalingStrokeTransform); + + context->save(); + context->concatCTM(nonScalingStrokeTransform.inverse()); + restoreContext = true; + } + + if (strokePaintingResource->applyResource(this, style, context, ApplyToStrokeMode)) + strokePaintingResource->postApplyResource(this, context, ApplyToStrokeMode, nonScalingStroke ? &path : &m_path); + else if (fallbackColor.isValid()) { + RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource(); + fallbackResource->setColor(fallbackColor); + if (fallbackResource->applyResource(this, style, context, ApplyToStrokeMode)) + fallbackResource->postApplyResource(this, context, ApplyToStrokeMode, nonScalingStroke ? &path : &m_path); + } + + if (restoreContext) + context->restore(); +} + +void RenderSVGPath::paint(PaintInfo& paintInfo, int, int) +{ + if (paintInfo.context->paintingDisabled() || style()->visibility() == HIDDEN || m_path.isEmpty()) + return; + + FloatRect boundingBox = repaintRectInLocalCoordinates(); + if (!SVGRenderSupport::paintInfoIntersectsRepaintRect(boundingBox, m_localTransform, paintInfo)) + return; + + PaintInfo childPaintInfo(paintInfo); + bool drawsOutline = style()->outlineWidth() && (childPaintInfo.phase == PaintPhaseOutline || childPaintInfo.phase == PaintPhaseSelfOutline); + if (drawsOutline || childPaintInfo.phase == PaintPhaseForeground) { + childPaintInfo.context->save(); + childPaintInfo.applyTransform(m_localTransform); + + if (childPaintInfo.phase == PaintPhaseForeground) { + PaintInfo savedInfo(childPaintInfo); + + if (SVGRenderSupport::prepareToRenderSVGContent(this, childPaintInfo)) { + const SVGRenderStyle* svgStyle = style()->svgStyle(); + if (svgStyle->shapeRendering() == SR_CRISPEDGES) + childPaintInfo.context->setShouldAntialias(false); + + fillAndStrokePath(childPaintInfo.context); + + if (svgStyle->hasMarkers()) + m_markerLayoutInfo.drawMarkers(childPaintInfo); + } + + SVGRenderSupport::finishRenderSVGContent(this, childPaintInfo, savedInfo.context); + } + + if (drawsOutline) + paintOutline(childPaintInfo.context, static_cast<int>(boundingBox.x()), static_cast<int>(boundingBox.y()), + static_cast<int>(boundingBox.width()), static_cast<int>(boundingBox.height())); + + childPaintInfo.context->restore(); + } +} + +// This method is called from inside paintOutline() since we call paintOutline() +// while transformed to our coord system, return local coords +void RenderSVGPath::addFocusRingRects(Vector<IntRect>& rects, int, int) +{ + IntRect rect = enclosingIntRect(repaintRectInLocalCoordinates()); + if (!rect.isEmpty()) + rects.append(rect); +} + +bool RenderSVGPath::nodeAtFloatPoint(const HitTestRequest& request, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction) +{ + // We only draw in the forground phase, so we only hit-test then. + if (hitTestAction != HitTestForeground) + return false; + + FloatPoint localPoint = m_localTransform.inverse().mapPoint(pointInParent); + + if (!SVGRenderSupport::pointInClippingArea(this, localPoint)) + return false; + + PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_PATH_HITTESTING, request, style()->pointerEvents()); + bool isVisible = (style()->visibility() == VISIBLE); + if (isVisible || !hitRules.requireVisible) { + const SVGRenderStyle* svgStyle = style()->svgStyle(); + WindRule fillRule = svgStyle->fillRule(); + if (request.svgClipContent()) + fillRule = svgStyle->clipRule(); + if ((hitRules.canHitStroke && (svgStyle->hasStroke() || !hitRules.requireStroke) && strokeContains(localPoint, hitRules.requireStroke)) + || (hitRules.canHitFill && (svgStyle->hasFill() || !hitRules.requireFill) && fillContains(localPoint, hitRules.requireFill, fillRule))) { + updateHitTestResult(result, roundedIntPoint(localPoint)); + return true; + } + } + return false; +} + +FloatRect RenderSVGPath::calculateMarkerBoundsIfNeeded() +{ + SVGElement* svgElement = static_cast<SVGElement*>(node()); + ASSERT(svgElement && svgElement->document()); + if (!svgElement->isStyled()) + return FloatRect(); + + SVGStyledElement* styledElement = static_cast<SVGStyledElement*>(svgElement); + if (!styledElement->supportsMarkers()) + return FloatRect(); + + const SVGRenderStyle* svgStyle = style()->svgStyle(); + ASSERT(svgStyle->hasMarkers()); + + SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this); + if (!resources) + return FloatRect(); + + RenderSVGResourceMarker* markerStart = resources->markerStart(); + RenderSVGResourceMarker* markerMid = resources->markerMid(); + RenderSVGResourceMarker* markerEnd = resources->markerEnd(); + if (!markerStart && !markerMid && !markerEnd) + return FloatRect(); + + return m_markerLayoutInfo.calculateBoundaries(markerStart, markerMid, markerEnd, svgStyle->strokeWidth().value(svgElement), m_path); +} + +void RenderSVGPath::updateCachedBoundaries() +{ + if (m_path.isEmpty()) { + m_fillBoundingBox = FloatRect(); + m_strokeAndMarkerBoundingBox = FloatRect(); + m_repaintBoundingBox = FloatRect(); + return; + } + + // Cache _unclipped_ fill bounding box, used for calculations in resources + m_fillBoundingBox = m_path.boundingRect(); + + // Cache _unclipped_ stroke bounding box, used for calculations in resources (includes marker boundaries) + m_strokeAndMarkerBoundingBox = m_fillBoundingBox; + + const SVGRenderStyle* svgStyle = style()->svgStyle(); + if (svgStyle->hasStroke()) { + BoundingRectStrokeStyleApplier strokeStyle(this, style()); + m_strokeAndMarkerBoundingBox.unite(m_path.strokeBoundingRect(&strokeStyle)); + } + + if (svgStyle->hasMarkers()) { + FloatRect markerBounds = calculateMarkerBoundsIfNeeded(); + if (!markerBounds.isEmpty()) + m_strokeAndMarkerBoundingBox.unite(markerBounds); + } + + // Cache smallest possible repaint rectangle + m_repaintBoundingBox = m_strokeAndMarkerBoundingBox; + SVGRenderSupport::intersectRepaintRectWithResources(this, m_repaintBoundingBox); +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/RenderSVGPath.h b/Source/WebCore/rendering/svg/RenderSVGPath.h new file mode 100644 index 0000000..41b0e51 --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGPath.h @@ -0,0 +1,105 @@ +/* + Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> + 2004, 2005 Rob Buis <buis@kde.org> + 2005 Eric Seidel <eric@webkit.org> + 2006 Apple Computer, Inc + 2009 Google, Inc. + + 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 + aint 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. +*/ + +#ifndef RenderSVGPath_h +#define RenderSVGPath_h + +#if ENABLE(SVG) +#include "AffineTransform.h" +#include "FloatRect.h" +#include "RenderSVGModelObject.h" +#include "SVGMarkerLayoutInfo.h" + +namespace WebCore { + +class FloatPoint; +class RenderSVGContainer; +class SVGStyledTransformableElement; + +class RenderSVGPath : public RenderSVGModelObject { +public: + explicit RenderSVGPath(SVGStyledTransformableElement*); + virtual ~RenderSVGPath(); + + const Path& path() const { return m_path; } + void setNeedsPathUpdate() { m_needsPathUpdate = true; } + virtual void setNeedsBoundariesUpdate() { m_needsBoundariesUpdate = true; } + virtual void setNeedsTransformUpdate() { m_needsTransformUpdate = true; } + +private: + // Hit-detection seperated for the fill and the stroke + bool fillContains(const FloatPoint&, bool requiresFill = true, WindRule fillRule = RULE_NONZERO); + bool strokeContains(const FloatPoint&, bool requiresStroke = true); + + virtual FloatRect objectBoundingBox() const { return m_fillBoundingBox; } + virtual FloatRect strokeBoundingBox() const { return m_strokeAndMarkerBoundingBox; } + virtual FloatRect repaintRectInLocalCoordinates() const { return m_repaintBoundingBox; } + virtual const AffineTransform& localToParentTransform() const { return m_localTransform; } + + virtual bool isSVGPath() const { return true; } + virtual const char* renderName() const { return "RenderSVGPath"; } + + virtual void layout(); + virtual void paint(PaintInfo&, int parentX, int parentY); + virtual void addFocusRingRects(Vector<IntRect>&, int tx, int ty); + + virtual bool nodeAtFloatPoint(const HitTestRequest&, HitTestResult&, const FloatPoint& pointInParent, HitTestAction); + + FloatRect calculateMarkerBoundsIfNeeded(); + void updateCachedBoundaries(); + +private: + virtual AffineTransform localTransform() const { return m_localTransform; } + void fillAndStrokePath(GraphicsContext*); + + bool m_needsBoundariesUpdate : 1; + bool m_needsPathUpdate : 1; + bool m_needsTransformUpdate : 1; + + mutable Path m_path; + FloatRect m_fillBoundingBox; + FloatRect m_strokeAndMarkerBoundingBox; + FloatRect m_repaintBoundingBox; + SVGMarkerLayoutInfo m_markerLayoutInfo; + AffineTransform m_localTransform; +}; + +inline RenderSVGPath* toRenderSVGPath(RenderObject* object) +{ + ASSERT(!object || object->isSVGPath()); + return static_cast<RenderSVGPath*>(object); +} + +inline const RenderSVGPath* toRenderSVGPath(const RenderObject* object) +{ + ASSERT(!object || object->isSVGPath()); + return static_cast<const RenderSVGPath*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderSVGPath(const RenderSVGPath*); + +} + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/RenderSVGTSpan.cpp b/Source/WebCore/rendering/svg/RenderSVGTSpan.cpp new file mode 100644 index 0000000..90ff36c --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGTSpan.cpp @@ -0,0 +1,38 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * (C) 2006 Apple Computer Inc. + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGTSpan.h" + +namespace WebCore { + +RenderSVGTSpan::RenderSVGTSpan(Node* n) + : RenderSVGInline(n) +{ +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/RenderSVGTSpan.h b/Source/WebCore/rendering/svg/RenderSVGTSpan.h new file mode 100644 index 0000000..97e0eb0 --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGTSpan.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * (C) 2006 Apple Computer Inc. + * (C) 2009 Google Inc. + * + * 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. + * + */ + +#ifndef RenderSVGTSpan_h +#define RenderSVGTSpan_h + +#if ENABLE(SVG) +#include "RenderSVGInline.h" + +namespace WebCore { +class RenderSVGTSpan : public RenderSVGInline { +public: + explicit RenderSVGTSpan(Node*); + virtual const char* renderName() const { return "RenderSVGTSpan"; } +}; +} + +#endif // ENABLE(SVG) +#endif // !RenderSVGTSpan_h diff --git a/Source/WebCore/rendering/svg/RenderSVGText.cpp b/Source/WebCore/rendering/svg/RenderSVGText.cpp new file mode 100644 index 0000000..01a92b0 --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGText.cpp @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. + * Copyright (C) 2006 Alexander Kellett <lypanov@kde.org> + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) 2008 Rob Buis <buis@kde.org> + * Copyright (C) 2009 Dirk Schulze <krit@webkit.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGText.h" + +#include "FloatConversion.h" +#include "FloatQuad.h" +#include "GraphicsContext.h" +#include "HitTestRequest.h" +#include "PointerEventsHitRules.h" +#include "RenderLayer.h" +#include "RenderSVGResource.h" +#include "RenderSVGRoot.h" +#include "SVGLengthList.h" +#include "SVGRenderSupport.h" +#include "SVGRootInlineBox.h" +#include "SVGTextElement.h" +#include "SVGTextLayoutAttributesBuilder.h" +#include "SVGTransformList.h" +#include "SVGURIReference.h" +#include "SimpleFontData.h" +#include "TransformState.h" +#include "VisiblePosition.h" + +namespace WebCore { + +RenderSVGText::RenderSVGText(SVGTextElement* node) + : RenderSVGBlock(node) + , m_needsPositioningValuesUpdate(true) + , m_needsTransformUpdate(true) +{ +} + +RenderSVGText* RenderSVGText::locateRenderSVGTextAncestor(RenderObject* start) +{ + ASSERT(start); + while (start && !start->isSVGText()) + start = start->parent(); + if (!start || !start->isSVGText()) + return 0; + return toRenderSVGText(start); +} + +const RenderSVGText* RenderSVGText::locateRenderSVGTextAncestor(const RenderObject* start) +{ + ASSERT(start); + while (start && !start->isSVGText()) + start = start->parent(); + if (!start || !start->isSVGText()) + return 0; + return toRenderSVGText(start); +} + +IntRect RenderSVGText::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) +{ + return SVGRenderSupport::clippedOverflowRectForRepaint(this, repaintContainer); +} + +void RenderSVGText::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& repaintRect, bool fixed) +{ + SVGRenderSupport::computeRectForRepaint(this, repaintContainer, repaintRect, fixed); +} + +void RenderSVGText::mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool fixed, bool useTransforms, TransformState& transformState) const +{ + SVGRenderSupport::mapLocalToContainer(this, repaintContainer, fixed, useTransforms, transformState); +} + +void RenderSVGText::layout() +{ + ASSERT(needsLayout()); + LayoutRepainter repainter(*this, checkForRepaintDuringLayout()); + + bool updateCachedBoundariesInParents = false; + if (m_needsTransformUpdate) { + SVGTextElement* text = static_cast<SVGTextElement*>(node()); + m_localTransform = text->animatedLocalTransform(); + m_needsTransformUpdate = false; + updateCachedBoundariesInParents = true; + } + + if (m_needsPositioningValuesUpdate) { + // Perform SVG text layout phase one (see SVGTextLayoutAttributesBuilder for details). + SVGTextLayoutAttributesBuilder layoutAttributesBuilder; + layoutAttributesBuilder.buildLayoutAttributesForTextSubtree(this); + m_needsPositioningValuesUpdate = false; + updateCachedBoundariesInParents = true; + } + + // Reduced version of RenderBlock::layoutBlock(), which only takes care of SVG text. + // All if branches that could cause early exit in RenderBlocks layoutBlock() method are turned into assertions. + ASSERT(!isInline()); + ASSERT(!layoutOnlyPositionedObjects()); + ASSERT(!scrollsOverflow()); + ASSERT(!hasControlClip()); + ASSERT(!hasColumns()); + ASSERT(!positionedObjects()); + ASSERT(!m_overflow); + ASSERT(!isAnonymousBlock()); + + if (!firstChild()) + setChildrenInline(true); + + // FIXME: We need to find a way to only layout the child boxes, if needed. + FloatRect oldBoundaries = objectBoundingBox(); + ASSERT(childrenInline()); + forceLayoutInlineChildren(); + + if (!updateCachedBoundariesInParents) + updateCachedBoundariesInParents = oldBoundaries != objectBoundingBox(); + + // Invalidate all resources of this client if our layout changed. + if (m_everHadLayout && selfNeedsLayout()) + SVGResourcesCache::clientLayoutChanged(this); + + // If our bounds changed, notify the parents. + if (updateCachedBoundariesInParents) + RenderSVGBlock::setNeedsBoundariesUpdate(); + + repainter.repaintAfterLayout(); + setNeedsLayout(false); +} + +RootInlineBox* RenderSVGText::createRootInlineBox() +{ + RootInlineBox* box = new (renderArena()) SVGRootInlineBox(this); + box->setHasVirtualLogicalHeight(); + return box; +} + +bool RenderSVGText::nodeAtFloatPoint(const HitTestRequest& request, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction) +{ + PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING, request, style()->pointerEvents()); + bool isVisible = (style()->visibility() == VISIBLE); + if (isVisible || !hitRules.requireVisible) { + if ((hitRules.canHitStroke && (style()->svgStyle()->hasStroke() || !hitRules.requireStroke)) + || (hitRules.canHitFill && (style()->svgStyle()->hasFill() || !hitRules.requireFill))) { + FloatPoint localPoint = localToParentTransform().inverse().mapPoint(pointInParent); + + if (!SVGRenderSupport::pointInClippingArea(this, localPoint)) + return false; + + return RenderBlock::nodeAtPoint(request, result, (int)localPoint.x(), (int)localPoint.y(), 0, 0, hitTestAction); + } + } + + return false; +} + +bool RenderSVGText::nodeAtPoint(const HitTestRequest&, HitTestResult&, int, int, int, int, HitTestAction) +{ + ASSERT_NOT_REACHED(); + return false; +} + +VisiblePosition RenderSVGText::positionForPoint(const IntPoint& pointInContents) +{ + RootInlineBox* rootBox = firstRootBox(); + if (!rootBox) + return createVisiblePosition(0, DOWNSTREAM); + + ASSERT(rootBox->isSVGRootInlineBox()); + ASSERT(!rootBox->nextRootBox()); + ASSERT(childrenInline()); + + InlineBox* closestBox = static_cast<SVGRootInlineBox*>(rootBox)->closestLeafChildForPosition(pointInContents); + if (!closestBox) + return createVisiblePosition(0, DOWNSTREAM); + + return closestBox->renderer()->positionForPoint(IntPoint(pointInContents.x(), closestBox->m_y)); +} + +void RenderSVGText::absoluteQuads(Vector<FloatQuad>& quads) +{ + quads.append(localToAbsoluteQuad(strokeBoundingBox())); +} + +void RenderSVGText::paint(PaintInfo& paintInfo, int, int) +{ + if (paintInfo.context->paintingDisabled()) + return; + + if (paintInfo.phase != PaintPhaseForeground + && paintInfo.phase != PaintPhaseSelfOutline + && paintInfo.phase != PaintPhaseSelection) + return; + + PaintInfo blockInfo(paintInfo); + blockInfo.context->save(); + blockInfo.applyTransform(localToParentTransform()); + RenderBlock::paint(blockInfo, 0, 0); + blockInfo.context->restore(); +} + +FloatRect RenderSVGText::strokeBoundingBox() const +{ + FloatRect strokeBoundaries = objectBoundingBox(); + const SVGRenderStyle* svgStyle = style()->svgStyle(); + if (!svgStyle->hasStroke()) + return strokeBoundaries; + + ASSERT(node()); + ASSERT(node()->isSVGElement()); + strokeBoundaries.inflate(svgStyle->strokeWidth().value(static_cast<SVGElement*>(node()))); + return strokeBoundaries; +} + +FloatRect RenderSVGText::repaintRectInLocalCoordinates() const +{ + FloatRect repaintRect = strokeBoundingBox(); + SVGRenderSupport::intersectRepaintRectWithResources(this, repaintRect); + + if (const ShadowData* textShadow = style()->textShadow()) + textShadow->adjustRectForShadow(repaintRect); + + return repaintRect; +} + +// Fix for <rdar://problem/8048875>. We should not render :first-line CSS Style +// in a SVG text element context. +RenderBlock* RenderSVGText::firstLineBlock() const +{ + return 0; +} + +// Fix for <rdar://problem/8048875>. We should not render :first-letter CSS Style +// in a SVG text element context. +void RenderSVGText::updateFirstLetter() +{ +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/RenderSVGText.h b/Source/WebCore/rendering/svg/RenderSVGText.h new file mode 100644 index 0000000..deae78c --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGText.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. + * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSVGText_h +#define RenderSVGText_h + +#if ENABLE(SVG) + +#include "AffineTransform.h" +#include "RenderSVGBlock.h" + +namespace WebCore { + +class SVGTextElement; + +class RenderSVGText : public RenderSVGBlock { +public: + RenderSVGText(SVGTextElement* node); + + void setNeedsPositioningValuesUpdate() { m_needsPositioningValuesUpdate = true; } + virtual void setNeedsTransformUpdate() { m_needsTransformUpdate = true; } + virtual FloatRect repaintRectInLocalCoordinates() const; + + static RenderSVGText* locateRenderSVGTextAncestor(RenderObject*); + static const RenderSVGText* locateRenderSVGTextAncestor(const RenderObject*); + +private: + virtual const char* renderName() const { return "RenderSVGText"; } + virtual bool isSVGText() const { return true; } + + virtual void paint(PaintInfo&, int tx, int ty); + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + virtual bool nodeAtFloatPoint(const HitTestRequest&, HitTestResult&, const FloatPoint& pointInParent, HitTestAction); + virtual VisiblePosition positionForPoint(const IntPoint&); + + virtual bool requiresLayer() const { return false; } + virtual void layout(); + + virtual void absoluteQuads(Vector<FloatQuad>&); + + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer); + virtual void computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect&, bool fixed = false); + + virtual void mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool useTransforms, bool fixed, TransformState&) const; + + virtual FloatRect objectBoundingBox() const { return frameRect(); } + virtual FloatRect strokeBoundingBox() const; + + virtual const AffineTransform& localToParentTransform() const { return m_localTransform; } + virtual AffineTransform localTransform() const { return m_localTransform; } + virtual RootInlineBox* createRootInlineBox(); + + virtual RenderBlock* firstLineBlock() const; + virtual void updateFirstLetter(); + + bool m_needsPositioningValuesUpdate : 1; + bool m_needsTransformUpdate : 1; + AffineTransform m_localTransform; +}; + +inline RenderSVGText* toRenderSVGText(RenderObject* object) +{ + ASSERT(!object || object->isSVGText()); + return static_cast<RenderSVGText*>(object); +} + +inline const RenderSVGText* toRenderSVGText(const RenderObject* object) +{ + ASSERT(!object || object->isSVGText()); + return static_cast<const RenderSVGText*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderSVGText(const RenderSVGText*); + +} + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/RenderSVGTextPath.cpp b/Source/WebCore/rendering/svg/RenderSVGTextPath.cpp new file mode 100644 index 0000000..4ba2eeb --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGTextPath.cpp @@ -0,0 +1,85 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * + * 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" + +#if ENABLE(SVG) +#include "RenderSVGTextPath.h" + +#include "FloatQuad.h" +#include "RenderBlock.h" +#include "SVGInlineTextBox.h" +#include "SVGNames.h" +#include "SVGPathElement.h" +#include "SVGRootInlineBox.h" +#include "SVGTextPathElement.h" +#include "SVGTransformList.h" + +namespace WebCore { + +RenderSVGTextPath::RenderSVGTextPath(Node* n) + : RenderSVGInline(n) + , m_startOffset(0.0f) + , m_exactAlignment(true) + , m_stretchMethod(false) +{ +} + +Path RenderSVGTextPath::layoutPath() const +{ + SVGTextPathElement* textPathElement = static_cast<SVGTextPathElement*>(node()); + String pathId = SVGURIReference::getTarget(textPathElement->href()); + Element* targetElement = textPathElement->document()->getElementById(pathId); + if (!targetElement || !targetElement->hasTagName(SVGNames::pathTag)) + return Path(); + + SVGPathElement* pathElement = static_cast<SVGPathElement*>(targetElement); + + Path pathData; + pathElement->toPathData(pathData); + // Spec: The transform attribute on the referenced 'path' element represents a + // supplemental transformation relative to the current user coordinate system for + // the current 'text' element, including any adjustments to the current user coordinate + // system due to a possible transform attribute on the current 'text' element. + // http://www.w3.org/TR/SVG/text.html#TextPathElement + pathData.transform(pathElement->animatedLocalTransform()); + return pathData; +} + +float RenderSVGTextPath::startOffset() const +{ + return static_cast<SVGTextPathElement*>(node())->startOffset().valueAsPercentage(); +} + +bool RenderSVGTextPath::exactAlignment() const +{ + return static_cast<SVGTextPathElement*>(node())->spacing() == SVG_TEXTPATH_SPACINGTYPE_EXACT; +} + +bool RenderSVGTextPath::stretchMethod() const +{ + return static_cast<SVGTextPathElement*>(node())->method() == SVG_TEXTPATH_METHODTYPE_STRETCH; +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/RenderSVGTextPath.h b/Source/WebCore/rendering/svg/RenderSVGTextPath.h new file mode 100644 index 0000000..a71edf5 --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGTextPath.h @@ -0,0 +1,66 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSVGTextPath_h +#define RenderSVGTextPath_h + +#if ENABLE(SVG) +#include "RenderSVGInline.h" + +namespace WebCore { + +class RenderSVGTextPath : public RenderSVGInline { +public: + RenderSVGTextPath(Node*); + + Path layoutPath() const; + float startOffset() const; + bool exactAlignment() const; + bool stretchMethod() const; + + virtual bool isSVGTextPath() const { return true; } + +private: + virtual const char* renderName() const { return "RenderSVGTextPath"; } + + float m_startOffset; + + bool m_exactAlignment : 1; + bool m_stretchMethod : 1; + + Path m_layoutPath; +}; + +inline RenderSVGTextPath* toRenderSVGTextPath(RenderObject* object) +{ + ASSERT(!object || !strcmp(object->renderName(), "RenderSVGTextPath")); + return static_cast<RenderSVGTextPath*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderSVGTextPath(const RenderSVGTextPath*); + +} + +#endif // ENABLE(SVG) +#endif // RenderSVGTextPath_h diff --git a/Source/WebCore/rendering/svg/SVGInlineFlowBox.cpp b/Source/WebCore/rendering/svg/SVGInlineFlowBox.cpp new file mode 100644 index 0000000..ea806c7 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGInlineFlowBox.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * (C) 2006 Apple Computer Inc. + * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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 "SVGInlineFlowBox.h" + +#if ENABLE(SVG) +#include "DocumentMarkerController.h" +#include "GraphicsContext.h" +#include "RenderSVGInlineText.h" +#include "SVGInlineTextBox.h" +#include "SVGRenderSupport.h" + +using namespace std; + +namespace WebCore { + +void SVGInlineFlowBox::paintSelectionBackground(PaintInfo& paintInfo) +{ + ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); + ASSERT(!paintInfo.context->paintingDisabled()); + + PaintInfo childPaintInfo(paintInfo); + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) { + if (child->isSVGInlineTextBox()) + static_cast<SVGInlineTextBox*>(child)->paintSelectionBackground(childPaintInfo); + else if (child->isSVGInlineFlowBox()) + static_cast<SVGInlineFlowBox*>(child)->paintSelectionBackground(childPaintInfo); + } +} + +void SVGInlineFlowBox::paint(PaintInfo& paintInfo, int, int) +{ + ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); + ASSERT(!paintInfo.context->paintingDisabled()); + + RenderObject* boxRenderer = renderer(); + ASSERT(boxRenderer); + + PaintInfo childPaintInfo(paintInfo); + childPaintInfo.context->save(); + + if (SVGRenderSupport::prepareToRenderSVGContent(boxRenderer, childPaintInfo)) { + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) { + if (child->isSVGInlineTextBox()) + computeTextMatchMarkerRectForRenderer(toRenderSVGInlineText(static_cast<SVGInlineTextBox*>(child)->textRenderer())); + + child->paint(childPaintInfo, 0, 0); + } + } + + SVGRenderSupport::finishRenderSVGContent(boxRenderer, childPaintInfo, paintInfo.context); + childPaintInfo.context->restore(); +} + +IntRect SVGInlineFlowBox::calculateBoundaries() const +{ + IntRect childRect; + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) + childRect.unite(child->calculateBoundaries()); + return childRect; +} + +void SVGInlineFlowBox::computeTextMatchMarkerRectForRenderer(RenderSVGInlineText* textRenderer) +{ + ASSERT(textRenderer); + + Node* node = textRenderer->node(); + if (!node || !node->inDocument()) + return; + + RenderStyle* style = textRenderer->style(); + ASSERT(style); + + Document* document = textRenderer->document(); + Vector<DocumentMarker> markers = document->markers()->markersForNode(textRenderer->node()); + + Vector<DocumentMarker>::iterator markerEnd = markers.end(); + for (Vector<DocumentMarker>::iterator markerIt = markers.begin(); markerIt != markerEnd; ++markerIt) { + const DocumentMarker& marker = *markerIt; + + // SVG is only interessted in the TextMatch marker, for now. + if (marker.type != DocumentMarker::TextMatch) + continue; + + FloatRect markerRect; + for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { + ASSERT(box->isSVGInlineTextBox()); + SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(box); + + int markerStartPosition = max<int>(marker.startOffset - textBox->start(), 0); + int markerEndPosition = min<int>(marker.endOffset - textBox->start(), textBox->len()); + + if (markerStartPosition >= markerEndPosition) + continue; + + int fragmentStartPosition = 0; + int fragmentEndPosition = 0; + + const Vector<SVGTextFragment>& fragments = textBox->textFragments(); + unsigned textFragmentsSize = fragments.size(); + for (unsigned i = 0; i < textFragmentsSize; ++i) { + const SVGTextFragment& fragment = fragments.at(i); + + fragmentStartPosition = markerStartPosition; + fragmentEndPosition = markerEndPosition; + if (!textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition)) + continue; + + FloatRect fragmentRect = textBox->selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style); + if (!fragment.transform.isIdentity()) + fragmentRect = fragment.transform.mapRect(fragmentRect); + + markerRect.unite(fragmentRect); + } + } + + document->markers()->setRenderedRectForMarker(node, marker, textRenderer->localToAbsoluteQuad(markerRect).enclosingBoundingBox()); + } +} + +} // namespace WebCore + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGInlineFlowBox.h b/Source/WebCore/rendering/svg/SVGInlineFlowBox.h new file mode 100644 index 0000000..2358f2d --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGInlineFlowBox.h @@ -0,0 +1,61 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * (C) 2006 Apple Computer Inc. + * + * 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. + * + */ + +#ifndef SVGInlineFlowBox_h +#define SVGInlineFlowBox_h + +#if ENABLE(SVG) +#include "InlineFlowBox.h" + +namespace WebCore { + +class RenderSVGInlineText; + +class SVGInlineFlowBox : public InlineFlowBox { +public: + SVGInlineFlowBox(RenderObject* obj) + : InlineFlowBox(obj) + , m_logicalHeight(0) + { + } + + virtual bool isSVGInlineFlowBox() const { return true; } + virtual int virtualLogicalHeight() const { return m_logicalHeight; } + void setLogicalHeight(int h) { m_logicalHeight = h; } + + void paintSelectionBackground(PaintInfo&); + virtual void paint(PaintInfo&, int tx, int ty); + + virtual IntRect calculateBoundaries() const; + + static void computeTextMatchMarkerRectForRenderer(RenderSVGInlineText*); + +private: + int m_logicalHeight; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) + +#endif // SVGInlineFlowBox_h diff --git a/Source/WebCore/rendering/svg/SVGInlineTextBox.cpp b/Source/WebCore/rendering/svg/SVGInlineTextBox.cpp new file mode 100644 index 0000000..5d0278b --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGInlineTextBox.cpp @@ -0,0 +1,608 @@ +/** + * Copyright (C) 2007 Rob Buis <buis@kde.org> + * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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 "SVGInlineTextBox.h" + +#if ENABLE(SVG) +#include "FloatConversion.h" +#include "GraphicsContext.h" +#include "InlineFlowBox.h" +#include "RenderBlock.h" +#include "RenderSVGInlineText.h" +#include "RenderSVGResource.h" +#include "RenderSVGResourceSolidColor.h" +#include "SVGRootInlineBox.h" + +using namespace std; + +namespace WebCore { + +SVGInlineTextBox::SVGInlineTextBox(RenderObject* object) + : InlineTextBox(object) + , m_logicalHeight(0) + , m_paintingResourceMode(ApplyToDefaultMode) + , m_startsNewTextChunk(false) + , m_paintingResource(0) +{ +} + +int SVGInlineTextBox::offsetForPosition(int, bool) const +{ + // SVG doesn't use the standard offset <-> position selection system, as it's not suitable for SVGs complex needs. + // vertical text selection, inline boxes spanning multiple lines (contrary to HTML, etc.) + ASSERT_NOT_REACHED(); + return 0; +} + +int SVGInlineTextBox::offsetForPositionInFragment(const SVGTextFragment& fragment, float position, bool includePartialGlyphs) const +{ + RenderText* textRenderer = this->textRenderer(); + ASSERT(textRenderer); + + RenderStyle* style = textRenderer->style(); + ASSERT(style); + + TextRun textRun(constructTextRun(style, fragment)); + + // Eventually handle lengthAdjust="spacingAndGlyphs". + // FIXME: Handle vertical text. + if (!fragment.transform.isIdentity()) + textRun.setHorizontalGlyphStretch(narrowPrecisionToFloat(fragment.transform.xScale())); + + return fragment.positionListOffset - start() + style->font().offsetForPosition(textRun, position, includePartialGlyphs); +} + +int SVGInlineTextBox::positionForOffset(int) const +{ + // SVG doesn't use the offset <-> position selection system. + ASSERT_NOT_REACHED(); + return 0; +} + +FloatRect SVGInlineTextBox::selectionRectForTextFragment(const SVGTextFragment& fragment, int startPosition, int endPosition, RenderStyle* style) +{ + ASSERT(startPosition < endPosition); + + const Font& font = style->font(); + FloatPoint textOrigin(fragment.x, fragment.y - font.ascent()); + return font.selectionRectForText(constructTextRun(style, fragment), textOrigin, fragment.height, startPosition, endPosition); +} + +IntRect SVGInlineTextBox::selectionRect(int, int, int startPosition, int endPosition) +{ + int boxStart = start(); + startPosition = max(startPosition - boxStart, 0); + endPosition = min(endPosition - boxStart, static_cast<int>(len())); + if (startPosition >= endPosition) + return IntRect(); + + RenderText* text = textRenderer(); + ASSERT(text); + + RenderStyle* style = text->style(); + ASSERT(style); + + FloatRect selectionRect; + int fragmentStartPosition = 0; + int fragmentEndPosition = 0; + + unsigned textFragmentsSize = m_textFragments.size(); + for (unsigned i = 0; i < textFragmentsSize; ++i) { + const SVGTextFragment& fragment = m_textFragments.at(i); + + fragmentStartPosition = startPosition; + fragmentEndPosition = endPosition; + if (!mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition)) + continue; + + FloatRect fragmentRect = selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style); + if (!fragment.transform.isIdentity()) + fragmentRect = fragment.transform.mapRect(fragmentRect); + + selectionRect.unite(fragmentRect); + } + + return enclosingIntRect(selectionRect); +} + +void SVGInlineTextBox::paintSelectionBackground(PaintInfo& paintInfo) +{ + ASSERT(paintInfo.shouldPaintWithinRoot(renderer())); + ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); + ASSERT(truncation() == cNoTruncation); + + if (renderer()->style()->visibility() != VISIBLE) + return; + + RenderObject* parentRenderer = parent()->renderer(); + ASSERT(parentRenderer); + ASSERT(!parentRenderer->document()->printing()); + + // Determine whether or not we're selected. + bool paintSelectedTextOnly = paintInfo.phase == PaintPhaseSelection; + bool hasSelection = selectionState() != RenderObject::SelectionNone; + if (!hasSelection || paintSelectedTextOnly) + return; + + Color backgroundColor = renderer()->selectionBackgroundColor(); + if (!backgroundColor.isValid() || !backgroundColor.alpha()) + return; + + RenderStyle* style = parentRenderer->style(); + ASSERT(style); + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + + bool hasFill = svgStyle->hasFill(); + bool hasStroke = svgStyle->hasStroke(); + + RenderStyle* selectionStyle = style; + if (hasSelection) { + selectionStyle = parentRenderer->getCachedPseudoStyle(SELECTION); + if (selectionStyle) { + const SVGRenderStyle* svgSelectionStyle = selectionStyle->svgStyle(); + ASSERT(svgSelectionStyle); + + if (!hasFill) + hasFill = svgSelectionStyle->hasFill(); + if (!hasStroke) + hasStroke = svgSelectionStyle->hasStroke(); + } else + selectionStyle = style; + } + + int startPosition, endPosition; + selectionStartEnd(startPosition, endPosition); + + int fragmentStartPosition = 0; + int fragmentEndPosition = 0; + unsigned textFragmentsSize = m_textFragments.size(); + for (unsigned i = 0; i < textFragmentsSize; ++i) { + SVGTextFragment& fragment = m_textFragments.at(i); + ASSERT(!m_paintingResource); + + fragmentStartPosition = startPosition; + fragmentEndPosition = endPosition; + if (!mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition)) + continue; + + paintInfo.context->save(); + + if (!fragment.transform.isIdentity()) + paintInfo.context->concatCTM(fragment.transform); + + paintInfo.context->setFillColor(backgroundColor, style->colorSpace()); + paintInfo.context->fillRect(selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style), backgroundColor, style->colorSpace()); + + m_paintingResourceMode = ApplyToDefaultMode; + paintInfo.context->restore(); + } + + ASSERT(!m_paintingResource); +} + +void SVGInlineTextBox::paint(PaintInfo& paintInfo, int, int) +{ + ASSERT(paintInfo.shouldPaintWithinRoot(renderer())); + ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); + ASSERT(truncation() == cNoTruncation); + + if (renderer()->style()->visibility() != VISIBLE) + return; + + // Note: We're explicitely not supporting composition & custom underlines and custom highlighters - unlike InlineTextBox. + // If we ever need that for SVG, it's very easy to refactor and reuse the code. + + RenderObject* parentRenderer = parent()->renderer(); + ASSERT(parentRenderer); + + bool paintSelectedTextOnly = paintInfo.phase == PaintPhaseSelection; + bool hasSelection = !parentRenderer->document()->printing() && selectionState() != RenderObject::SelectionNone; + if (!hasSelection && paintSelectedTextOnly) + return; + + RenderStyle* style = parentRenderer->style(); + ASSERT(style); + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + + bool hasFill = svgStyle->hasFill(); + bool hasStroke = svgStyle->hasStroke(); + + RenderStyle* selectionStyle = style; + if (hasSelection) { + selectionStyle = parentRenderer->getCachedPseudoStyle(SELECTION); + if (selectionStyle) { + const SVGRenderStyle* svgSelectionStyle = selectionStyle->svgStyle(); + ASSERT(svgSelectionStyle); + + if (!hasFill) + hasFill = svgSelectionStyle->hasFill(); + if (!hasStroke) + hasStroke = svgSelectionStyle->hasStroke(); + } else + selectionStyle = style; + } + + unsigned textFragmentsSize = m_textFragments.size(); + for (unsigned i = 0; i < textFragmentsSize; ++i) { + SVGTextFragment& fragment = m_textFragments.at(i); + ASSERT(!m_paintingResource); + + paintInfo.context->save(); + + if (!fragment.transform.isIdentity()) + paintInfo.context->concatCTM(fragment.transform); + + // Spec: All text decorations except line-through should be drawn before the text is filled and stroked; thus, the text is rendered on top of these decorations. + int decorations = style->textDecorationsInEffect(); + if (decorations & UNDERLINE) + paintDecoration(paintInfo.context, UNDERLINE, fragment); + if (decorations & OVERLINE) + paintDecoration(paintInfo.context, OVERLINE, fragment); + + // Fill text + if (hasFill) { + m_paintingResourceMode = ApplyToFillMode | ApplyToTextMode; + paintText(paintInfo.context, style, selectionStyle, fragment, hasSelection, paintSelectedTextOnly); + } + + // Stroke text + if (hasStroke) { + m_paintingResourceMode = ApplyToStrokeMode | ApplyToTextMode; + paintText(paintInfo.context, style, selectionStyle, fragment, hasSelection, paintSelectedTextOnly); + } + + // Spec: Line-through should be drawn after the text is filled and stroked; thus, the line-through is rendered on top of the text. + if (decorations & LINE_THROUGH) + paintDecoration(paintInfo.context, LINE_THROUGH, fragment); + + m_paintingResourceMode = ApplyToDefaultMode; + paintInfo.context->restore(); + } + + ASSERT(!m_paintingResource); +} + +bool SVGInlineTextBox::acquirePaintingResource(GraphicsContext*& context, RenderObject* renderer, RenderStyle* style) +{ + ASSERT(renderer); + ASSERT(style); + ASSERT(m_paintingResourceMode != ApplyToDefaultMode); + + Color fallbackColor; + if (m_paintingResourceMode & ApplyToFillMode) + m_paintingResource = RenderSVGResource::fillPaintingResource(renderer, style, fallbackColor); + else if (m_paintingResourceMode & ApplyToStrokeMode) + m_paintingResource = RenderSVGResource::strokePaintingResource(renderer, style, fallbackColor); + else { + // We're either called for stroking or filling. + ASSERT_NOT_REACHED(); + } + + if (!m_paintingResource) + return false; + + if (!m_paintingResource->applyResource(renderer, style, context, m_paintingResourceMode)) { + if (fallbackColor.isValid()) { + RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource(); + fallbackResource->setColor(fallbackColor); + + m_paintingResource = fallbackResource; + m_paintingResource->applyResource(renderer, style, context, m_paintingResourceMode); + } + } + + return true; +} + +void SVGInlineTextBox::releasePaintingResource(GraphicsContext*& context, const Path* path) +{ + ASSERT(m_paintingResource); + + RenderObject* parentRenderer = parent()->renderer(); + ASSERT(parentRenderer); + + m_paintingResource->postApplyResource(parentRenderer, context, m_paintingResourceMode, path); + m_paintingResource = 0; +} + +bool SVGInlineTextBox::prepareGraphicsContextForTextPainting(GraphicsContext*& context, TextRun& textRun, RenderStyle* style) +{ + bool acquiredResource = acquirePaintingResource(context, parent()->renderer(), style); + +#if ENABLE(SVG_FONTS) + // SVG Fonts need access to the painting resource used to draw the current text chunk. + if (acquiredResource) + textRun.setActivePaintingResource(m_paintingResource); +#endif + + return acquiredResource; +} + +void SVGInlineTextBox::restoreGraphicsContextAfterTextPainting(GraphicsContext*& context, TextRun& textRun) +{ + releasePaintingResource(context, /* path */0); + +#if ENABLE(SVG_FONTS) + textRun.setActivePaintingResource(0); +#endif +} + +TextRun SVGInlineTextBox::constructTextRun(RenderStyle* style, const SVGTextFragment& fragment) const +{ + ASSERT(style); + ASSERT(textRenderer()); + + RenderText* text = textRenderer(); + ASSERT(text); + + TextRun run(text->characters() + fragment.positionListOffset + , fragment.length + , false /* allowTabs */ + , 0 /* xPos, only relevant with allowTabs=true */ + , 0 /* padding, only relevant for justified text, not relevant for SVG */ + , direction() == RTL + , m_dirOverride || style->visuallyOrdered() /* directionalOverride */); + +#if ENABLE(SVG_FONTS) + RenderObject* parentRenderer = parent()->renderer(); + ASSERT(parentRenderer); + + run.setReferencingRenderObject(parentRenderer); +#endif + + // Disable any word/character rounding. + run.disableRoundingHacks(); + + // We handle letter & word spacing ourselves. + run.disableSpacing(); + return run; +} + +bool SVGInlineTextBox::mapStartEndPositionsIntoFragmentCoordinates(const SVGTextFragment& fragment, int& startPosition, int& endPosition) const +{ + if (startPosition >= endPosition) + return false; + + int offset = static_cast<int>(fragment.positionListOffset) - start(); + int length = static_cast<int>(fragment.length); + + if (startPosition >= offset + length || endPosition <= offset) + return false; + + if (startPosition < offset) + startPosition = 0; + else + startPosition -= offset; + + if (endPosition > offset + length) + endPosition = length; + else { + ASSERT(endPosition >= offset); + endPosition -= offset; + } + + ASSERT(startPosition < endPosition); + return true; +} + +static inline float positionOffsetForDecoration(ETextDecoration decoration, const Font& font, float thickness) +{ + // FIXME: For SVG Fonts we need to use the attributes defined in the <font-face> if specified. + // Compatible with Batik/Opera. + if (decoration == UNDERLINE) + return font.ascent() + thickness * 1.5f; + if (decoration == OVERLINE) + return thickness; + if (decoration == LINE_THROUGH) + return font.ascent() * 5.0f / 8.0f; + + ASSERT_NOT_REACHED(); + return 0.0f; +} + +static inline float thicknessForDecoration(ETextDecoration, const Font& font) +{ + // FIXME: For SVG Fonts we need to use the attributes defined in the <font-face> if specified. + // Compatible with Batik/Opera + return font.size() / 20.0f; +} + +static inline RenderObject* findRenderObjectDefininingTextDecoration(InlineFlowBox* parentBox) +{ + // Lookup first render object in parent hierarchy which has text-decoration set. + RenderObject* renderer = 0; + while (parentBox) { + renderer = parentBox->renderer(); + + if (renderer->style() && renderer->style()->textDecoration() != TDNONE) + break; + + parentBox = parentBox->parent(); + } + + ASSERT(renderer); + return renderer; +} + +void SVGInlineTextBox::paintDecoration(GraphicsContext* context, ETextDecoration decoration, const SVGTextFragment& fragment) +{ + if (textRenderer()->style()->textDecorationsInEffect() == TDNONE) + return; + + // Find out which render style defined the text-decoration, as its fill/stroke properties have to be used for drawing instead of ours. + RenderObject* decorationRenderer = findRenderObjectDefininingTextDecoration(parent()); + RenderStyle* decorationStyle = decorationRenderer->style(); + ASSERT(decorationStyle); + + if (decorationStyle->visibility() == HIDDEN) + return; + + const SVGRenderStyle* svgDecorationStyle = decorationStyle->svgStyle(); + ASSERT(svgDecorationStyle); + + bool hasDecorationFill = svgDecorationStyle->hasFill(); + bool hasDecorationStroke = svgDecorationStyle->hasStroke(); + + if (hasDecorationFill) { + m_paintingResourceMode = ApplyToFillMode; + paintDecorationWithStyle(context, decoration, fragment, decorationRenderer); + } + + if (hasDecorationStroke) { + m_paintingResourceMode = ApplyToStrokeMode; + paintDecorationWithStyle(context, decoration, fragment, decorationRenderer); + } +} + +void SVGInlineTextBox::paintDecorationWithStyle(GraphicsContext* context, ETextDecoration decoration, const SVGTextFragment& fragment, RenderObject* decorationRenderer) +{ + ASSERT(!m_paintingResource); + ASSERT(m_paintingResourceMode != ApplyToDefaultMode); + + RenderStyle* decorationStyle = decorationRenderer->style(); + ASSERT(decorationStyle); + + const Font& font = decorationStyle->font(); + + // The initial y value refers to overline position. + float thickness = thicknessForDecoration(decoration, font); + + if (fragment.width <= 0 && thickness <= 0) + return; + + float y = fragment.y - font.ascent() + positionOffsetForDecoration(decoration, font, thickness); + + Path path; + path.addRect(FloatRect(fragment.x, y, fragment.width, thickness)); + + context->save(); + + if (acquirePaintingResource(context, decorationRenderer, decorationStyle)) + releasePaintingResource(context, &path); + + context->restore(); +} + +void SVGInlineTextBox::paintTextWithShadows(GraphicsContext* context, RenderStyle* style, TextRun& textRun, const SVGTextFragment& fragment, int startPosition, int endPosition) +{ + const Font& font = style->font(); + const ShadowData* shadow = style->textShadow(); + + FloatPoint textOrigin(fragment.x, fragment.y); + FloatRect shadowRect(FloatPoint(textOrigin.x(), textOrigin.y() - font.ascent()), FloatSize(fragment.width, fragment.height)); + + do { + if (!prepareGraphicsContextForTextPainting(context, textRun, style)) + break; + + FloatSize extraOffset; + if (shadow) + extraOffset = applyShadowToGraphicsContext(context, shadow, shadowRect, false /* stroked */, true /* opaque */, true /* horizontal */); + + font.drawText(context, textRun, textOrigin + extraOffset, startPosition, endPosition); + restoreGraphicsContextAfterTextPainting(context, textRun); + + if (!shadow) + break; + + if (shadow->next()) + context->restore(); + else + context->clearShadow(); + + shadow = shadow->next(); + } while (shadow); +} + +void SVGInlineTextBox::paintText(GraphicsContext* context, RenderStyle* style, RenderStyle* selectionStyle, const SVGTextFragment& fragment, bool hasSelection, bool paintSelectedTextOnly) +{ + ASSERT(style); + ASSERT(selectionStyle); + + int startPosition = 0; + int endPosition = 0; + if (hasSelection) { + selectionStartEnd(startPosition, endPosition); + hasSelection = mapStartEndPositionsIntoFragmentCoordinates(fragment, startPosition, endPosition); + } + + // Fast path if there is no selection, just draw the whole chunk part using the regular style + TextRun textRun(constructTextRun(style, fragment)); + if (!hasSelection || startPosition >= endPosition) { + paintTextWithShadows(context, style, textRun, fragment, 0, fragment.length); + return; + } + + // Eventually draw text using regular style until the start position of the selection + if (startPosition > 0 && !paintSelectedTextOnly) + paintTextWithShadows(context, style, textRun, fragment, 0, startPosition); + + // Draw text using selection style from the start to the end position of the selection + if (style != selectionStyle) + SVGResourcesCache::clientStyleChanged(parent()->renderer(), StyleDifferenceRepaint, selectionStyle); + + TextRun selectionTextRun(constructTextRun(selectionStyle, fragment)); + paintTextWithShadows(context, selectionStyle, textRun, fragment, startPosition, endPosition); + + if (style != selectionStyle) + SVGResourcesCache::clientStyleChanged(parent()->renderer(), StyleDifferenceRepaint, style); + + // Eventually draw text using regular style from the end position of the selection to the end of the current chunk part + if (endPosition < static_cast<int>(fragment.length) && !paintSelectedTextOnly) + paintTextWithShadows(context, style, textRun, fragment, endPosition, fragment.length); +} + +IntRect SVGInlineTextBox::calculateBoundaries() const +{ + FloatRect textRect; + + RenderText* textRenderer = this->textRenderer(); + ASSERT(textRenderer); + + RenderStyle* style = textRenderer->style(); + ASSERT(style); + + int baseline = baselinePosition(AlphabeticBaseline); + int heightDifference = baseline - style->font().ascent(); + + unsigned textFragmentsSize = m_textFragments.size(); + for (unsigned i = 0; i < textFragmentsSize; ++i) { + const SVGTextFragment& fragment = m_textFragments.at(i); + FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height + heightDifference); + + if (!fragment.transform.isIdentity()) + fragmentRect = fragment.transform.mapRect(fragmentRect); + + textRect.unite(fragmentRect); + } + + return enclosingIntRect(textRect); +} + +} // namespace WebCore + +#endif diff --git a/Source/WebCore/rendering/svg/SVGInlineTextBox.h b/Source/WebCore/rendering/svg/SVGInlineTextBox.h new file mode 100644 index 0000000..acc5e9f --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGInlineTextBox.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2007 Rob Buis <buis@kde.org> + * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + * + */ + +#ifndef SVGInlineTextBox_h +#define SVGInlineTextBox_h + +#if ENABLE(SVG) +#include "InlineTextBox.h" +#include "SVGTextLayoutEngine.h" + +namespace WebCore { + +class RenderSVGResource; +class SVGRootInlineBox; + +class SVGInlineTextBox : public InlineTextBox { +public: + SVGInlineTextBox(RenderObject*); + + virtual bool isSVGInlineTextBox() const { return true; } + + virtual int virtualLogicalHeight() const { return m_logicalHeight; } + void setLogicalHeight(int height) { m_logicalHeight = height; } + + virtual int selectionTop() { return m_y; } + virtual int selectionHeight() { return m_logicalHeight; } + virtual int offsetForPosition(int x, bool includePartialGlyphs = true) const; + virtual int positionForOffset(int offset) const; + + void paintSelectionBackground(PaintInfo&); + virtual void paint(PaintInfo&, int tx, int ty); + virtual IntRect selectionRect(int absx, int absy, int startPosition, int endPosition); + + bool mapStartEndPositionsIntoFragmentCoordinates(const SVGTextFragment&, int& startPosition, int& endPosition) const; + + virtual IntRect calculateBoundaries() const; + + void clearTextFragments() { m_textFragments.clear(); } + Vector<SVGTextFragment>& textFragments() { return m_textFragments; } + const Vector<SVGTextFragment>& textFragments() const { return m_textFragments; } + + bool startsNewTextChunk() const { return m_startsNewTextChunk; } + void setStartsNewTextChunk(bool newTextChunk) { m_startsNewTextChunk = newTextChunk; } + + int offsetForPositionInFragment(const SVGTextFragment&, float position, bool includePartialGlyphs) const; + FloatRect selectionRectForTextFragment(const SVGTextFragment&, int fragmentStartPosition, int fragmentEndPosition, RenderStyle*); + +private: + TextRun constructTextRun(RenderStyle*, const SVGTextFragment&) const; + + bool acquirePaintingResource(GraphicsContext*&, RenderObject*, RenderStyle*); + void releasePaintingResource(GraphicsContext*&, const Path*); + + bool prepareGraphicsContextForTextPainting(GraphicsContext*&, TextRun&, RenderStyle*); + void restoreGraphicsContextAfterTextPainting(GraphicsContext*&, TextRun&); + + void paintDecoration(GraphicsContext*, ETextDecoration, const SVGTextFragment&); + void paintDecorationWithStyle(GraphicsContext*, ETextDecoration, const SVGTextFragment&, RenderObject* decorationRenderer); + void paintTextWithShadows(GraphicsContext*, RenderStyle*, TextRun&, const SVGTextFragment&, int startPosition, int endPosition); + void paintText(GraphicsContext*, RenderStyle*, RenderStyle* selectionStyle, const SVGTextFragment&, bool hasSelection, bool paintSelectedTextOnly); + +private: + int m_logicalHeight; + int m_paintingResourceMode; + bool m_startsNewTextChunk : 1; + RenderSVGResource* m_paintingResource; + Vector<SVGTextFragment> m_textFragments; +}; + +} // namespace WebCore + +#endif +#endif // SVGInlineTextBox_h diff --git a/Source/WebCore/rendering/svg/SVGRootInlineBox.cpp b/Source/WebCore/rendering/svg/SVGRootInlineBox.cpp new file mode 100644 index 0000000..49c76de --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGRootInlineBox.cpp @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * Copyright (C) 2006 Apple Computer Inc. + * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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 "SVGRootInlineBox.h" + +#if ENABLE(SVG) +#include "GraphicsContext.h" +#include "RenderBlock.h" +#include "RenderSVGInlineText.h" +#include "SVGInlineFlowBox.h" +#include "SVGInlineTextBox.h" +#include "SVGNames.h" +#include "SVGRenderSupport.h" +#include "SVGTextPositioningElement.h" + +namespace WebCore { + +void SVGRootInlineBox::paint(PaintInfo& paintInfo, int, int) +{ + ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); + ASSERT(!paintInfo.context->paintingDisabled()); + + RenderObject* boxRenderer = renderer(); + ASSERT(boxRenderer); + + bool isPrinting = renderer()->document()->printing(); + bool hasSelection = !isPrinting && selectionState() != RenderObject::SelectionNone; + + PaintInfo childPaintInfo(paintInfo); + if (hasSelection) { + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) { + if (child->isSVGInlineTextBox()) + static_cast<SVGInlineTextBox*>(child)->paintSelectionBackground(childPaintInfo); + else if (child->isSVGInlineFlowBox()) + static_cast<SVGInlineFlowBox*>(child)->paintSelectionBackground(childPaintInfo); + } + } + + childPaintInfo.context->save(); + + if (SVGRenderSupport::prepareToRenderSVGContent(boxRenderer, childPaintInfo)) { + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) { + if (child->isSVGInlineTextBox()) + SVGInlineFlowBox::computeTextMatchMarkerRectForRenderer(toRenderSVGInlineText(static_cast<SVGInlineTextBox*>(child)->textRenderer())); + + child->paint(childPaintInfo, 0, 0); + } + } + + SVGRenderSupport::finishRenderSVGContent(boxRenderer, childPaintInfo, paintInfo.context); + childPaintInfo.context->restore(); +} + +void SVGRootInlineBox::computePerCharacterLayoutInformation() +{ + // Perform SVG text layout phase two (see SVGTextLayoutEngine for details). + SVGTextLayoutEngine characterLayout; + layoutCharactersInTextBoxes(this, characterLayout); + + // Perform SVG text layout phase three (see SVGTextChunkBuilder for details). + characterLayout.finishLayout(); + + // Perform SVG text layout phase four + // Position & resize all SVGInlineText/FlowBoxes in the inline box tree, resize the root box as well as the RenderSVGText parent block. + layoutChildBoxes(this); + layoutRootBox(); +} + +void SVGRootInlineBox::layoutCharactersInTextBoxes(InlineFlowBox* start, SVGTextLayoutEngine& characterLayout) +{ + for (InlineBox* child = start->firstChild(); child; child = child->nextOnLine()) { + if (child->isSVGInlineTextBox()) { + ASSERT(child->renderer()); + ASSERT(child->renderer()->isSVGInlineText()); + + SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(child); + characterLayout.layoutInlineTextBox(textBox); + } else { + ASSERT(child->isInlineFlowBox()); + + // Skip generated content. + Node* node = child->renderer()->node(); + if (!node) + continue; + + SVGInlineFlowBox* flowBox = static_cast<SVGInlineFlowBox*>(child); + bool isTextPath = node->hasTagName(SVGNames::textPathTag); + if (isTextPath) { + // Build text chunks for all <textPath> children, using the line layout algorithm. + // This is needeed as text-anchor is just an additional startOffset for text paths. + SVGTextLayoutEngine lineLayout; + layoutCharactersInTextBoxes(flowBox, lineLayout); + characterLayout.beginTextPathLayout(child->renderer(), lineLayout); + } + + layoutCharactersInTextBoxes(flowBox, characterLayout); + + if (isTextPath) + characterLayout.endTextPathLayout(); + } + } +} + +void SVGRootInlineBox::layoutChildBoxes(InlineFlowBox* start) +{ + for (InlineBox* child = start->firstChild(); child; child = child->nextOnLine()) { + if (child->isSVGInlineTextBox()) { + ASSERT(child->renderer()); + ASSERT(child->renderer()->isSVGInlineText()); + + SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(child); + IntRect boxRect = textBox->calculateBoundaries(); + textBox->setX(boxRect.x()); + textBox->setY(boxRect.y()); + textBox->setLogicalWidth(boxRect.width()); + textBox->setLogicalHeight(boxRect.height()); + } else { + ASSERT(child->isInlineFlowBox()); + + // Skip generated content. + if (!child->renderer()->node()) + continue; + + SVGInlineFlowBox* flowBox = static_cast<SVGInlineFlowBox*>(child); + layoutChildBoxes(flowBox); + + IntRect boxRect = flowBox->calculateBoundaries(); + flowBox->setX(boxRect.x()); + flowBox->setY(boxRect.y()); + flowBox->setLogicalWidth(boxRect.width()); + flowBox->setLogicalHeight(boxRect.height()); + } + } +} + +void SVGRootInlineBox::layoutRootBox() +{ + RenderBlock* parentBlock = block(); + ASSERT(parentBlock); + + IntRect childRect; + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) { + // Skip generated content. + if (!child->renderer()->node()) + continue; + childRect.unite(child->calculateBoundaries()); + } + + int xBlock = childRect.x(); + int yBlock = childRect.y(); + int widthBlock = childRect.width(); + int heightBlock = childRect.height(); + + // Finally, assign the root block position, now that all content is laid out. + parentBlock->setLocation(xBlock, yBlock); + parentBlock->setWidth(widthBlock); + parentBlock->setHeight(heightBlock); + + // Position all children relative to the parent block. + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) { + // Skip generated content. + if (!child->renderer()->node()) + continue; + child->adjustPosition(-xBlock, -yBlock); + } + + // Position ourselves. + setX(0); + setY(0); + setLogicalWidth(widthBlock); + setLogicalHeight(heightBlock); + setBlockLogicalHeight(heightBlock); + setLineTopBottomPositions(0, heightBlock); +} + +InlineBox* SVGRootInlineBox::closestLeafChildForPosition(const IntPoint& point) +{ + InlineBox* firstLeaf = firstLeafChild(); + InlineBox* lastLeaf = lastLeafChild(); + if (firstLeaf == lastLeaf) + return firstLeaf; + + // FIXME: Check for vertical text! + InlineBox* closestLeaf = 0; + for (InlineBox* leaf = firstLeaf; leaf; leaf = leaf->nextLeafChild()) { + if (point.y() < leaf->m_y) + continue; + if (point.y() > leaf->m_y + leaf->virtualLogicalHeight()) + continue; + + closestLeaf = leaf; + if (point.x() < leaf->m_x + leaf->m_logicalWidth) + return leaf; + } + + return closestLeaf ? closestLeaf : lastLeaf; +} + +} // namespace WebCore + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGRootInlineBox.h b/Source/WebCore/rendering/svg/SVGRootInlineBox.h new file mode 100644 index 0000000..418c289 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGRootInlineBox.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * (C) 2006 Apple Computer Inc. + * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + * + */ + +#ifndef SVGRootInlineBox_h +#define SVGRootInlineBox_h + +#if ENABLE(SVG) +#include "RootInlineBox.h" +#include "SVGRenderSupport.h" +#include "SVGTextLayoutEngine.h" + +namespace WebCore { + +class SVGInlineTextBox; + +class SVGRootInlineBox : public RootInlineBox { +public: + SVGRootInlineBox(RenderBlock* block) + : RootInlineBox(block) + , m_logicalHeight(0) + { + } + + virtual bool isSVGRootInlineBox() const { return true; } + + virtual int virtualLogicalHeight() const { return m_logicalHeight; } + void setLogicalHeight(int height) { m_logicalHeight = height; } + + virtual void paint(PaintInfo&, int tx, int ty); + + void computePerCharacterLayoutInformation(); + + virtual FloatRect objectBoundingBox() const { return FloatRect(); } + virtual FloatRect repaintRectInLocalCoordinates() const { return FloatRect(); } + + InlineBox* closestLeafChildForPosition(const IntPoint&); + +private: + void layoutCharactersInTextBoxes(InlineFlowBox*, SVGTextLayoutEngine&); + void layoutChildBoxes(InlineFlowBox*); + void layoutRootBox(); + +private: + int m_logicalHeight; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) + +#endif // SVGRootInlineBox_h diff --git a/Source/WebCore/rendering/svg/SVGTextChunk.cpp b/Source/WebCore/rendering/svg/SVGTextChunk.cpp new file mode 100644 index 0000000..5dea6ad --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextChunk.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "SVGTextChunk.h" + +#include "SVGInlineTextBox.h" +#include "SVGTextFragment.h" + +namespace WebCore { + +SVGTextChunk::SVGTextChunk(bool isVerticalText, ETextAnchor textAnchor, SVGTextContentElement::SVGLengthAdjustType lengthAdjust, float desiredTextLength) + : m_isVerticalText(isVerticalText) + , m_textAnchor(textAnchor) + , m_lengthAdjust(lengthAdjust) + , m_desiredTextLength(desiredTextLength) +{ +} + +void SVGTextChunk::calculateLength(float& length, unsigned& characters) const +{ + SVGTextFragment* lastFragment = 0; + + unsigned boxCount = m_boxes.size(); + for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { + SVGInlineTextBox* textBox = m_boxes.at(boxPosition); + Vector<SVGTextFragment>& fragments = textBox->textFragments(); + + unsigned size = fragments.size(); + if (!size) + continue; + + for (unsigned i = 0; i < size; ++i) { + SVGTextFragment& fragment = fragments.at(i); + characters += fragment.length; + + if (m_isVerticalText) + length += fragment.height; + else + length += fragment.width; + + if (!lastFragment) { + lastFragment = &fragment; + continue; + } + + // Resepect gap between chunks. + if (m_isVerticalText) + length += fragment.y - (lastFragment->y + lastFragment->height); + else + length += fragment.x - (lastFragment->x + lastFragment->width); + + lastFragment = &fragment; + } + } +} + +float SVGTextChunk::calculateTextAnchorShift(float length) const +{ + switch (m_textAnchor) { + case TA_START: + return 0; + case TA_MIDDLE: + return -length / 2; + case TA_END: + return -length; + }; + + ASSERT_NOT_REACHED(); + return 0; +} + +} // namespace WebCore + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGTextChunk.h b/Source/WebCore/rendering/svg/SVGTextChunk.h new file mode 100644 index 0000000..ebe6d81 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextChunk.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextChunk_h +#define SVGTextChunk_h + +#if ENABLE(SVG) +#include "SVGRenderStyleDefs.h" +#include "SVGTextContentElement.h" + +namespace WebCore { + +class SVGInlineTextBox; + +// A SVGTextChunk describes a range of SVGTextFragments, see the SVG spec definition of a "text chunk". +class SVGTextChunk { +public: + SVGTextChunk(bool isVerticalText, ETextAnchor, SVGTextContentElement::SVGLengthAdjustType, float desiredTextLength); + + void calculateLength(float& length, unsigned& characters) const; + float calculateTextAnchorShift(float length) const; + + bool isVerticalText() const { return m_isVerticalText; } + ETextAnchor textAnchor() const { return m_textAnchor; } + SVGTextContentElement::SVGLengthAdjustType lengthAdjust() const { return m_lengthAdjust; } + float desiredTextLength() const { return m_desiredTextLength; } + + Vector<SVGInlineTextBox*>& boxes() { return m_boxes; } + const Vector<SVGInlineTextBox*>& boxes() const { return m_boxes; } + + bool hasDesiredTextLength() const { return m_lengthAdjust != SVGTextContentElement::LENGTHADJUST_UNKNOWN && m_desiredTextLength > 0; } + bool hasTextAnchor() const { return m_textAnchor != TA_START; } + +private: + // Contains all SVGInlineTextBoxes this chunk spans. + Vector<SVGInlineTextBox*> m_boxes; + + // writing-mode specific property. + bool m_isVerticalText; + + // text-anchor specific properties. + ETextAnchor m_textAnchor; + + // textLength/lengthAdjust specific properties. + SVGTextContentElement::SVGLengthAdjustType m_lengthAdjust; + float m_desiredTextLength; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextChunkBuilder.cpp b/Source/WebCore/rendering/svg/SVGTextChunkBuilder.cpp new file mode 100644 index 0000000..bbbae6c --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextChunkBuilder.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "SVGTextChunkBuilder.h" + +#include "RenderSVGInlineText.h" +#include "SVGElement.h" +#include "SVGInlineTextBox.h" + +namespace WebCore { + +SVGTextChunkBuilder::SVGTextChunkBuilder() +{ +} + +void SVGTextChunkBuilder::transformationForTextBox(SVGInlineTextBox* textBox, AffineTransform& transform) const +{ + DEFINE_STATIC_LOCAL(const AffineTransform, s_identityTransform, ()); + if (!m_textBoxTransformations.contains(textBox)) { + transform = s_identityTransform; + return; + } + + transform = m_textBoxTransformations.get(textBox); +} + +void SVGTextChunkBuilder::buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes) +{ + if (lineLayoutBoxes.isEmpty()) + return; + + bool foundStart = false; + unsigned lastChunkStartPosition = 0; + unsigned boxPosition = 0; + unsigned boxCount = lineLayoutBoxes.size(); + for (; boxPosition < boxCount; ++boxPosition) { + SVGInlineTextBox* textBox = lineLayoutBoxes.at(boxPosition); + if (!textBox->startsNewTextChunk()) + continue; + + if (!foundStart) { + lastChunkStartPosition = boxPosition; + foundStart = true; + } else { + ASSERT(boxPosition > lastChunkStartPosition); + addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition); + lastChunkStartPosition = boxPosition; + } + } + + if (!foundStart) + return; + + if (boxPosition - lastChunkStartPosition > 0) + addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition); +} + +void SVGTextChunkBuilder::layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes) +{ + buildTextChunks(lineLayoutBoxes); + if (m_textChunks.isEmpty()) + return; + + unsigned chunkCount = m_textChunks.size(); + for (unsigned i = 0; i < chunkCount; ++i) + processTextChunk(m_textChunks.at(i)); + + m_textChunks.clear(); +} + +void SVGTextChunkBuilder::addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxStart, unsigned boxCount) +{ + SVGInlineTextBox* textBox = lineLayoutBoxes.at(boxStart); + ASSERT(textBox); + + RenderSVGInlineText* textRenderer = toRenderSVGInlineText(textBox->textRenderer()); + ASSERT(textRenderer); + + const RenderStyle* style = textRenderer->style(); + ASSERT(style); + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + + SVGTextContentElement::SVGLengthAdjustType lengthAdjust = SVGTextContentElement::LENGTHADJUST_UNKNOWN; + float desiredTextLength = 0; + + if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textRenderer->parent())) { + lengthAdjust = static_cast<SVGTextContentElement::SVGLengthAdjustType>(textContentElement->lengthAdjust()); + desiredTextLength = textContentElement->textLength().value(textContentElement); + } + + SVGTextChunk chunk(svgStyle->isVerticalWritingMode(), svgStyle->textAnchor(), lengthAdjust, desiredTextLength); + + Vector<SVGInlineTextBox*>& boxes = chunk.boxes(); + for (unsigned i = boxStart; i < boxStart + boxCount; ++i) + boxes.append(lineLayoutBoxes.at(i)); + + m_textChunks.append(chunk); +} + +void SVGTextChunkBuilder::processTextChunk(const SVGTextChunk& chunk) +{ + bool processTextLength = chunk.hasDesiredTextLength(); + bool processTextAnchor = chunk.hasTextAnchor(); + if (!processTextAnchor && !processTextLength) + return; + + const Vector<SVGInlineTextBox*>& boxes = chunk.boxes(); + unsigned boxCount = boxes.size(); + if (!boxCount) + return; + + // Calculate absolute length of whole text chunk (starting from text box 'start', spanning 'length' text boxes). + float chunkLength = 0; + unsigned chunkCharacters = 0; + chunk.calculateLength(chunkLength, chunkCharacters); + + bool isVerticalText = chunk.isVerticalText(); + if (processTextLength) { + if (chunk.lengthAdjust() == SVGTextContentElement::LENGTHADJUST_SPACING) { + float textLengthShift = (chunk.desiredTextLength() - chunkLength) / chunkCharacters; + unsigned atCharacter = 0; + for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { + Vector<SVGTextFragment>& fragments = boxes.at(boxPosition)->textFragments(); + if (fragments.isEmpty()) + continue; + processTextLengthSpacingCorrection(isVerticalText, textLengthShift, fragments, atCharacter); + } + } else { + ASSERT(chunk.lengthAdjust() == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS); + float scale = chunk.desiredTextLength() / chunkLength; + AffineTransform spacingAndGlyphsTransform; + + bool foundFirstFragment = false; + for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { + SVGInlineTextBox* textBox = boxes.at(boxPosition); + Vector<SVGTextFragment>& fragments = textBox->textFragments(); + if (fragments.isEmpty()) + continue; + + if (!foundFirstFragment) { + foundFirstFragment = true; + buildSpacingAndGlyphsTransform(isVerticalText, scale, fragments.first(), spacingAndGlyphsTransform); + } + + m_textBoxTransformations.set(textBox, spacingAndGlyphsTransform); + } + } + } + + if (!processTextAnchor) + return; + + // If we previously applied a lengthAdjust="spacing" correction, we have to recalculate the chunk length, to be able to apply the text-anchor shift. + if (processTextLength && chunk.lengthAdjust() == SVGTextContentElement::LENGTHADJUST_SPACING) { + chunkLength = 0; + chunkCharacters = 0; + chunk.calculateLength(chunkLength, chunkCharacters); + } + + float textAnchorShift = chunk.calculateTextAnchorShift(chunkLength); + for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { + Vector<SVGTextFragment>& fragments = boxes.at(boxPosition)->textFragments(); + if (fragments.isEmpty()) + continue; + processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments); + } +} + +void SVGTextChunkBuilder::processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>& fragments, unsigned& atCharacter) +{ + unsigned fragmentCount = fragments.size(); + for (unsigned i = 0; i < fragmentCount; ++i) { + SVGTextFragment& fragment = fragments.at(i); + + if (isVerticalText) + fragment.y += textLengthShift * atCharacter; + else + fragment.x += textLengthShift * atCharacter; + + atCharacter += fragment.length; + } +} + +void SVGTextChunkBuilder::processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector<SVGTextFragment>& fragments) +{ + unsigned fragmentCount = fragments.size(); + for (unsigned i = 0; i < fragmentCount; ++i) { + SVGTextFragment& fragment = fragments.at(i); + + if (isVerticalText) + fragment.y += textAnchorShift; + else + fragment.x += textAnchorShift; + } +} + +void SVGTextChunkBuilder::buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment& fragment, AffineTransform& spacingAndGlyphsTransform) +{ + spacingAndGlyphsTransform.translate(fragment.x, fragment.y); + + if (isVerticalText) + spacingAndGlyphsTransform.scaleNonUniform(1, scale); + else + spacingAndGlyphsTransform.scaleNonUniform(scale, 1); + + spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y); +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGTextChunkBuilder.h b/Source/WebCore/rendering/svg/SVGTextChunkBuilder.h new file mode 100644 index 0000000..36342e7 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextChunkBuilder.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextChunkBuilder_h +#define SVGTextChunkBuilder_h + +#if ENABLE(SVG) +#include "SVGTextChunk.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class SVGInlineTextBox; +struct SVGTextFragment; + +// SVGTextChunkBuilder performs the third layout phase for SVG text. +// +// Phase one built the layout information from the SVG DOM stored in the RenderSVGInlineText objects (SVGTextLayoutAttributes). +// Phase two performed the actual per-character layout, computing the final positions for each character, stored in the SVGInlineTextBox objects (SVGTextFragment). +// Phase three performs all modifications that have to be applied to each individual text chunk (text-anchor & textLength). + +class SVGTextChunkBuilder : public Noncopyable { +public: + SVGTextChunkBuilder(); + + const Vector<SVGTextChunk>& textChunks() const { return m_textChunks; } + void transformationForTextBox(SVGInlineTextBox*, AffineTransform&) const; + + void buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes); + void layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes); + +private: + void addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxPosition, unsigned boxCount); + void processTextChunk(const SVGTextChunk&); + + void processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>&, unsigned& atCharacter); + void processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector<SVGTextFragment>&); + void buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment&, AffineTransform&); + +private: + Vector<SVGTextChunk> m_textChunks; + HashMap<SVGInlineTextBox*, AffineTransform> m_textBoxTransformations; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextFragment.h b/Source/WebCore/rendering/svg/SVGTextFragment.h new file mode 100644 index 0000000..2e520da --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextFragment.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextFragment_h +#define SVGTextFragment_h + +#if ENABLE(SVG) +#include "AffineTransform.h" + +namespace WebCore { + +// A SVGTextFragment describes a text fragment of a RenderSVGInlineText which can be rendered at once. +struct SVGTextFragment { + SVGTextFragment() + : positionListOffset(0) + , length(0) + , x(0) + , y(0) + , width(0) + , height(0) + { + } + + // The first rendered character starts at RenderSVGInlineText::characters() + positionListOffset. + unsigned positionListOffset; + unsigned length; + + float x; + float y; + float width; + float height; + + // Includes rotation/glyph-orientation-(horizontal|vertical) transforms, lengthAdjust="spacingAndGlyphs" (for textPath only), + // as well as orientation related shifts (see SVGTextLayoutEngine, which builds this transformation). + AffineTransform transform; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.cpp new file mode 100644 index 0000000..3037b77 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "SVGTextLayoutAttributes.h" + +#include <stdio.h> +#include <wtf/text/CString.h> + +namespace WebCore { + +SVGTextLayoutAttributes::SVGTextLayoutAttributes() +{ +} + +void SVGTextLayoutAttributes::reserveCapacity(unsigned length) +{ + m_xValues.reserveCapacity(length); + m_yValues.reserveCapacity(length); + m_dxValues.reserveCapacity(length); + m_dyValues.reserveCapacity(length); + m_rotateValues.reserveCapacity(length); +} + +float SVGTextLayoutAttributes::emptyValue() +{ + static float s_emptyValue = std::numeric_limits<float>::max() - 1; + return s_emptyValue; +} + +static inline void dumpLayoutVector(const Vector<float>& values) +{ + if (values.isEmpty()) { + fprintf(stderr, "empty"); + return; + } + + unsigned size = values.size(); + for (unsigned i = 0; i < size; ++i) { + float value = values.at(i); + if (value == SVGTextLayoutAttributes::emptyValue()) + fprintf(stderr, "x "); + else + fprintf(stderr, "%lf ", value); + } +} + +void SVGTextLayoutAttributes::dump() const +{ + fprintf(stderr, "x values: "); + dumpLayoutVector(m_xValues); + fprintf(stderr, "\n"); + + fprintf(stderr, "y values: "); + dumpLayoutVector(m_yValues); + fprintf(stderr, "\n"); + + fprintf(stderr, "dx values: "); + dumpLayoutVector(m_dxValues); + fprintf(stderr, "\n"); + + fprintf(stderr, "dy values: "); + dumpLayoutVector(m_dyValues); + fprintf(stderr, "\n"); + + fprintf(stderr, "rotate values: "); + dumpLayoutVector(m_rotateValues); + fprintf(stderr, "\n"); + + fprintf(stderr, "character data values:\n"); + unsigned textMetricsSize = m_textMetricsValues.size(); + for (unsigned i = 0; i < textMetricsSize; ++i) { + const SVGTextMetrics& metrics = m_textMetricsValues.at(i); + fprintf(stderr, "| {length=%i, glyphName='%s', unicodeString='%s', width=%lf, height=%lf}\n", + metrics.length(), metrics.glyph().name.utf8().data(), metrics.glyph().unicodeString.utf8().data(), metrics.width(), metrics.height()); + } + fprintf(stderr, "\n"); +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.h b/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.h new file mode 100644 index 0000000..d08d5b7 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextLayoutAttributes_h +#define SVGTextLayoutAttributes_h + +#if ENABLE(SVG) +#include "SVGTextMetrics.h" +#include <wtf/Vector.h> +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class SVGTextLayoutAttributes { +public: + SVGTextLayoutAttributes(); + + void reserveCapacity(unsigned length); + void dump() const; + + static float emptyValue(); + + Vector<float>& xValues() { return m_xValues; } + const Vector<float>& xValues() const { return m_xValues; } + + Vector<float>& yValues() { return m_yValues; } + const Vector<float>& yValues() const { return m_yValues; } + + Vector<float>& dxValues() { return m_dxValues; } + const Vector<float>& dxValues() const { return m_dxValues; } + + Vector<float>& dyValues() { return m_dyValues; } + const Vector<float>& dyValues() const { return m_dyValues; } + + Vector<float>& rotateValues() { return m_rotateValues; } + const Vector<float>& rotateValues() const { return m_rotateValues; } + + Vector<SVGTextMetrics>& textMetricsValues() { return m_textMetricsValues; } + const Vector<SVGTextMetrics>& textMetricsValues() const { return m_textMetricsValues; } + +private: + Vector<float> m_xValues; + Vector<float> m_yValues; + Vector<float> m_dxValues; + Vector<float> m_dyValues; + Vector<float> m_rotateValues; + Vector<SVGTextMetrics> m_textMetricsValues; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.cpp new file mode 100644 index 0000000..3122912 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.cpp @@ -0,0 +1,308 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "SVGTextLayoutAttributesBuilder.h" + +#include "RenderSVGInlineText.h" +#include "RenderSVGText.h" +#include "SVGTextPositioningElement.h" + +// Set to a value > 0 to dump the text layout attributes +#define DUMP_TEXT_LAYOUT_ATTRIBUTES 0 + +namespace WebCore { + +SVGTextLayoutAttributesBuilder::SVGTextLayoutAttributesBuilder() +{ +} + +void SVGTextLayoutAttributesBuilder::buildLayoutAttributesForTextSubtree(RenderSVGText* textRoot) +{ + ASSERT(textRoot); + m_scopes.clear(); + + // Build list of x/y/dx/dy/rotate values for each subtree element that may define these values (tspan/textPath etc). + unsigned atCharacter = 0; + UChar lastCharacter = '\0'; + buildLayoutScopes(textRoot, atCharacter, lastCharacter); + + if (!atCharacter) + return; + + // Build list of x/y/dx/dy/rotate values for the outermost <text> element. + buildOutermostLayoutScope(textRoot, atCharacter); + + // Propagate layout attributes to each RenderSVGInlineText object. + atCharacter = 0; + lastCharacter = '\0'; + propagateLayoutAttributes(textRoot, atCharacter, lastCharacter); +} + +static inline void extractFloatValuesFromSVGLengthList(SVGElement* lengthContext, const SVGLengthList& list, Vector<float>& floatValues, unsigned textContentLength) +{ + ASSERT(lengthContext); + + unsigned length = list.size(); + if (length > textContentLength) + length = textContentLength; + floatValues.reserveCapacity(length); + + for (unsigned i = 0; i < length; ++i) { + const SVGLength& length = list.at(i); + floatValues.append(length.value(lengthContext)); + } +} + +static inline void extractFloatValuesFromSVGNumberList(const SVGNumberList& list, Vector<float>& floatValues, unsigned textContentLength) +{ + unsigned length = list.size(); + if (length > textContentLength) + length = textContentLength; + floatValues.reserveCapacity(length); + + for (unsigned i = 0; i < length; ++i) + floatValues.append(list.at(i)); +} + +void SVGTextLayoutAttributesBuilder::buildLayoutScope(LayoutScope& scope, RenderObject* renderer, unsigned textContentStart, unsigned textContentLength) const +{ + ASSERT(renderer); + ASSERT(renderer->style()); + + scope.textContentStart = textContentStart; + scope.textContentLength = textContentLength; + + SVGTextPositioningElement* element = SVGTextPositioningElement::elementFromRenderer(renderer); + if (!element) + return; + + SVGTextLayoutAttributes& attributes = scope.attributes; + extractFloatValuesFromSVGLengthList(element, element->x(), attributes.xValues(), textContentLength); + extractFloatValuesFromSVGLengthList(element, element->y(), attributes.yValues(), textContentLength); + extractFloatValuesFromSVGLengthList(element, element->dx(), attributes.dxValues(), textContentLength); + extractFloatValuesFromSVGLengthList(element, element->dy(), attributes.dyValues(), textContentLength); + extractFloatValuesFromSVGNumberList(element->rotate(), attributes.rotateValues(), textContentLength); + + // The last rotation value spans the whole scope. + Vector<float>& rotateValues = attributes.rotateValues(); + if (rotateValues.isEmpty()) + return; + + unsigned rotateValuesSize = rotateValues.size(); + if (rotateValuesSize == textContentLength) + return; + + float lastRotation = rotateValues.last(); + + rotateValues.resize(textContentLength); + for (unsigned i = rotateValuesSize; i < textContentLength; ++i) + rotateValues.at(i) = lastRotation; +} + +static inline bool characterIsSpace(const UChar& character) +{ + return character == ' '; +} + +static inline bool characterIsSpaceOrNull(const UChar& character) +{ + return character == ' ' || character == '\0'; +} + +static inline bool shouldPreserveAllWhiteSpace(RenderStyle* style) +{ + ASSERT(style); + return style->whiteSpace() == PRE; +} + +void SVGTextLayoutAttributesBuilder::buildLayoutScopes(RenderObject* start, unsigned& atCharacter, UChar& lastCharacter) +{ + for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { + if (child->isSVGInlineText()) { + RenderSVGInlineText* text = toRenderSVGInlineText(child); + + if (!shouldPreserveAllWhiteSpace(text->style())) { + const UChar* characters = text->characters(); + unsigned textLength = text->textLength(); + for (unsigned textPosition = 0; textPosition < textLength; ++textPosition) { + const UChar& currentCharacter = characters[textPosition]; + if (characterIsSpace(currentCharacter) && characterIsSpaceOrNull(lastCharacter)) + continue; + + lastCharacter = currentCharacter; + ++atCharacter; + } + } else + atCharacter += text->textLength(); + + continue; + } + + if (!child->isSVGInline()) + continue; + + unsigned textContentStart = atCharacter; + buildLayoutScopes(child, atCharacter, lastCharacter); + + LayoutScope scope; + buildLayoutScope(scope, child, textContentStart, atCharacter - textContentStart); + m_scopes.append(scope); + } +} + +void SVGTextLayoutAttributesBuilder::buildOutermostLayoutScope(RenderSVGText* textRoot, unsigned textLength) +{ + LayoutScope scope; + buildLayoutScope(scope, textRoot, 0, textLength); + + // Handle <text> x/y default attributes. + Vector<float>& xValues = scope.attributes.xValues(); + if (xValues.isEmpty()) + xValues.append(0); + + Vector<float>& yValues = scope.attributes.yValues(); + if (yValues.isEmpty()) + yValues.append(0); + + m_scopes.prepend(scope); +} + +void SVGTextLayoutAttributesBuilder::propagateLayoutAttributes(RenderObject* start, unsigned& atCharacter, UChar& lastCharacter) const +{ + for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { + if (child->isSVGInlineText()) { + RenderSVGInlineText* text = toRenderSVGInlineText(child); + const UChar* characters = text->characters(); + unsigned textLength = text->textLength(); + bool preserveWhiteSpace = shouldPreserveAllWhiteSpace(text->style()); + + SVGTextLayoutAttributes attributes; + attributes.reserveCapacity(textLength); + + unsigned valueListPosition = atCharacter; + unsigned metricsLength = 1; + for (unsigned textPosition = 0; textPosition < textLength; textPosition += metricsLength) { + const UChar& currentCharacter = characters[textPosition]; + + SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(text, textPosition, 1); + metricsLength = metrics.length(); + + if (!preserveWhiteSpace && characterIsSpace(currentCharacter) && characterIsSpaceOrNull(lastCharacter)) { + assignEmptyLayoutAttributesForCharacter(attributes); + attributes.textMetricsValues().append(SVGTextMetrics::emptyMetrics()); + continue; + } + + assignLayoutAttributesForCharacter(attributes, metrics, valueListPosition); + + if (metricsLength > 1) { + for (unsigned i = 0; i < metricsLength - 1; ++i) + assignEmptyLayoutAttributesForCharacter(attributes); + } + + lastCharacter = currentCharacter; + valueListPosition += metricsLength; + } + +#if DUMP_TEXT_LAYOUT_ATTRIBUTES > 0 + fprintf(stderr, "\nDumping layout attributes for RenderSVGInlineText, renderer=%p, node=%p (atCharacter: %i)\n", text, text->node(), atCharacter); + attributes.dump(); +#endif + + text->storeLayoutAttributes(attributes); + atCharacter = valueListPosition; + continue; + } + + if (!child->isSVGInline()) + continue; + + propagateLayoutAttributes(child, atCharacter, lastCharacter); + } +} + +float SVGTextLayoutAttributesBuilder::nextLayoutValue(LayoutValueType type, unsigned atCharacter) const +{ + for (int i = m_scopes.size() - 1; i >= 0; --i) { + const LayoutScope& scope = m_scopes.at(i); + if (scope.textContentStart > atCharacter || scope.textContentStart + scope.textContentLength < atCharacter) + continue; + + const Vector<float>* valuesPointer = 0; + switch (type) { + case XValueAttribute: + valuesPointer = &scope.attributes.xValues(); + break; + case YValueAttribute: + valuesPointer = &scope.attributes.yValues(); + break; + case DxValueAttribute: + valuesPointer = &scope.attributes.dxValues(); + break; + case DyValueAttribute: + valuesPointer = &scope.attributes.dyValues(); + break; + case RotateValueAttribute: + valuesPointer = &scope.attributes.rotateValues(); + break; + default: + ASSERT_NOT_REACHED(); + } + + ASSERT(valuesPointer); + const Vector<float>& values = *valuesPointer; + if (values.isEmpty()) + continue; + + unsigned position = atCharacter - scope.textContentStart; + if (position >= values.size()) + continue; + + return values.at(position); + } + + return SVGTextLayoutAttributes::emptyValue(); +} + +void SVGTextLayoutAttributesBuilder::assignLayoutAttributesForCharacter(SVGTextLayoutAttributes& attributes, SVGTextMetrics& metrics, unsigned valueListPosition) const +{ + attributes.xValues().append(nextLayoutValue(XValueAttribute, valueListPosition)); + attributes.yValues().append(nextLayoutValue(YValueAttribute, valueListPosition)); + attributes.dxValues().append(nextLayoutValue(DxValueAttribute, valueListPosition)); + attributes.dyValues().append(nextLayoutValue(DyValueAttribute, valueListPosition)); + attributes.rotateValues().append(nextLayoutValue(RotateValueAttribute, valueListPosition)); + attributes.textMetricsValues().append(metrics); +} + +void SVGTextLayoutAttributesBuilder::assignEmptyLayoutAttributesForCharacter(SVGTextLayoutAttributes& attributes) const +{ + attributes.xValues().append(SVGTextLayoutAttributes::emptyValue()); + attributes.yValues().append(SVGTextLayoutAttributes::emptyValue()); + attributes.dxValues().append(SVGTextLayoutAttributes::emptyValue()); + attributes.dyValues().append(SVGTextLayoutAttributes::emptyValue()); + attributes.rotateValues().append(SVGTextLayoutAttributes::emptyValue()); + // This doesn't add an empty value to textMetricsValues() on purpose! +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.h b/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.h new file mode 100644 index 0000000..f29ac64 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextLayoutAttributesBuilder_h +#define SVGTextLayoutAttributesBuilder_h + +#if ENABLE(SVG) +#include "SVGTextLayoutAttributes.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class RenderObject; +class RenderSVGText; + +// SVGTextLayoutAttributesBuilder performs the first layout phase for SVG text. +// +// It extracts the x/y/dx/dy/rotate values from the SVGTextPositioningElements in the DOM, +// measures all characters in the RenderSVGText subtree and extracts kerning/ligature information. +// These values are propagated to the corresponding RenderSVGInlineText renderers. +// The first layout phase only extracts the relevant information needed in RenderBlockLineLayout +// to create the InlineBox tree based on text chunk boundaries & BiDi information. +// The second layout phase is carried out by SVGTextLayoutEngine. + +class SVGTextLayoutAttributesBuilder : public Noncopyable { +public: + SVGTextLayoutAttributesBuilder(); + void buildLayoutAttributesForTextSubtree(RenderSVGText*); + +private: + struct LayoutScope { + LayoutScope() + : textContentStart(0) + , textContentLength(0) + { + } + + unsigned textContentStart; + unsigned textContentLength; + SVGTextLayoutAttributes attributes; + }; + + void buildLayoutScope(LayoutScope&, RenderObject*, unsigned textContentStart, unsigned textContentLength) const; + void buildLayoutScopes(RenderObject*, unsigned& atCharacter, UChar& lastCharacter); + void buildOutermostLayoutScope(RenderSVGText*, unsigned textLength); + void propagateLayoutAttributes(RenderObject*, unsigned& atCharacter, UChar& lastCharacter) const; + + enum LayoutValueType { + XValueAttribute, + YValueAttribute, + DxValueAttribute, + DyValueAttribute, + RotateValueAttribute + }; + + float nextLayoutValue(LayoutValueType, unsigned atCharacter) const; + void assignLayoutAttributesForCharacter(SVGTextLayoutAttributes&, SVGTextMetrics&, unsigned valueListPosition) const; + void assignEmptyLayoutAttributesForCharacter(SVGTextLayoutAttributes&) const; + +private: + Vector<LayoutScope> m_scopes; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp new file mode 100644 index 0000000..7eefad6 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp @@ -0,0 +1,579 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "SVGTextLayoutEngine.h" + +#include "RenderSVGInlineText.h" +#include "RenderSVGTextPath.h" +#include "SVGElement.h" +#include "SVGInlineTextBox.h" +#include "SVGTextLayoutEngineBaseline.h" +#include "SVGTextLayoutEngineSpacing.h" + +// Set to a value > 0 to dump the text fragments +#define DUMP_TEXT_FRAGMENTS 0 + +namespace WebCore { + +SVGTextLayoutEngine::SVGTextLayoutEngine() + : m_x(0) + , m_y(0) + , m_dx(0) + , m_dy(0) + , m_isVerticalText(false) + , m_inPathLayout(false) + , m_textPathLength(0) + , m_textPathCurrentOffset(0) + , m_textPathSpacing(0) + , m_textPathScaling(1) +{ +} + +void SVGTextLayoutEngine::updateCharacerPositionIfNeeded(float& x, float& y) +{ + if (m_inPathLayout) + return; + + // Replace characters x/y position, with the current text position plus any + // relative adjustments, if it doesn't specify an absolute position itself. + if (x == SVGTextLayoutAttributes::emptyValue()) + x = m_x + m_dx; + + if (y == SVGTextLayoutAttributes::emptyValue()) + y = m_y + m_dy; + + m_dx = 0; + m_dy = 0; +} + +void SVGTextLayoutEngine::updateCurrentTextPosition(float x, float y, float glyphAdvance) +{ + // Update current text position after processing the character. + if (m_isVerticalText) { + m_x = x; + m_y = y + glyphAdvance; + } else { + m_x = x + glyphAdvance; + m_y = y; + } +} + +void SVGTextLayoutEngine::updateRelativePositionAdjustmentsIfNeeded(Vector<float>& dxValues, Vector<float>& dyValues, unsigned positionListOffset) +{ + // Update relative positioning information. + if (dxValues.isEmpty() && dyValues.isEmpty()) + return; + + float dx = 0; + if (!dxValues.isEmpty()) { + float& dxCurrent = dxValues.at(positionListOffset); + if (dxCurrent != SVGTextLayoutAttributes::emptyValue()) + dx = dxCurrent; + } + + float dy = 0; + if (!dyValues.isEmpty()) { + float& dyCurrent = dyValues.at(positionListOffset); + if (dyCurrent != SVGTextLayoutAttributes::emptyValue()) + dy = dyCurrent; + } + + if (m_inPathLayout) { + if (m_isVerticalText) { + m_dx += dx; + m_dy = dy; + } else { + m_dx = dx; + m_dy += dy; + } + + return; + } + + m_dx = dx; + m_dy = dy; +} + +void SVGTextLayoutEngine::recordTextFragment(SVGInlineTextBox* textBox, RenderSVGInlineText* text, unsigned positionListOffset, const SVGTextMetrics& lastCharacterMetrics) +{ + ASSERT(!m_currentTextFragment.length); + + // Figure out length of fragment. + m_currentTextFragment.length = positionListOffset - m_currentTextFragment.positionListOffset; + + // Figure out fragment metrics. + if (m_currentTextFragment.length == 1) { + // Fast path, can rely on already computed per-character metrics. + m_currentTextFragment.width = lastCharacterMetrics.width(); + m_currentTextFragment.height = lastCharacterMetrics.height(); + } else { + // Need to measure the whole range (range metrics != sum of character metrics) + SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(text, m_currentTextFragment.positionListOffset, m_currentTextFragment.length); + m_currentTextFragment.width = metrics.width(); + m_currentTextFragment.height = metrics.height(); + } + + textBox->textFragments().append(m_currentTextFragment); + m_currentTextFragment = SVGTextFragment(); +} + +bool SVGTextLayoutEngine::parentDefinesTextLength(RenderObject* parent) const +{ + RenderObject* currentParent = parent; + while (currentParent) { + SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(currentParent); + if (textContentElement) { + SVGTextContentElement::SVGLengthAdjustType lengthAdjust = static_cast<SVGTextContentElement::SVGLengthAdjustType>(textContentElement->lengthAdjust()); + if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACING && textContentElement->textLength().value(textContentElement) > 0) + return true; + } + + if (currentParent->isSVGText()) + return false; + + currentParent = currentParent->parent(); + } + + ASSERT_NOT_REACHED(); + return false; +} + +void SVGTextLayoutEngine::beginTextPathLayout(RenderObject* object, SVGTextLayoutEngine& lineLayout) +{ + ASSERT(object); + + m_inPathLayout = true; + RenderSVGTextPath* textPath = toRenderSVGTextPath(object); + + m_textPath = textPath->layoutPath(); + m_textPathStartOffset = textPath->startOffset(); + m_textPathLength = m_textPath.length(); + if (m_textPathStartOffset > 0 && m_textPathStartOffset <= 1) + m_textPathStartOffset *= m_textPathLength; + + float totalLength = 0; + unsigned totalCharacters = 0; + + lineLayout.m_chunkLayoutBuilder.buildTextChunks(lineLayout.m_lineLayoutBoxes); + const Vector<SVGTextChunk>& textChunks = lineLayout.m_chunkLayoutBuilder.textChunks(); + + unsigned size = textChunks.size(); + for (unsigned i = 0; i < size; ++i) { + const SVGTextChunk& chunk = textChunks.at(i); + + float length = 0; + unsigned characters = 0; + chunk.calculateLength(length, characters); + + // Handle text-anchor as additional start offset for text paths. + m_textPathStartOffset += chunk.calculateTextAnchorShift(length); + + totalLength += length; + totalCharacters += characters; + } + + m_textPathCurrentOffset = m_textPathStartOffset; + + // Eventually handle textLength adjustments. + SVGTextContentElement::SVGLengthAdjustType lengthAdjust = SVGTextContentElement::LENGTHADJUST_UNKNOWN; + float desiredTextLength = 0; + + if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textPath)) { + lengthAdjust = static_cast<SVGTextContentElement::SVGLengthAdjustType>(textContentElement->lengthAdjust()); + desiredTextLength = textContentElement->textLength().value(textContentElement); + } + + if (!desiredTextLength) + return; + + if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACING) + m_textPathSpacing = (desiredTextLength - totalLength) / totalCharacters; + else + m_textPathScaling = desiredTextLength / totalLength; +} + +void SVGTextLayoutEngine::endTextPathLayout() +{ + m_inPathLayout = false; + m_textPath = Path(); + m_textPathLength = 0; + m_textPathStartOffset = 0; + m_textPathCurrentOffset = 0; + m_textPathSpacing = 0; + m_textPathScaling = 1; +} + +void SVGTextLayoutEngine::layoutInlineTextBox(SVGInlineTextBox* textBox) +{ + ASSERT(textBox); + + RenderSVGInlineText* text = toRenderSVGInlineText(textBox->textRenderer()); + ASSERT(text); + ASSERT(text->parent()); + ASSERT(text->parent()->node()); + ASSERT(text->parent()->node()->isSVGElement()); + + const RenderStyle* style = text->style(); + ASSERT(style); + + textBox->clearTextFragments(); + m_isVerticalText = style->svgStyle()->isVerticalWritingMode(); + layoutTextOnLineOrPath(textBox, text, style); + + if (m_inPathLayout) { + m_pathLayoutBoxes.append(textBox); + return; + } + + m_lineLayoutBoxes.append(textBox); +} + +void SVGTextLayoutEngine::finishLayout() +{ + // After all text fragments are stored in their correpsonding SVGInlineTextBoxes, we can layout individual text chunks. + // Chunk layouting is only performed for line layout boxes, not for path layout, where it has already been done. + m_chunkLayoutBuilder.layoutTextChunks(m_lineLayoutBoxes); + + // Finalize transform matrices, after the chunk layout corrections have been applied, and all fragment x/y positions are finalized. + if (!m_lineLayoutBoxes.isEmpty()) { +#if DUMP_TEXT_FRAGMENTS > 0 + fprintf(stderr, "Line layout: "); +#endif + + finalizeTransformMatrices(m_lineLayoutBoxes); + } + + if (!m_pathLayoutBoxes.isEmpty()) { +#if DUMP_TEXT_FRAGMENTS > 0 + fprintf(stderr, "Path layout: "); +#endif + finalizeTransformMatrices(m_pathLayoutBoxes); + } +} + +void SVGTextLayoutEngine::finalizeTransformMatrices(Vector<SVGInlineTextBox*>& boxes) +{ + unsigned boxCount = boxes.size(); + +#if DUMP_TEXT_FRAGMENTS > 0 + fprintf(stderr, "Dumping all text fragments in text sub tree, %i boxes\n", boxCount); + + for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { + SVGInlineTextBox* textBox = boxes.at(boxPosition); + Vector<SVGTextFragment>& fragments = textBox->textFragments(); + fprintf(stderr, "-> Box %i: Dumping text fragments for SVGInlineTextBox, textBox=%p, textRenderer=%p\n", boxPosition, textBox, textBox->textRenderer()); + fprintf(stderr, " textBox properties, start=%i, len=%i\n", textBox->start(), textBox->len()); + fprintf(stderr, " textRenderer properties, textLength=%i\n", textBox->textRenderer()->textLength()); + + const UChar* characters = textBox->textRenderer()->characters(); + + unsigned fragmentCount = fragments.size(); + for (unsigned i = 0; i < fragmentCount; ++i) { + SVGTextFragment& fragment = fragments.at(i); + String fragmentString(characters + fragment.positionListOffset, fragment.length); + fprintf(stderr, " -> Fragment %i, x=%lf, y=%lf, width=%lf, height=%lf, positionListOffset=%i, length=%i, characters='%s'\n" + , i, fragment.x, fragment.y, fragment.width, fragment.height, fragment.positionListOffset, fragment.length, fragmentString.utf8().data()); + } + } +#endif + + + if (!boxCount) + return; + + AffineTransform textBoxTransformation; + for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { + SVGInlineTextBox* textBox = boxes.at(boxPosition); + Vector<SVGTextFragment>& fragments = textBox->textFragments(); + + unsigned fragmentCount = fragments.size(); + for (unsigned i = 0; i < fragmentCount; ++i) { + SVGTextFragment& fragment = fragments.at(i); + AffineTransform& transform = fragment.transform; + if (!transform.isIdentity()) { + transform.translateRight(fragment.x, fragment.y); + transform.translate(-fragment.x, -fragment.y); + } + + m_chunkLayoutBuilder.transformationForTextBox(textBox, textBoxTransformation); + if (textBoxTransformation.isIdentity()) + continue; + + if (transform.isIdentity()) + transform = textBoxTransformation; + else + transform.multiply(textBoxTransformation); + } + } + + boxes.clear(); +} + +void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, RenderSVGInlineText* text, const RenderStyle* style) +{ + SVGElement* lengthContext = static_cast<SVGElement*>(text->parent()->node()); + + RenderObject* textParent = text->parent(); + bool definesTextLength = textParent ? parentDefinesTextLength(textParent) : false; + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + + SVGTextLayoutAttributes& attributes = text->layoutAttributes(); + Vector<float>& xValues = attributes.xValues(); + Vector<float>& yValues = attributes.yValues(); + Vector<float>& dxValues = attributes.dxValues(); + Vector<float>& dyValues = attributes.dyValues(); + Vector<float>& rotateValues = attributes.rotateValues(); + Vector<SVGTextMetrics>& textMetricsValues = attributes.textMetricsValues(); + + unsigned boxStart = textBox->start(); + unsigned boxLength = textBox->len(); + unsigned textMetricsSize = textMetricsValues.size(); + ASSERT(textMetricsSize <= xValues.size()); + ASSERT(textMetricsSize <= yValues.size()); + ASSERT(xValues.size() == yValues.size()); + + if (boxLength > textMetricsSize) + textMetricsSize = boxLength; + + unsigned positionListOffset = 0; + unsigned metricsListOffset = 0; + const UChar* characters = text->characters(); + + const Font& font = style->font(); + SVGTextLayoutEngineSpacing spacingLayout(font); + SVGTextLayoutEngineBaseline baselineLayout(font); + + bool didStartTextFragment = false; + bool applySpacingToNextCharacter = false; + + float lastAngle = 0; + float baselineShift = baselineLayout.calculateBaselineShift(svgStyle, lengthContext); + baselineShift -= baselineLayout.calculateAlignmentBaselineShift(m_isVerticalText, text); + + // Main layout algorithm. + unsigned positionListSize = xValues.size(); + for (; metricsListOffset < textMetricsSize && positionListOffset < positionListSize; ++metricsListOffset) { + SVGTextMetrics& metrics = textMetricsValues.at(metricsListOffset); + // Advance to text box start location. + if (positionListOffset < boxStart) { + positionListOffset += metrics.length(); + continue; + } + + // Stop if we've finished processing this text box. + if (positionListOffset >= boxStart + boxLength) + break; + + float x = xValues.at(positionListOffset); + float y = yValues.at(positionListOffset); + + // When we've advanced to the box start offset, determine using the original x/y values, + // wheter this character starts a new text chunk, before doing any further processing. + if (positionListOffset == boxStart) + textBox->setStartsNewTextChunk(text->characterStartsNewTextChunk(boxStart)); + + if (metrics == SVGTextMetrics::emptyMetrics()) { + positionListOffset += metrics.length(); + continue; + } + + const UChar* currentCharacter = characters + positionListOffset; + float angle = 0; + if (!rotateValues.isEmpty()) { + float newAngle = rotateValues.at(positionListOffset); + if (newAngle != SVGTextLayoutAttributes::emptyValue()) + angle = newAngle; + } + + // Calculate glyph orientation angle. + float orientationAngle = baselineLayout.calculateGlyphOrientationAngle(m_isVerticalText, svgStyle, *currentCharacter); + + // Calculate glyph advance & x/y orientation shifts. + float xOrientationShift = 0; + float yOrientationShift = 0; + float glyphAdvance = baselineLayout.calculateGlyphAdvanceAndOrientation(m_isVerticalText, metrics, orientationAngle, xOrientationShift, yOrientationShift); + + // Assign current text position to x/y values, if needed. + updateCharacerPositionIfNeeded(x, y); + + // Apply dx/dy value adjustments to current text position, if needed. + updateRelativePositionAdjustmentsIfNeeded(dxValues, dyValues, positionListOffset); + + // Calculate SVG Fonts kerning, if needed. + float kerning = spacingLayout.calculateSVGKerning(m_isVerticalText, metrics.glyph()); + + // Calculate CSS 'kerning', 'letter-spacing' and 'word-spacing' for next character, if needed. + float spacing = spacingLayout.calculateCSSKerningAndSpacing(svgStyle, lengthContext, currentCharacter); + + float textPathOffset = 0; + if (m_inPathLayout) { + float scaledGlyphAdvance = glyphAdvance * m_textPathScaling; + if (m_isVerticalText) { + // If there's an absolute y position available, it marks the beginning of a new position along the path. + if (y != SVGTextLayoutAttributes::emptyValue()) + m_textPathCurrentOffset = y + m_textPathStartOffset; + + m_textPathCurrentOffset += m_dy - kerning; + m_dy = 0; + + // Apply dx/dy correction and setup translations that move to the glyph midpoint. + xOrientationShift += m_dx + baselineShift; + yOrientationShift -= scaledGlyphAdvance / 2; + } else { + // If there's an absolute x position available, it marks the beginning of a new position along the path. + if (x != SVGTextLayoutAttributes::emptyValue()) + m_textPathCurrentOffset = x + m_textPathStartOffset; + + m_textPathCurrentOffset += m_dx - kerning; + m_dx = 0; + + // Apply dx/dy correction and setup translations that move to the glyph midpoint. + xOrientationShift -= scaledGlyphAdvance / 2; + yOrientationShift += m_dy - baselineShift; + } + + // Calculate current offset along path. + textPathOffset = m_textPathCurrentOffset + scaledGlyphAdvance / 2; + + // Move to next character. + m_textPathCurrentOffset += scaledGlyphAdvance + m_textPathSpacing + spacing * m_textPathScaling; + + // Skip character, if we're before the path. + if (textPathOffset < 0) { + positionListOffset += metrics.length(); + continue; + } + + // Stop processing, if the next character lies behind the path. + if (textPathOffset > m_textPathLength) + break; + + bool ok = false; + FloatPoint point = m_textPath.pointAtLength(textPathOffset, ok); + ASSERT(ok); + + x = point.x(); + y = point.y(); + angle = m_textPath.normalAngleAtLength(textPathOffset, ok); + ASSERT(ok); + + // For vertical text on path, the actual angle has to be rotated 90 degrees anti-clockwise, not the orientation angle! + if (m_isVerticalText) + angle -= 90; + } else { + // Apply all previously calculated shift values. + if (m_isVerticalText) { + x += baselineShift; + y -= kerning; + } else { + x -= kerning; + y -= baselineShift; + } + + x += m_dx; + y += m_dy; + } + + // Determine wheter we have to start a new fragment. + bool shouldStartNewFragment = false; + + if (m_dx || m_dy) + shouldStartNewFragment = true; + + if (!shouldStartNewFragment && (m_isVerticalText || m_inPathLayout)) + shouldStartNewFragment = true; + + if (!shouldStartNewFragment && (angle || angle != lastAngle || orientationAngle)) + shouldStartNewFragment = true; + + if (!shouldStartNewFragment && (kerning || applySpacingToNextCharacter || definesTextLength)) + shouldStartNewFragment = true; + + // If we already started a fragment, close it now. + if (didStartTextFragment && shouldStartNewFragment) { + applySpacingToNextCharacter = false; + recordTextFragment(textBox, text, positionListOffset, textMetricsValues.at(metricsListOffset - 1)); + } + + // Eventually start a new fragment, if not yet done. + if (!didStartTextFragment || shouldStartNewFragment) { + ASSERT(!m_currentTextFragment.positionListOffset); + ASSERT(!m_currentTextFragment.length); + + didStartTextFragment = true; + m_currentTextFragment.positionListOffset = positionListOffset; + m_currentTextFragment.x = x; + m_currentTextFragment.y = y; + + // Build fragment transformation. + if (angle) + m_currentTextFragment.transform.rotate(angle); + + if (xOrientationShift || yOrientationShift) + m_currentTextFragment.transform.translate(xOrientationShift, yOrientationShift); + + if (orientationAngle) + m_currentTextFragment.transform.rotate(orientationAngle); + + if (m_inPathLayout && m_textPathScaling != 1) { + if (m_isVerticalText) + m_currentTextFragment.transform.scaleNonUniform(1, m_textPathScaling); + else + m_currentTextFragment.transform.scaleNonUniform(m_textPathScaling, 1); + } + } + + // Update current text position, after processing of the current character finished. + if (m_inPathLayout) + updateCurrentTextPosition(x, y, glyphAdvance); + else { + // Apply CSS 'kerning', 'letter-spacing' and 'word-spacing' to next character, if needed. + if (spacing) + applySpacingToNextCharacter = true; + + float xNew = x - m_dx; + float yNew = y - m_dy; + + if (m_isVerticalText) + xNew -= baselineShift; + else + yNew += baselineShift; + + updateCurrentTextPosition(xNew, yNew, glyphAdvance + spacing); + } + + positionListOffset += metrics.length(); + lastAngle = angle; + } + + if (!didStartTextFragment) + return; + + // Close last open fragment, if needed. + recordTextFragment(textBox, text, positionListOffset, textMetricsValues.at(metricsListOffset - 1)); +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngine.h b/Source/WebCore/rendering/svg/SVGTextLayoutEngine.h new file mode 100644 index 0000000..ad058d8 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngine.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextLayoutEngine_h +#define SVGTextLayoutEngine_h + +#if ENABLE(SVG) +#include "Path.h" +#include "SVGTextChunkBuilder.h" +#include "SVGTextFragment.h" +#include "SVGTextMetrics.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class RenderObject; +class RenderStyle; +class RenderSVGInlineText; +class SVGElement; +class SVGInlineTextBox; +class SVGRenderStyle; + +// SVGTextLayoutEngine performs the second layout phase for SVG text. +// +// The InlineBox tree was created, containing the text chunk information, necessary to apply +// certain SVG specific text layout properties (text-length adjustments and text-anchor). +// The second layout phase uses the SVGTextLayoutAttributes stored in the individual +// RenderSVGInlineText renderers to compute the final positions for each character +// which are stored in the SVGInlineTextBox objects. + +class SVGTextLayoutEngine : public Noncopyable { +public: + SVGTextLayoutEngine(); + SVGTextChunkBuilder& chunkLayoutBuilder() { return m_chunkLayoutBuilder; } + + void beginTextPathLayout(RenderObject*, SVGTextLayoutEngine& lineLayout); + void endTextPathLayout(); + + void layoutInlineTextBox(SVGInlineTextBox*); + void finishLayout(); + +private: + void updateCharacerPositionIfNeeded(float& x, float& y); + void updateCurrentTextPosition(float x, float y, float glyphAdvance); + void updateRelativePositionAdjustmentsIfNeeded(Vector<float>& dxValues, Vector<float>& dyValues, unsigned valueListPosition); + + void recordTextFragment(SVGInlineTextBox*, RenderSVGInlineText*, unsigned positionListOffset, const SVGTextMetrics& lastCharacterMetrics); + bool parentDefinesTextLength(RenderObject*) const; + + void layoutTextOnLineOrPath(SVGInlineTextBox*, RenderSVGInlineText*, const RenderStyle*); + void finalizeTransformMatrices(Vector<SVGInlineTextBox*>&); + +private: + Vector<SVGInlineTextBox*> m_lineLayoutBoxes; + Vector<SVGInlineTextBox*> m_pathLayoutBoxes; + SVGTextChunkBuilder m_chunkLayoutBuilder; + + SVGTextFragment m_currentTextFragment; + float m_x; + float m_y; + float m_dx; + float m_dy; + bool m_isVerticalText; + bool m_inPathLayout; + + // Text on path layout + Path m_textPath; + float m_textPathLength; + float m_textPathStartOffset; + float m_textPathCurrentOffset; + float m_textPathSpacing; + float m_textPathScaling; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.cpp new file mode 100644 index 0000000..7060ac6 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.cpp @@ -0,0 +1,234 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "SVGTextLayoutEngineBaseline.h" + +#include "Font.h" +#include "RenderObject.h" +#include "SVGRenderStyle.h" +#include "SVGTextMetrics.h" +#include "UnicodeRange.h" + +namespace WebCore { + +SVGTextLayoutEngineBaseline::SVGTextLayoutEngineBaseline(const Font& font) + : m_font(font) +{ +} + +float SVGTextLayoutEngineBaseline::calculateBaselineShift(const SVGRenderStyle* style, SVGElement* lengthContext) const +{ + if (style->baselineShift() == BS_LENGTH) { + SVGLength baselineShiftValueLength = style->baselineShiftValue(); + if (baselineShiftValueLength.unitType() == LengthTypePercentage) + return baselineShiftValueLength.valueAsPercentage() * m_font.pixelSize(); + + return baselineShiftValueLength.value(lengthContext); + } + + switch (style->baselineShift()) { + case BS_BASELINE: + return 0; + case BS_SUB: + return -m_font.height() / 2; + case BS_SUPER: + return m_font.height() / 2; + default: + ASSERT_NOT_REACHED(); + return 0; + } +} + +EAlignmentBaseline SVGTextLayoutEngineBaseline::dominantBaselineToAlignmentBaseline(bool isVerticalText, const RenderObject* textRenderer) const +{ + ASSERT(textRenderer); + ASSERT(textRenderer->style()); + ASSERT(textRenderer->parent()); + ASSERT(textRenderer->parent()->style()); + + const SVGRenderStyle* style = textRenderer->style()->svgStyle(); + ASSERT(style); + + EDominantBaseline baseline = style->dominantBaseline(); + if (baseline == DB_AUTO) { + if (isVerticalText) + baseline = DB_CENTRAL; + else + baseline = DB_ALPHABETIC; + } + + switch (baseline) { + case DB_USE_SCRIPT: + // FIXME: The dominant-baseline and the baseline-table components are set by determining the predominant script of the character data content. + return AB_ALPHABETIC; + case DB_NO_CHANGE: + return dominantBaselineToAlignmentBaseline(isVerticalText, textRenderer->parent()); + case DB_RESET_SIZE: + return dominantBaselineToAlignmentBaseline(isVerticalText, textRenderer->parent()); + case DB_IDEOGRAPHIC: + return AB_IDEOGRAPHIC; + case DB_ALPHABETIC: + return AB_ALPHABETIC; + case DB_HANGING: + return AB_HANGING; + case DB_MATHEMATICAL: + return AB_MATHEMATICAL; + case DB_CENTRAL: + return AB_CENTRAL; + case DB_MIDDLE: + return AB_MIDDLE; + case DB_TEXT_AFTER_EDGE: + return AB_TEXT_AFTER_EDGE; + case DB_TEXT_BEFORE_EDGE: + return AB_TEXT_BEFORE_EDGE; + default: + ASSERT_NOT_REACHED(); + return AB_AUTO; + } +} + +float SVGTextLayoutEngineBaseline::calculateAlignmentBaselineShift(bool isVerticalText, const RenderObject* textRenderer) const +{ + ASSERT(textRenderer); + ASSERT(textRenderer->style()); + ASSERT(textRenderer->style()->svgStyle()); + ASSERT(textRenderer->parent()); + + const RenderObject* textRendererParent = textRenderer->parent(); + ASSERT(textRendererParent); + + EAlignmentBaseline baseline = textRenderer->style()->svgStyle()->alignmentBaseline(); + if (baseline == AB_AUTO) { + baseline = dominantBaselineToAlignmentBaseline(isVerticalText, textRendererParent); + ASSERT(baseline != AB_AUTO); + } + + // Note: http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling + switch (baseline) { + case AB_BASELINE: + return dominantBaselineToAlignmentBaseline(isVerticalText, textRendererParent); + case AB_BEFORE_EDGE: + case AB_TEXT_BEFORE_EDGE: + return m_font.ascent(); + case AB_MIDDLE: + return m_font.xHeight() / 2; + case AB_CENTRAL: + return (m_font.ascent() - m_font.descent()) / 2; + case AB_AFTER_EDGE: + case AB_TEXT_AFTER_EDGE: + case AB_IDEOGRAPHIC: + return m_font.descent(); + case AB_ALPHABETIC: + return 0; + case AB_HANGING: + return m_font.ascent() * 8 / 10.f; + case AB_MATHEMATICAL: + return m_font.ascent() / 2; + default: + ASSERT_NOT_REACHED(); + return 0; + } +} + +float SVGTextLayoutEngineBaseline::calculateGlyphOrientationAngle(bool isVerticalText, const SVGRenderStyle* style, const UChar& character) const +{ + ASSERT(style); + + switch (isVerticalText ? style->glyphOrientationVertical() : style->glyphOrientationHorizontal()) { + case GO_AUTO: + { + // Spec: Fullwidth ideographic and fullwidth Latin text will be set with a glyph-orientation of 0-degrees. + // Text which is not fullwidth will be set with a glyph-orientation of 90-degrees. + unsigned int unicodeRange = findCharUnicodeRange(character); + if (unicodeRange == cRangeSetLatin || unicodeRange == cRangeArabic) + return 90; + + return 0; + } + case GO_90DEG: + return 90; + case GO_180DEG: + return 180; + case GO_270DEG: + return 270; + case GO_0DEG: + default: + return 0; + } +} + +static inline bool glyphOrientationIsMultiplyOf180Degrees(float orientationAngle) +{ + return !fabsf(fmodf(orientationAngle, 180)); +} + +float SVGTextLayoutEngineBaseline::calculateGlyphAdvanceAndOrientation(bool isVerticalText, SVGTextMetrics& metrics, float angle, float& xOrientationShift, float& yOrientationShift) const +{ + bool orientationIsMultiplyOf180Degrees = glyphOrientationIsMultiplyOf180Degrees(angle); + + // The function is based on spec requirements: + // + // Spec: If the 'glyph-orientation-horizontal' results in an orientation angle that is not a multiple of + // of 180 degrees, then the current text position is incremented according to the vertical metrics of the glyph. + // + // Spec: If if the 'glyph-orientation-vertical' results in an orientation angle that is not a multiple of + // 180 degrees, then the current text position is incremented according to the horizontal metrics of the glyph. + + // Vertical orientation handling. + if (isVerticalText) { + float ascentMinusDescent = m_font.ascent() - m_font.descent(); + if (!angle) { + xOrientationShift = (ascentMinusDescent - metrics.width()) / 2; + yOrientationShift = m_font.ascent(); + } else if (angle == 180) + xOrientationShift = (ascentMinusDescent + metrics.width()) / 2; + else if (angle == 270) { + yOrientationShift = metrics.width(); + xOrientationShift = ascentMinusDescent; + } + + // Vertical advance calculation. + if (angle && !orientationIsMultiplyOf180Degrees) + return metrics.width(); + + return metrics.height(); + } + + // Horizontal orientation handling. + if (angle == 90) + yOrientationShift = -metrics.width(); + else if (angle == 180) { + xOrientationShift = metrics.width(); + yOrientationShift = -m_font.ascent(); + } else if (angle == 270) + xOrientationShift = metrics.width(); + + // Horizontal advance calculation. + if (angle && !orientationIsMultiplyOf180Degrees) + return metrics.height(); + + return metrics.width(); +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.h b/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.h new file mode 100644 index 0000000..d753b39 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextLayoutEngineBaseline_h +#define SVGTextLayoutEngineBaseline_h + +#if ENABLE(SVG) +#include "SVGRenderStyleDefs.h" +#include <wtf/Noncopyable.h> + +namespace WebCore { + +class Font; +class RenderObject; +class SVGElement; +class SVGRenderStyle; +class SVGTextMetrics; + +// Helper class used by SVGTextLayoutEngine to handle 'alignment-baseline' / 'dominant-baseline' and 'baseline-shift'. +class SVGTextLayoutEngineBaseline : public Noncopyable { +public: + SVGTextLayoutEngineBaseline(const Font&); + + float calculateBaselineShift(const SVGRenderStyle*, SVGElement* lengthContext) const; + float calculateAlignmentBaselineShift(bool isVerticalText, const RenderObject* textRenderer) const; + float calculateGlyphOrientationAngle(bool isVerticalText, const SVGRenderStyle*, const UChar& character) const; + float calculateGlyphAdvanceAndOrientation(bool isVerticalText, SVGTextMetrics&, float angle, float& xOrientationShift, float& yOrientationShift) const; + +private: + EAlignmentBaseline dominantBaselineToAlignmentBaseline(bool isVerticalText, const RenderObject* textRenderer) const; + + const Font& m_font; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.cpp new file mode 100644 index 0000000..6c54b67 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "SVGTextLayoutEngineSpacing.h" + +#include "Font.h" +#include "SVGRenderStyle.h" + +#if ENABLE(SVG_FONTS) +#include "SVGFontElement.h" +#endif + +namespace WebCore { + +SVGTextLayoutEngineSpacing::SVGTextLayoutEngineSpacing(const Font& font) + : m_font(font) + , m_lastCharacter(0) +{ +} + +float SVGTextLayoutEngineSpacing::calculateSVGKerning(bool isVerticalText, const SVGTextMetrics::Glyph& currentGlyph) +{ +#if ENABLE(SVG_FONTS) + if (!m_font.isSVGFont()) { + m_lastGlyph.isValid = false; + return 0; + } + + SVGFontElement* svgFont = m_font.svgFont(); + ASSERT(svgFont); + + float kerning = 0; + if (m_lastGlyph.isValid) { + if (isVerticalText) + kerning = svgFont->verticalKerningForPairOfStringsAndGlyphs(m_lastGlyph.unicodeString, m_lastGlyph.name, currentGlyph.unicodeString, currentGlyph.name); + else + kerning = svgFont->horizontalKerningForPairOfStringsAndGlyphs(m_lastGlyph.unicodeString, m_lastGlyph.name, currentGlyph.unicodeString, currentGlyph.name); + } + + m_lastGlyph = currentGlyph; + m_lastGlyph.isValid = true; + kerning *= m_font.size() / m_font.primaryFont()->unitsPerEm(); + return kerning; +#else + UNUSED_PARAM(isVerticalText); + UNUSED_PARAM(currentGlyph); + return false; +#endif +} + +float SVGTextLayoutEngineSpacing::calculateCSSKerningAndSpacing(const SVGRenderStyle* style, SVGElement* lengthContext, const UChar* currentCharacter) +{ + float kerning = 0; + SVGLength kerningLength = style->kerning(); + if (kerningLength.unitType() == LengthTypePercentage) + kerning = kerningLength.valueAsPercentage() * m_font.pixelSize(); + else + kerning = kerningLength.value(lengthContext); + + const UChar* lastCharacter = m_lastCharacter; + m_lastCharacter = currentCharacter; + + if (!kerning && !m_font.letterSpacing() && !m_font.wordSpacing()) + return 0; + + float spacing = m_font.letterSpacing() + kerning; + if (currentCharacter && lastCharacter && m_font.wordSpacing()) { + if (Font::treatAsSpace(*currentCharacter) && !Font::treatAsSpace(*lastCharacter)) + spacing += m_font.wordSpacing(); + } + + return spacing; +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.h b/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.h new file mode 100644 index 0000000..0a6d736 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextLayoutEngineSpacing_h +#define SVGTextLayoutEngineSpacing_h + +#if ENABLE(SVG) +#include "SVGTextMetrics.h" + +namespace WebCore { + +class Font; +class SVGRenderStyle; +class SVGElement; + +// Helper class used by SVGTextLayoutEngine to handle 'kerning' / 'letter-spacing' and 'word-spacing'. +class SVGTextLayoutEngineSpacing : public Noncopyable { +public: + SVGTextLayoutEngineSpacing(const Font&); + + float calculateSVGKerning(bool isVerticalText, const SVGTextMetrics::Glyph& currentGlyph); + float calculateCSSKerningAndSpacing(const SVGRenderStyle*, SVGElement* lengthContext, const UChar* currentCharacter); + +private: + const Font& m_font; + const UChar* m_lastCharacter; + +#if ENABLE(SVG_FONTS) + SVGTextMetrics::Glyph m_lastGlyph; +#endif +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextMetrics.cpp b/Source/WebCore/rendering/svg/SVGTextMetrics.cpp new file mode 100644 index 0000000..58d0ad9 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextMetrics.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" + +#if ENABLE(SVG) +#include "SVGTextMetrics.h" + +#include "RenderSVGInlineText.h" + +namespace WebCore { + +SVGTextMetrics::SVGTextMetrics() + : m_width(0) + , m_height(0) + , m_length(0) +{ +} + +SVGTextMetrics::SVGTextMetrics(const Font& font, const TextRun& run, unsigned position, unsigned textLength) + : m_width(0) + , m_height(0) + , m_length(0) +{ + int extraCharsAvailable = textLength - (position + run.length()); + int length = 0; + + m_width = font.floatWidth(run, extraCharsAvailable, length, m_glyph.name); + m_height = font.height(); + m_glyph.unicodeString = String(run.characters(), length); + m_glyph.isValid = true; + + ASSERT(length >= 0); + m_length = static_cast<unsigned>(length); +} + +bool SVGTextMetrics::operator==(const SVGTextMetrics& other) +{ + return m_width == other.m_width + && m_height == other.m_height + && m_length == other.m_length + && m_glyph == other.m_glyph; +} + +SVGTextMetrics SVGTextMetrics::emptyMetrics() +{ + DEFINE_STATIC_LOCAL(SVGTextMetrics, s_emptyMetrics, ()); + s_emptyMetrics.m_length = 1; + return s_emptyMetrics; +} + +static TextRun constructTextRun(RenderSVGInlineText* text, const UChar* characters, unsigned position, unsigned length) +{ + TextRun run(characters + position, length); + +#if ENABLE(SVG_FONTS) + ASSERT(text->parent()); + run.setReferencingRenderObject(text->parent()); +#endif + + // Disable any word/character rounding. + run.disableRoundingHacks(); + + // We handle letter & word spacing ourselves. + run.disableSpacing(); + return run; +} + +SVGTextMetrics SVGTextMetrics::measureCharacterRange(RenderSVGInlineText* text, unsigned position, unsigned length) +{ + ASSERT(text); + ASSERT(text->style()); + + TextRun run(constructTextRun(text, text->characters(), position, length)); + return SVGTextMetrics(text->style()->font(), run, position, text->textLength()); +} + +void SVGTextMetrics::measureAllCharactersIndividually(RenderSVGInlineText* text, Vector<SVGTextMetrics>& allMetrics) +{ + ASSERT(text); + ASSERT(text->style()); + + const Font& font = text->style()->font(); + const UChar* characters = text->characters(); + unsigned length = text->textLength(); + + TextRun run(constructTextRun(text, 0, 0, 0)); + for (unsigned position = 0; position < length; ) { + run.setText(characters + position, 1); + + SVGTextMetrics metrics(font, run, position, text->textLength()); + allMetrics.append(metrics); + position += metrics.length(); + } +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGTextMetrics.h b/Source/WebCore/rendering/svg/SVGTextMetrics.h new file mode 100644 index 0000000..ba18589 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextMetrics.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextMetrics_h +#define SVGTextMetrics_h + +#if ENABLE(SVG) +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class Font; +class RenderSVGInlineText; +class TextRun; + +class SVGTextMetrics { +public: + static SVGTextMetrics emptyMetrics(); + static SVGTextMetrics measureCharacterRange(RenderSVGInlineText*, unsigned position, unsigned length); + static void measureAllCharactersIndividually(RenderSVGInlineText*, Vector<SVGTextMetrics>&); + + bool operator==(const SVGTextMetrics&); + + float width() const { return m_width; } + float height() const { return m_height; } + unsigned length() const { return m_length; } + + struct Glyph { + Glyph() + : isValid(false) + { + } + + bool operator==(const Glyph& other) + { + return isValid == other.isValid + && name == other.name + && unicodeString == other.unicodeString; + } + + bool isValid; + String name; + String unicodeString; + }; + + // Only useful when measuring individual characters, to lookup ligatures. + const Glyph& glyph() const { return m_glyph; } + +private: + SVGTextMetrics(); + SVGTextMetrics(const Font&, const TextRun&, unsigned position, unsigned textLength); + + float m_width; + float m_height; + unsigned m_length; + Glyph m_glyph; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextQuery.cpp b/Source/WebCore/rendering/svg/SVGTextQuery.cpp new file mode 100644 index 0000000..fcc7924 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextQuery.cpp @@ -0,0 +1,562 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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 "SVGTextQuery.h" + +#if ENABLE(SVG) +#include "FloatConversion.h" +#include "InlineFlowBox.h" +#include "RenderBlock.h" +#include "RenderInline.h" +#include "RenderSVGInlineText.h" +#include "SVGInlineTextBox.h" +#include "SVGTextMetrics.h" +#include "VisiblePosition.h" + +#include <wtf/MathExtras.h> + +namespace WebCore { + +// Base structure for callback user data +struct SVGTextQuery::Data { + Data() + : isVerticalText(false) + , processedCharacters(0) + , textRenderer(0) + , textBox(0) + { + } + + bool isVerticalText; + unsigned processedCharacters; + RenderSVGInlineText* textRenderer; + const SVGInlineTextBox* textBox; +}; + +static inline InlineFlowBox* flowBoxForRenderer(RenderObject* renderer) +{ + if (!renderer) + return 0; + + if (renderer->isRenderBlock()) { + // If we're given a block element, it has to be a RenderSVGText. + ASSERT(renderer->isSVGText()); + RenderBlock* renderBlock = toRenderBlock(renderer); + + // RenderSVGText only ever contains a single line box. + InlineFlowBox* flowBox = renderBlock->firstLineBox(); + ASSERT(flowBox == renderBlock->lastLineBox()); + return flowBox; + } + + if (renderer->isRenderInline()) { + // We're given a RenderSVGInline or objects that derive from it (RenderSVGTSpan / RenderSVGTextPath) + RenderInline* renderInline = toRenderInline(renderer); + + // RenderSVGInline only ever contains a single line box. + InlineFlowBox* flowBox = renderInline->firstLineBox(); + ASSERT(flowBox == renderInline->lastLineBox()); + return flowBox; + } + + ASSERT_NOT_REACHED(); + return 0; +} + +static inline float mapLengthThroughFragmentTransformation(const SVGTextFragment& fragment, bool isVerticalText, float length) +{ + if (fragment.transform.isIdentity()) + return length; + + if (isVerticalText) + return narrowPrecisionToFloat(static_cast<double>(length) * fragment.transform.yScale()); + + return narrowPrecisionToFloat(static_cast<double>(length) * fragment.transform.xScale()); +} + +SVGTextQuery::SVGTextQuery(RenderObject* renderer) +{ + collectTextBoxesInFlowBox(flowBoxForRenderer(renderer)); +} + +void SVGTextQuery::collectTextBoxesInFlowBox(InlineFlowBox* flowBox) +{ + if (!flowBox) + return; + + for (InlineBox* child = flowBox->firstChild(); child; child = child->nextOnLine()) { + if (child->isInlineFlowBox()) { + // Skip generated content. + if (!child->renderer()->node()) + continue; + + collectTextBoxesInFlowBox(static_cast<InlineFlowBox*>(child)); + continue; + } + + ASSERT(child->isSVGInlineTextBox()); + m_textBoxes.append(static_cast<SVGInlineTextBox*>(child)); + } +} + +bool SVGTextQuery::executeQuery(Data* queryData, ProcessTextFragmentCallback fragmentCallback) const +{ + ASSERT(!m_textBoxes.isEmpty()); + + unsigned processedCharacters = 0; + unsigned textBoxCount = m_textBoxes.size(); + + // Loop over all text boxes + for (unsigned textBoxPosition = 0; textBoxPosition < textBoxCount; ++textBoxPosition) { + queryData->textBox = m_textBoxes.at(textBoxPosition); + queryData->textRenderer = toRenderSVGInlineText(queryData->textBox->textRenderer()); + ASSERT(queryData->textRenderer); + ASSERT(queryData->textRenderer->style()); + ASSERT(queryData->textRenderer->style()->svgStyle()); + + queryData->isVerticalText = queryData->textRenderer->style()->svgStyle()->isVerticalWritingMode(); + const Vector<SVGTextFragment>& fragments = queryData->textBox->textFragments(); + + // Loop over all text fragments in this text box, firing a callback for each. + unsigned fragmentCount = fragments.size(); + for (unsigned i = 0; i < fragmentCount; ++i) { + const SVGTextFragment& fragment = fragments.at(i); + if ((this->*fragmentCallback)(queryData, fragment)) + return true; + + processedCharacters += fragment.length; + } + + queryData->processedCharacters = processedCharacters; + } + + return false; +} + +bool SVGTextQuery::mapStartEndPositionsIntoFragmentCoordinates(Data* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition) const +{ + // Reuse the same logic used for text selection & painting, to map our query start/length into start/endPositions of the current text fragment. + startPosition -= queryData->processedCharacters; + endPosition -= queryData->processedCharacters; + + if (startPosition >= endPosition || startPosition < 0 || endPosition < 0) + return false; + + modifyStartEndPositionsRespectingLigatures(queryData, startPosition, endPosition); + if (!queryData->textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, startPosition, endPosition)) + return false; + + ASSERT(startPosition < endPosition); + return true; +} + +void SVGTextQuery::modifyStartEndPositionsRespectingLigatures(Data* queryData, int& startPosition, int& endPosition) const +{ + const SVGTextLayoutAttributes& layoutAttributes = queryData->textRenderer->layoutAttributes(); + const Vector<float>& xValues = layoutAttributes.xValues(); + const Vector<SVGTextMetrics>& textMetricsValues = layoutAttributes.textMetricsValues(); + + unsigned boxStart = queryData->textBox->start(); + unsigned boxLength = queryData->textBox->len(); + + unsigned textMetricsOffset = 0; + unsigned textMetricsSize = textMetricsValues.size(); + + unsigned positionOffset = 0; + unsigned positionSize = xValues.size(); + + bool alterStartPosition = true; + bool alterEndPosition = true; + + int lastPositionOffset = -1; + for (; textMetricsOffset < textMetricsSize && positionOffset < positionSize; ++textMetricsOffset) { + const SVGTextMetrics& metrics = textMetricsValues.at(textMetricsOffset); + + // Advance to text box start location. + if (positionOffset < boxStart) { + positionOffset += metrics.length(); + continue; + } + + // Stop if we've finished processing this text box. + if (positionOffset >= boxStart + boxLength) + break; + + // If the start position maps to a character in the metrics list, we don't need to modify it. + if (startPosition == static_cast<int>(positionOffset)) + alterStartPosition = false; + + // If the start position maps to a character in the metrics list, we don't need to modify it. + if (endPosition == static_cast<int>(positionOffset)) + alterEndPosition = false; + + // Detect ligatures. + if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) { + if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) { + startPosition = lastPositionOffset; + alterStartPosition = false; + } + + if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) { + endPosition = positionOffset; + alterEndPosition = false; + } + } + + if (!alterStartPosition && !alterEndPosition) + break; + + lastPositionOffset = positionOffset; + positionOffset += metrics.length(); + } + + if (!alterStartPosition && !alterEndPosition) + return; + + if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) { + if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) { + startPosition = lastPositionOffset; + alterStartPosition = false; + } + + if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) { + endPosition = positionOffset; + alterEndPosition = false; + } + } +} + +// numberOfCharacters() implementation +bool SVGTextQuery::numberOfCharactersCallback(Data*, const SVGTextFragment&) const +{ + // no-op + return false; +} + +unsigned SVGTextQuery::numberOfCharacters() const +{ + if (m_textBoxes.isEmpty()) + return 0; + + Data data; + executeQuery(&data, &SVGTextQuery::numberOfCharactersCallback); + return data.processedCharacters; +} + +// textLength() implementation +struct TextLengthData : SVGTextQuery::Data { + TextLengthData() + : textLength(0) + { + } + + float textLength; +}; + +bool SVGTextQuery::textLengthCallback(Data* queryData, const SVGTextFragment& fragment) const +{ + TextLengthData* data = static_cast<TextLengthData*>(queryData); + + float fragmentLength = queryData->isVerticalText ? fragment.height : fragment.width; + data->textLength += mapLengthThroughFragmentTransformation(fragment, queryData->isVerticalText, fragmentLength); + return false; +} + +float SVGTextQuery::textLength() const +{ + if (m_textBoxes.isEmpty()) + return 0; + + TextLengthData data; + executeQuery(&data, &SVGTextQuery::textLengthCallback); + return data.textLength; +} + +// subStringLength() implementation +struct SubStringLengthData : SVGTextQuery::Data { + SubStringLengthData(unsigned queryStartPosition, unsigned queryLength) + : startPosition(queryStartPosition) + , length(queryLength) + , subStringLength(0) + { + } + + unsigned startPosition; + unsigned length; + + float subStringLength; +}; + +bool SVGTextQuery::subStringLengthCallback(Data* queryData, const SVGTextFragment& fragment) const +{ + SubStringLengthData* data = static_cast<SubStringLengthData*>(queryData); + + int startPosition = data->startPosition; + int endPosition = startPosition + data->length; + if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) + return false; + + SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.positionListOffset + startPosition, endPosition - startPosition); + float fragmentLength = queryData->isVerticalText ? metrics.height() : metrics.width(); + + data->subStringLength += mapLengthThroughFragmentTransformation(fragment, queryData->isVerticalText, fragmentLength); + return false; +} + +float SVGTextQuery::subStringLength(unsigned startPosition, unsigned length) const +{ + if (m_textBoxes.isEmpty()) + return 0; + + SubStringLengthData data(startPosition, length); + executeQuery(&data, &SVGTextQuery::subStringLengthCallback); + return data.subStringLength; +} + +// startPositionOfCharacter() implementation +struct StartPositionOfCharacterData : SVGTextQuery::Data { + StartPositionOfCharacterData(unsigned queryPosition) + : position(queryPosition) + { + } + + unsigned position; + FloatPoint startPosition; +}; + +bool SVGTextQuery::startPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const +{ + StartPositionOfCharacterData* data = static_cast<StartPositionOfCharacterData*>(queryData); + + int startPosition = data->position; + int endPosition = startPosition + 1; + if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) + return false; + + data->startPosition = FloatPoint(fragment.x, fragment.y); + + if (startPosition) { + SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.positionListOffset, startPosition); + if (queryData->isVerticalText) + data->startPosition.move(0, metrics.height()); + else + data->startPosition.move(metrics.width(), 0); + } + + if (fragment.transform.isIdentity()) + return true; + + data->startPosition = fragment.transform.mapPoint(data->startPosition); + return true; +} + +FloatPoint SVGTextQuery::startPositionOfCharacter(unsigned position) const +{ + if (m_textBoxes.isEmpty()) + return FloatPoint(); + + StartPositionOfCharacterData data(position); + executeQuery(&data, &SVGTextQuery::startPositionOfCharacterCallback); + return data.startPosition; +} + +// endPositionOfCharacter() implementation +struct EndPositionOfCharacterData : SVGTextQuery::Data { + EndPositionOfCharacterData(unsigned queryPosition) + : position(queryPosition) + { + } + + unsigned position; + FloatPoint endPosition; +}; + +bool SVGTextQuery::endPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const +{ + EndPositionOfCharacterData* data = static_cast<EndPositionOfCharacterData*>(queryData); + + int startPosition = data->position; + int endPosition = startPosition + 1; + if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) + return false; + + data->endPosition = FloatPoint(fragment.x, fragment.y); + + SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.positionListOffset, startPosition + 1); + if (queryData->isVerticalText) + data->endPosition.move(0, metrics.height()); + else + data->endPosition.move(metrics.width(), 0); + + if (fragment.transform.isIdentity()) + return true; + + data->endPosition = fragment.transform.mapPoint(data->endPosition); + return true; +} + +FloatPoint SVGTextQuery::endPositionOfCharacter(unsigned position) const +{ + if (m_textBoxes.isEmpty()) + return FloatPoint(); + + EndPositionOfCharacterData data(position); + executeQuery(&data, &SVGTextQuery::endPositionOfCharacterCallback); + return data.endPosition; +} + +// rotationOfCharacter() implementation +struct RotationOfCharacterData : SVGTextQuery::Data { + RotationOfCharacterData(unsigned queryPosition) + : position(queryPosition) + , rotation(0) + { + } + + unsigned position; + float rotation; +}; + +bool SVGTextQuery::rotationOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const +{ + RotationOfCharacterData* data = static_cast<RotationOfCharacterData*>(queryData); + + int startPosition = data->position; + int endPosition = startPosition + 1; + if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) + return false; + + AffineTransform newTransform(fragment.transform); + newTransform.scale(1 / fragment.transform.xScale(), 1 / fragment.transform.yScale()); + data->rotation = narrowPrecisionToFloat(rad2deg(atan2(newTransform.b(), newTransform.a()))); + return true; +} + +float SVGTextQuery::rotationOfCharacter(unsigned position) const +{ + if (m_textBoxes.isEmpty()) + return 0; + + RotationOfCharacterData data(position); + executeQuery(&data, &SVGTextQuery::rotationOfCharacterCallback); + return data.rotation; +} + +// extentOfCharacter() implementation +struct ExtentOfCharacterData : SVGTextQuery::Data { + ExtentOfCharacterData(unsigned queryPosition) + : position(queryPosition) + { + } + + unsigned position; + FloatRect extent; +}; + +static inline void calculateGlyphBoundaries(SVGTextQuery::Data* queryData, const SVGTextFragment& fragment, int startPosition, FloatRect& extent) +{ + extent.setLocation(FloatPoint(fragment.x, fragment.y - queryData->textRenderer->style()->font().ascent())); + + if (startPosition) { + SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.positionListOffset, startPosition); + if (queryData->isVerticalText) + extent.move(0, metrics.height()); + else + extent.move(metrics.width(), 0); + } + + SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.positionListOffset + startPosition, 1); + extent.setSize(FloatSize(metrics.width(), metrics.height())); + + if (fragment.transform.isIdentity()) + return; + + extent = fragment.transform.mapRect(extent); +} + +bool SVGTextQuery::extentOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const +{ + ExtentOfCharacterData* data = static_cast<ExtentOfCharacterData*>(queryData); + + int startPosition = data->position; + int endPosition = startPosition + 1; + if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) + return false; + + calculateGlyphBoundaries(queryData, fragment, startPosition, data->extent); + return true; +} + +FloatRect SVGTextQuery::extentOfCharacter(unsigned position) const +{ + if (m_textBoxes.isEmpty()) + return FloatRect(); + + ExtentOfCharacterData data(position); + executeQuery(&data, &SVGTextQuery::extentOfCharacterCallback); + return data.extent; +} + +// characterNumberAtPosition() implementation +struct CharacterNumberAtPositionData : SVGTextQuery::Data { + CharacterNumberAtPositionData(const FloatPoint& queryPosition) + : position(queryPosition) + { + } + + FloatPoint position; +}; + +bool SVGTextQuery::characterNumberAtPositionCallback(Data* queryData, const SVGTextFragment& fragment) const +{ + CharacterNumberAtPositionData* data = static_cast<CharacterNumberAtPositionData*>(queryData); + + FloatRect extent; + for (unsigned i = 0; i < fragment.length; ++i) { + int startPosition = data->processedCharacters + i; + int endPosition = startPosition + 1; + if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) + continue; + + calculateGlyphBoundaries(queryData, fragment, startPosition, extent); + if (extent.contains(data->position)) { + data->processedCharacters += i; + return true; + } + } + + return false; +} + +int SVGTextQuery::characterNumberAtPosition(const FloatPoint& position) const +{ + if (m_textBoxes.isEmpty()) + return -1; + + CharacterNumberAtPositionData data(position); + if (!executeQuery(&data, &SVGTextQuery::characterNumberAtPositionCallback)) + return -1; + + return data.processedCharacters; +} + +} + +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextQuery.h b/Source/WebCore/rendering/svg/SVGTextQuery.h new file mode 100644 index 0000000..9a671f4 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextQuery.h @@ -0,0 +1,75 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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. +*/ + +#ifndef SVGTextQuery_h +#define SVGTextQuery_h + +#if ENABLE(SVG) +#include "FloatRect.h" +#include "SVGTextFragment.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class InlineFlowBox; +class RenderObject; +class SVGInlineTextBox; + +class SVGTextQuery { +public: + SVGTextQuery(RenderObject*); + + unsigned numberOfCharacters() const; + float textLength() const; + float subStringLength(unsigned startPosition, unsigned length) const; + FloatPoint startPositionOfCharacter(unsigned position) const; + FloatPoint endPositionOfCharacter(unsigned position) const; + float rotationOfCharacter(unsigned position) const; + FloatRect extentOfCharacter(unsigned position) const; + int characterNumberAtPosition(const FloatPoint&) const; + + // Public helper struct. Private classes in SVGTextQuery inherit from it. + struct Data; + +private: + typedef bool (SVGTextQuery::*ProcessTextFragmentCallback)(Data*, const SVGTextFragment&) const; + bool executeQuery(Data*, ProcessTextFragmentCallback) const; + + void collectTextBoxesInFlowBox(InlineFlowBox*); + bool mapStartEndPositionsIntoFragmentCoordinates(Data*, const SVGTextFragment&, int& startPosition, int& endPosition) const; + void modifyStartEndPositionsRespectingLigatures(Data*, int& startPosition, int& endPosition) const; + +private: + bool numberOfCharactersCallback(Data*, const SVGTextFragment&) const; + bool textLengthCallback(Data*, const SVGTextFragment&) const; + bool subStringLengthCallback(Data*, const SVGTextFragment&) const; + bool startPositionOfCharacterCallback(Data*, const SVGTextFragment&) const; + bool endPositionOfCharacterCallback(Data*, const SVGTextFragment&) const; + bool rotationOfCharacterCallback(Data*, const SVGTextFragment&) const; + bool extentOfCharacterCallback(Data*, const SVGTextFragment&) const; + bool characterNumberAtPositionCallback(Data*, const SVGTextFragment&) const; + +private: + Vector<SVGInlineTextBox*> m_textBoxes; +}; + +} + +#endif +#endif |