/** * This file is part of the DOM implementation for KDE. * * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2002-2003 Apple Computer, Inc. * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) * (C) 2006 Samuel Weinig (sam.weinig@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. * */ // ------------------------------------------------------------------------- //#define DEBUG_LAYOUT //#define CLIP_DEBUG #include #include "misc/loader.h" #include "rendering/render_replaced.h" #include "rendering/render_canvas.h" #include "rendering/render_table.h" #include "rendering/render_inline.h" #include "rendering/render_block.h" #include "rendering/render_line.h" #include "rendering/render_layer.h" #include "misc/htmlhashes.h" #include "xml/dom_nodeimpl.h" #include "xml/dom_docimpl.h" #include "html/html_elementimpl.h" #include #include #include #include using namespace DOM; using namespace khtml; #define TABLECELLMARGIN -0x4000 RenderBox::RenderBox(DOM::NodeImpl* node) : RenderContainer(node) { m_minWidth = -1; m_maxWidth = -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_placeHolderBox = 0; m_layer = 0; } RenderBlock* RenderBox::createAnonymousBlock() { RenderStyle *newStyle = new RenderStyle(); newStyle->inheritFrom(style()); newStyle->setDisplay(BLOCK); RenderBlock *newBox = new (renderArena()) RenderBlock(document() /* anonymous*/); newBox->setStyle(newStyle); return newBox; } void RenderBox::restructureParentFlow() { if (!parent() || parent()->childrenInline() == isInline()) return; // 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. if (!isInline()) { if (parent()->isRenderInline()) { // We have to split the parent flow. RenderInline* parentInline = static_cast(parent()); RenderBlock* newBox = parentInline->createAnonymousBlock(); RenderFlow* oldContinuation = parent()->continuation(); parentInline->setContinuation(newBox); RenderObject* beforeChild = nextSibling(); parent()->removeChildNode(this); parentInline->splitFlow(beforeChild, newBox, this, oldContinuation); } else if (parent()->isRenderBlock()) static_cast(parent())->makeChildrenNonInline(); } else { // An anonymous block must be made to wrap this inline. RenderBlock* box = createAnonymousBlock(); parent()->insertChildNode(box, this); box->appendChildNode(parent()->removeChildNode(this)); } } static inline bool overflowAppliesTo(RenderObject* o) { // css 2.1-11.1.1 // 1) overflow only applies to non-replaced block-level elements, table cells, and inline-block elements if (o->isRenderBlock() || o->isTableRow() || o->isTableSection()) // 2) overflow on root applies to the viewport (cf. KHTMLView::layout) if (!o->isRoot()) // 3) overflow on body may apply to the viewport... if (!o->isBody() // ...but only for HTML documents... || !o->document()->isHTMLDocument() // ...and only when the root has a visible overflow || !o->document()->documentElement()->renderer() || !o->document()->documentElement()->renderer()->style() || o->document()->documentElement()->renderer()->style()->hidesOverflow()) return true; return false; } void RenderBox::setStyle(RenderStyle *_style) { bool affectsParent = style() && isFloatingOrPositioned() && (!_style->isFloating() && _style->position() != ABSOLUTE && _style->position() != FIXED) && parent() && (parent()->isBlockFlow() || parent()->isInlineFlow()); RenderContainer::setStyle(_style); // The root always paints its background/border. if (isRoot()) setShouldPaintBackgroundOrBorder(true); switch(_style->display()) { case INLINE: case INLINE_BLOCK: case INLINE_TABLE: setInline(true); break; case RUN_IN: if (isInline() && parent() && parent()->childrenInline()) break; default: setInline(false); } switch(_style->position()) { case ABSOLUTE: case FIXED: setPositioned(true); break; default: setPositioned(false); if( !isTableCell() && _style->isFloating() ) setFloating(true); if( _style->position() == RELATIVE ) setRelPositioned(true); } if (overflowAppliesTo(this) && _style->hidesOverflow()) setHasOverflowClip(); if (requiresLayer()) { if (!m_layer) { m_layer = new (renderArena()) RenderLayer(this); m_layer->insertOnlyThisLayer(); if (parent() && containingBlock()) m_layer->updateLayerPosition(); } } else if (m_layer && !isCanvas()) { m_layer->removeOnlyThisLayer(); m_layer = 0; } if (m_layer) m_layer->styleChanged(); if (style()->outlineWidth() > 0 && style()->outlineSize() > maximalOutlineSize(PaintActionOutline)) static_cast(document()->renderer())->setMaximalOutlineSize(style()->outlineSize()); if (affectsParent) restructureParentFlow(); } RenderBox::~RenderBox() { //kdDebug( 6040 ) << "Element destructor: this=" << nodeName().string() << endl; } void RenderBox::detach() { RenderLayer* layer = m_layer; RenderArena* arena = renderArena(); RenderContainer::detach(); if (layer) layer->detach(arena); } InlineBox* RenderBox::createInlineBox(bool /*makePlaceHolderBox*/, bool /*isRootLineBox*/) { if (m_placeHolderBox) m_placeHolderBox->detach(renderArena()); return (m_placeHolderBox = new (renderArena()) InlineBox(this)); } void RenderBox::deleteInlineBoxes(RenderArena* arena) { if (m_placeHolderBox) { m_placeHolderBox->detach( arena ? arena : renderArena() ); m_placeHolderBox = 0; } } short RenderBox::contentWidth() const { short w = m_width - style()->borderLeftWidth() - style()->borderRightWidth(); w -= paddingLeft() + paddingRight(); if (m_layer && scrollsOverflowY()) w -= m_layer->verticalScrollbarWidth(); //kdDebug( 6040 ) << "RenderBox::contentWidth(2) = " << w << endl; return w; } int RenderBox::contentHeight() const { int h = m_height - style()->borderTopWidth() - style()->borderBottomWidth(); h -= paddingTop() + paddingBottom(); if (m_layer && scrollsOverflowX()) h -= m_layer->horizontalScrollbarHeight(); return h; } void RenderBox::setPos( int xPos, int yPos ) { m_x = xPos; m_y = yPos; } short RenderBox::width() const { return m_width; } int RenderBox::height() const { return m_height; } void RenderBox::setWidth( int width ) { m_width = width; } void RenderBox::setHeight( int height ) { m_height = height; } int RenderBox::calcBoxHeight(int h) const { if (style()->boxSizing() == CONTENT_BOX) h += borderTop() + borderBottom() + paddingTop() + paddingBottom(); return h; } int RenderBox::calcBoxWidth(int w) const { if (style()->boxSizing() == CONTENT_BOX) w += borderLeft() + borderRight() + paddingLeft() + paddingRight(); return w; } int RenderBox::calcContentHeight(int h) const { if (style()->boxSizing() == BORDER_BOX) h -= borderTop() + borderBottom() + paddingTop() + paddingBottom(); return kMax(0, h); } int RenderBox::calcContentWidth(int w) const { if (style()->boxSizing() == BORDER_BOX) w -= borderLeft() + borderRight() + paddingLeft() + paddingRight(); return kMax(0, w); } // --------------------- painting stuff ------------------------------- void RenderBox::paint(PaintInfo& i, int _tx, int _ty) { _tx += m_x; _ty += m_y; if (hasOverflowClip() && m_layer) m_layer->subtractScrollOffset(_tx, _ty); // default implementation. Just pass things through to the children for(RenderObject* child = firstChild(); child; child = child->nextSibling()) child->paint(i, _tx, _ty); } void RenderBox::paintRootBoxDecorations(PaintInfo& paintInfo, int _tx, int _ty) { //kdDebug( 6040 ) << renderName() << "::paintRootBoxDecorations()" << _tx << "/" << _ty << endl; const BackgroundLayer* bgLayer = style()->backgroundLayers(); TQColor 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(); } } if( !bgColor.isValid() && canvas()->view()) bgColor = canvas()->view()->palette().active().color(TQColorGroup::Base); int w = width(); int h = height(); // kdDebug(0) << "width = " << w <view()) { rw = canvas()->view()->contentsWidth(); rh = canvas()->view()->contentsHeight(); } else { rw = canvas()->docWidth(); rh = canvas()->docHeight(); } // kdDebug(0) << "rw = " << rw <hasBorder()) paintBorder( paintInfo.p, _tx, _ty, w, h, style() ); } void RenderBox::paintBoxDecorations(PaintInfo& paintInfo, int _tx, int _ty) { //kdDebug( 6040 ) << renderName() << "::paintDecorations()" << endl; if(isRoot()) return paintRootBoxDecorations(paintInfo, _tx, _ty); int w = width(); int h = height() + borderTopExtra() + borderBottomExtra(); _ty -= borderTopExtra(); int my = kMax(_ty,paintInfo.r.y()); int end = kMin( paintInfo.r.y() + paintInfo.r.height(), _ty + h ); int mh = end - my; // 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(paintInfo.p, style()->backgroundColor(), style()->backgroundLayers(), my, mh, _tx, _ty, w, h); if(style()->hasBorder()) { paintBorder(paintInfo.p, _tx, _ty, w, h, style()); } } void RenderBox::paintBackgrounds(TQPainter *p, const TQColor& 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(TQPainter *p, const TQColor& 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(), paddingLeft(), paddingRight()); } static void calculateBackgroundSize(const BackgroundLayer* bgLayer, int& scaledWidth, int& scaledHeight) { CachedImage* bg = bgLayer->backgroundImage(); if (bgLayer->isBackgroundSizeSet()) { Length bgWidth = bgLayer->backgroundSize().width; Length bgHeight = bgLayer->backgroundSize().height; if (bgWidth.isPercent()) scaledWidth = scaledWidth * bgWidth.value() / 100; else if (bgWidth.isFixed()) scaledWidth = bgWidth.value(); else if (bgWidth.isVariable()) { // If the width is auto and the height is not, we have to use the appropriate // scale to maintain our aspect ratio. if (bgHeight.isPercent()) { int scaledH = scaledHeight * bgHeight.value() / 100; scaledWidth = bg->pixmap_size().width() * scaledH / bg->pixmap_size().height(); } else if (bgHeight.isFixed()) scaledWidth = bg->pixmap_size().width() * bgHeight.value() / bg->pixmap_size().height(); } if (bgHeight.isPercent()) scaledHeight = scaledHeight * bgHeight.value() / 100; else if (bgHeight.isFixed()) scaledHeight = bgHeight.value(); else if (bgHeight.isVariable()) { // If the height is auto and the width is not, we have to use the appropriate // scale to maintain our aspect ratio. if (bgWidth.isPercent()) scaledHeight = bg->pixmap_size().height() * scaledWidth / bg->pixmap_size().width(); else if (bgWidth.isFixed()) scaledHeight = bg->pixmap_size().height() * bgWidth.value() / bg->pixmap_size().width(); else if (bgWidth.isVariable()) { // If both width and height are auto, we just want to use the image's // intrinsic size. scaledWidth = bg->pixmap_size().width(); scaledHeight = bg->pixmap_size().height(); } } } else { scaledWidth = bg->pixmap_size().width(); scaledHeight = bg->pixmap_size().height(); } } void RenderBox::paintBackgroundExtended(TQPainter *p, const TQColor &c, const BackgroundLayer* bgLayer, int clipy, int cliph, int _tx, int _ty, int w, int h, int bleft, int bright, int pleft, int pright) { if ( cliph < 0 ) return; if (bgLayer->backgroundClip() != BGBORDER) { // Clip to the padding or content boxes as necessary. bool includePadding = bgLayer->backgroundClip() == BGCONTENT; 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); p->save(); p->setClipRect(TQRect(x, y, width, height), TQPainter::CoordPainter); } CachedImage* bg = bgLayer->backgroundImage(); bool shouldPaintBackgroundImage = bg && bg->pixmap_size() == bg->valid_rect().size() && !bg->isTransparent() && !bg->isErrorImage(); TQColor bgColor = c; // Paint the color first underneath all images. if (!bgLayer->next() && bgColor.isValid() && tqAlpha(bgColor.rgb()) > 0) 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 scaledImageWidth, scaledImageHeight; // CSS2 chapter 14.2.1 if (bgLayer->backgroundAttachment()) { //scroll int hpab = 0, vpab = 0, left = 0, top = 0; // Init to 0 for background-origin of 'border' if (bgLayer->backgroundOrigin() != BGBORDER) { hpab += bleft + bright; vpab += borderTop() + borderBottom(); left += bleft; top += borderTop(); if (bgLayer->backgroundOrigin() == BGCONTENT) { hpab += pleft + pright; vpab += paddingTop() + paddingBottom(); left += pleft; top += paddingTop(); } } int pw = w - hpab; int ph = h - vpab; scaledImageWidth = pw; scaledImageHeight = ph; calculateBackgroundSize(bgLayer, scaledImageWidth, scaledImageHeight); EBackgroundRepeat bgr = bgLayer->backgroundRepeat(); if (bgr == NO_REPEAT || bgr == REPEAT_Y) { cw = scaledImageWidth; int xPosition = bgLayer->backgroundXPosition().minWidth(pw-scaledImageWidth); if ( xPosition >= 0 ) { cx = _tx + xPosition; cw = kMin(scaledImageWidth, pw - xPosition); } else { cx = _tx; if (scaledImageWidth > 0) { sx = -xPosition; cw = kMin(scaledImageWidth+xPosition, pw); } } cx += left; } else { // repeat over x cw = w; cx = _tx; if (scaledImageWidth > 0) { int xPosition = bgLayer->backgroundXPosition().minWidth(pw-scaledImageWidth); sx = scaledImageWidth - (xPosition % scaledImageWidth); sx -= left % scaledImageWidth; } } if (bgr == NO_REPEAT || bgr == REPEAT_X) { ch = scaledImageHeight; int yPosition = bgLayer->backgroundYPosition().minWidth(ph - scaledImageHeight); if ( yPosition >= 0 ) { cy = _ty + yPosition; ch = kMin(ch, ph - yPosition); } else { cy = _ty; if (scaledImageHeight > 0) { sy = -yPosition; ch = kMin(scaledImageHeight+yPosition, ph); } } cy += top; } else { // repeat over y ch = h; cy = _ty; if (scaledImageHeight > 0) { int yPosition = bgLayer->backgroundYPosition().minWidth(ph - scaledImageHeight); sy = scaledImageHeight - (yPosition % scaledImageHeight); sy -= top % scaledImageHeight; } } if (layer()) layer()->scrollOffset(sx, sy); } else { //fixed TQRect vr = viewRect(); int pw = vr.width(); int ph = vr.height(); scaledImageWidth = pw; scaledImageHeight = ph; calculateBackgroundSize(bgLayer, scaledImageWidth, scaledImageHeight); EBackgroundRepeat bgr = bgLayer->backgroundRepeat(); int xPosition = bgLayer->backgroundXPosition().minWidth(pw-scaledImageWidth); if (bgr == NO_REPEAT || bgr == REPEAT_Y) { cw = kMin(scaledImageWidth, pw - xPosition); cx = vr.x() + xPosition; } else { cw = pw; cx = vr.x(); if (scaledImageWidth > 0) sx = scaledImageWidth - xPosition % scaledImageWidth; } int yPosition = bgLayer->backgroundYPosition().minWidth(ph-scaledImageHeight); if (bgr == NO_REPEAT || bgr == REPEAT_X) { ch = kMin(scaledImageHeight, ph - yPosition); cy = vr.y() + yPosition; } else { ch = ph; cy = vr.y(); if (scaledImageHeight > 0) sy = scaledImageHeight - yPosition % scaledImageHeight; } TQRect fix(cx, cy, cw, ch); TQRect ele(_tx, _ty, w, h); TQRect b = fix.intersect(ele); //kdDebug() <<" ele is " << ele << " b is " << b << " fix is " << fix << endl; sx+=b.x()-cx; sy+=b.y()-cy; cx=b.x();cy=b.y();cw=b.width();ch=b.height(); } // restrict painting to tqrepaint-clip if (cy < clipy) { ch -= (clipy - cy); sy += (clipy - cy); cy = clipy; } ch = kMin(ch, cliph); // kdDebug() << " clipy, cliph: " << clipy << ", " << cliph << endl; // kdDebug() << " drawTiledPixmap(" << cx << ", " << cy << ", " << cw << ", " << ch << ", " << sx << ", " << sy << ")" << endl; if (cw>0 && ch>0) p->drawTiledPixmap(cx, cy, cw, ch, bg->tiled_pixmap(c, scaledImageWidth, scaledImageHeight), sx, sy); } if (bgLayer->backgroundClip() != BGBORDER) p->restore(); // Undo the background clip } void RenderBox::outlineBox(TQPainter *p, int _tx, int _ty, const char *color) { p->setPen(TQPen(TQColor(color), 1, Qt::DotLine)); p->setBrush( Qt::NoBrush ); p->drawRect(_tx, _ty, m_width, m_height); } TQRect RenderBox::getOverflowClipRect(int tx, int ty) { // XXX When overflow-clip (CSS3) is implemented, we'll obtain the property // here. int bl=borderLeft(),bt=borderTop(),bb=borderBottom(),br=borderRight(); int clipx = tx+bl; int clipy = ty+bt; int clipw = m_width-bl-br; int cliph = m_height-bt-bb+borderTopExtra()+borderBottomExtra(); // Substract out scrollbars if we have them. if (m_layer) { clipw -= m_layer->verticalScrollbarWidth(); cliph -= m_layer->horizontalScrollbarHeight(); } return TQRect(clipx,clipy,clipw,cliph); } TQRect RenderBox::getClipRect(int tx, int ty) { int bl=borderLeft(),bt=borderTop(),bb=borderBottom(),br=borderRight(); // ### what about paddings? int clipw = m_width-bl-br; int cliph = m_height-bt-bb; bool rtl = (style()->direction() == RTL); int clipleft = 0; int clipright = clipw; int cliptop = 0; int clipbottom = cliph; if ( style()->hasClip() && style()->position() == ABSOLUTE ) { // the only case we use the clip property according to CSS 2.1 if (!style()->clipLeft().isVariable()) { int c = style()->clipLeft().width(clipw); if ( rtl ) clipleft = clipw - c; else clipleft = c; } if (!style()->clipRight().isVariable()) { int w = style()->clipRight().width(clipw); if ( rtl ) { clipright = clipw - w; } else { clipright = w; } } if (!style()->clipTop().isVariable()) cliptop = style()->clipTop().width(cliph); if (!style()->clipBottom().isVariable()) clipbottom = style()->clipBottom().width(cliph); } int clipx = tx + clipleft; int clipy = ty + cliptop; clipw = clipright-clipleft; cliph = clipbottom-cliptop; //kdDebug( 6040 ) << "setting clip("<view()) { if (canvas()->pagedMode()) return canvas()->width(); else return canvas()->view()->visibleWidth(); } RenderBlock* cb = containingBlock(); if (isRenderBlock() && cb->isTable() && static_cast(cb)->caption() == this) { //captions are not affected by table border or padding return cb->width(); } if (usesLineWidth()) return cb->lineWidth(m_y); else return cb->contentWidth(); } bool RenderBox::absolutePosition(int &_xPos, int &_yPos, bool f) const { if ( style()->position() == FIXED ) f = true; RenderObject *o = container(); if( o && o->absolutePosition(_xPos, _yPos, f)) { if ( o->layer() ) { if (o->hasOverflowClip()) o->layer()->subtractScrollOffset( _xPos, _yPos ); if (isPositioned()) o->layer()->checkInlineRelOffset(this, _xPos, _yPos); } if(!isInline() || isReplaced()) { _xPos += xPos(), _yPos += yPos(); } if(isRelPositioned()) relativePositionOffset(_xPos, _yPos); return true; } else { _xPos = 0; _yPos = 0; return false; } } 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(); } } else if (isReplaced()) setPos( box->xPos(), box->yPos() ); } void RenderBox::tqrepaint(Priority prior) { int ow = style() ? style()->outlineSize() : 0; if( isInline() && !isReplaced() ) { RenderObject* p = parent(); Q_ASSERT(p); while( p->isInline() && !p->isReplaced() ) p = p->parent(); int xoff = p->hasOverflowClip() ? 0 : p->overflowLeft(); int yoff = p->hasOverflowClip() ? 0 : p->overflowTop(); p->tqrepaintRectangle( -ow + xoff, -ow + yoff, p->effectiveWidth()+ow*2, p->effectiveHeight()+ow*2, prior); } else { int xoff = hasOverflowClip() ? 0 : overflowLeft(); int yoff = hasOverflowClip() ? 0 : overflowTop(); tqrepaintRectangle( -ow + xoff, -ow + yoff, effectiveWidth()+ow*2, effectiveHeight()+ow*2, prior); } } void RenderBox::tqrepaintRectangle(int x, int y, int w, int h, Priority p, bool f) { x += m_x; 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) relativePositionOffset(x,y); if (style()->position() == FIXED) f=true; // kdDebug( 6040 ) << "RenderBox(" <layer()) { if (o->style()->hidesOverflow() && o->layer() && !o->isInlineFlow()) o->layer()->subtractScrollOffset(x,y); // For overflow:auto/scroll/hidden. if (style()->position() == ABSOLUTE) o->layer()->checkInlineRelOffset(this,x,y); } o->tqrepaintRectangle(x, y, w, h, p, f); } } void RenderBox::relativePositionOffset(int &tx, int &ty) const { 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("<width(); Length ml = style()->marginLeft(); Length mr = style()->marginRight(); 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 = calcBoxWidth(w.width(cw)); m_width = KMAX(m_width, m_minWidth); } return; } else { LengthType widthType, minWidthType, maxWidthType; if (treatAsReplaced) { m_width = calcBoxWidth(w.width(cw)); 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()) { if (containingBlock()->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; // size to max width? if (sizesToMaxWidth()) { width = KMAX(width, (int)m_minWidth); width = KMIN(width, (int)m_maxWidth); } } else { width = calcBoxWidth(w.width(cw)); } 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.isVariable() && mr.isVariable() && m_widthstyle()->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.isVariable() && m_widthstyle()->direction() == RTL && containingBlock()->style()->textAlign() == KHTML_LEFT)) { m_marginLeft = ml.width(cw); m_marginRight = cw - m_width - m_marginLeft; } else if ( (ml.isVariable() && m_widthstyle()->direction() == LTR && containingBlock()->style()->textAlign() == KHTML_RIGHT)) { m_marginRight = mr.width(cw); m_marginLeft = cw - m_width - m_marginRight; } else { // this makes auto margins 0 if we failed a m_widthheight(); checkMinMaxHeight = true; } 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 = calcBoxHeight(h.value()); } if (heightoverflowX() == 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()); } } 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 = calcBoxHeight(height); return height; } } return height; } int RenderBox::calcImplicitHeight() const { assert(hasImplicitHeight()); RenderBlock* cb = containingBlock(); // padding-box height int ch = cb->height() - cb->borderTop() + cb->borderBottom(); int top = style()->top().width(ch); int bottom = style()->bottom().width(ch); return ch - top - bottom; } int RenderBox::calcPercentageHeight(const Length& height, bool treatAsReplaced) const { int result = -1; RenderBlock* cb = containingBlock(); // In quirk mode, table cells violate what the CSS spec says to do with heights. if (cb->isTableCell() && style()->htmlHacks()) { result = static_cast(cb)->cellPercentageHeight(); } // Otherwise we only use our percentage height if our containing block had a specified // height. else if (cb->style()->height().isFixed()) result = cb->calcContentHeight(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(), treatAsReplaced); if (result != -1) result = cb->calcContentHeight(result); } else if (cb->isCanvas()) { if (!canvas()->pagedMode()) result = static_cast(cb)->viewportHeight(); else result = static_cast(cb)->height(); result -= cb->style()->borderTopWidth() - cb->style()->borderBottomWidth(); result -= cb->paddingTop() + cb->paddingBottom(); } else if (cb->isBody() && style()->htmlHacks() && cb->style()->height().isVariable() && !cb->isFloatingOrPositioned()) { int margins = cb->collapsedMarginTop() + cb->collapsedMarginBottom(); int visHeight = canvas()->viewportHeight(); RenderObject* p = cb->parent(); result = visHeight - (margins + p->marginTop() + p->marginBottom() + p->borderTop() + p->borderBottom() + p->paddingTop() + p->paddingBottom()); } else if (cb->isRoot() && style()->htmlHacks() && cb->style()->height().isVariable()) { int visHeight = canvas()->viewportHeight(); result = visHeight - (marginTop() + marginBottom() + borderTop() + borderBottom() + paddingTop() + paddingBottom()); } else if (cb->isAnonymousBlock() || treatAsReplaced && style()->htmlHacks()) { // IE quirk. result = cb->calcPercentageHeight(cb->style()->height(), treatAsReplaced); } else if (cb->hasImplicitHeight()) { result = cb->calcImplicitHeight(); } if (result != -1) { result = height.width(result); if (cb->isTableCell() && style()->boxSizing() != BORDER_BOX) { result -= (borderTop() + paddingTop() + borderBottom() + paddingBottom()); result = kMax(0, result); } } return result; } short 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 Fixed: return h.value(); case Percent: { int th = calcPercentageHeight(h, true); if (th != -1) return th; // fall through } default: return intrinsicHeight(); }; } int RenderBox::availableHeight() const { return availableHeightUsing(style()->height()); } int RenderBox::availableHeightUsing(const Length& h) const { if (h.isFixed()) return calcContentHeight(h.value()); if (isCanvas()) if (static_cast(this)->pagedMode()) return static_cast(this)->pageHeight(); else 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())) { const RenderTableCell* tableCell = static_cast(this); return tableCell->cellPercentageHeight() - (borderTop()+borderBottom()+paddingTop()+paddingBottom()); } if (h.isPercent()) return calcContentHeight(h.width(containingBlock()->availableHeight())); // Check for implicit height if (hasImplicitHeight()) return calcImplicitHeight(); return containingBlock()->availableHeight(); } int RenderBox::availableWidth() const { return availableWidthUsing(style()->width()); } int RenderBox::availableWidthUsing(const Length& w) const { if (w.isFixed()) return calcContentWidth(w.value()); if (isCanvas()) return static_cast(this)->viewportWidth(); if (w.isPercent()) return calcContentWidth(w.width(containingBlock()->availableWidth())); return containingBlock()->availableWidth(); } 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(short staticX) { m_staticX = staticX; } void RenderBox::setStaticY(int staticY) { m_staticY = staticY; } void RenderBox::calcAbsoluteHorizontal() { if (isReplaced()) { calcAbsoluteHorizontalReplaced(); 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 calcWidth() // and calcHorizontalMargins()). For now we are using the parent for quirks // mode and the containing block for strict mode. // FIXME 2: Can perhaps optimize out cases when max-width/min-width are greater // than or less than the computed m_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" // // (block-style-comments in this function and in calcAbsoluteHorizontalValues() // correspond to text from the spec) // We don't use containingBlock(), since we may be positioned by an enclosing // relative positioned inline. const RenderObject* containerBlock = container(); // FIXME: This is incorrect for cases where the container block is a relatively // positioned inline. const int containerWidth = containingBlockWidth() + containerBlock->paddingLeft() + containerBlock->paddingRight(); // To match WinIE, in quirks mode use the parent's 'direction' property // instead of the the container block's. EDirection containerDirection = (style()->htmlHacks()) ? parent()->style()->direction() : containerBlock->style()->direction(); const int bordersPlusPadding = borderLeft() + borderRight() + paddingLeft() + paddingRight(); 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. \*---------------------------------------------------------------------------*/ // Calculate the static distance if needed. if (left.isVariable() && right.isVariable()) { if (containerDirection == LTR) { // 'm_staticX' should already have been set through layout of the parent. int staticPosition = m_staticX - containerBlock->borderLeft(); for (RenderObject* po = parent(); po && po != containerBlock; po = po->parent()) staticPosition += po->xPos(); left = Length(staticPosition, Fixed); } else { RenderObject* po = parent(); // 'm_staticX' should already have been set through layout of the parent. int staticPosition = m_staticX + containerWidth + containerBlock->borderRight() - po->width(); for (; po && po != containerBlock; po = po->parent()) staticPosition -= po->xPos(); right = Length(staticPosition, Fixed); } } // Calculate constraint equation values for 'width' case. calcAbsoluteHorizontalValues(style()->width(), containerBlock, containerDirection, containerWidth, bordersPlusPadding, left, right, marginLeft, marginRight, m_width, m_marginLeft, m_marginRight, m_x); // Calculate constraint equation values for 'max-width' case.calcContentWidth(width.width(containerWidth)); if (style()->maxWidth().value() != UNDEFINED) { short maxWidth; short maxMarginLeft; short maxMarginRight; short maxXPos; calcAbsoluteHorizontalValues(style()->maxWidth(), containerBlock, containerDirection, containerWidth, bordersPlusPadding, left, right, marginLeft, marginRight, maxWidth, maxMarginLeft, maxMarginRight, maxXPos); if (m_width > maxWidth) { m_width = maxWidth; m_marginLeft = maxMarginLeft; m_marginRight = maxMarginRight; m_x = maxXPos; } } // Calculate constraint equation values for 'min-width' case. if (style()->minWidth().value()) { short minWidth; short minMarginLeft; short minMarginRight; short minXPos; calcAbsoluteHorizontalValues(style()->minWidth(), containerBlock, containerDirection, containerWidth, bordersPlusPadding, left, right, marginLeft, marginRight, minWidth, minMarginLeft, minMarginRight, minXPos); if (m_width < minWidth) { m_width = minWidth; m_marginLeft = minMarginLeft; m_marginRight = minMarginRight; m_x = minXPos; } } // Put m_width into correct form. m_width += bordersPlusPadding; } void RenderBox::calcAbsoluteHorizontalValues(Length width, const RenderObject* containerBlock, EDirection containerDirection, const int containerWidth, const int bordersPlusPadding, const Length left, const Length right, const Length marginLeft, const Length marginRight, short& widthValue, short& marginLeftValue, short& marginRightValue, short& xPos) { // 'left' and 'right' cannot both be 'auto' because one would of been // converted to the static postion already assert(!(left.isVariable() && right.isVariable())); int leftValue = 0; bool widthIsAuto = width.isVariable(); bool leftIsAuto = left.isVariable(); bool rightIsAuto = right.isVariable(); 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.width(containerWidth); widthValue = calcContentWidth(width.width(containerWidth)); const int availableSpace = containerWidth - (leftValue + widthValue + right.width(containerWidth) + bordersPlusPadding); // Margins are now the only unknown if (marginLeft.isVariable() && marginRight.isVariable()) { // Both margins auto, solve for equality if (availableSpace >= 0) { marginLeftValue = availableSpace / 2; // split the diference 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.isVariable()) { // Solve for left margin marginRightValue = marginRight.width(containerWidth); marginLeftValue = availableSpace - marginRightValue; } else if (marginRight.isVariable()) { // Solve for right margin marginLeftValue = marginLeft.width(containerWidth); marginRightValue = availableSpace - marginLeftValue; } else { // Over-constrained, solve for left if direction is RTL marginLeftValue = marginLeft.width(containerWidth); marginRightValue = marginRight.width(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: * kMin(kMax(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.minWidth(containerWidth); marginRightValue = marginRight.minWidth(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.width(containerWidth); // FIXME: would it be better to have shrink-to-fit in one step? int preferredWidth = m_maxWidth - bordersPlusPadding; int preferredMinWidth = m_minWidth - bordersPlusPadding; int availableWidth = availableSpace - rightValue; widthValue = kMin(kMax(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.width(containerWidth); // FIXME: would it be better to have shrink-to-fit in one step? int preferredWidth = m_maxWidth - bordersPlusPadding; int preferredMinWidth = m_minWidth - bordersPlusPadding; int availableWidth = availableSpace - leftValue; widthValue = kMin(kMax(preferredMinWidth, availableWidth), preferredWidth); } else if (leftIsAuto && !width.isVariable() && !rightIsAuto) { // RULE 4: (solve for left) widthValue = calcContentWidth(width.width(containerWidth)); leftValue = availableSpace - (widthValue + right.width(containerWidth)); } else if (!leftIsAuto && widthIsAuto && !rightIsAuto) { // RULE 5: (solve for width) leftValue = left.width(containerWidth); widthValue = availableSpace - (leftValue + right.width(containerWidth)); } else if (!leftIsAuto&& !widthIsAuto && rightIsAuto) { // RULE 6: (no need solve for right) leftValue = left.width(containerWidth); widthValue = calcContentWidth(width.width(containerWidth)); } } // Use computed values to calculate the horizontal position. xPos = leftValue + marginLeftValue + containerBlock->borderLeft(); } void RenderBox::calcAbsoluteVertical() { if (isReplaced()) { calcAbsoluteVerticalReplaced(); 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" // // (block-style-comments in this function and in calcAbsoluteVerticalValues() // correspond to text from the spec) // We don't use containingBlock(), since we may be positioned by an enclosing relpositioned inline. const RenderObject* containerBlock = container(); const int containerHeight = containerBlock->height() - containerBlock->borderTop() - containerBlock->borderBottom(); const int bordersPlusPadding = borderTop() + borderBottom() + paddingTop() + paddingBottom(); 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. \*---------------------------------------------------------------------------*/ // Calculate the static distance if needed. if (top.isVariable() && bottom.isVariable()) { // m_staticY should already have been set through layout of the parent() int staticTop = m_staticY - containerBlock->borderTop(); for (RenderObject* po = parent(); po && po != containerBlock; po = po->parent()) { staticTop += po->yPos(); } top.setValue(Fixed, staticTop); } int height; // Needed to compute overflow. // Calculate constraint equation values for 'height' case. calcAbsoluteVerticalValues(style()->height(), containerBlock, containerHeight, bordersPlusPadding, top, bottom, marginTop, marginBottom, height, m_marginTop, m_marginBottom, m_y); // Avoid doing any work in the common case (where the values of min-height and max-height are their defaults). // see FIXME 2 // Calculate constraint equation values for 'max-height' case. if (style()->maxHeight().value() != UNDEFINED) { int maxHeight; short maxMarginTop; short maxMarginBottom; int maxYPos; calcAbsoluteVerticalValues(style()->maxHeight(), containerBlock, containerHeight, bordersPlusPadding, top, bottom, marginTop, marginBottom, maxHeight, maxMarginTop, maxMarginBottom, maxYPos); if (height > maxHeight) { height = maxHeight; m_marginTop = maxMarginTop; m_marginBottom = maxMarginBottom; m_y = maxYPos; } } // Calculate constraint equation values for 'min-height' case. if (style()->minHeight().value()) { int minHeight; short minMarginTop; short minMarginBottom; int minYPos; calcAbsoluteVerticalValues(style()->minHeight(), containerBlock, containerHeight, bordersPlusPadding, top, bottom, marginTop, marginBottom, minHeight, minMarginTop, minMarginBottom, minYPos); if (height < minHeight) { height = minHeight; m_marginTop = minMarginTop; m_marginBottom = minMarginBottom; m_y = minYPos; } } height += bordersPlusPadding; // Set final height value. m_height = height; } void RenderBox::calcAbsoluteVerticalValues(Length height, const RenderObject* containerBlock, const int containerHeight, const int bordersPlusPadding, const Length top, const Length bottom, const Length marginTop, const Length marginBottom, int& heightValue, short& marginTopValue, short& marginBottomValue, int& yPos) { // 'top' and 'bottom' cannot both be 'auto' because 'top would of been // converted to the static position in calcAbsoluteVertical() assert(!(top.isVariable() && bottom.isVariable())); int contentHeight = m_height - bordersPlusPadding; int topValue = 0; bool heightIsAuto = height.isVariable(); bool topIsAuto = top.isVariable(); bool bottomIsAuto = bottom.isVariable(); if (isTable() && heightIsAuto) { // Height is never unsolved for tables. "auto" means shrink to fit. // Use our height instead. heightValue = contentHeight; heightIsAuto = false; } else if (!heightIsAuto) { heightValue = calcContentHeight(height.width(containerHeight)); if (contentHeight > heightValue) { if (!isTable()) contentHeight = heightValue; else heightValue = contentHeight; } } 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. topValue = top.width(containerHeight); const int availableSpace = containerHeight - (topValue + heightValue + bottom.width(containerHeight) + bordersPlusPadding); // Margins are now the only unknown if (marginTop.isVariable() && marginBottom.isVariable()) { // Both margins auto, solve for equality // NOTE: This may result in negative values. marginTopValue = availableSpace / 2; // split the diference marginBottomValue = availableSpace - marginTopValue; // account for odd valued differences } else if (marginTop.isVariable()) { // Solve for top margin marginBottomValue = marginBottom.width(containerHeight); marginTopValue = availableSpace - marginBottomValue; } else if (marginBottom.isVariable()) { // Solve for bottom margin marginTopValue = marginTop.width(containerHeight); marginBottomValue = availableSpace - marginTopValue; } else { // Over-constrained, (no need solve for bottom) marginTopValue = marginTop.width(containerHeight); marginBottomValue = marginBottom.width(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.minWidth(containerHeight); marginBottomValue = marginBottom.minWidth(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.width(containerHeight)); } else if (topIsAuto && !heightIsAuto && bottomIsAuto) { // RULE 2: (shouldn't happen) } else if (!topIsAuto && heightIsAuto && bottomIsAuto) { // RULE 3: (height is content based, no need solve of bottom) heightValue = contentHeight; topValue = top.width(containerHeight); } else if (topIsAuto && !heightIsAuto && !bottomIsAuto) { // RULE 4: (solve of top) topValue = availableSpace - (heightValue + bottom.width(containerHeight)); } else if (!topIsAuto && heightIsAuto && !bottomIsAuto) { // RULE 5: (solve of height) topValue = top.width(containerHeight); heightValue = kMax(0, availableSpace - (topValue + bottom.width(containerHeight))); } else if (!topIsAuto && !heightIsAuto && bottomIsAuto) { // RULE 6: (no need solve of bottom) topValue = top.width(containerHeight); } } // Use computed values to calculate the vertical position. yPos = topValue + marginTopValue + containerBlock->borderTop(); } void RenderBox::calcAbsoluteHorizontalReplaced() { // The following is based off of the W3C Working Draft from April 11, 2006 of // CSS 2.1: Section 10.3.8 "Absolutly positioned, replaced elements" // // (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 RenderObject* containerBlock = container(); // FIXME: This is incorrect for cases where the container block is a relatively // positioned inline. const int containerWidth = containingBlockWidth() + containerBlock->paddingLeft() + containerBlock->paddingRight(); // To match WinIE, in quirks mode use the parent's 'direction' property // instead of the the container block's. EDirection containerDirection = (style()->htmlHacks()) ? 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 calcReplacedWidth(). This means that the steps to produce // correct max/min in the non-replaced version, are not necessary. m_width = calcReplacedWidth() + borderLeft() + borderRight() + paddingLeft() + paddingRight(); const int availableSpace = containerWidth - m_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. \*-----------------------------------------------------------------------*/ if (left.isVariable() && right.isVariable()) { // see FIXME 1 if (containerDirection == LTR) { // 'm_staticX' should already have been set through layout of the parent. int staticPosition = m_staticX - containerBlock->borderLeft(); for (RenderObject* po = parent(); po && po != containerBlock; po = po->parent()) staticPosition += po->xPos(); left.setValue(Fixed, staticPosition); } else { RenderObject* po = parent(); // 'm_staticX' should already have been set through layout of the parent. int staticPosition = m_staticX + containerWidth + containerBlock->borderRight() - po->width(); for (; po && po != containerBlock; po = po->parent()) staticPosition -= po->xPos(); right.setValue(Fixed, staticPosition); } } /*-----------------------------------------------------------------------*\ * 3. If 'left' or 'right' are 'auto', replace any 'auto' on 'margin-left' * or 'margin-right' with '0'. \*-----------------------------------------------------------------------*/ if (left.isVariable() || right.isVariable()) { if (marginLeft.isVariable()) marginLeft.setValue(Fixed, 0); if (marginRight.isVariable()) 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.isVariable() && marginRight.isVariable()) { // 'left' and 'right' cannot be 'auto' due to step 3 assert(!(left.isVariable() && right.isVariable())); leftValue = left.width(containerWidth); rightValue = right.width(containerWidth); int difference = availableSpace - (leftValue + rightValue); if (difference > 0) { m_marginLeft = difference / 2; // split the diference 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.isVariable()) { m_marginLeft = marginLeft.width(containerWidth); m_marginRight = marginRight.width(containerWidth); rightValue = right.width(containerWidth); // Solve for 'left' leftValue = availableSpace - (rightValue + m_marginLeft + m_marginRight); } else if (right.isVariable()) { m_marginLeft = marginLeft.width(containerWidth); m_marginRight = marginRight.width(containerWidth); leftValue = left.width(containerWidth); // Solve for 'right' rightValue = availableSpace - (leftValue + m_marginLeft + m_marginRight); } else if (marginLeft.isVariable()) { m_marginRight = marginRight.width(containerWidth); leftValue = left.width(containerWidth); rightValue = right.width(containerWidth); // Solve for 'margin-left' m_marginLeft = availableSpace - (leftValue + rightValue + m_marginRight); } else if (marginRight.isVariable()) { m_marginLeft = marginLeft.width(containerWidth); leftValue = left.width(containerWidth); rightValue = right.width(containerWidth); // Solve for 'margin-right' m_marginRight = availableSpace - (leftValue + rightValue + m_marginLeft); } /*-----------------------------------------------------------------------*\ * 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. \*-----------------------------------------------------------------------*/ else { m_marginLeft = marginLeft.width(containerWidth); m_marginRight = marginRight.width(containerWidth); if (containerDirection == LTR) { leftValue = left.width(containerWidth); rightValue = availableSpace - (leftValue + m_marginLeft + m_marginRight); } else { rightValue = right.width(containerWidth); leftValue = availableSpace - (rightValue + m_marginLeft + m_marginRight); } } int totalWidth = m_width + leftValue + rightValue + m_marginLeft + m_marginRight; if (totalWidth > containerWidth && (containerDirection == RTL)) leftValue = containerWidth - (totalWidth - leftValue); // Use computed values to calculate the horizontal position. m_x = leftValue + m_marginLeft + containerBlock->borderLeft(); } void RenderBox::calcAbsoluteVerticalReplaced() { // The following is based off of the W3C Working Draft from April 11, 2006 of // CSS 2.1: Section 10.6.5 "Absolutly positioned, replaced elements" // // (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 RenderObject* containerBlock = container(); const int containerHeight = containerBlock->height() - containerBlock->borderTop() - containerBlock->borderBottom(); // 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 calcReplacedHeight(). This means that the steps to produce // correct max/min in the non-replaced version, are not necessary. m_height = calcReplacedHeight() + borderTop() + borderBottom() + paddingTop() + paddingBottom(); const int availableSpace = containerHeight - m_height; /*-----------------------------------------------------------------------*\ * 2. If both 'top' and 'bottom' have the value 'auto', replace 'top' * with the element's static position. \*-----------------------------------------------------------------------*/ if (top.isVariable() && bottom.isVariable()) { // m_staticY should already have been set through layout of the parent(). int staticTop = m_staticY - containerBlock->borderTop(); for (RenderObject* po = parent(); po && po != containerBlock; po = po->parent()) { staticTop += po->yPos(); } 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.isVariable() || bottom.isVariable()) { if (marginTop.isVariable()) marginTop.setValue(Fixed, 0); if (marginBottom.isVariable()) 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.isVariable() && marginBottom.isVariable()) { // 'top' and 'bottom' cannot be 'auto' due to step 2 and 3 combinded. assert(!(top.isVariable() || bottom.isVariable())); topValue = top.width(containerHeight); bottomValue = bottom.width(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.isVariable()) { m_marginTop = marginTop.width(containerHeight); m_marginBottom = marginBottom.width(containerHeight); bottomValue = bottom.width(containerHeight); // Solve for 'top' topValue = availableSpace - (bottomValue + m_marginTop + m_marginBottom); } else if (bottom.isVariable()) { m_marginTop = marginTop.width(containerHeight); m_marginBottom = marginBottom.width(containerHeight); topValue = top.width(containerHeight); // Solve for 'bottom' // NOTE: It is not necessary to solve for 'bottom' because we don't ever // use the value. } else if (marginTop.isVariable()) { m_marginBottom = marginBottom.width(containerHeight); topValue = top.width(containerHeight); bottomValue = bottom.width(containerHeight); // Solve for 'margin-top' m_marginTop = availableSpace - (topValue + bottomValue + m_marginBottom); } else if (marginBottom.isVariable()) { m_marginTop = marginTop.width(containerHeight); topValue = top.width(containerHeight); bottomValue = bottom.width(containerHeight); // Solve for 'margin-bottom' m_marginBottom = availableSpace - (topValue + bottomValue + m_marginTop); } /*-----------------------------------------------------------------------*\ * 6. If at this point the values are over-constrained, ignore the value * for 'bottom' and solve for that value. \*-----------------------------------------------------------------------*/ else { m_marginTop = marginTop.width(containerHeight); m_marginBottom = marginBottom.width(containerHeight); topValue = top.width(containerHeight); // Solve for 'bottom' // NOTE: It is not necessary to solve for 'bottom' because we don't ever // use the value. } // Use computed values to calculate the vertical position. m_y = topValue + m_marginTop + containerBlock->borderTop(); } int RenderBox::highestPosition(bool /*includeOverflowInterior*/, bool includeSelf) const { return includeSelf ? 0 : m_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; } int RenderBox::pageTopAfter(int y) const { RenderObject* cb = container(); if (cb) return cb->pageTopAfter(y+yPos()) - yPos(); else return 0; } int RenderBox::crossesPageBreak(int t, int b) const { RenderObject* cb = container(); if (cb) return cb->crossesPageBreak(yPos()+t, yPos()+b); else return false; } void RenderBox::caretPos(int /*offset*/, int flags, int &_x, int &_y, int &width, int &height) { #if 0 _x = -1; // propagate it downwards to its children, someone will feel responsible RenderObject *child = firstChild(); // if (child) kdDebug(6040) << "delegating caretPos to " << child->renderName() << endl; if (child) child->caretPos(offset, override, _x, _y, width, height); // if not, use the extents of this box. offset 0 means left, offset 1 means // right if (_x == -1) { //kdDebug(6040) << "no delegation" << endl; _x = xPos() + (offset == 0 ? 0 : m_width); _y = yPos(); height = m_height; width = override && offset == 0 ? m_width : 1; // If height of box is smaller than font height, use the latter one, // otherwise the caret might become invisible. // FIXME: ignoring :first-line, missing good reason to take care of int fontHeight = style()->fontMetrics().height(); if (fontHeight > height) height = fontHeight; int absx, absy; RenderObject *cb = containingBlock(); if (cb && cb != this && cb->absolutePosition(absx,absy)) { //kdDebug(6040) << "absx=" << absx << " absy=" << absy << endl; _x += absx; _y += absy; } else { // we don't know our absolute position, and there is no point returning // just a relative one _x = _y = -1; } } #endif _x = xPos(); _y = yPos(); // kdDebug(6040) << "_x " << _x << " _y " << _y << endl; width = 1; // no override is indicated in boxes RenderBlock *cb = containingBlock(); // Place caret outside the border if (flags & CFOutside) { RenderStyle *s = element() && element()->parent() && element()->parent()->renderer() ? element()->parent()->renderer()->style() : cb->style(); const TQFontMetrics &fm = s->fontMetrics(); height = fm.height(); bool rtl = s->direction() == RTL; bool outsideEnd = flags & CFOutsideEnd; if (outsideEnd) { _x += this->width(); } else { _x--; } int hl = fm.leading() / 2; if (!isReplaced() || style()->display() == BLOCK) { if (!outsideEnd ^ rtl) _y -= hl; else _y += kMax(this->height() - fm.ascent() - hl, 0); } else { _y += baselinePosition(false) - fm.ascent() - hl; } // Place caret inside the element } else { const TQFontMetrics &fm = style()->fontMetrics(); height = fm.height(); RenderStyle *s = style(); _x += borderLeft() + paddingLeft(); _y += borderTop() + paddingTop(); // ### regard direction switch (s->textAlign()) { case LEFT: case KHTML_LEFT: case TAAUTO: // ### find out what this does case JUSTIFY: break; case CENTER: case KHTML_CENTER: _x += contentWidth() / 2; break; case KHTML_RIGHT: case RIGHT: _x += contentWidth(); break; } } int absx, absy; if (cb && cb != this && cb->absolutePosition(absx,absy)) { // kdDebug(6040) << "absx=" << absx << " absy=" << absy << endl; _x += absx; _y += absy; } else { // we don't know our absolute position, and there is no point returning // just a relative one _x = _y = -1; } } #undef DEBUG_LAYOUT