diff options
Diffstat (limited to 'tdehtml/rendering/render_line.cpp')
-rw-r--r-- | tdehtml/rendering/render_line.cpp | 996 |
1 files changed, 996 insertions, 0 deletions
diff --git a/tdehtml/rendering/render_line.cpp b/tdehtml/rendering/render_line.cpp new file mode 100644 index 000000000..b518d3a43 --- /dev/null +++ b/tdehtml/rendering/render_line.cpp @@ -0,0 +1,996 @@ +/** +* This file is part of the html renderer for KDE. + * + * Copyright (C) 2003-2006 Apple Computer, Inc. + * (C) 2006 Germain Garand (germain@ebooksfrance.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 <kdebug.h> +#include <assert.h> +#include <tqpainter.h> +#include <kglobal.h> + +#include "rendering/render_flow.h" +#include "rendering/render_text.h" +#include "rendering/render_table.h" +#include "rendering/render_inline.h" +#include "rendering/render_block.h" +#include "rendering/render_arena.h" +#include "rendering/render_line.h" +#include "xml/dom_nodeimpl.h" +#include "xml/dom_docimpl.h" +#include "html/html_formimpl.h" +#include "misc/htmltags.h" +#include "tdehtmlview.h" + +using namespace DOM; +using namespace tdehtml; + +#ifndef NDEBUG +static bool inInlineBoxDetach; +#endif + +class tdehtml::EllipsisBox : public InlineBox +{ +public: + EllipsisBox(RenderObject* obj, const DOM::DOMString& ellipsisStr, InlineFlowBox* p, + int w, int y, int h, int b, bool firstLine, InlineBox* markupBox) + :InlineBox(obj), m_str(ellipsisStr) { + m_parent = p; + m_width = w; + m_y = y; + m_height = h; + m_baseline = b; + m_firstLine = firstLine; + m_constructed = true; + m_markupBox = markupBox; + } + + void paint(RenderObject::PaintInfo& i, int _tx, int _ty); + bool nodeAtPoint(RenderObject::NodeInfo& info, int _x, int _y, int _tx, int _ty); + +private: + DOM::DOMString m_str; + InlineBox* m_markupBox; +}; + +void InlineBox::detach(RenderArena* renderArena) +{ + if (m_parent) + m_parent->removeFromLine(this); +#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 detach can find it. + *(size_t *)ptr = sz; +} + +static bool needsOutlinePhaseRepaint(RenderObject* o, RenderObject::PaintInfo& i, int tx, int ty) { + if (o->style()->outlineWidth() <= 0) + return false; + TQRect r(tx+o->xPos(),ty+o->yPos(),o->width(),o->height()); + if (r.intersects(i.r)) + return false; + r.addCoords(-o->style()->outlineSize(), + -o->style()->outlineSize(), + o->style()->outlineSize(), + o->style()->outlineSize()); + if (!r.intersects(i.r)) + return false; + return true; +} + +void InlineBox::paint(RenderObject::PaintInfo& i, int tx, int ty) +{ + if ( i.phase == PaintActionOutline && !needsOutlinePhaseRepaint(object(), i, tx, ty) ) + return; + + // 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 paintSelectionOnly = i.phase == PaintActionSelection; + RenderObject::PaintInfo info(i.p, i.r, paintSelectionOnly ? i.phase : PaintActionElementBackground); + object()->paint(info, tx, ty); + if (!paintSelectionOnly) { + info.phase = PaintActionChildBackgrounds; + object()->paint(info, tx, ty); + info.phase = PaintActionFloat; + object()->paint(info, tx, ty); + info.phase = PaintActionForeground; + object()->paint(info, tx, ty); + info.phase = PaintActionOutline; + object()->paint(info, tx, ty); + } +} + +bool InlineBox::nodeAtPoint(RenderObject::NodeInfo& i, 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.) + bool inside = false; + return object()->nodeAtPoint(i, x, y, tx, ty, HitTestAll, inside); // ### port hitTest +} + +RootInlineBox* InlineBox::root() +{ + if (m_parent) + return m_parent->root(); + return static_cast<RootInlineBox*>(this); +} + +InlineFlowBox::~InlineFlowBox() +{ + /* If we're destroyed, set the children free, and break their links */ + while (m_firstChild) + removeFromLine(m_firstChild); +} + +void InlineFlowBox::removeFromLine(InlineBox *child) +{ + if (child == m_firstChild) { + m_firstChild = child->nextOnLine(); + } + if (child == m_lastChild) { + m_lastChild = child->prevOnLine(); + } + if (child->nextOnLine()) { + child->nextOnLine()->m_prev = child->prevOnLine(); + } + if (child->prevOnLine()) { + child->prevOnLine()->m_next = child->nextOnLine(); + } + + child->setParent(0); +} + +bool InlineBox::canAccommodateEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth) +{ + // Non-replaced elements can always accommodate an ellipsis. + if (!m_object || !m_object->isReplaced()) + return true; + + TQRect boxRect(m_x, 0, m_width, 10); + TQRect ellipsisRect(ltr ? blockEdge - ellipsisWidth : blockEdge, 0, ellipsisWidth, 10); + return !(boxRect.intersects(ellipsisRect)); +} + +int InlineBox::placeEllipsisBox(bool /*ltr*/, int /*blockEdge*/, int /*ellipsisWidth*/, bool&) +{ + // Use -1 to mean "we didn't set the position." + return -1; +} + +int InlineFlowBox::marginLeft() const +{ + if (!includeLeftEdge()) + return 0; + + RenderStyle* cstyle = object()->style(); + Length margin = cstyle->marginLeft(); + if (!margin.isVariable()) + return (margin.isFixed() ? margin.value() : object()->marginLeft()); + return 0; +} + +int InlineFlowBox::marginRight() const +{ + if (!includeRightEdge()) + return 0; + + RenderStyle* cstyle = object()->style(); + Length margin = cstyle->marginRight(); + if (!margin.isVariable()) + return (margin.isFixed() ? margin.value() : object()->marginRight()); + return 0; +} + +int InlineFlowBox::marginBorderPaddingLeft() const +{ + return marginLeft() + borderLeft() + paddingLeft(); +} + +int InlineFlowBox::marginBorderPaddingRight() const +{ + return marginRight() + borderRight() + paddingRight(); +} + +int InlineFlowBox::getFlowSpacingWidth() const +{ + int totWidth = marginBorderPaddingLeft() + marginBorderPaddingRight(); + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->isInlineFlowBox()) + totWidth += static_cast<InlineFlowBox*>(curr)->getFlowSpacingWidth(); + } + return totWidth; +} + +bool InlineFlowBox::nextOnLineExists() +{ + if (!parent()) + return false; + + if (nextOnLine()) + return true; + + return parent()->nextOnLineExists(); +} + +bool InlineFlowBox::prevOnLineExists() +{ + if (!parent()) + return false; + + if (prevOnLine()) + return true; + + return parent()->prevOnLineExists(); +} + +bool InlineFlowBox::onEndChain(RenderObject* endObject) +{ + if (!endObject) + return false; + + if (endObject == object()) + return true; + + RenderObject* curr = endObject; + RenderObject* parent = curr->parent(); + while (parent && !parent->isRenderBlock()) { + if (parent->lastChild() != curr || parent == object()) + 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; + + RenderFlow* flow = static_cast<RenderFlow*>(object()); + + if (!flow->firstChild()) + includeLeftEdge = includeRightEdge = true; // Empty inlines never split across lines. + else if (parent()) { // The root inline box never has borders/margins/padding. + bool ltr = flow->style()->direction() == LTR; + + // Check to see if all initial lines are unconstructed. If so, then + // we know the inline began on this line. + if (!flow->firstLineBox()->isConstructed()) { + if (ltr && flow->firstLineBox() == this) + includeLeftEdge = true; + else if (!ltr && flow->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 (!flow->lastLineBox()->isConstructed()) { + if (ltr) { + if (!nextLineBox() && + ((lastLine && !object()->continuation()) || nextOnLineExists() + || onEndChain(endObject))) + includeRightEdge = true; + } + else { + if ((!prevLineBox() || !prevLineBox()->isConstructed()) && + ((lastLine && !object()->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::placeBoxesHorizontally(int x) +{ + // Set our x position. + setXPos(x); + + int startX = x; + x += borderLeft() + paddingLeft(); + + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->object()->isText()) { + InlineTextBox* text = static_cast<InlineTextBox*>(curr); + text->setXPos(x); + x += curr->width(); + } + else { + if (curr->object()->isPositioned()) { + if (curr->object()->parent()->style()->direction() == LTR) + curr->setXPos(x); + 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 by walking up to the root line box). + InlineBox* root = this; + while (!root->isRootInlineBox()) + root = root->parent(); + curr->setXPos(root->object()->width()-x); + } + continue; // The positioned object has no effect on the width. + } + if (curr->object()->isInlineFlow()) { + InlineFlowBox* flow = static_cast<InlineFlowBox*>(curr); + x += flow->marginLeft(); + x = flow->placeBoxesHorizontally(x); + x += flow->marginRight(); + } + else { + x += curr->object()->marginLeft(); + curr->setXPos(x); + x += curr->width() + curr->object()->marginRight(); + } + } + } + + x += borderRight() + paddingRight(); + setWidth(x-startX); + return x; +} + +void InlineFlowBox::verticallyAlignBoxes(int& heightOfBlock) +{ + int maxPositionTop = 0; + int maxPositionBottom = 0; + int maxAscent = 0; + int maxDescent = 0; + + // Figure out if we're in strict mode. + RenderObject* curr = object(); + while (curr && !curr->element()) + curr = curr->container(); + bool strictMode = (curr && curr->element()->getDocument()->inStrictMode()); + + computeLogicalBoxHeights(maxPositionTop, maxPositionBottom, maxAscent, maxDescent, strictMode); + + if (maxAscent + maxDescent < kMax(maxPositionTop, maxPositionBottom)) + adjustMaxAscentAndDescent(maxAscent, maxDescent, maxPositionTop, maxPositionBottom); + + int maxHeight = maxAscent + maxDescent; + int topPosition = heightOfBlock; + int bottomPosition = heightOfBlock; + placeBoxesVertically(heightOfBlock, maxHeight, maxAscent, strictMode, topPosition, bottomPosition); + + setOverflowPositions(topPosition, bottomPosition); + + // Shrink boxes with no text children in quirks and almost strict mode. + if (!strictMode) + shrinkBoxesWithNoTextChildren(topPosition, bottomPosition); + + heightOfBlock += maxHeight; +} + +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 + // see tdehtmltests/rendering/html_align.html + + if (curr->object()->isPositioned()) + continue; // Positioned placeholders don't affect calculations. + if (curr->yPos() == PositionTop || curr->yPos() == PositionBottom) { + if (curr->yPos() == PositionTop) { + if (maxAscent + maxDescent < curr->height()) + maxDescent = curr->height() - maxAscent; + } + else { + if (maxAscent + maxDescent < curr->height()) + maxAscent = curr->height() - maxDescent; + } + + if ( maxAscent + maxDescent >= kMax( maxPositionTop, maxPositionBottom ) ) + break; + } + + if (curr->isInlineFlowBox()) + static_cast<InlineFlowBox*>(curr)->adjustMaxAscentAndDescent(maxAscent, maxDescent, maxPositionTop, maxPositionBottom); + } +} + +void InlineFlowBox::computeLogicalBoxHeights(int& maxPositionTop, int& maxPositionBottom, + int& maxAscent, int& maxDescent, bool strictMode) +{ + if (isRootInlineBox()) { + // Examine our root box. + setHeight(object()->lineHeight(m_firstLine)); + bool isTableCell = object()->isTableCell(); + if (isTableCell) { + RenderTableCell* tableCell = static_cast<RenderTableCell*>(object()); + setBaseline(tableCell->RenderBlock::baselinePosition(m_firstLine)); + } + else + setBaseline(object()->baselinePosition(m_firstLine)); + if (hasTextChildren() || strictMode) { + int ascent = baseline(); + int descent = height() - ascent; + if (maxAscent < ascent) + maxAscent = ascent; + if (maxDescent < descent) + maxDescent = descent; + } + } + + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->object()->isPositioned()) + continue; // Positioned placeholders don't affect calculations. + + curr->setHeight(curr->object()->lineHeight(m_firstLine)); + curr->setBaseline(curr->object()->baselinePosition(m_firstLine)); + curr->setYPos(curr->object()->verticalPositionHint(m_firstLine)); + if (curr->yPos() == PositionTop) { + if (maxPositionTop < curr->height()) + maxPositionTop = curr->height(); + } + else if (curr->yPos() == PositionBottom) { + if (maxPositionBottom < curr->height()) + maxPositionBottom = curr->height(); + } + else if (curr->hasTextChildren() || strictMode) { + int ascent = curr->baseline() - curr->yPos(); + int descent = curr->height() - ascent; + if (maxAscent < ascent) + maxAscent = ascent; + if (maxDescent < descent) + maxDescent = descent; + } + + if (curr->isInlineFlowBox()) + static_cast<InlineFlowBox*>(curr)->computeLogicalBoxHeights(maxPositionTop, maxPositionBottom, maxAscent, maxDescent, strictMode); + } +} + +void InlineFlowBox::placeBoxesVertically(int y, int maxHeight, int maxAscent, bool strictMode, + int& topPosition, int& bottomPosition) +{ + if (isRootInlineBox()) { + setYPos(y + maxAscent - baseline());// Place our root box. + // CSS2: 10.8.1 - line-height on the block level element specifies the *minimum* + // height of the generated line box + if (hasTextChildren() && maxHeight < object()->lineHeight(m_firstLine)) + maxHeight = object()->lineHeight(m_firstLine); + } + + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->object()->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). + if (curr->isInlineFlowBox()) + static_cast<InlineFlowBox*>(curr)->placeBoxesVertically(y, maxHeight, maxAscent, strictMode, + topPosition, bottomPosition); + + bool childAffectsTopBottomPos = true; + + if (curr->yPos() == PositionTop) + curr->setYPos(y); + else if (curr->yPos() == PositionBottom) + curr->setYPos(y + maxHeight - curr->height()); + else { + if (!strictMode && !curr->hasTextDescendant()) + childAffectsTopBottomPos = false; + curr->setYPos(curr->yPos() + y + maxAscent - curr->baseline()); + } + int newY = curr->yPos(); + int newHeight = curr->height(); + int newBaseline = curr->baseline(); + int overflowTop = 0; + int overflowBottom = 0; + if (curr->isInlineTextBox() || curr->isInlineFlowBox()) { + const TQFontMetrics &fm = curr->object()->fontMetrics( m_firstLine ); +#ifdef APPLE_CHANGES + newBaseline = fm.ascent(); + newY += curr->baseline() - newBaseline; + newHeight = newBaseline+fm.descent(); +#else + // only adjust if the leading delta is superior to the font's natural leading + if ( kAbs(fm.ascent() - curr->baseline()) > fm.leading()/2 ) { + int ascent = fm.ascent()+fm.leading()/2; + newBaseline = ascent; + newY += curr->baseline() - newBaseline; + newHeight = fm.lineSpacing(); + } +#endif + for (ShadowData* shadow = curr->object()->style()->textShadow(); shadow; shadow = shadow->next) { + overflowTop = kMin(overflowTop, shadow->y - shadow->blur); + overflowBottom = kMax(overflowBottom, shadow->y + shadow->blur); + } + if (curr->isInlineFlowBox()) { + newHeight += curr->object()->borderTop() + curr->object()->paddingTop() + + curr->object()->borderBottom() + curr->object()->paddingBottom(); + newY -= curr->object()->borderTop() + curr->object()->paddingTop(); + newBaseline += curr->object()->borderTop() + curr->object()->paddingTop(); + } + } else { + newY += curr->object()->marginTop(); + newHeight = curr->height() - (curr->object()->marginTop() + curr->object()->marginBottom()); + overflowTop = curr->object()->overflowTop(); + overflowBottom = curr->object()->overflowHeight() - newHeight; + } + curr->setYPos(newY); + curr->setHeight(newHeight); + curr->setBaseline(newBaseline); + + if (childAffectsTopBottomPos) { + topPosition = kMin(topPosition, newY + overflowTop); + bottomPosition = kMax(bottomPosition, newY + newHeight + overflowBottom); + } + } + + if (isRootInlineBox()) { + const TQFontMetrics &fm = object()->fontMetrics( m_firstLine ); +#ifdef APPLE_CHANGES + setHeight(fm.ascent()+fm.descent()); + setYPos(yPos() + baseline() - fm.ascent()); + setBaseline(fm.ascent()); +#else + if ( kAbs(fm.ascent() - baseline()) > fm.leading()/2 ) { + int ascent = fm.ascent()+fm.leading()/2; + setHeight(fm.lineSpacing()); + setYPos(yPos() + baseline() - ascent); + setBaseline(ascent); + } +#endif + if (hasTextDescendant() || strictMode) { + if (yPos() < topPosition) + topPosition = yPos(); + if (yPos() + height() > bottomPosition) + bottomPosition = yPos() + height(); + } + } +} + +void InlineFlowBox::shrinkBoxesWithNoTextChildren(int topPos, int bottomPos) +{ + // First shrink our kids. + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->object()->isPositioned()) + continue; // Positioned placeholders don't affect calculations. + + if (curr->isInlineFlowBox()) + static_cast<InlineFlowBox*>(curr)->shrinkBoxesWithNoTextChildren(topPos, bottomPos); + } + + // See if we have text children. If not, then we need to shrink ourselves to fit on the line. + if (!hasTextDescendant()) { + if (yPos() < topPos) + setYPos(topPos); + if (yPos() + height() > bottomPos) + setHeight(bottomPos - yPos()); + if (baseline() > height()) + setBaseline(height()); + } +} + +bool InlineFlowBox::nodeAtPoint(RenderObject::NodeInfo& i, int x, int y, int tx, int ty) +{ + // Check children first. + for (InlineBox* curr = lastChild(); curr; curr = curr->prevOnLine()) { + if (!curr->object()->layer() && curr->nodeAtPoint(i, x, y, tx, ty)) { + object()->setInnerNode(i); + return true; + } + } + + // Now check ourselves. + TQRect rect(tx + m_x, ty + m_y, m_width, m_height); + if (object()->style()->visibility() == VISIBLE && rect.contains(x, y)) { + object()->setInnerNode(i); + return true; + } + + return false; +} + + +void InlineFlowBox::paint(RenderObject::PaintInfo& i, int tx, int ty) +{ + bool intersectsDamageRect = true; + int xPos = tx + m_x - object()->maximalOutlineSize(i.phase); + int w = width() + 2 * object()->maximalOutlineSize(i.phase); + if ((xPos >= i.r.x() + i.r.width()) || (xPos + w <= i.r.x())) + intersectsDamageRect = false; + + if (intersectsDamageRect) { + if (i.phase == PaintActionOutline) { + // Add ourselves to the paint info struct's list of inlines that need to paint their + // outlines. + if (object()->style()->visibility() == VISIBLE && object()->style()->outlineWidth() > 0 && + !object()->isInlineContinuation() && !isRootInlineBox()) { + if (!i.outlineObjects) + i.outlineObjects = new TQValueList<RenderFlow*>; + i.outlineObjects->append(static_cast<RenderFlow*>(object())); + } + } + else { + // 1. Paint our background and border. + paintBackgroundAndBorder(i, tx, ty); + + // 2. Paint our underline and overline. + paintDecorations(i, tx, ty, false); + } + } + + // 3. Paint our children. + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (!curr->object()->layer()) + curr->paint(i, tx, ty); + } + + // 4. Paint our strike-through + if (intersectsDamageRect && i.phase != PaintActionOutline) + paintDecorations(i, tx, ty, true); +} + + +void InlineFlowBox::paintBackgrounds(TQPainter* p, const TQColor& c, const BackgroundLayer* bgLayer, + int my, int mh, int _tx, int _ty, int w, int h) +{ + if (!bgLayer) + return; + paintBackgrounds(p, c, bgLayer->next(), my, mh, _tx, _ty, w, h); + paintBackground(p, c, bgLayer, my, mh, _tx, _ty, w, h); +} + +void InlineFlowBox::paintBackground(TQPainter* p, const TQColor& c, const BackgroundLayer* bgLayer, + int my, int mh, int _tx, int _ty, int w, int h) +{ + CachedImage* bg = bgLayer->backgroundImage(); + bool hasBackgroundImage = bg && (bg->pixmap_size() == bg->valid_rect().size()) && + !bg->isTransparent() && !bg->isErrorImage(); + if (!hasBackgroundImage || (!prevLineBox() && !nextLineBox()) || !parent()) + object()->paintBackgroundExtended(p, c, bgLayer, my, mh, _tx, _ty, w, h, borderLeft(), borderRight(), paddingLeft(), paddingRight()); + else { + // We have a background 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 xOffsetOnLine = 0; + for (InlineRunBox* curr = prevLineBox(); curr; curr = curr->prevLineBox()) + xOffsetOnLine += curr->width(); + int startX = _tx - xOffsetOnLine; + int totalWidth = xOffsetOnLine; + for (InlineRunBox* curr = this; curr; curr = curr->nextLineBox()) + totalWidth += curr->width(); + p->save(); + p->setClipRect(TQRect(_tx, _ty, width(), height()), TQPainter::CoordPainter); + object()->paintBackgroundExtended(p, c, bgLayer, my, mh, startX, _ty, + totalWidth, h, borderLeft(), borderRight(), paddingLeft(), paddingRight()); + p->restore(); + } +} + +void InlineFlowBox::paintBackgroundAndBorder(RenderObject::PaintInfo& pI, int _tx, int _ty) +{ + if (object()->style()->visibility() != VISIBLE || pI.phase != PaintActionForeground) + return; + + // Move x/y to our coordinates. + _tx += m_x; + _ty += m_y; + + int w = width(); + int h = height(); + + int my = kMax(_ty, pI.r.y()); + int mh; + if (_ty<pI.r.y()) + mh= kMax(0,h-(pI.r.y()-_ty)); + else + mh = kMin(pI.r.height(),h); + + // 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 = object()->style(m_firstLine); + if ((!parent() && m_firstLine && styleToUse != object()->style()) || + (parent() && object()->shouldPaintBackgroundOrBorder())) { + TQColor c = styleToUse->backgroundColor(); + paintBackgrounds(pI.p, c, styleToUse->backgroundLayers(), my, mh, _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() && object()->style()->hasBorder()) + object()->paintBorder(pI.p, _tx, _ty, w, h, object()->style(), includeLeftEdge(), includeRightEdge()); + } +} + +static bool shouldDrawDecoration(RenderObject* obj) +{ + bool shouldDraw = false; + for (RenderObject* curr = obj->firstChild(); + curr; curr = curr->nextSibling()) { + if (curr->isInlineFlow()) { + shouldDraw = true; + break; + } + else if (curr->isText() && !curr->isBR() && (curr->style()->preserveWS() || + !curr->element() || !curr->element()->containsOnlyWhitespace())) { + shouldDraw = true; + break; + } + } + return shouldDraw; +} + +void InlineFlowBox::paintDecorations(RenderObject::PaintInfo& pI, int _tx, int _ty, bool paintedChildren) +{ + // Now paint our text decorations. We only do this if we aren't in quirks mode (i.e., in + // almost-strict mode or strict mode). + if (object()->style()->htmlHacks() || object()->style()->visibility() != VISIBLE) + return; + + _tx += m_x; + _ty += m_y; + RenderStyle* styleToUse = object()->style(m_firstLine); + int deco = parent() ? styleToUse->textDecoration() : styleToUse->textDecorationsInEffect(); + if (deco != TDNONE && + ((!paintedChildren && ((deco & UNDERLINE) || (deco & OVERLINE))) || (paintedChildren && (deco & LINE_THROUGH))) && + shouldDrawDecoration(object())) { + // We must have child boxes and have decorations defined. + _tx += borderLeft() + paddingLeft(); + int w = m_width - (borderLeft() + paddingLeft() + borderRight() + paddingRight()); + if ( !w ) + return; + const TQFontMetrics &fm = object()->fontMetrics( m_firstLine ); + // thick lines on small fonts look ugly + int thickness = fm.height() > 20 ? fm.lineWidth() : 1; + TQColor underline, overline, linethrough; + underline = overline = linethrough = styleToUse->color(); + if (!parent()) + object()->getTextDecorationColors(deco, underline, overline, linethrough); + + if (styleToUse->font() != pI.p->font()) + pI.p->setFont(styleToUse->font()); + + if (deco & UNDERLINE && !paintedChildren) { + int underlineOffset = ( fm.height() + m_baseline ) / 2; + if (underlineOffset <= m_baseline) underlineOffset = m_baseline+1; + + pI.p->fillRect(_tx, _ty + underlineOffset, w, thickness, underline ); + } + if (deco & OVERLINE && !paintedChildren) { + pI.p->fillRect(_tx, _ty, w, thickness, overline ); + } + if (deco & LINE_THROUGH && paintedChildren) { + pI.p->fillRect(_tx, _ty + 2*m_baseline/3, w, thickness, linethrough ); + } + } +} + +bool InlineFlowBox::canAccommodateEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth) +{ + for (InlineBox *box = firstChild(); box; box = box->nextOnLine()) { + if (!box->canAccommodateEllipsisBox(ltr, blockEdge, ellipsisWidth)) + return false; + } + return true; +} + +int InlineFlowBox::placeEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth, bool& foundBox) +{ + int result = -1; + for (InlineBox *box = firstChild(); box; box = box->nextOnLine()) { + int currResult = box->placeEllipsisBox(ltr, blockEdge, ellipsisWidth, foundBox); + if (currResult != -1 && result == -1) + result = currResult; + } + return result; +} + +void InlineFlowBox::clearTruncation() +{ + for (InlineBox *box = firstChild(); box; box = box->nextOnLine()) + box->clearTruncation(); +} + +void EllipsisBox::paint(RenderObject::PaintInfo& i, int _tx, int _ty) +{ + TQPainter* p = i.p; + RenderStyle* _style = m_firstLine ? m_object->style(true) : m_object->style(); + if (_style->font() != p->font()) + p->setFont(_style->font()); + + const Font* font = &_style->htmlFont(); + TQColor textColor = _style->color(); + if (textColor != p->pen().color()) + p->setPen(textColor); + /* + bool setShadow = false; + if (_style->textShadow()) { + p->setShadow(_style->textShadow()->x, _style->textShadow()->y, + _style->textShadow()->blur, _style->textShadow()->color); + setShadow = true; + }*/ + + const DOMString& str = m_str.string(); + font->drawText(p, m_x + _tx, + m_y + _ty + m_baseline, + (str.implementation())->s, + str.length(), 0, str.length(), + 0, + TQPainter::LTR, _style->visuallyOrdered()); + + /* + if (setShadow) + p->clearShadow(); + */ + + if (m_markupBox) { + // Paint the markup box + _tx += m_x + m_width - m_markupBox->xPos(); + _ty += m_y + m_baseline - (m_markupBox->yPos() + m_markupBox->baseline()); + m_markupBox->object()->paint(i, _tx, _ty); + } +} + +bool EllipsisBox::nodeAtPoint(RenderObject::NodeInfo& info, int _x, int _y, int _tx, int _ty) +{ + // Hit test the markup box. + if (m_markupBox) { + _tx += m_x + m_width - m_markupBox->xPos(); + _ty += m_y + m_baseline - (m_markupBox->yPos() + m_markupBox->baseline()); + if (m_markupBox->nodeAtPoint(info, _x, _y, _tx, _ty)) { + object()->setInnerNode(info); + return true; + } + } + + TQRect rect(_tx + m_x, _ty + m_y, m_width, m_height); + if (object()->style()->visibility() == VISIBLE && rect.contains(_x, _y)) { + object()->setInnerNode(info); + return true; + } + return false; +} + +void RootInlineBox::detach(RenderArena* arena) +{ + detachEllipsisBox(arena); + InlineFlowBox::detach(arena); +} + +void RootInlineBox::detachEllipsisBox(RenderArena* arena) +{ + if (m_ellipsisBox) { + m_ellipsisBox->detach(arena); + m_ellipsisBox = 0; + } +} + +void RootInlineBox::clearTruncation() +{ + if (m_ellipsisBox) { + detachEllipsisBox(m_object->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 (width() - 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::canAccommodateEllipsisBox(ltr, blockEdge, ellipsisWidth); +} + +void RootInlineBox::placeEllipsis(const DOMString& ellipsisStr, bool ltr, int blockEdge, int ellipsisWidth, InlineBox* markupBox) +{ + // Create an ellipsis box. + m_ellipsisBox = new (m_object->renderArena()) EllipsisBox(m_object, ellipsisStr, this, + ellipsisWidth - (markupBox ? markupBox->width() : 0), + yPos(), height(), baseline(), !prevRootBox(), + markupBox); + + if (ltr && (xPos() + width() + ellipsisWidth) <= blockEdge) { + m_ellipsisBox->m_x = xPos() + width(); + 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; + m_ellipsisBox->m_x = placeEllipsisBox(ltr, blockEdge, ellipsisWidth, foundBox); +} + +int RootInlineBox::placeEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth, bool& foundBox) +{ + int result = InlineFlowBox::placeEllipsisBox(ltr, blockEdge, ellipsisWidth, foundBox); + if (result == -1) + result = ltr ? blockEdge - ellipsisWidth : blockEdge; + return result; +} + +void RootInlineBox::paintEllipsisBox(RenderObject::PaintInfo& i, int _tx, int _ty) const +{ + if (m_ellipsisBox) + m_ellipsisBox->paint(i, _tx, _ty); +} + +void RootInlineBox::paint(RenderObject::PaintInfo& i, int tx, int ty) +{ + InlineFlowBox::paint(i, tx, ty); + paintEllipsisBox(i, tx, ty); +} + +bool RootInlineBox::nodeAtPoint(RenderObject::NodeInfo& i, int x, int y, int tx, int ty) +{ + if (m_ellipsisBox && object()->style()->visibility() == VISIBLE) { + if (m_ellipsisBox->nodeAtPoint(i, x, y, tx, ty)) { + object()->setInnerNode(i); + return true; + } + } + return InlineFlowBox::nodeAtPoint(i, x, y, tx, ty); +} + |