/** * This file is part of the DOM implementation for KDE. * * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * Copyright (C) 2004 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., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * */ // ------------------------------------------------------------------------- //#define DEBUG_LAYOUT #include #include "rendering/render_box.h" #include "rendering/render_replaced.h" #include "rendering/render_canvas.h" #include "rendering/render_table.h" #include "render_flexbox.h" #include "render_arena.h" #include "misc/htmlhashes.h" #include "xml/dom_nodeimpl.h" #include "xml/dom_docimpl.h" #include "html/html_elementimpl.h" #include "render_line.h" #include #include #include using namespace DOM; using namespace khtml; #define TABLECELLMARGIN -0x4000 RenderBox::RenderBox(DOM::NodeImpl* node) : RenderObject(node) { m_minWidth = -1; m_maxWidth = -1; m_overrideSize = -1; m_width = m_height = 0; m_x = 0; m_y = 0; m_marginTop = 0; m_marginBottom = 0; m_marginLeft = 0; m_marginRight = 0; m_staticX = 0; m_staticY = 0; m_layer = 0; m_inlineBoxWrapper = 0; } void RenderBox::setStyle(RenderStyle *_style) { RenderObject::setStyle(_style); // The root always paints its background/border. if (isRoot()) setShouldPaintBackgroundOrBorder(true); setInline(_style->isDisplayInlineType()); switch(_style->position()) { case ABSOLUTE: case FIXED: setPositioned(true); break; default: setPositioned(false); if (_style->isFloating()) setFloating(true); if (_style->position() == RELATIVE) setRelPositioned(true); } // FIXME: Note that we restrict overflow to blocks for now. One day table bodies // will need to support overflow. // We also handle and , whose overflow applies to the viewport. if (_style->overflow() != OVISIBLE && isBlockFlow() && !isRoot() && (!isBody() || !document()->isHTMLDocument())) setHasOverflowClip(); if (requiresLayer()) { if (!m_layer) { m_layer = new (renderArena()) RenderLayer(this); m_layer->insertOnlyThisLayer(); if (parent() && containingBlock()) m_layer->updateLayerPositions(); } } else if (m_layer && !isRoot() && !isCanvas()) { m_layer->removeOnlyThisLayer(); m_layer = 0; } if (m_layer) m_layer->styleChanged(); // Set the text color if we're the body. if (isBody()) element()->getDocument()->setTextColor(_style->color()); if (style()->outlineWidth() > 0 && style()->outlineSize() > maximalOutlineSize(PaintActionOutline)) static_cast(document()->renderer())->setMaximalOutlineSize(style()->outlineSize()); } RenderBox::~RenderBox() { //kdDebug( 6040 ) << "Element destructor: this=" << nodeName().string() << endl; } void RenderBox::detach() { RenderLayer* layer = m_layer; RenderArena* arena = renderArena(); // This must be done before we detach the RenderObject. if (layer) layer->clearClipRect(); if (m_inlineBoxWrapper) { if (!documentBeingDestroyed()) m_inlineBoxWrapper->remove(); m_inlineBoxWrapper->detach(arena); m_inlineBoxWrapper = 0; } RenderObject::detach(); if (layer) layer->detach(arena); } int RenderBox::contentWidth() const { int w = m_width - borderLeft() - borderRight(); w -= paddingLeft() + paddingRight(); if (includeScrollbarSize()) w -= m_layer->verticalScrollbarWidth(); //kdDebug( 6040 ) << "RenderBox::contentWidth(2) = " << w << endl; return w; } int RenderBox::contentHeight() const { int h = m_height - borderTop() - borderBottom(); h -= paddingTop() + paddingBottom(); if (includeScrollbarSize()) h -= m_layer->horizontalScrollbarHeight(); return h; } int RenderBox::overrideWidth() const { return m_overrideSize == -1 ? m_width : m_overrideSize; } int RenderBox::overrideHeight() const { return m_overrideSize == -1 ? m_height : m_overrideSize; } void RenderBox::setPos( int xPos, int yPos ) { if (xPos == m_x && yPos == m_y) return; // Optimize for the case where we don't move at all. m_x = xPos; m_y = yPos; } int RenderBox::width() const { return m_width; } int RenderBox::height() const { return m_height; } // Hit Testing bool RenderBox::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction) { // Check kids first. _tx += m_x; _ty += m_y; 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
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->layer() && !child->isInlineFlow() && child->nodeAtPoint(info, _x, _y, _tx, _ty, hitTestAction)) { setInnerNode(info); 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). if (hitTestAction != HitTestForeground) return false; QRect boundsRect(_tx, _ty, m_width, m_height); if (boundsRect.contains(_x, _y)) { setInnerNode(info); return true; } return false; } // --------------------- painting stuff ------------------------------- void RenderBox::paint(PaintInfo& i, int _tx, int _ty) { _tx += m_x; _ty += m_y; // default implementation. Just pass paint through to the children for (RenderObject* child = firstChild(); child; child = child->nextSibling()) child->paint(i, _tx, _ty); } void RenderBox::paintRootBoxDecorations(PaintInfo& i, int _tx, int _ty) { //kdDebug( 6040 ) << renderName() << "::paintBoxDecorations()" << _tx << "/" << _ty << endl; const BackgroundLayer* bgLayer = style()->backgroundLayers(); QColor bgColor = style()->backgroundColor(); if (document()->isHTMLDocument() && !style()->hasBackground()) { // Locate the 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 tags etc. We can locate the // render object very easily via the DOM. HTMLElementImpl* body = document()->body(); RenderObject* bodyObject = (body && body->id() == ID_BODY) ? body->renderer() : 0; if (bodyObject) { bgLayer = bodyObject->style()->backgroundLayers(); bgColor = bodyObject->style()->backgroundColor(); } } int w = width(); int h = height(); // kdDebug(0) << "width = " << w <view()) { rw = canvas()->view()->contentsWidth(); rh = canvas()->view()->contentsHeight(); } else { rw = canvas()->width(); rh = canvas()->height(); } // kdDebug(0) << "rw = " << rw <hasBorder() && style()->display() != INLINE) paintBorder( i.p, _tx, _ty, w, h, style() ); } void RenderBox::paintBoxDecorations(PaintInfo& i, int _tx, int _ty) { if (!shouldPaintWithinRoot(i)) return; //kdDebug( 6040 ) << renderName() << "::paintDecorations()" << endl; if (isRoot()) return paintRootBoxDecorations(i, _tx, _ty); int w = width(); int h = height() + borderTopExtra() + borderBottomExtra(); _ty -= borderTopExtra(); int my = kMax(_ty, i.r.y()); int mh; if (_ty < i.r.y()) mh= kMax(0, h - (i.r.y() - _ty)); else mh = kMin(i.r.height(), h); // The 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()->isHTMLDocument() || document()->documentElement()->renderer()->style()->hasBackground()) paintBackgrounds(i.p, style()->backgroundColor(), style()->backgroundLayers(), my, mh, _tx, _ty, w, h); if (style()->hasBorder()) paintBorder(i.p, _tx, _ty, w, h, style()); } void RenderBox::paintBackgrounds(QPainter *p, const QColor& c, const BackgroundLayer* bgLayer, int clipy, int cliph, int _tx, int _ty, int w, int height) { if (!bgLayer) return; paintBackgrounds(p, c, bgLayer->next(), clipy, cliph, _tx, _ty, w, height); paintBackground(p, c, bgLayer, clipy, cliph, _tx, _ty, w, height); } void RenderBox::paintBackground(QPainter *p, const QColor& c, const BackgroundLayer* bgLayer, int clipy, int cliph, int _tx, int _ty, int w, int height) { paintBackgroundExtended(p, c, bgLayer, clipy, cliph, _tx, _ty, w, height, borderLeft(), borderRight()); } void RenderBox::paintBackgroundExtended(QPainter *p, const QColor& c, const BackgroundLayer* bgLayer, int clipy, int cliph, int _tx, int _ty, int w, int h, int bleft, int bright) { CachedImage* bg = bgLayer->backgroundImage(); bool shouldPaintBackgroundImage = bg && bg->pixmap_size() == bg->valid_rect().size() && !bg->isTransparent() && !bg->isErrorImage(); QColor 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() && qAlpha(bgColor.rgb()) > 0; if (shouldPaintBackgroundImage || shouldPaintBackgroundColor) { bgColor = Qt::white; shouldPaintBackgroundImage = false; } } // 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. if (!bgLayer->next() && isRoot() && !(bgColor.isValid() && qAlpha(bgColor.rgb()) > 0) && canvas()->view()) { bool isTransparent; DOM::NodeImpl* elt = document()->ownerElement(); if (elt) { if (elt->id() == ID_FRAME) isTransparent = false; else { // Locate the 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 tags etc. We can locate the // render object very easily via the DOM. HTMLElementImpl* body = document()->body(); isTransparent = !body || body->id() != ID_FRAMESET; // Can't scroll a frameset document anyway. } } else isTransparent = canvas()->view()->isTransparent(); if (isTransparent) canvas()->view()->useSlowRepaints(); // The parent must show behind the child. else bgColor = canvas()->view()->palette().active().color(QColorGroup::Base); } // Paint the color first underneath all images. if (!bgLayer->next() && bgColor.isValid() && qAlpha(bgColor.rgb()) > 0) { // If we have an alpha and we are painting the root element, go ahead and blend with our default // background color (typically white). if (qAlpha(bgColor.rgb()) < 0xFF && isRoot() && !canvas()->view()->isTransparent()) p->fillRect(_tx, clipy, w, cliph, canvas()->view()->palette().active().color(QColorGroup::Base)); p->fillRect(_tx, clipy, w, cliph, bgColor); } // no progressive loading of the background image if (shouldPaintBackgroundImage) { int sx = 0; int sy = 0; int cw,ch; int cx,cy; int vpab = bleft + bright; int hpab = borderTop() + borderBottom(); // CSS2 chapter 14.2.1 if (bgLayer->backgroundAttachment()) { //scroll int pw = w - vpab; int ph = h - hpab; int pixw = bg->pixmap_size().width(); int pixh = bg->pixmap_size().height(); EBackgroundRepeat bgr = bgLayer->backgroundRepeat(); if( (bgr == NO_REPEAT || bgr == REPEAT_Y) && w > pixw ) { cw = pixw; int xPosition = bgLayer->backgroundXPosition().minWidth(pw-pixw); if (xPosition >= 0) cx = _tx + xPosition; else { cx = _tx; if (pixw == 0) sx = 0; else { sx = -xPosition; cw += xPosition; } } cx += bleft; } else { cw = w; cx = _tx; if (pixw == 0) sx = 0; else { sx = pixw - ((bgLayer->backgroundXPosition().minWidth(pw-pixw)) % pixw ); sx -= bleft % pixw; } } if( (bgr == NO_REPEAT || bgr == REPEAT_X) && h > pixh ) { ch = pixh; int yPosition = bgLayer->backgroundYPosition().minWidth(ph-pixh); if (yPosition >= 0) cy = _ty + yPosition; else { cy = _ty; if (pixh == 0) sy = 0; else { sy = -yPosition; ch += yPosition; } } cy += borderTop(); } else { ch = h; cy = _ty; if(pixh == 0){ sy = 0; }else{ sy = pixh - ((bgLayer->backgroundYPosition().minWidth(ph-pixh)) % pixh ); sy -= borderTop() % pixh; } } } else { //fixed QRect vr = viewRect(); int pw = vr.width(); int ph = vr.height(); int pixw = bg->pixmap_size().width(); int pixh = bg->pixmap_size().height(); EBackgroundRepeat bgr = bgLayer->backgroundRepeat(); if( (bgr == NO_REPEAT || bgr == REPEAT_Y) && pw > pixw ) { cw = pixw; cx = vr.x() + bgLayer->backgroundXPosition().minWidth(pw-pixw); } else { cw = pw; cx = vr.x(); if(pixw == 0){ sx = 0; }else{ sx = pixw - ((bgLayer->backgroundXPosition().minWidth(pw-pixw)) % pixw ); } } if( (bgr == NO_REPEAT || bgr == REPEAT_X) && ph > pixh ) { ch = pixh; cy = vr.y() + bgLayer->backgroundYPosition().minWidth(ph-pixh); } else { ch = ph; cy = vr.y(); if(pixh == 0){ sy = 0; }else{ sy = pixh - ((bgLayer->backgroundYPosition().minWidth(ph-pixh)) % pixh ); } } QRect fix(cx,cy,cw,ch); QRect ele(_tx,_ty,w,h); QRect b = fix.intersect(ele); sx+=b.x()-cx; sy+=b.y()-cy; cx=b.x();cy=b.y();cw=b.width();ch=b.height(); } // kdDebug() << "cx="<lineWidth(m_y); else return cb->contentWidth(); } bool RenderBox::absolutePosition(int &xPos, int &yPos, bool f) { if (style()->position() == FIXED) f = true; RenderObject *o = container(); if (o && o->absolutePosition(xPos, yPos, f)) { if (o->hasOverflowClip()) o->layer()->subtractScrollOffset(xPos, yPos); if (!isInline() || isReplaced()) xPos += m_x, yPos += m_y; if (isRelPositioned()) relativePositionOffset(xPos, yPos); return true; } else { xPos = yPos = 0; return false; } } void RenderBox::dirtyLineBoxes(bool fullLayout, bool) { if (m_inlineBoxWrapper) { if (fullLayout) { m_inlineBoxWrapper->detach(renderArena()); m_inlineBoxWrapper = 0; } else m_inlineBoxWrapper->dirtyLineBoxes(); } } void RenderBox::position(InlineBox* box, int from, int len, bool reverse) { if (isPositioned()) { // Cache the x position only if we were an INLINE type originally. bool wasInline = style()->isOriginalDisplayInlineType(); if (wasInline && 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. m_staticX = box->xPos(); } else if (!wasInline && 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 yPos() of the box. m_staticY = box->yPos(); // Nuke the box. box->remove(); box->detach(renderArena()); } else if (isReplaced()) { m_x = box->xPos(); m_y = box->yPos(); m_inlineBoxWrapper = box; } } // 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* RenderBox::inlineBoxWrapper() const { return m_inlineBoxWrapper; } void RenderBox::deleteLineBoxWrapper() { if (m_inlineBoxWrapper) m_inlineBoxWrapper->detach(renderArena()); m_inlineBoxWrapper = 0; } void RenderBox::setInlineBoxWrapper(InlineBox* b) { m_inlineBoxWrapper = b; } QRect RenderBox::getAbsoluteRepaintRect() { int ow = style() ? style()->outlineSize() : 0; QRect r(-ow, -ow, overflowWidth(false)+ow*2, overflowHeight(false)+ow*2); computeAbsoluteRepaintRect(r); return r; } void RenderBox::computeAbsoluteRepaintRect(QRect& r, bool f) { int x = r.x() + m_x; int y = r.y() + m_y; // 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(). if (style()->position() == RELATIVE && m_layer) m_layer->relativePositionOffset(x,y); if (style()->position()==FIXED) f = true; RenderObject* o = container(); if (o) { // may not have overflow, since it might be applying its overflow value to the // scrollbars. 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. QRect boxRect(0, 0, o->layer()->width(), o->layer()->height()); o->layer()->subtractScrollOffset(x,y); // For overflow:auto/scroll/hidden. QRect repaintRect(x, y, r.width(), r.height()); r = repaintRect.intersect(boxRect); if (r.isEmpty()) return; } else { r.setX(x); r.setY(y); } o->computeAbsoluteRepaintRect(r, f); } } void RenderBox::repaintDuringLayoutIfMoved(int oldX, int oldY) { int newX = m_x; int newY = m_y; if (oldX != newX || oldY != 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_x = oldX; m_y = oldY; repaint(); repaintFloatingDescendants(); m_x = newX; m_y = newY; repaint(); repaintFloatingDescendants(); } } void RenderBox::relativePositionOffset(int &tx, int &ty) { if(!style()->left().isVariable()) tx += style()->left().width(containingBlockWidth()); else if(!style()->right().isVariable()) tx -= style()->right().width(containingBlockWidth()); if(!style()->top().isVariable()) { if (!style()->top().isPercent() || containingBlock()->style()->height().isFixed()) ty += style()->top().width(containingBlockHeight()); } else if(!style()->bottom().isVariable()) { if (!style()->bottom().isPercent() || containingBlock()->style()->height().isFixed()) ty -= style()->bottom().width(containingBlockHeight()); } } void RenderBox::calcWidth() { #ifdef DEBUG_LAYOUT kdDebug( 6040 ) << "RenderBox("<isFlexibleBox() && parent()->style()->boxOrient() == HORIZONTAL && parent()->isFlexingChildren()) { m_width = m_overrideSize; return; } bool inVerticalBox = parent()->isFlexibleBox() && parent()->style()->boxOrient() == VERTICAL; bool stretching = parent()->style()->boxAlign() == BSTRETCH; bool treatAsReplaced = isReplaced() && !isInlineBlockOrInlineTable() && (!inVerticalBox || !stretching); Length w; if (treatAsReplaced) w = Length( calcReplacedWidth(), Fixed ); else w = style()->width(); Length ml = style()->marginLeft(); Length mr = style()->marginRight(); RenderBlock *cb = containingBlock(); int cw = containingBlockWidth(); if (cw<0) cw = 0; m_marginLeft = 0; m_marginRight = 0; if (isInline() && !isInlineBlockOrInlineTable()) { // just calculate margins m_marginLeft = ml.minWidth(cw); m_marginRight = mr.minWidth(cw); if (treatAsReplaced) { m_width = w.width(cw); m_width += paddingLeft() + paddingRight() + borderLeft() + borderRight(); if(m_width < m_minWidth) m_width = m_minWidth; } return; } else { LengthType widthType, minWidthType, maxWidthType; if (treatAsReplaced) { m_width = w.width(cw); m_width += paddingLeft() + paddingRight() + borderLeft() + borderRight(); widthType = w.type; } else { m_width = calcWidthUsing(Width, cw, widthType); int minW = calcWidthUsing(MinWidth, cw, minWidthType); int maxW = style()->maxWidth().value == UNDEFINED ? m_width : calcWidthUsing(MaxWidth, cw, maxWidthType); if (m_width > maxW) { m_width = maxW; widthType = maxWidthType; } if (m_width < minW) { m_width = minW; widthType = minWidthType; } } if (widthType == Variable) { // kdDebug( 6040 ) << "variable" << endl; m_marginLeft = ml.minWidth(cw); m_marginRight = mr.minWidth(cw); } else { // kdDebug( 6040 ) << "non-variable " << w.type << ","<< w.value << endl; calcHorizontalMargins(ml,mr,cw); } } if (cw && cw != m_width + m_marginLeft + m_marginRight && !isFloating() && !isInline() && !cb->isFlexibleBox()) { if (cb->style()->direction()==LTR) m_marginRight = cw - m_width - m_marginLeft; else m_marginLeft = cw - m_width - m_marginRight; } } #ifdef DEBUG_LAYOUT kdDebug( 6040 ) << "RenderBox::calcWidth(): m_width=" << m_width << " containingBlockWidth()=" << containingBlockWidth() << endl; kdDebug( 6040 ) << "m_marginLeft=" << m_marginLeft << " m_marginRight=" << m_marginRight << endl; #endif } int RenderBox::calcWidthUsing(WidthType widthType, int cw, LengthType& lengthType) { int width = m_width; Length w; if (widthType == Width) w = style()->width(); else if (widthType == MinWidth) w = style()->minWidth(); else w = style()->maxWidth(); lengthType = w.type; if (lengthType == Variable) { int marginLeft = style()->marginLeft().minWidth(cw); int marginRight = style()->marginRight().minWidth(cw); if (cw) width = cw - marginLeft - marginRight; if (sizesToMaxWidth()) { if (width < m_minWidth) width = m_minWidth; if (width > m_maxWidth) width = m_maxWidth; } } else { width = w.width(cw); width += paddingLeft() + paddingRight() + borderLeft() + borderRight(); } return width; } void RenderBox::calcHorizontalMargins(const Length& ml, const Length& mr, int cw) { if (isFloating() || isInline()) // Inline blocks/tables and floats don't have their margins increased. { m_marginLeft = ml.minWidth(cw); m_marginRight = mr.minWidth(cw); } else { if ( (ml.type == Variable && mr.type == Variable) || (ml.type != Variable && mr.type != Variable && containingBlock()->style()->textAlign() == KHTML_CENTER) ) { m_marginLeft = (cw - m_width)/2; if (m_marginLeft<0) m_marginLeft=0; m_marginRight = cw - m_width - m_marginLeft; } else if (mr.type == Variable || (ml.type != Variable && containingBlock()->style()->direction() == RTL && containingBlock()->style()->textAlign() == KHTML_LEFT)) { m_marginLeft = ml.width(cw); m_marginRight = cw - m_width - m_marginLeft; } else if (ml.type == Variable || (mr.type != Variable && containingBlock()->style()->direction() == LTR && containingBlock()->style()->textAlign() == KHTML_RIGHT)) { m_marginRight = mr.width(cw); m_marginLeft = cw - m_width - m_marginRight; } else { m_marginLeft = ml.minWidth(cw); m_marginRight = mr.minWidth(cw); } } } void RenderBox::calcHeight() { #ifdef DEBUG_LAYOUT kdDebug( 6040 ) << "RenderBox::calcHeight()" << endl; #endif // Cell height is managed by the table and inline non-replaced elements do not support a height property. if (isTableCell() || (isInline() && !isReplaced())) return; if (isPositioned()) calcAbsoluteVertical(); else { calcVerticalMargins(); // For tables, calculate margins only if (isTable()) return; Length h; bool inHorizontalBox = parent()->isFlexibleBox() && parent()->style()->boxOrient() == HORIZONTAL; bool stretching = parent()->style()->boxAlign() == BSTRETCH; bool treatAsReplaced = isReplaced() && !isInlineBlockOrInlineTable() && (!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. if (m_overrideSize != -1 && parent()->isFlexibleBox() && parent()->style()->boxOrient() == VERTICAL && parent()->isFlexingChildren()) h = Length(m_overrideSize - borderTop() - borderBottom() - paddingTop() - paddingBottom(), Fixed); else if (treatAsReplaced) h = Length(calcReplacedHeight(), Fixed); else { h = style()->height(); checkMinMaxHeight = true; } // Block children of horizontal flexible boxes fill the height of the box. if (h.isVariable() && parent()->isFlexibleBox() && parent()->style()->boxOrient() == HORIZONTAL && parent()->isStretchingChildren()) { h = Length(parent()->contentHeight() - marginTop() - marginBottom() - borderTop() - paddingTop() - borderBottom() - paddingBottom(), Fixed); checkMinMaxHeight = false; } int height; if (checkMinMaxHeight) { height = calcHeightUsing(style()->height()); if (height == -1) height = m_height; int minH = calcHeightUsing(style()->minHeight()); // Leave as -1 if unset. int maxH = style()->maxHeight().value == UNDEFINED ? height : calcHeightUsing(style()->maxHeight()); if (maxH == -1) maxH = height; height = kMin(maxH, height); height = kMax(minH, height); } 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. height = h.value + borderTop() + paddingTop() + borderBottom() + paddingBottom(); m_height = height; } // Unfurling marquees override with the furled height. if (style()->overflow() == OMARQUEE && m_layer && m_layer->marquee() && m_layer->marquee()->isUnfurlMarquee() && !m_layer->marquee()->isHorizontal()) { m_layer->marquee()->setEnd(m_height); m_height = kMin(m_height, m_layer->marquee()->unfurlPos()); } // WinIE quirk: The block always fills the entire canvas in quirks mode. The always fills the // block in quirks mode. Only apply this quirk if the block is normal flow and no height // is specified. if (style()->htmlHacks() && style()->height().isVariable() && !isFloatingOrPositioned() && (isRoot() || isBody())) { int margins = collapsedMarginTop() + collapsedMarginBottom(); int visHeight = canvas()->view()->visibleHeight(); if (isRoot()) m_height = kMax(m_height, visHeight - margins); else m_height = kMax(m_height, visHeight - (margins + parent()->marginTop() + parent()->marginBottom() + parent()->borderTop() + parent()->borderBottom() + parent()->paddingTop() + parent()->paddingBottom())); } } int RenderBox::calcHeightUsing(const Length& h) { int height = -1; if (!h.isVariable()) { if (h.isFixed()) height = h.value; else if (h.isPercent()) height = calcPercentageHeight(h); if (height != -1) { height += borderTop() + paddingTop() + borderBottom() + paddingBottom(); return height; } } return height; } int RenderBox::calcPercentageHeight(const Length& height) { int result = -1; bool includeBorderPadding = isTable(); RenderBlock* cb = containingBlock(); if (style()->htmlHacks()) { // 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. for ( ; !cb->isCanvas() && !cb->isBody() && !cb->isTableCell() && !cb->isPositioned() && cb->style()->height().isVariable(); cb = cb->containingBlock()); } // 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()) { 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 = static_cast(cb); if (scrollsOverflow() && (!cell->style()->height().isVariable() || !cell->table()->style()->height().isVariable())) 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()->height().isFixed()) result = cb->style()->height().value; else if (cb->style()->height().isPercent()) // We need to recur and compute the percentage height for our containing block. result = cb->calcPercentageHeight(cb->style()->height()); else if (cb->isCanvas() || (cb->isBody() && style()->htmlHacks())) { // Don't allow this to affect the block' m_height member variable, since this // can get called while the block is still laying out its kids. int oldHeight = cb->height(); cb->calcHeight(); result = cb->contentHeight(); cb->setHeight(oldHeight); } if (result != -1) { result = height.width(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 -= (borderTop() + paddingTop() + borderBottom() + paddingBottom()); result = kMax(0, result); } } return result; } int RenderBox::calcReplacedWidth() const { int width = calcReplacedWidthUsing(Width); int minW = calcReplacedWidthUsing(MinWidth); int maxW = style()->maxWidth().value == UNDEFINED ? width : calcReplacedWidthUsing(MaxWidth); if (width > maxW) width = maxW; if (width < minW) width = minW; return width; } int RenderBox::calcReplacedWidthUsing(WidthType widthType) const { Length w; if (widthType == Width) w = style()->width(); else if (widthType == MinWidth) w = style()->minWidth(); else w = style()->maxWidth(); switch (w.type) { case Fixed: return w.value; case Percent: { const int cw = containingBlockWidth(); if (cw > 0) { int result = w.minWidth(cw); return result; } } // fall through default: return intrinsicWidth(); } } int RenderBox::calcReplacedHeight() const { int height = calcReplacedHeightUsing(Height); int minH = calcReplacedHeightUsing(MinHeight); int maxH = style()->maxHeight().value == UNDEFINED ? height : calcReplacedHeightUsing(MaxHeight); if (height > maxH) height = maxH; if (height < minH) height = minH; return height; } int RenderBox::calcReplacedHeightUsing(HeightType heightType) const { Length h; if (heightType == Height) h = style()->height(); else if (heightType == MinHeight) h = style()->minHeight(); else h = style()->maxHeight(); switch( h.type ) { case Percent: return availableHeightUsing(h); case Fixed: return h.value; default: return intrinsicHeight(); }; } int RenderBox::availableHeight() const { return availableHeightUsing(style()->height()); } int RenderBox::availableHeightUsing(const Length& h) const { if (h.isFixed()) return h.value; if (isCanvas()) return static_cast(this)->viewportHeight(); // 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.isVariable() || h.isPercent())) { return overrideSize() - (borderLeft()+borderRight()+paddingLeft()+paddingRight()); } if (h.isPercent()) return h.width(containingBlock()->availableHeight()); return containingBlock()->availableHeight(); } void RenderBox::calcVerticalMargins() { if( isTableCell() ) { // table margins are basically infinite m_marginTop = TABLECELLMARGIN; m_marginBottom = TABLECELLMARGIN; return; } Length tm = style()->marginTop(); Length bm = style()->marginBottom(); // margins are calculated with respect to the _width_ of // the containing block (8.3) int cw = containingBlock()->contentWidth(); m_marginTop = tm.minWidth(cw); m_marginBottom = bm.minWidth(cw); } void RenderBox::setStaticX(int staticX) { m_staticX = staticX; } void RenderBox::setStaticY(int staticY) { m_staticY = staticY; } void RenderBox::calcAbsoluteHorizontal() { const int AUTO = -666666; int l, r, cw; int pab = borderLeft()+ borderRight()+ paddingLeft()+ paddingRight(); l = r = AUTO; // We don't use containingBlock(), since we may be positioned by an enclosing relpositioned inline. RenderObject* cb = container(); cw = containingBlockWidth() + cb->paddingLeft() + cb->paddingRight(); if (!style()->left().isVariable()) l = style()->left().width(cw); if (!style()->right().isVariable()) r = style()->right().width(cw); int static_distance=0; if ((parent()->style()->direction()==LTR && (l == AUTO && r == AUTO)) || style()->left().isStatic()) { static_distance = m_staticX - cb->borderLeft(); // Should already have been set through layout of the parent(). RenderObject* po = parent(); for (; po && po != cb; po = po->parent()) static_distance += po->xPos(); if (l == AUTO || style()->left().isStatic()) l = static_distance; } else if ((parent()->style()->direction()==RTL && (l==AUTO && r==AUTO )) || style()->right().isStatic()) { RenderObject* po = parent(); static_distance = m_staticX - cb->borderLeft(); // Should already have been set through layout of the parent(). while (po && po!=containingBlock()) { static_distance+=po->xPos(); po=po->parent(); } if (r==AUTO || style()->right().isStatic()) r = static_distance; } calcAbsoluteHorizontalValues(Width, cb, cw, pab, static_distance, l, r, m_width, m_marginLeft, m_marginRight, m_x); // Avoid doing any work in the common case (where the values of min-width and max-width are their defaults). int minW = m_width, minML, minMR, minX; calcAbsoluteHorizontalValues(MinWidth, cb, cw, pab, static_distance, l, r, minW, minML, minMR, minX); int maxW = m_width, maxML, maxMR, maxX; if (style()->maxWidth().value != UNDEFINED) calcAbsoluteHorizontalValues(MaxWidth, cb, cw, static_distance, pab, l, r, maxW, maxML, maxMR, maxX); if (m_width > maxW) { m_width = maxW; m_marginLeft = maxML; m_marginRight = maxMR; m_x = maxX; } if (m_width < minW) { m_width = minW; m_marginLeft = minML; m_marginRight = minMR; m_x = minX; } } void RenderBox::calcAbsoluteHorizontalValues(WidthType widthType, RenderObject* cb, int cw, int pab, int static_distance, int l, int r, int& w, int& ml, int& mr, int& x) { const int AUTO = -666666; w = ml = mr = AUTO; if (!style()->marginLeft().isVariable()) ml = style()->marginLeft().width(cw); if (!style()->marginRight().isVariable()) mr = style()->marginRight().width(cw); Length width; if (widthType == Width) width = style()->width(); else if (widthType == MinWidth) width = style()->minWidth(); else width = style()->maxWidth(); if (!width.isVariable()) w = width.width(cw); else if (isReplaced()) w = intrinsicWidth(); if (l != AUTO && w != AUTO && r != AUTO) { // left, width, right all given, play with margins int ot = l + w + r + pab; if (ml==AUTO && mr==AUTO) { // both margins auto, solve for equality ml = (cw - ot)/2; mr = cw - ot - ml; } else if (ml==AUTO) // solve for left margin ml = cw - ot - mr; else if (mr==AUTO) // solve for right margin mr = cw - ot - ml; else { // overconstrained, solve according to dir if (style()->direction() == LTR) r = cw - ( l + w + ml + mr + pab); else l = cw - ( r + w + ml + mr + pab); } } else { // one or two of (left, width, right) missing, solve // auto margins are ignored if (ml==AUTO) ml = 0; if (mr==AUTO) mr = 0; //1. solve left & width. if (l == AUTO && w == AUTO && r != AUTO) { // From section 10.3.7 of the CSS2.1 specification. // "The shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width)." w = kMin(kMax(m_minWidth - pab, cw - (r + ml + mr + pab)), m_maxWidth - pab); l = cw - (r + w + ml + mr + pab); } else //2. solve left & right. use static positioning. if (l == AUTO && w != AUTO && r == AUTO) { if (style()->direction()==RTL) { r = static_distance; l = cw - (r + w + ml + mr + pab); } else { l = static_distance; r = cw - (l + w + ml + mr + pab); } } //3. solve width & right. else if (l != AUTO && w == AUTO && r == AUTO) { // From section 10.3.7 of the CSS2.1 specification. // "The shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width)." w = kMin(kMax(m_minWidth - pab, cw - (l + ml + mr + pab)), m_maxWidth - pab); r = cw - (l + w + ml + mr + pab); } else //4. solve left if (l==AUTO && w!=AUTO && r!=AUTO) l = cw - (r + w + ml + mr + pab); else //5. solve width if (l!=AUTO && w==AUTO && r!=AUTO) w = cw - (r + l + ml + mr + pab); else //6. solve right if (l!=AUTO && w!=AUTO && r==AUTO) r = cw - (l + w + ml + mr + pab); } w += pab; x = l + ml + cb->borderLeft(); } void RenderBox::calcAbsoluteVertical() { // css2 spec 10.6.4 & 10.6.5 // based on // http://www.w3.org/Style/css2-updates/REC-CSS2-19980512-errata // (actually updated 2000-10-24) // that introduces static-position value for top, left & right const int AUTO = -666666; int t, b, ch; t = b = AUTO; int pab = borderTop()+borderBottom()+paddingTop()+paddingBottom(); // We don't use containingBlock(), since we may be positioned by an enclosing relpositioned inline. RenderObject* cb = container(); if (cb->isRoot()) // Even in strict mode (where we don't grow the root to fill the viewport) other browsers // position as though the root fills the viewport. ch = cb->availableHeight(); else ch = cb->height() - cb->borderTop() - cb->borderBottom(); if (!style()->top().isVariable()) t = style()->top().width(ch); if (!style()->bottom().isVariable()) b = style()->bottom().width(ch); int h, mt, mb, y; calcAbsoluteVerticalValues(Height, cb, ch, pab, t, b, h, mt, mb, y); // Avoid doing any work in the common case (where the values of min-height and max-height are their defaults). int minH = h, minMT, minMB, minY; calcAbsoluteVerticalValues(MinHeight, cb, ch, pab, t, b, minH, minMT, minMB, minY); int maxH = h, maxMT, maxMB, maxY; if (style()->maxHeight().value != UNDEFINED) calcAbsoluteVerticalValues(MaxHeight, cb, ch, pab, t, b, maxH, maxMT, maxMB, maxY); if (h > maxH) { h = maxH; mt = maxMT; mb = maxMB; y = maxY; } if (h < minH) { h = minH; mt = minMT; mb = minMB; y = minY; } // If our natural height exceeds the new height once we've set it, then we need to make sure to update // overflow to track the spillout. if (m_height > h) setOverflowHeight(m_height); // Set our final values. m_height = h; m_marginTop = mt; m_marginBottom = mb; m_y = y; } void RenderBox::calcAbsoluteVerticalValues(HeightType heightType, RenderObject* cb, int ch, int pab, int t, int b, int& h, int& mt, int& mb, int& y) { const int AUTO = -666666; h = mt = mb = AUTO; if (!style()->marginTop().isVariable()) mt = style()->marginTop().width(ch); if (!style()->marginBottom().isVariable()) mb = style()->marginBottom().width(ch); Length height; if (heightType == Height) height = style()->height(); else if (heightType == MinHeight) height = style()->minHeight(); else height = style()->maxHeight(); int ourHeight = m_height; if (isTable() && height.isVariable()) // Height is never unsolved for tables. "auto" means shrink to fit. Use our // height instead. h = ourHeight - pab; else if (!height.isVariable()) { h = height.width(ch); if (ourHeight - pab > h) ourHeight = h + pab; } else if (isReplaced()) h = intrinsicHeight(); int static_top=0; if ((t == AUTO && b == AUTO) || style()->top().isStatic()) { // calc hypothetical location in the normal flow // used for 1) top=static-position // 2) top, bottom, height are all auto -> calc top -> 3. // 3) precalc for case 2 below static_top = m_staticY - cb->borderTop(); // Should already have been set through layout of the parent(). RenderObject* po = parent(); for (; po && po != cb; po = po->parent()) static_top += po->yPos(); if (h == AUTO || style()->top().isStatic()) t = static_top; } if (t != AUTO && h != AUTO && b != AUTO) { // top, height, bottom all given, play with margins int ot = h + t + b + pab; if (mt == AUTO && mb == AUTO) { // both margins auto, solve for equality mt = (ch - ot)/2; mb = ch - ot - mt; } else if (mt==AUTO) // solve for top margin mt = ch - ot - mb; else if (mb==AUTO) // solve for bottom margin mb = ch - ot - mt; else // overconstrained, solve for bottom b = ch - (h + t + mt + mb + pab); } else { // one or two of (top, height, bottom) missing, solve // auto margins are ignored if (mt == AUTO) mt = 0; if (mb == AUTO) mb = 0; //1. solve top & height. use content height. if (t == AUTO && h == AUTO && b != AUTO) { h = ourHeight - pab; t = ch - (h + b + mt + mb + pab); } else if (t == AUTO && h != AUTO && b == AUTO) //2. solve top & bottom. use static positioning. { t = static_top; b = ch - (h + t + mt + mb + pab); } else if (t != AUTO && h == AUTO && b == AUTO) //3. solve height & bottom. use content height. { h = ourHeight - pab; b = ch - (h + t + mt + mb + pab); } else //4. solve top if (t == AUTO && h != AUTO && b != AUTO) t = ch - (h + b + mt + mb + pab); else //5. solve height if (t != AUTO && h == AUTO && b != AUTO) h = ch - (t + b + mt + mb + pab); else //6. solve bottom if (t != AUTO && h != AUTO && b == AUTO) b = ch - (h + t + mt + mb + pab); } if (ourHeight < h + pab) //content must still fit ourHeight = h + pab; if (hasOverflowClip() && ourHeight > h + pab) ourHeight = h + pab; // Do not allow the height to be negative. This can happen when someone specifies both top and bottom // but the containing block height is less than top, e.g., top:20px, bottom:0, containing block height 16. ourHeight = kMax(0, ourHeight); h = ourHeight; y = t + mt + cb->borderTop(); } QRect RenderBox::caretRect(int offset, EAffinity affinity, int *extraWidthToEndOfLine) { // FIXME: Is it OK to check only first child instead of picking // right child based on offset? Is it OK to pass the same offset // along to the child instead of offset 0 or whatever? // propagate it downwards to its children, someone will feel responsible RenderObject *child = firstChild(); if (child) { QRect result = child->caretRect(offset, affinity, extraWidthToEndOfLine); // FIXME: in-band signalling! if (result.isEmpty()) return result; } int _x, _y, height; // if not, use the extents of this box // offset 0 means left, offset 1 means right _x = xPos() + (offset == 0 ? 0 : m_width); InlineBox *box = inlineBoxWrapper(); if (box) { height = box->root()->bottomOverflow() - box->root()->topOverflow(); _y = box->root()->topOverflow(); } else { _y = yPos(); height = m_height; } // 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: // 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()->fontMetrics().height(); if (fontHeight > height || !isReplaced()) height = fontHeight; int absx, absy; RenderObject *cb = containingBlock(); if (cb && cb != this && cb->absolutePosition(absx,absy)) { _x += absx; _y += absy; } else { // we don't know our absolute position, and there is no point returning // just a relative one return QRect(); } if (extraWidthToEndOfLine) *extraWidthToEndOfLine = m_width - _x; return QRect(_x, _y, 1, height); } int RenderBox::lowestPosition(bool includeOverflowInterior, bool includeSelf) const { return includeSelf ? m_height : 0; } int RenderBox::rightmostPosition(bool includeOverflowInterior, bool includeSelf) const { return includeSelf ? m_width : 0; } int RenderBox::leftmostPosition(bool includeOverflowInterior, bool includeSelf) const { return includeSelf ? 0 : m_width; } #undef DEBUG_LAYOUT