diff options
Diffstat (limited to 'Source/WebCore/rendering/RenderBoxModelObject.cpp')
-rw-r--r-- | Source/WebCore/rendering/RenderBoxModelObject.cpp | 1858 |
1 files changed, 1858 insertions, 0 deletions
diff --git a/Source/WebCore/rendering/RenderBoxModelObject.cpp b/Source/WebCore/rendering/RenderBoxModelObject.cpp new file mode 100644 index 0000000..5098306 --- /dev/null +++ b/Source/WebCore/rendering/RenderBoxModelObject.cpp @@ -0,0 +1,1858 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) + * (C) 2005, 2006 Samuel Weinig (sam.weinig@gmail.com) + * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" +#include "RenderBoxModelObject.h" + +#include "GraphicsContext.h" +#include "HTMLFrameOwnerElement.h" +#include "HTMLNames.h" +#include "ImageBuffer.h" +#include "Path.h" +#include "RenderBlock.h" +#include "RenderInline.h" +#include "RenderLayer.h" +#include "RenderView.h" +#include <wtf/CurrentTime.h> + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +bool RenderBoxModelObject::s_wasFloating = false; +bool RenderBoxModelObject::s_hadLayer = false; +bool RenderBoxModelObject::s_layerWasSelfPainting = false; + +static const double cInterpolationCutoff = 800. * 800.; +static const double cLowQualityTimeThreshold = 0.500; // 500 ms + +typedef pair<RenderBoxModelObject*, const void*> LastPaintSizeMapKey; +typedef HashMap<LastPaintSizeMapKey, IntSize> LastPaintSizeMap; + +// The HashMap for storing continuation pointers. +// An inline can be split with blocks occuring in between the inline content. +// When this occurs we need a pointer to the next object. We can basically be +// split into a sequence of inlines and blocks. The continuation will either be +// an anonymous block (that houses other blocks) or it will be an inline flow. +// <b><i><p>Hello</p></i></b>. In this example the <i> will have a block as +// its continuation but the <b> will just have an inline as its continuation. +typedef HashMap<const RenderBoxModelObject*, RenderBoxModelObject*> ContinuationMap; +static ContinuationMap* continuationMap = 0; + +class ImageQualityController : public Noncopyable { +public: + ImageQualityController(); + bool shouldPaintAtLowQuality(GraphicsContext*, RenderBoxModelObject*, Image*, const void* layer, const IntSize&); + void keyDestroyed(LastPaintSizeMapKey key); + void objectDestroyed(RenderBoxModelObject*); + +private: + void highQualityRepaintTimerFired(Timer<ImageQualityController>*); + void restartTimer(); + + LastPaintSizeMap m_lastPaintSizeMap; + Timer<ImageQualityController> m_timer; + bool m_animatedResizeIsActive; +}; + +ImageQualityController::ImageQualityController() + : m_timer(this, &ImageQualityController::highQualityRepaintTimerFired) + , m_animatedResizeIsActive(false) +{ +} + +void ImageQualityController::keyDestroyed(LastPaintSizeMapKey key) +{ + m_lastPaintSizeMap.remove(key); + if (m_lastPaintSizeMap.isEmpty()) { + m_animatedResizeIsActive = false; + m_timer.stop(); + } +} + +void ImageQualityController::objectDestroyed(RenderBoxModelObject* object) +{ + Vector<LastPaintSizeMapKey> keysToDie; + for (LastPaintSizeMap::iterator it = m_lastPaintSizeMap.begin(); it != m_lastPaintSizeMap.end(); ++it) + if (it->first.first == object) + keysToDie.append(it->first); + for (Vector<LastPaintSizeMapKey>::iterator it = keysToDie.begin(); it != keysToDie.end(); ++it) + keyDestroyed(*it); +} + +void ImageQualityController::highQualityRepaintTimerFired(Timer<ImageQualityController>*) +{ + if (m_animatedResizeIsActive) { + m_animatedResizeIsActive = false; + for (LastPaintSizeMap::iterator it = m_lastPaintSizeMap.begin(); it != m_lastPaintSizeMap.end(); ++it) + it->first.first->repaint(); + } +} + +void ImageQualityController::restartTimer() +{ + m_timer.startOneShot(cLowQualityTimeThreshold); +} + +bool ImageQualityController::shouldPaintAtLowQuality(GraphicsContext* context, RenderBoxModelObject* object, Image* image, const void *layer, const IntSize& size) +{ + // If the image is not a bitmap image, then none of this is relevant and we just paint at high + // quality. + if (!image || !image->isBitmapImage() || context->paintingDisabled()) + return false; + + // Make sure to use the unzoomed image size, since if a full page zoom is in effect, the image + // is actually being scaled. + IntSize imageSize(image->width(), image->height()); + + // Look ourselves up in the hashtable. + LastPaintSizeMapKey key(object, layer); + LastPaintSizeMap::iterator i = m_lastPaintSizeMap.find(key); + + const AffineTransform& currentTransform = context->getCTM(); + bool contextIsScaled = !currentTransform.isIdentityOrTranslationOrFlipped(); + if (!contextIsScaled && imageSize == size) { + // There is no scale in effect. If we had a scale in effect before, we can just remove this object from the list. + if (i != m_lastPaintSizeMap.end()) + m_lastPaintSizeMap.remove(key); + + return false; + } + + // There is no need to hash scaled images that always use low quality mode when the page demands it. This is the iChat case. + if (object->document()->page()->inLowQualityImageInterpolationMode()) { + double totalPixels = static_cast<double>(image->width()) * static_cast<double>(image->height()); + if (totalPixels > cInterpolationCutoff) + return true; + } + // If an animated resize is active, paint in low quality and kick the timer ahead. + if (m_animatedResizeIsActive) { + m_lastPaintSizeMap.set(key, size); + restartTimer(); + return true; + } + // If this is the first time resizing this image, or its size is the + // same as the last resize, draw at high res, but record the paint + // size and set the timer. + if (i == m_lastPaintSizeMap.end() || size == i->second) { + restartTimer(); + m_lastPaintSizeMap.set(key, size); + return false; + } + // If the timer is no longer active, draw at high quality and don't + // set the timer. + if (!m_timer.isActive()) { + keyDestroyed(key); + return false; + } + // This object has been resized to two different sizes while the timer + // is active, so draw at low quality, set the flag for animated resizes and + // the object to the list for high quality redraw. + m_lastPaintSizeMap.set(key, size); + m_animatedResizeIsActive = true; + restartTimer(); + return true; +} + +static ImageQualityController* imageQualityController() +{ + static ImageQualityController* controller = new ImageQualityController; + return controller; +} + +void RenderBoxModelObject::setSelectionState(SelectionState s) +{ + if (selectionState() == s) + return; + + if (s == SelectionInside && selectionState() != SelectionNone) + return; + + if ((s == SelectionStart && selectionState() == SelectionEnd) + || (s == SelectionEnd && selectionState() == SelectionStart)) + RenderObject::setSelectionState(SelectionBoth); + else + RenderObject::setSelectionState(s); + + // FIXME: + // We should consider whether it is OK propagating to ancestor RenderInlines. + // This is a workaround for http://webkit.org/b/32123 + RenderBlock* cb = containingBlock(); + if (cb && !cb->isRenderView()) + cb->setSelectionState(s); +} + +bool RenderBoxModelObject::shouldPaintAtLowQuality(GraphicsContext* context, Image* image, const void* layer, const IntSize& size) +{ + return imageQualityController()->shouldPaintAtLowQuality(context, this, image, layer, size); +} + +RenderBoxModelObject::RenderBoxModelObject(Node* node) + : RenderObject(node) + , m_layer(0) +{ +} + +RenderBoxModelObject::~RenderBoxModelObject() +{ + // Our layer should have been destroyed and cleared by now + ASSERT(!hasLayer()); + ASSERT(!m_layer); + imageQualityController()->objectDestroyed(this); +} + +void RenderBoxModelObject::destroyLayer() +{ + ASSERT(!hasLayer()); // Callers should have already called setHasLayer(false) + ASSERT(m_layer); + m_layer->destroy(renderArena()); + m_layer = 0; +} + +void RenderBoxModelObject::destroy() +{ + // This must be done before we destroy the RenderObject. + if (m_layer) + m_layer->clearClipRects(); + + // A continuation of this RenderObject should be destroyed at subclasses. + ASSERT(!continuation()); + + // RenderObject::destroy calls back to destroyLayer() for layer destruction + RenderObject::destroy(); +} + +bool RenderBoxModelObject::hasSelfPaintingLayer() const +{ + return m_layer && m_layer->isSelfPaintingLayer(); +} + +void RenderBoxModelObject::styleWillChange(StyleDifference diff, const RenderStyle* newStyle) +{ + s_wasFloating = isFloating(); + s_hadLayer = hasLayer(); + if (s_hadLayer) + s_layerWasSelfPainting = layer()->isSelfPaintingLayer(); + + // If our z-index changes value or our visibility changes, + // we need to dirty our stacking context's z-order list. + if (style() && newStyle) { + if (parent()) { + // Do a repaint with the old style first, e.g., for example if we go from + // having an outline to not having an outline. + if (diff == StyleDifferenceRepaintLayer) { + layer()->repaintIncludingDescendants(); + if (!(style()->clip() == newStyle->clip())) + layer()->clearClipRectsIncludingDescendants(); + } else if (diff == StyleDifferenceRepaint || newStyle->outlineSize() < style()->outlineSize()) + repaint(); + } + + if (diff == StyleDifferenceLayout) { + // When a layout hint happens, we go ahead and do a repaint of the layer, since the layer could + // end up being destroyed. + if (hasLayer()) { + if (style()->position() != newStyle->position() || + style()->zIndex() != newStyle->zIndex() || + style()->hasAutoZIndex() != newStyle->hasAutoZIndex() || + !(style()->clip() == newStyle->clip()) || + style()->hasClip() != newStyle->hasClip() || + style()->opacity() != newStyle->opacity() || + style()->transform() != newStyle->transform()) + layer()->repaintIncludingDescendants(); + } else if (newStyle->hasTransform() || newStyle->opacity() < 1) { + // If we don't have a layer yet, but we are going to get one because of transform or opacity, + // then we need to repaint the old position of the object. + repaint(); + } + } + + if (hasLayer() && (style()->hasAutoZIndex() != newStyle->hasAutoZIndex() || + style()->zIndex() != newStyle->zIndex() || + style()->visibility() != newStyle->visibility())) { + layer()->dirtyStackingContextZOrderLists(); + if (style()->hasAutoZIndex() != newStyle->hasAutoZIndex() || style()->visibility() != newStyle->visibility()) + layer()->dirtyZOrderLists(); + } + } + + RenderObject::styleWillChange(diff, newStyle); +} + +void RenderBoxModelObject::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderObject::styleDidChange(diff, oldStyle); + updateBoxModelInfoFromStyle(); + + if (requiresLayer()) { + if (!layer()) { + if (s_wasFloating && isFloating()) + setChildNeedsLayout(true); + m_layer = new (renderArena()) RenderLayer(this); + setHasLayer(true); + m_layer->insertOnlyThisLayer(); + if (parent() && !needsLayout() && containingBlock()) + m_layer->updateLayerPositions(); + } + } else if (layer() && layer()->parent()) { + setHasTransform(false); // Either a transform wasn't specified or the object doesn't support transforms, so just null out the bit. + setHasReflection(false); + m_layer->removeOnlyThisLayer(); // calls destroyLayer() which clears m_layer + if (s_wasFloating && isFloating()) + setChildNeedsLayout(true); + } + + if (layer()) { + layer()->styleChanged(diff, oldStyle); + if (s_hadLayer && layer()->isSelfPaintingLayer() != s_layerWasSelfPainting) + setChildNeedsLayout(true); + } +} + +void RenderBoxModelObject::updateBoxModelInfoFromStyle() +{ + // Set the appropriate bits for a box model object. Since all bits are cleared in styleWillChange, + // we only check for bits that could possibly be set to true. + setHasBoxDecorations(hasBackground() || style()->hasBorder() || style()->hasAppearance() || style()->boxShadow()); + setInline(style()->isDisplayInlineType()); + setRelPositioned(style()->position() == RelativePosition); +} + +int RenderBoxModelObject::relativePositionOffsetX() const +{ + // Objects that shrink to avoid floats normally use available line width when computing containing block width. However + // in the case of relative positioning using percentages, we can't do this. The offset should always be resolved using the + // available width of the containing block. Therefore we don't use containingBlockLogicalWidthForContent() here, but instead explicitly + // call availableWidth on our containing block. + if (!style()->left().isAuto()) { + RenderBlock* cb = containingBlock(); + if (!style()->right().isAuto() && !containingBlock()->style()->isLeftToRightDirection()) + return -style()->right().calcValue(cb->availableWidth()); + return style()->left().calcValue(cb->availableWidth()); + } + if (!style()->right().isAuto()) { + RenderBlock* cb = containingBlock(); + return -style()->right().calcValue(cb->availableWidth()); + } + return 0; +} + +int RenderBoxModelObject::relativePositionOffsetY() const +{ + RenderBlock* containingBlock = this->containingBlock(); + + // If the containing block of a relatively positioned element does not + // specify a height, a percentage top or bottom offset should be resolved as + // auto. An exception to this is if the containing block has the WinIE quirk + // where <html> and <body> assume the size of the viewport. In this case, + // calculate the percent offset based on this height. + // See <https://bugs.webkit.org/show_bug.cgi?id=26396>. + if (!style()->top().isAuto() + && (!containingBlock->style()->height().isAuto() + || !style()->top().isPercent() + || containingBlock->stretchesToViewport())) + return style()->top().calcValue(containingBlock->availableHeight()); + + if (!style()->bottom().isAuto() + && (!containingBlock->style()->height().isAuto() + || !style()->bottom().isPercent() + || containingBlock->stretchesToViewport())) + return -style()->bottom().calcValue(containingBlock->availableHeight()); + + return 0; +} + +int RenderBoxModelObject::offsetLeft() const +{ + // If the element is the HTML body element or does not have an associated box + // return 0 and stop this algorithm. + if (isBody()) + return 0; + + RenderBoxModelObject* offsetPar = offsetParent(); + int xPos = (isBox() ? toRenderBox(this)->x() : 0); + + // If the offsetParent of the element is null, or is the HTML body element, + // return the distance between the canvas origin and the left border edge + // of the element and stop this algorithm. + if (offsetPar) { + if (offsetPar->isBox() && !offsetPar->isBody()) + xPos -= toRenderBox(offsetPar)->borderLeft(); + if (!isPositioned()) { + if (isRelPositioned()) + xPos += relativePositionOffsetX(); + RenderObject* curr = parent(); + while (curr && curr != offsetPar) { + // FIXME: What are we supposed to do inside SVG content? + if (curr->isBox() && !curr->isTableRow()) + xPos += toRenderBox(curr)->x(); + curr = curr->parent(); + } + if (offsetPar->isBox() && offsetPar->isBody() && !offsetPar->isRelPositioned() && !offsetPar->isPositioned()) + xPos += toRenderBox(offsetPar)->x(); + } + } + + return xPos; +} + +int RenderBoxModelObject::offsetTop() const +{ + // If the element is the HTML body element or does not have an associated box + // return 0 and stop this algorithm. + if (isBody()) + return 0; + + RenderBoxModelObject* offsetPar = offsetParent(); + int yPos = (isBox() ? toRenderBox(this)->y() : 0); + + // If the offsetParent of the element is null, or is the HTML body element, + // return the distance between the canvas origin and the top border edge + // of the element and stop this algorithm. + if (offsetPar) { + if (offsetPar->isBox() && !offsetPar->isBody()) + yPos -= toRenderBox(offsetPar)->borderTop(); + if (!isPositioned()) { + if (isRelPositioned()) + yPos += relativePositionOffsetY(); + RenderObject* curr = parent(); + while (curr && curr != offsetPar) { + // FIXME: What are we supposed to do inside SVG content? + if (curr->isBox() && !curr->isTableRow()) + yPos += toRenderBox(curr)->y(); + curr = curr->parent(); + } + if (offsetPar->isBox() && offsetPar->isBody() && !offsetPar->isRelPositioned() && !offsetPar->isPositioned()) + yPos += toRenderBox(offsetPar)->y(); + } + } + return yPos; +} + +int RenderBoxModelObject::paddingTop(bool) const +{ + int w = 0; + Length padding = style()->paddingTop(); + if (padding.isPercent()) + w = containingBlock()->availableLogicalWidth(); + return padding.calcMinValue(w); +} + +int RenderBoxModelObject::paddingBottom(bool) const +{ + int w = 0; + Length padding = style()->paddingBottom(); + if (padding.isPercent()) + w = containingBlock()->availableLogicalWidth(); + return padding.calcMinValue(w); +} + +int RenderBoxModelObject::paddingLeft(bool) const +{ + int w = 0; + Length padding = style()->paddingLeft(); + if (padding.isPercent()) + w = containingBlock()->availableLogicalWidth(); + return padding.calcMinValue(w); +} + +int RenderBoxModelObject::paddingRight(bool) const +{ + int w = 0; + Length padding = style()->paddingRight(); + if (padding.isPercent()) + w = containingBlock()->availableLogicalWidth(); + return padding.calcMinValue(w); +} + +int RenderBoxModelObject::paddingBefore(bool) const +{ + int w = 0; + Length padding = style()->paddingBefore(); + if (padding.isPercent()) + w = containingBlock()->availableLogicalWidth(); + return padding.calcMinValue(w); +} + +int RenderBoxModelObject::paddingAfter(bool) const +{ + int w = 0; + Length padding = style()->paddingAfter(); + if (padding.isPercent()) + w = containingBlock()->availableLogicalWidth(); + return padding.calcMinValue(w); +} + +int RenderBoxModelObject::paddingStart(bool) const +{ + int w = 0; + Length padding = style()->paddingStart(); + if (padding.isPercent()) + w = containingBlock()->availableLogicalWidth(); + return padding.calcMinValue(w); +} + +int RenderBoxModelObject::paddingEnd(bool) const +{ + int w = 0; + Length padding = style()->paddingEnd(); + if (padding.isPercent()) + w = containingBlock()->availableLogicalWidth(); + return padding.calcMinValue(w); +} + +void RenderBoxModelObject::paintFillLayerExtended(const PaintInfo& paintInfo, const Color& c, const FillLayer* bgLayer, int tx, int ty, int w, int h, InlineFlowBox* box, CompositeOperator op, RenderObject* backgroundObject) +{ + GraphicsContext* context = paintInfo.context; + if (context->paintingDisabled()) + return; + + bool includeLeftEdge = box ? box->includeLogicalLeftEdge() : true; + bool includeRightEdge = box ? box->includeLogicalRightEdge() : true; + int bLeft = includeLeftEdge ? borderLeft() : 0; + int bRight = includeRightEdge ? borderRight() : 0; + int pLeft = includeLeftEdge ? paddingLeft() : 0; + int pRight = includeRightEdge ? paddingRight() : 0; + + bool clippedToBorderRadius = false; + if (style()->hasBorderRadius() && (includeLeftEdge || includeRightEdge)) { + IntRect borderRect(tx, ty, w, h); + + if (borderRect.isEmpty()) + return; + + context->save(); + + IntSize topLeft, topRight, bottomLeft, bottomRight; + style()->getBorderRadiiForRect(borderRect, topLeft, topRight, bottomLeft, bottomRight); + + if (!includeLeftEdge) { + topLeft = IntSize(); + if (box->isHorizontal()) + bottomLeft = IntSize(); + else + topRight = IntSize(); + } + + if (!includeRightEdge) { + if (box->isHorizontal()) + topRight = IntSize(); + else + bottomLeft = IntSize(); + bottomRight = IntSize(); + } + + context->addRoundedRectClip(borderRect, topLeft, topRight, bottomLeft, bottomRight); + clippedToBorderRadius = true; + } + + bool clippedWithLocalScrolling = hasOverflowClip() && bgLayer->attachment() == LocalBackgroundAttachment; + if (clippedWithLocalScrolling) { + // Clip to the overflow area. + context->save(); + context->clip(toRenderBox(this)->overflowClipRect(tx, ty)); + + // Now adjust our tx, ty, w, h to reflect a scrolled content box with borders at the ends. + IntSize offset = layer()->scrolledContentOffset(); + tx -= offset.width(); + ty -= offset.height(); + w = bLeft + layer()->scrollWidth() + bRight; + h = borderTop() + layer()->scrollHeight() + borderBottom(); + } + + if (bgLayer->clip() == PaddingFillBox || bgLayer->clip() == ContentFillBox) { + // Clip to the padding or content boxes as necessary. + bool includePadding = bgLayer->clip() == ContentFillBox; + int x = tx + bLeft + (includePadding ? pLeft : 0); + int y = ty + borderTop() + (includePadding ? paddingTop() : 0); + int width = w - bLeft - bRight - (includePadding ? pLeft + pRight : 0); + int height = h - borderTop() - borderBottom() - (includePadding ? paddingTop() + paddingBottom() : 0); + context->save(); + context->clip(IntRect(x, y, width, height)); + } else if (bgLayer->clip() == TextFillBox) { + // We have to draw our text into a mask that can then be used to clip background drawing. + // First figure out how big the mask has to be. It should be no bigger than what we need + // to actually render, so we should intersect the dirty rect with the border box of the background. + IntRect maskRect(tx, ty, w, h); + maskRect.intersect(paintInfo.rect); + + // Now create the mask. + OwnPtr<ImageBuffer> maskImage = ImageBuffer::create(maskRect.size()); + if (!maskImage) + return; + + GraphicsContext* maskImageContext = maskImage->context(); + maskImageContext->translate(-maskRect.x(), -maskRect.y()); + + // Now add the text to the clip. We do this by painting using a special paint phase that signals to + // InlineTextBoxes that they should just add their contents to the clip. + PaintInfo info(maskImageContext, maskRect, PaintPhaseTextClip, true, 0, 0); + if (box) + box->paint(info, tx - box->x(), ty - box->y()); + else { + int x = isBox() ? toRenderBox(this)->x() : 0; + int y = isBox() ? toRenderBox(this)->y() : 0; + paint(info, tx - x, ty - y); + } + + // The mask has been created. Now we just need to clip to it. + context->save(); + context->clipToImageBuffer(maskImage.get(), maskRect); + } + + StyleImage* bg = bgLayer->image(); + bool shouldPaintBackgroundImage = bg && bg->canRender(style()->effectiveZoom()); + Color bgColor = c; + + // When this style flag is set, change existing background colors and images to a solid white background. + // If there's no bg color or image, leave it untouched to avoid affecting transparency. + // We don't try to avoid loading the background images, because this style flag is only set + // when printing, and at that point we've already loaded the background images anyway. (To avoid + // loading the background images we'd have to do this check when applying styles rather than + // while rendering.) + if (style()->forceBackgroundsToWhite()) { + // Note that we can't reuse this variable below because the bgColor might be changed + bool shouldPaintBackgroundColor = !bgLayer->next() && bgColor.isValid() && bgColor.alpha() > 0; + if (shouldPaintBackgroundImage || shouldPaintBackgroundColor) { + bgColor = Color::white; + shouldPaintBackgroundImage = false; + } + } + + bool isRoot = this->isRoot(); + + // Only fill with a base color (e.g., white) if we're the root document, since iframes/frames with + // no background in the child document should show the parent's background. + bool isOpaqueRoot = false; + if (isRoot) { + isOpaqueRoot = true; + if (!bgLayer->next() && !(bgColor.isValid() && bgColor.alpha() == 255) && view()->frameView()) { + Element* ownerElement = document()->ownerElement(); + if (ownerElement) { + if (!ownerElement->hasTagName(frameTag)) { + // Locate the <body> element using the DOM. This is easier than trying + // to crawl around a render tree with potential :before/:after content and + // anonymous blocks created by inline <body> tags etc. We can locate the <body> + // render object very easily via the DOM. + HTMLElement* body = document()->body(); + if (body) { + // Can't scroll a frameset document anyway. + isOpaqueRoot = body->hasLocalName(framesetTag); + } +#if ENABLE(SVG) + else { + // SVG documents and XML documents with SVG root nodes are transparent. + isOpaqueRoot = !document()->hasSVGRootNode(); + } +#endif + } + } else + isOpaqueRoot = !view()->frameView()->isTransparent(); + } + view()->frameView()->setContentIsOpaque(isOpaqueRoot); + } + + // Paint the color first underneath all images. + if (!bgLayer->next()) { + IntRect rect(tx, ty, w, h); + rect.intersect(paintInfo.rect); + // If we have an alpha and we are painting the root element, go ahead and blend with the base background color. + if (isOpaqueRoot) { + Color baseColor = view()->frameView()->baseBackgroundColor(); + if (baseColor.alpha() > 0) { + CompositeOperator previousOperator = context->compositeOperation(); + context->setCompositeOperation(CompositeCopy); + context->fillRect(rect, baseColor, style()->colorSpace()); + context->setCompositeOperation(previousOperator); + } else + context->clearRect(rect); + } + + if (bgColor.isValid() && bgColor.alpha() > 0) + context->fillRect(rect, bgColor, style()->colorSpace()); + } + + // no progressive loading of the background image + if (shouldPaintBackgroundImage) { + IntRect destRect; + IntPoint phase; + IntSize tileSize; + + calculateBackgroundImageGeometry(bgLayer, tx, ty, w, h, destRect, phase, tileSize); + IntPoint destOrigin = destRect.location(); + destRect.intersect(paintInfo.rect); + if (!destRect.isEmpty()) { + phase += destRect.location() - destOrigin; + CompositeOperator compositeOp = op == CompositeSourceOver ? bgLayer->composite() : op; + RenderObject* clientForBackgroundImage = backgroundObject ? backgroundObject : this; + Image* image = bg->image(clientForBackgroundImage, tileSize); + bool useLowQualityScaling = shouldPaintAtLowQuality(context, image, bgLayer, tileSize); + context->drawTiledImage(image, style()->colorSpace(), destRect, phase, tileSize, compositeOp, useLowQualityScaling); + } + } + + if (bgLayer->clip() != BorderFillBox) + // Undo the background clip + context->restore(); + + if (clippedToBorderRadius) + // Undo the border radius clip + context->restore(); + + if (clippedWithLocalScrolling) // Undo the clip for local background attachments. + context->restore(); +} + +IntSize RenderBoxModelObject::calculateFillTileSize(const FillLayer* fillLayer, IntSize positioningAreaSize) const +{ + StyleImage* image = fillLayer->image(); + image->setImageContainerSize(positioningAreaSize); // Use the box established by background-origin. + + EFillSizeType type = fillLayer->size().type; + + switch (type) { + case SizeLength: { + int w = positioningAreaSize.width(); + int h = positioningAreaSize.height(); + + Length layerWidth = fillLayer->size().size.width(); + Length layerHeight = fillLayer->size().size.height(); + + if (layerWidth.isFixed()) + w = layerWidth.value(); + else if (layerWidth.isPercent()) + w = layerWidth.calcValue(positioningAreaSize.width()); + + if (layerHeight.isFixed()) + h = layerHeight.value(); + else if (layerHeight.isPercent()) + h = layerHeight.calcValue(positioningAreaSize.height()); + + // If one of the values is auto we have to use the appropriate + // scale to maintain our aspect ratio. + if (layerWidth.isAuto() && !layerHeight.isAuto()) { + IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom()); + if (imageIntrinsicSize.height()) + w = imageIntrinsicSize.width() * h / imageIntrinsicSize.height(); + } else if (!layerWidth.isAuto() && layerHeight.isAuto()) { + IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom()); + if (imageIntrinsicSize.width()) + h = imageIntrinsicSize.height() * w / imageIntrinsicSize.width(); + } else if (layerWidth.isAuto() && layerHeight.isAuto()) { + // If both width and height are auto, use the image's intrinsic size. + IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom()); + w = imageIntrinsicSize.width(); + h = imageIntrinsicSize.height(); + } + + return IntSize(max(1, w), max(1, h)); + } + case Contain: + case Cover: { + IntSize imageIntrinsicSize = image->imageSize(this, 1); + float horizontalScaleFactor = imageIntrinsicSize.width() + ? static_cast<float>(positioningAreaSize.width()) / imageIntrinsicSize.width() : 1; + float verticalScaleFactor = imageIntrinsicSize.height() + ? static_cast<float>(positioningAreaSize.height()) / imageIntrinsicSize.height() : 1; + float scaleFactor = type == Contain ? min(horizontalScaleFactor, verticalScaleFactor) : max(horizontalScaleFactor, verticalScaleFactor); + return IntSize(max<int>(1, imageIntrinsicSize.width() * scaleFactor), max<int>(1, imageIntrinsicSize.height() * scaleFactor)); + } + case SizeNone: + break; + } + + return image->imageSize(this, style()->effectiveZoom()); +} + +void RenderBoxModelObject::calculateBackgroundImageGeometry(const FillLayer* fillLayer, int tx, int ty, int w, int h, + IntRect& destRect, IntPoint& phase, IntSize& tileSize) +{ + int left = 0; + int top = 0; + IntSize positioningAreaSize; + + // Determine the background positioning area and set destRect to the background painting area. + // destRect will be adjusted later if the background is non-repeating. + bool fixedAttachment = fillLayer->attachment() == FixedBackgroundAttachment; + +#if ENABLE(FAST_MOBILE_SCROLLING) + if (view()->frameView() && view()->frameView()->canBlitOnScroll()) { + // As a side effect of an optimization to blit on scroll, we do not honor the CSS + // property "background-attachment: fixed" because it may result in rendering + // artifacts. Note, these artifacts only appear if we are blitting on scroll of + // a page that has fixed background images. + fixedAttachment = false; + } +#endif + + if (!fixedAttachment) { + destRect = IntRect(tx, ty, w, h); + + int right = 0; + int bottom = 0; + // Scroll and Local. + if (fillLayer->origin() != BorderFillBox) { + left = borderLeft(); + right = borderRight(); + top = borderTop(); + bottom = borderBottom(); + if (fillLayer->origin() == ContentFillBox) { + left += paddingLeft(); + right += paddingRight(); + top += paddingTop(); + bottom += paddingBottom(); + } + } + + // The background of the box generated by the root element covers the entire canvas including + // its margins. Since those were added in already, we have to factor them out when computing + // the background positioning area. + if (isRoot()) { + positioningAreaSize = IntSize(toRenderBox(this)->width() - left - right, toRenderBox(this)->height() - top - bottom); + left += marginLeft(); + top += marginTop(); + } else + positioningAreaSize = IntSize(w - left - right, h - top - bottom); + } else { + destRect = viewRect(); + positioningAreaSize = destRect.size(); + } + + tileSize = calculateFillTileSize(fillLayer, positioningAreaSize); + + EFillRepeat backgroundRepeatX = fillLayer->repeatX(); + EFillRepeat backgroundRepeatY = fillLayer->repeatY(); + + int xPosition = fillLayer->xPosition().calcMinValue(positioningAreaSize.width() - tileSize.width(), true); + if (backgroundRepeatX == RepeatFill) + phase.setX(tileSize.width() ? tileSize.width() - (xPosition + left) % tileSize.width() : 0); + else { + destRect.move(max(xPosition + left, 0), 0); + phase.setX(-min(xPosition + left, 0)); + destRect.setWidth(tileSize.width() + min(xPosition + left, 0)); + } + + int yPosition = fillLayer->yPosition().calcMinValue(positioningAreaSize.height() - tileSize.height(), true); + if (backgroundRepeatY == RepeatFill) + phase.setY(tileSize.height() ? tileSize.height() - (yPosition + top) % tileSize.height() : 0); + else { + destRect.move(0, max(yPosition + top, 0)); + phase.setY(-min(yPosition + top, 0)); + destRect.setHeight(tileSize.height() + min(yPosition + top, 0)); + } + + if (fixedAttachment) + phase.move(max(tx - destRect.x(), 0), max(ty - destRect.y(), 0)); + + destRect.intersect(IntRect(tx, ty, w, h)); +} + +bool RenderBoxModelObject::paintNinePieceImage(GraphicsContext* graphicsContext, int tx, int ty, int w, int h, const RenderStyle* style, + const NinePieceImage& ninePieceImage, CompositeOperator op) +{ + StyleImage* styleImage = ninePieceImage.image(); + if (!styleImage) + return false; + + if (!styleImage->isLoaded()) + return true; // Never paint a nine-piece image incrementally, but don't paint the fallback borders either. + + if (!styleImage->canRender(style->effectiveZoom())) + return false; + + // FIXME: border-image is broken with full page zooming when tiling has to happen, since the tiling function + // doesn't have any understanding of the zoom that is in effect on the tile. + styleImage->setImageContainerSize(IntSize(w, h)); + IntSize imageSize = styleImage->imageSize(this, 1.0f); + int imageWidth = imageSize.width(); + int imageHeight = imageSize.height(); + + int topSlice = min(imageHeight, ninePieceImage.slices().top().calcValue(imageHeight)); + int bottomSlice = min(imageHeight, ninePieceImage.slices().bottom().calcValue(imageHeight)); + int leftSlice = min(imageWidth, ninePieceImage.slices().left().calcValue(imageWidth)); + int rightSlice = min(imageWidth, ninePieceImage.slices().right().calcValue(imageWidth)); + + ENinePieceImageRule hRule = ninePieceImage.horizontalRule(); + ENinePieceImageRule vRule = ninePieceImage.verticalRule(); + + bool fitToBorder = style->borderImage() == ninePieceImage; + + int leftWidth = fitToBorder ? style->borderLeftWidth() : leftSlice; + int topWidth = fitToBorder ? style->borderTopWidth() : topSlice; + int rightWidth = fitToBorder ? style->borderRightWidth() : rightSlice; + int bottomWidth = fitToBorder ? style->borderBottomWidth() : bottomSlice; + + bool drawLeft = leftSlice > 0 && leftWidth > 0; + bool drawTop = topSlice > 0 && topWidth > 0; + bool drawRight = rightSlice > 0 && rightWidth > 0; + bool drawBottom = bottomSlice > 0 && bottomWidth > 0; + bool drawMiddle = (imageWidth - leftSlice - rightSlice) > 0 && (w - leftWidth - rightWidth) > 0 && + (imageHeight - topSlice - bottomSlice) > 0 && (h - topWidth - bottomWidth) > 0; + + Image* image = styleImage->image(this, imageSize); + ColorSpace colorSpace = style->colorSpace(); + + if (drawLeft) { + // Paint the top and bottom left corners. + + // The top left corner rect is (tx, ty, leftWidth, topWidth) + // The rect to use from within the image is obtained from our slice, and is (0, 0, leftSlice, topSlice) + if (drawTop) + graphicsContext->drawImage(image, colorSpace, IntRect(tx, ty, leftWidth, topWidth), + IntRect(0, 0, leftSlice, topSlice), op); + + // The bottom left corner rect is (tx, ty + h - bottomWidth, leftWidth, bottomWidth) + // The rect to use from within the image is (0, imageHeight - bottomSlice, leftSlice, botomSlice) + if (drawBottom) + graphicsContext->drawImage(image, colorSpace, IntRect(tx, ty + h - bottomWidth, leftWidth, bottomWidth), + IntRect(0, imageHeight - bottomSlice, leftSlice, bottomSlice), op); + + // Paint the left edge. + // Have to scale and tile into the border rect. + graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx, ty + topWidth, leftWidth, + h - topWidth - bottomWidth), + IntRect(0, topSlice, leftSlice, imageHeight - topSlice - bottomSlice), + Image::StretchTile, (Image::TileRule)vRule, op); + } + + if (drawRight) { + // Paint the top and bottom right corners + // The top right corner rect is (tx + w - rightWidth, ty, rightWidth, topWidth) + // The rect to use from within the image is obtained from our slice, and is (imageWidth - rightSlice, 0, rightSlice, topSlice) + if (drawTop) + graphicsContext->drawImage(image, colorSpace, IntRect(tx + w - rightWidth, ty, rightWidth, topWidth), + IntRect(imageWidth - rightSlice, 0, rightSlice, topSlice), op); + + // The bottom right corner rect is (tx + w - rightWidth, ty + h - bottomWidth, rightWidth, bottomWidth) + // The rect to use from within the image is (imageWidth - rightSlice, imageHeight - bottomSlice, rightSlice, bottomSlice) + if (drawBottom) + graphicsContext->drawImage(image, colorSpace, IntRect(tx + w - rightWidth, ty + h - bottomWidth, rightWidth, bottomWidth), + IntRect(imageWidth - rightSlice, imageHeight - bottomSlice, rightSlice, bottomSlice), op); + + // Paint the right edge. + graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx + w - rightWidth, ty + topWidth, rightWidth, + h - topWidth - bottomWidth), + IntRect(imageWidth - rightSlice, topSlice, rightSlice, imageHeight - topSlice - bottomSlice), + Image::StretchTile, (Image::TileRule)vRule, op); + } + + // Paint the top edge. + if (drawTop) + graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx + leftWidth, ty, w - leftWidth - rightWidth, topWidth), + IntRect(leftSlice, 0, imageWidth - rightSlice - leftSlice, topSlice), + (Image::TileRule)hRule, Image::StretchTile, op); + + // Paint the bottom edge. + if (drawBottom) + graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx + leftWidth, ty + h - bottomWidth, + w - leftWidth - rightWidth, bottomWidth), + IntRect(leftSlice, imageHeight - bottomSlice, imageWidth - rightSlice - leftSlice, bottomSlice), + (Image::TileRule)hRule, Image::StretchTile, op); + + // Paint the middle. + if (drawMiddle) + graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx + leftWidth, ty + topWidth, w - leftWidth - rightWidth, + h - topWidth - bottomWidth), + IntRect(leftSlice, topSlice, imageWidth - rightSlice - leftSlice, imageHeight - topSlice - bottomSlice), + (Image::TileRule)hRule, (Image::TileRule)vRule, op); + + return true; +} + +#if HAVE(PATH_BASED_BORDER_RADIUS_DRAWING) +static bool borderWillArcInnerEdge(const IntSize& firstRadius, const IntSize& secondRadius, int firstBorderWidth, int secondBorderWidth, int middleBorderWidth) +{ + // FIXME: This test is insufficient. We need to take border style into account. + return (!firstRadius.width() || firstRadius.width() >= firstBorderWidth) + && (!firstRadius.height() || firstRadius.height() >= middleBorderWidth) + && (!secondRadius.width() || secondRadius.width() >= secondBorderWidth) + && (!secondRadius.height() || secondRadius.height() >= middleBorderWidth); +} + +void RenderBoxModelObject::paintBorder(GraphicsContext* graphicsContext, int tx, int ty, int w, int h, + const RenderStyle* style, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) +{ + if (paintNinePieceImage(graphicsContext, tx, ty, w, h, style, style->borderImage())) + return; + + if (graphicsContext->paintingDisabled()) + return; + + const Color& topColor = style->visitedDependentColor(CSSPropertyBorderTopColor); + const Color& bottomColor = style->visitedDependentColor(CSSPropertyBorderBottomColor); + const Color& leftColor = style->visitedDependentColor(CSSPropertyBorderLeftColor); + const Color& rightColor = style->visitedDependentColor(CSSPropertyBorderRightColor); + + bool topTransparent = style->borderTopIsTransparent(); + bool bottomTransparent = style->borderBottomIsTransparent(); + bool rightTransparent = style->borderRightIsTransparent(); + bool leftTransparent = style->borderLeftIsTransparent(); + + EBorderStyle topStyle = style->borderTopStyle(); + EBorderStyle bottomStyle = style->borderBottomStyle(); + EBorderStyle leftStyle = style->borderLeftStyle(); + EBorderStyle rightStyle = style->borderRightStyle(); + + bool horizontal = style->isHorizontalWritingMode(); + + bool renderTop = topStyle > BHIDDEN && !topTransparent && (horizontal || includeLogicalLeftEdge); + bool renderLeft = leftStyle > BHIDDEN && !leftTransparent && (!horizontal || includeLogicalLeftEdge); + bool renderRight = rightStyle > BHIDDEN && !rightTransparent && (!horizontal || includeLogicalRightEdge); + bool renderBottom = bottomStyle > BHIDDEN && !bottomTransparent && (horizontal || includeLogicalRightEdge); + + bool renderRadii = false; + Path roundedPath; + IntSize topLeft, topRight, bottomLeft, bottomRight; + IntRect borderRect(tx, ty, w, h); + + if (style->hasBorderRadius()) { + IntSize topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius; + style->getBorderRadiiForRect(borderRect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); + + int leftWidth = (!horizontal || includeLogicalLeftEdge) ? style->borderLeftWidth() : 0; + int rightWidth = (!horizontal || includeLogicalRightEdge) ? style->borderRightWidth() : 0; + int topWidth = (horizontal || includeLogicalLeftEdge) ? style->borderTopWidth() : 0; + int bottomWidth = (horizontal || includeLogicalRightEdge) ? style->borderBottomWidth() : 0; + + IntRect innerBorderRect = borderInnerRect(borderRect, topWidth, bottomWidth, leftWidth, rightWidth); + IntSize innerTopLeftRadius, innerTopRightRadius, innerBottomLeftRadius, innerBottomRightRadius; + + style->getInnerBorderRadiiForRectWithBorderWidths(innerBorderRect, topWidth, bottomWidth, leftWidth, rightWidth, innerTopLeftRadius, innerTopRightRadius, innerBottomLeftRadius, innerBottomRightRadius); + + IntSize innerTopLeft, innerTopRight, innerBottomLeft, innerBottomRight; + + if (includeLogicalLeftEdge) { + topLeft = topLeftRadius; + innerTopLeft = innerTopLeftRadius; + if (horizontal) { + bottomLeft = bottomLeftRadius; + innerBottomLeft = innerBottomLeftRadius; + } else { + topRight = topRightRadius; + innerTopRight = innerTopRightRadius; + } + } + + if (includeLogicalRightEdge) { + if (horizontal) { + topRight = topRightRadius; + innerTopRight = innerTopRightRadius; + } else { + bottomLeft = bottomLeftRadius; + innerBottomLeft = innerBottomLeftRadius; + } + bottomRight = bottomRightRadius; + innerBottomRight = innerBottomRightRadius; + } + + renderRadii = !topLeft.isZero() || !topRight.isZero() || !bottomLeft.isZero() || !bottomRight.isZero(); + + if (renderRadii) { + // Clip to the inner and outer radii rects. + graphicsContext->save(); + graphicsContext->addRoundedRectClip(borderRect, topLeft, topRight, bottomLeft, bottomRight); + graphicsContext->clipOutRoundedRect(innerBorderRect, innerTopLeft, innerTopRight, innerBottomLeft, innerBottomRight); + roundedPath.addRoundedRect(borderRect, topLeft, topRight, bottomLeft, bottomRight); + } + } + + bool upperLeftBorderStylesMatch = renderLeft && (topStyle == leftStyle) && (topColor == leftColor); + bool upperRightBorderStylesMatch = renderRight && (topStyle == rightStyle) && (topColor == rightColor) && (topStyle != OUTSET) && (topStyle != RIDGE) && (topStyle != INSET) && (topStyle != GROOVE); + bool lowerLeftBorderStylesMatch = renderLeft && (bottomStyle == leftStyle) && (bottomColor == leftColor) && (bottomStyle != OUTSET) && (bottomStyle != RIDGE) && (bottomStyle != INSET) && (bottomStyle != GROOVE); + bool lowerRightBorderStylesMatch = renderRight && (bottomStyle == rightStyle) && (bottomColor == rightColor); + + if (renderTop) { + int x = tx; + int x2 = tx + w; + + if (renderRadii && borderWillArcInnerEdge(topLeft, topRight, style->borderLeftWidth(), style->borderRightWidth(), style->borderTopWidth())) { + graphicsContext->save(); + clipBorderSidePolygon(graphicsContext, borderRect, topLeft, topRight, bottomLeft, bottomRight, BSTop, upperLeftBorderStylesMatch, upperRightBorderStylesMatch, style, includeLogicalLeftEdge, includeLogicalRightEdge); + float thickness = max(max(style->borderTopWidth(), style->borderLeftWidth()), style->borderRightWidth()); + drawBoxSideFromPath(graphicsContext, borderRect, roundedPath, style->borderTopWidth(), thickness, BSTop, style, topColor, topStyle); + graphicsContext->restore(); + } else { + bool ignoreLeft = (topColor == leftColor && topTransparent == leftTransparent && topStyle >= OUTSET + && (leftStyle == DOTTED || leftStyle == DASHED || leftStyle == SOLID || leftStyle == OUTSET)); + bool ignoreRight = (topColor == rightColor && topTransparent == rightTransparent && topStyle >= OUTSET + && (rightStyle == DOTTED || rightStyle == DASHED || rightStyle == SOLID || rightStyle == INSET)); + + drawLineForBoxSide(graphicsContext, x, ty, x2, ty + style->borderTopWidth(), BSTop, topColor, topStyle, + ignoreLeft ? 0 : style->borderLeftWidth(), ignoreRight ? 0 : style->borderRightWidth()); + } + } + + if (renderBottom) { + int x = tx; + int x2 = tx + w; + + if (renderRadii && borderWillArcInnerEdge(bottomLeft, bottomRight, style->borderLeftWidth(), style->borderRightWidth(), style->borderBottomWidth())) { + graphicsContext->save(); + clipBorderSidePolygon(graphicsContext, borderRect, topLeft, topRight, bottomLeft, bottomRight, BSBottom, lowerLeftBorderStylesMatch, lowerRightBorderStylesMatch, style, includeLogicalLeftEdge, includeLogicalRightEdge); + float thickness = max(max(style->borderBottomWidth(), style->borderLeftWidth()), style->borderRightWidth()); + drawBoxSideFromPath(graphicsContext, borderRect, roundedPath, style->borderBottomWidth(), thickness, BSBottom, style, bottomColor, bottomStyle); + graphicsContext->restore(); + } else { + bool ignoreLeft = (bottomColor == leftColor && bottomTransparent == leftTransparent && bottomStyle >= OUTSET + && (leftStyle == DOTTED || leftStyle == DASHED || leftStyle == SOLID || leftStyle == OUTSET)); + + bool ignoreRight = (bottomColor == rightColor && bottomTransparent == rightTransparent && bottomStyle >= OUTSET + && (rightStyle == DOTTED || rightStyle == DASHED || rightStyle == SOLID || rightStyle == INSET)); + + drawLineForBoxSide(graphicsContext, x, ty + h - style->borderBottomWidth(), x2, ty + h, BSBottom, bottomColor, + bottomStyle, ignoreLeft ? 0 : style->borderLeftWidth(), + ignoreRight ? 0 : style->borderRightWidth()); + } + } + + if (renderLeft) { + int y = ty; + int y2 = ty + h; + + if (renderRadii && borderWillArcInnerEdge(bottomLeft, topLeft, style->borderBottomWidth(), style->borderTopWidth(), style->borderLeftWidth())) { + graphicsContext->save(); + clipBorderSidePolygon(graphicsContext, borderRect, topLeft, topRight, bottomLeft, bottomRight, BSLeft, upperLeftBorderStylesMatch, lowerLeftBorderStylesMatch, style, includeLogicalLeftEdge, includeLogicalRightEdge); + float thickness = max(max(style->borderLeftWidth(), style->borderTopWidth()), style->borderBottomWidth()); + drawBoxSideFromPath(graphicsContext, borderRect, roundedPath, style->borderLeftWidth(), thickness, BSLeft, style, leftColor, leftStyle); + graphicsContext->restore(); + } else { + bool ignoreTop = (topColor == leftColor && topTransparent == leftTransparent && leftStyle >= OUTSET + && (topStyle == DOTTED || topStyle == DASHED || topStyle == SOLID || topStyle == OUTSET)); + + bool ignoreBottom = (bottomColor == leftColor && bottomTransparent == leftTransparent && leftStyle >= OUTSET + && (bottomStyle == DOTTED || bottomStyle == DASHED || bottomStyle == SOLID || bottomStyle == INSET)); + + drawLineForBoxSide(graphicsContext, tx, y, tx + style->borderLeftWidth(), y2, BSLeft, leftColor, + leftStyle, ignoreTop ? 0 : style->borderTopWidth(), ignoreBottom ? 0 : style->borderBottomWidth()); + } + } + + if (renderRight) { + if (renderRadii && borderWillArcInnerEdge(bottomRight, topRight, style->borderBottomWidth(), style->borderTopWidth(), style->borderRightWidth())) { + graphicsContext->save(); + clipBorderSidePolygon(graphicsContext, borderRect, topLeft, topRight, bottomLeft, bottomRight, BSRight, upperRightBorderStylesMatch, lowerRightBorderStylesMatch, style, includeLogicalLeftEdge, includeLogicalRightEdge); + float thickness = max(max(style->borderRightWidth(), style->borderTopWidth()), style->borderBottomWidth()); + drawBoxSideFromPath(graphicsContext, borderRect, roundedPath, style->borderRightWidth(), thickness, BSRight, style, rightColor, rightStyle); + graphicsContext->restore(); + } else { + bool ignoreTop = ((topColor == rightColor) && (topTransparent == rightTransparent) + && (rightStyle >= DOTTED || rightStyle == INSET) + && (topStyle == DOTTED || topStyle == DASHED || topStyle == SOLID || topStyle == OUTSET)); + + bool ignoreBottom = ((bottomColor == rightColor) && (bottomTransparent == rightTransparent) + && (rightStyle >= DOTTED || rightStyle == INSET) + && (bottomStyle == DOTTED || bottomStyle == DASHED || bottomStyle == SOLID || bottomStyle == INSET)); + + int y = ty; + int y2 = ty + h; + + drawLineForBoxSide(graphicsContext, tx + w - style->borderRightWidth(), y, tx + w, y2, BSRight, rightColor, + rightStyle, ignoreTop ? 0 : style->borderTopWidth(), + ignoreBottom ? 0 : style->borderBottomWidth()); + } + } + + if (renderRadii) + graphicsContext->restore(); +} +#else +void RenderBoxModelObject::paintBorder(GraphicsContext* graphicsContext, int tx, int ty, int w, int h, + const RenderStyle* style, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) +{ + // FIXME: This old version of paintBorder should be removed when all ports implement + // GraphicsContext::clipConvexPolygon()!! This should happen soon. + if (paintNinePieceImage(graphicsContext, tx, ty, w, h, style, style->borderImage())) + return; + + const Color& topColor = style->visitedDependentColor(CSSPropertyBorderTopColor); + const Color& bottomColor = style->visitedDependentColor(CSSPropertyBorderBottomColor); + const Color& leftColor = style->visitedDependentColor(CSSPropertyBorderLeftColor); + const Color& rightColor = style->visitedDependentColor(CSSPropertyBorderRightColor); + + bool topTransparent = style->borderTopIsTransparent(); + bool bottomTransparent = style->borderBottomIsTransparent(); + bool rightTransparent = style->borderRightIsTransparent(); + bool leftTransparent = style->borderLeftIsTransparent(); + + EBorderStyle topStyle = style->borderTopStyle(); + EBorderStyle bottomStyle = style->borderBottomStyle(); + EBorderStyle leftStyle = style->borderLeftStyle(); + EBorderStyle rightStyle = style->borderRightStyle(); + + bool horizontal = style->isHorizontalWritingMode(); + bool renderTop = topStyle > BHIDDEN && !topTransparent && (horizontal || includeLogicalLeftEdge); + bool renderLeft = leftStyle > BHIDDEN && !leftTransparent && (!horizontal || includeLogicalLeftEdge); + bool renderRight = rightStyle > BHIDDEN && !rightTransparent && (!horizontal || includeLogicalRightEdge); + bool renderBottom = bottomStyle > BHIDDEN && !bottomTransparent && (horizontal || includeLogicalRightEdge); + + bool renderRadii = false; + IntSize topLeft, topRight, bottomLeft, bottomRight; + + if (style->hasBorderRadius()) { + IntRect borderRect = IntRect(tx, ty, w, h); + + IntSize topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius; + style->getBorderRadiiForRect(borderRect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); + + if (includeLogicalLeftEdge) { + topLeft = topLeftRadius; + if (horizontal) + bottomLeft = bottomLeftRadius; + else + topRight = topRightRadius; + } + + if (includeLogicalRightEdge) { + if (horizontal) + topRight = topRightRadius; + else + bottomLeft = bottomLeftRadius; + bottomRight = bottomRightRadius; + } + + renderRadii = !topLeft.isZero() || !topRight.isZero() || !bottomLeft.isZero() || !bottomRight.isZero(); + + if (renderRadii) { + // Clip to the rounded rectangle. + graphicsContext->save(); + graphicsContext->addRoundedRectClip(borderRect, topLeft, topRight, bottomLeft, bottomRight); + } + } + + int firstAngleStart, secondAngleStart, firstAngleSpan, secondAngleSpan; + float thickness; + bool upperLeftBorderStylesMatch = renderLeft && (topStyle == leftStyle) && (topColor == leftColor); + bool upperRightBorderStylesMatch = renderRight && (topStyle == rightStyle) && (topColor == rightColor) && (topStyle != OUTSET) && (topStyle != RIDGE) && (topStyle != INSET) && (topStyle != GROOVE); + bool lowerLeftBorderStylesMatch = renderLeft && (bottomStyle == leftStyle) && (bottomColor == leftColor) && (bottomStyle != OUTSET) && (bottomStyle != RIDGE) && (bottomStyle != INSET) && (bottomStyle != GROOVE); + bool lowerRightBorderStylesMatch = renderRight && (bottomStyle == rightStyle) && (bottomColor == rightColor); + + if (renderTop) { + bool ignore_left = (renderRadii && topLeft.width() > 0) || + (topColor == leftColor && topTransparent == leftTransparent && topStyle >= OUTSET && + (leftStyle == DOTTED || leftStyle == DASHED || leftStyle == SOLID || leftStyle == OUTSET)); + + bool ignore_right = (renderRadii && topRight.width() > 0) || + (topColor == rightColor && topTransparent == rightTransparent && topStyle >= OUTSET && + (rightStyle == DOTTED || rightStyle == DASHED || rightStyle == SOLID || rightStyle == INSET)); + + int x = tx; + int x2 = tx + w; + if (renderRadii) { + x += topLeft.width(); + x2 -= topRight.width(); + } + + drawLineForBoxSide(graphicsContext, x, ty, x2, ty + style->borderTopWidth(), BSTop, topColor, topStyle, + ignore_left ? 0 : style->borderLeftWidth(), ignore_right ? 0 : style->borderRightWidth()); + + if (renderRadii) { + int leftY = ty; + + // We make the arc double thick and let the clip rect take care of clipping the extra off. + // We're doing this because it doesn't seem possible to match the curve of the clip exactly + // with the arc-drawing function. + thickness = style->borderTopWidth() * 2; + + if (topLeft.width()) { + int leftX = tx; + // The inner clip clips inside the arc. This is especially important for 1px borders. + bool applyLeftInnerClip = (style->borderLeftWidth() < topLeft.width()) + && (style->borderTopWidth() < topLeft.height()) + && (topStyle != DOUBLE || style->borderTopWidth() > 6); + if (applyLeftInnerClip) { + graphicsContext->save(); + graphicsContext->addInnerRoundedRectClip(IntRect(leftX, leftY, topLeft.width() * 2, topLeft.height() * 2), + style->borderTopWidth()); + } + + firstAngleStart = 90; + firstAngleSpan = upperLeftBorderStylesMatch ? 90 : 45; + + // Draw upper left arc + drawArcForBoxSide(graphicsContext, leftX, leftY, thickness, topLeft, firstAngleStart, firstAngleSpan, + BSTop, topColor, topStyle, true); + if (applyLeftInnerClip) + graphicsContext->restore(); + } + + if (topRight.width()) { + int rightX = tx + w - topRight.width() * 2; + bool applyRightInnerClip = (style->borderRightWidth() < topRight.width()) + && (style->borderTopWidth() < topRight.height()) + && (topStyle != DOUBLE || style->borderTopWidth() > 6); + if (applyRightInnerClip) { + graphicsContext->save(); + graphicsContext->addInnerRoundedRectClip(IntRect(rightX, leftY, topRight.width() * 2, topRight.height() * 2), + style->borderTopWidth()); + } + + if (upperRightBorderStylesMatch) { + secondAngleStart = 0; + secondAngleSpan = 90; + } else { + secondAngleStart = 45; + secondAngleSpan = 45; + } + + // Draw upper right arc + drawArcForBoxSide(graphicsContext, rightX, leftY, thickness, topRight, secondAngleStart, secondAngleSpan, + BSTop, topColor, topStyle, false); + if (applyRightInnerClip) + graphicsContext->restore(); + } + } + } + + if (renderBottom) { + bool ignore_left = (renderRadii && bottomLeft.width() > 0) || + (bottomColor == leftColor && bottomTransparent == leftTransparent && bottomStyle >= OUTSET && + (leftStyle == DOTTED || leftStyle == DASHED || leftStyle == SOLID || leftStyle == OUTSET)); + + bool ignore_right = (renderRadii && bottomRight.width() > 0) || + (bottomColor == rightColor && bottomTransparent == rightTransparent && bottomStyle >= OUTSET && + (rightStyle == DOTTED || rightStyle == DASHED || rightStyle == SOLID || rightStyle == INSET)); + + int x = tx; + int x2 = tx + w; + if (renderRadii) { + x += bottomLeft.width(); + x2 -= bottomRight.width(); + } + + drawLineForBoxSide(graphicsContext, x, ty + h - style->borderBottomWidth(), x2, ty + h, BSBottom, bottomColor, bottomStyle, + ignore_left ? 0 : style->borderLeftWidth(), ignore_right ? 0 : style->borderRightWidth()); + + if (renderRadii) { + thickness = style->borderBottomWidth() * 2; + + if (bottomLeft.width()) { + int leftX = tx; + int leftY = ty + h - bottomLeft.height() * 2; + bool applyLeftInnerClip = (style->borderLeftWidth() < bottomLeft.width()) + && (style->borderBottomWidth() < bottomLeft.height()) + && (bottomStyle != DOUBLE || style->borderBottomWidth() > 6); + if (applyLeftInnerClip) { + graphicsContext->save(); + graphicsContext->addInnerRoundedRectClip(IntRect(leftX, leftY, bottomLeft.width() * 2, bottomLeft.height() * 2), + style->borderBottomWidth()); + } + + if (lowerLeftBorderStylesMatch) { + firstAngleStart = 180; + firstAngleSpan = 90; + } else { + firstAngleStart = 225; + firstAngleSpan = 45; + } + + // Draw lower left arc + drawArcForBoxSide(graphicsContext, leftX, leftY, thickness, bottomLeft, firstAngleStart, firstAngleSpan, + BSBottom, bottomColor, bottomStyle, true); + if (applyLeftInnerClip) + graphicsContext->restore(); + } + + if (bottomRight.width()) { + int rightY = ty + h - bottomRight.height() * 2; + int rightX = tx + w - bottomRight.width() * 2; + bool applyRightInnerClip = (style->borderRightWidth() < bottomRight.width()) + && (style->borderBottomWidth() < bottomRight.height()) + && (bottomStyle != DOUBLE || style->borderBottomWidth() > 6); + if (applyRightInnerClip) { + graphicsContext->save(); + graphicsContext->addInnerRoundedRectClip(IntRect(rightX, rightY, bottomRight.width() * 2, bottomRight.height() * 2), + style->borderBottomWidth()); + } + + secondAngleStart = 270; + secondAngleSpan = lowerRightBorderStylesMatch ? 90 : 45; + + // Draw lower right arc + drawArcForBoxSide(graphicsContext, rightX, rightY, thickness, bottomRight, secondAngleStart, secondAngleSpan, + BSBottom, bottomColor, bottomStyle, false); + if (applyRightInnerClip) + graphicsContext->restore(); + } + } + } + + if (renderLeft) { + bool ignore_top = (renderRadii && topLeft.height() > 0) || + (topColor == leftColor && topTransparent == leftTransparent && leftStyle >= OUTSET && + (topStyle == DOTTED || topStyle == DASHED || topStyle == SOLID || topStyle == OUTSET)); + + bool ignore_bottom = (renderRadii && bottomLeft.height() > 0) || + (bottomColor == leftColor && bottomTransparent == leftTransparent && leftStyle >= OUTSET && + (bottomStyle == DOTTED || bottomStyle == DASHED || bottomStyle == SOLID || bottomStyle == INSET)); + + int y = ty; + int y2 = ty + h; + if (renderRadii) { + y += topLeft.height(); + y2 -= bottomLeft.height(); + } + + drawLineForBoxSide(graphicsContext, tx, y, tx + style->borderLeftWidth(), y2, BSLeft, leftColor, leftStyle, + ignore_top ? 0 : style->borderTopWidth(), ignore_bottom ? 0 : style->borderBottomWidth()); + + if (renderRadii && (!upperLeftBorderStylesMatch || !lowerLeftBorderStylesMatch)) { + int topX = tx; + thickness = style->borderLeftWidth() * 2; + + if (!upperLeftBorderStylesMatch && topLeft.width()) { + int topY = ty; + bool applyTopInnerClip = (style->borderLeftWidth() < topLeft.width()) + && (style->borderTopWidth() < topLeft.height()) + && (leftStyle != DOUBLE || style->borderLeftWidth() > 6); + if (applyTopInnerClip) { + graphicsContext->save(); + graphicsContext->addInnerRoundedRectClip(IntRect(topX, topY, topLeft.width() * 2, topLeft.height() * 2), + style->borderLeftWidth()); + } + + firstAngleStart = 135; + firstAngleSpan = 45; + + // Draw top left arc + drawArcForBoxSide(graphicsContext, topX, topY, thickness, topLeft, firstAngleStart, firstAngleSpan, + BSLeft, leftColor, leftStyle, true); + if (applyTopInnerClip) + graphicsContext->restore(); + } + + if (!lowerLeftBorderStylesMatch && bottomLeft.width()) { + int bottomY = ty + h - bottomLeft.height() * 2; + bool applyBottomInnerClip = (style->borderLeftWidth() < bottomLeft.width()) + && (style->borderBottomWidth() < bottomLeft.height()) + && (leftStyle != DOUBLE || style->borderLeftWidth() > 6); + if (applyBottomInnerClip) { + graphicsContext->save(); + graphicsContext->addInnerRoundedRectClip(IntRect(topX, bottomY, bottomLeft.width() * 2, bottomLeft.height() * 2), + style->borderLeftWidth()); + } + + secondAngleStart = 180; + secondAngleSpan = 45; + + // Draw bottom left arc + drawArcForBoxSide(graphicsContext, topX, bottomY, thickness, bottomLeft, secondAngleStart, secondAngleSpan, + BSLeft, leftColor, leftStyle, false); + if (applyBottomInnerClip) + graphicsContext->restore(); + } + } + } + + if (renderRight) { + bool ignore_top = (renderRadii && topRight.height() > 0) || + ((topColor == rightColor) && (topTransparent == rightTransparent) && + (rightStyle >= DOTTED || rightStyle == INSET) && + (topStyle == DOTTED || topStyle == DASHED || topStyle == SOLID || topStyle == OUTSET)); + + bool ignore_bottom = (renderRadii && bottomRight.height() > 0) || + ((bottomColor == rightColor) && (bottomTransparent == rightTransparent) && + (rightStyle >= DOTTED || rightStyle == INSET) && + (bottomStyle == DOTTED || bottomStyle == DASHED || bottomStyle == SOLID || bottomStyle == INSET)); + + int y = ty; + int y2 = ty + h; + if (renderRadii) { + y += topRight.height(); + y2 -= bottomRight.height(); + } + + drawLineForBoxSide(graphicsContext, tx + w - style->borderRightWidth(), y, tx + w, y2, BSRight, rightColor, rightStyle, + ignore_top ? 0 : style->borderTopWidth(), ignore_bottom ? 0 : style->borderBottomWidth()); + + if (renderRadii && (!upperRightBorderStylesMatch || !lowerRightBorderStylesMatch)) { + thickness = style->borderRightWidth() * 2; + + if (!upperRightBorderStylesMatch && topRight.width()) { + int topX = tx + w - topRight.width() * 2; + int topY = ty; + bool applyTopInnerClip = (style->borderRightWidth() < topRight.width()) + && (style->borderTopWidth() < topRight.height()) + && (rightStyle != DOUBLE || style->borderRightWidth() > 6); + if (applyTopInnerClip) { + graphicsContext->save(); + graphicsContext->addInnerRoundedRectClip(IntRect(topX, topY, topRight.width() * 2, topRight.height() * 2), + style->borderRightWidth()); + } + + firstAngleStart = 0; + firstAngleSpan = 45; + + // Draw top right arc + drawArcForBoxSide(graphicsContext, topX, topY, thickness, topRight, firstAngleStart, firstAngleSpan, + BSRight, rightColor, rightStyle, true); + if (applyTopInnerClip) + graphicsContext->restore(); + } + + if (!lowerRightBorderStylesMatch && bottomRight.width()) { + int bottomX = tx + w - bottomRight.width() * 2; + int bottomY = ty + h - bottomRight.height() * 2; + bool applyBottomInnerClip = (style->borderRightWidth() < bottomRight.width()) + && (style->borderBottomWidth() < bottomRight.height()) + && (rightStyle != DOUBLE || style->borderRightWidth() > 6); + if (applyBottomInnerClip) { + graphicsContext->save(); + graphicsContext->addInnerRoundedRectClip(IntRect(bottomX, bottomY, bottomRight.width() * 2, bottomRight.height() * 2), + style->borderRightWidth()); + } + + secondAngleStart = 315; + secondAngleSpan = 45; + + // Draw bottom right arc + drawArcForBoxSide(graphicsContext, bottomX, bottomY, thickness, bottomRight, secondAngleStart, secondAngleSpan, + BSRight, rightColor, rightStyle, false); + if (applyBottomInnerClip) + graphicsContext->restore(); + } + } + } + + if (renderRadii) + graphicsContext->restore(); +} +#endif + +void RenderBoxModelObject::clipBorderSidePolygon(GraphicsContext* graphicsContext, const IntRect& box, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, + const BoxSide side, bool firstEdgeMatches, bool secondEdgeMatches, const RenderStyle* style, + bool includeLogicalLeftEdge, bool includeLogicalRightEdge) +{ + FloatPoint quad[4]; + int tx = box.x(); + int ty = box.y(); + int w = box.width(); + int h = box.height(); + + bool horizontal = style->isHorizontalWritingMode(); + int leftWidth = (!horizontal || includeLogicalLeftEdge) ? style->borderLeftWidth() : 0; + int rightWidth = (!horizontal || includeLogicalRightEdge) ? style->borderRightWidth() : 0; + int topWidth = (horizontal || includeLogicalLeftEdge) ? style->borderTopWidth() : 0; + int bottomWidth = (horizontal || includeLogicalRightEdge) ? style->borderBottomWidth() : 0; + + // For each side, create an array of FloatPoints where each point is based on whichever value in each corner + // is larger -- the radius width/height or the border width/height -- as appropriate. + switch (side) { + case BSTop: + quad[0] = FloatPoint(tx, ty); + quad[1] = FloatPoint(tx + max(topLeft.width(), leftWidth), ty + max(topLeft.height(), topWidth)); + quad[2] = FloatPoint(tx + w - max(topRight.width(), rightWidth), ty + max(topRight.height(), topWidth)); + quad[3] = FloatPoint(tx + w, ty); + break; + case BSLeft: + quad[0] = FloatPoint(tx, ty); + quad[1] = FloatPoint(tx + max(topLeft.width(), leftWidth), ty + max(topLeft.height(), topWidth)); + quad[2] = FloatPoint(tx + max(bottomLeft.width(), leftWidth), ty + h - max(bottomLeft.height(), bottomWidth)); + quad[3] = FloatPoint(tx, ty + h); + break; + case BSBottom: + quad[0] = FloatPoint(tx, ty + h); + quad[1] = FloatPoint(tx + max(bottomLeft.width(), leftWidth), ty + h - max(bottomLeft.height(), bottomWidth)); + quad[2] = FloatPoint(tx + w - max(bottomRight.width(), rightWidth), ty + h - max(bottomRight.height(), bottomWidth)); + quad[3] = FloatPoint(tx + w, ty + h); + break; + case BSRight: + quad[0] = FloatPoint(tx + w, ty); + quad[1] = FloatPoint(tx + w - max(topRight.width(), rightWidth), ty + max(topRight.height(), topWidth)); + quad[2] = FloatPoint(tx + w - max(bottomRight.width(), rightWidth), ty + h - max(bottomRight.height(), bottomWidth)); + quad[3] = FloatPoint(tx + w, ty + h); + break; + default: + break; + } + + // If the border matches both of its adjacent sides, don't anti-alias the clip, and + // if neither side matches, anti-alias the clip. + if (firstEdgeMatches == secondEdgeMatches) { + graphicsContext->clipConvexPolygon(4, quad, !firstEdgeMatches); + return; + } + + FloatPoint firstQuad[4]; + firstQuad[0] = quad[0]; + firstQuad[1] = quad[1]; + firstQuad[2] = side == BSTop || side == BSBottom ? FloatPoint(quad[3].x(), quad[2].y()) + : FloatPoint(quad[2].x(), quad[3].y()); + firstQuad[3] = quad[3]; + graphicsContext->clipConvexPolygon(4, firstQuad, !firstEdgeMatches); + + FloatPoint secondQuad[4]; + secondQuad[0] = quad[0]; + secondQuad[1] = side == BSTop || side == BSBottom ? FloatPoint(quad[0].x(), quad[1].y()) + : FloatPoint(quad[1].x(), quad[0].y()); + secondQuad[2] = quad[2]; + secondQuad[3] = quad[3]; + graphicsContext->clipConvexPolygon(4, secondQuad, !secondEdgeMatches); +} + +static inline void uniformlyExpandBorderRadii(int delta, IntSize& topLeft, IntSize& topRight, IntSize& bottomLeft, IntSize& bottomRight) +{ + topLeft.expand(delta, delta); + topLeft.clampNegativeToZero(); + topRight.expand(delta, delta); + topRight.clampNegativeToZero(); + bottomLeft.expand(delta, delta); + bottomLeft.clampNegativeToZero(); + bottomRight.expand(delta, delta); + bottomRight.clampNegativeToZero(); +} + +void RenderBoxModelObject::paintBoxShadow(GraphicsContext* context, int tx, int ty, int w, int h, const RenderStyle* s, ShadowStyle shadowStyle, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) +{ + // FIXME: Deal with border-image. Would be great to use border-image as a mask. + + if (context->paintingDisabled()) + return; + + IntRect rect(tx, ty, w, h); + IntSize topLeft; + IntSize topRight; + IntSize bottomLeft; + IntSize bottomRight; + + bool hasBorderRadius = s->hasBorderRadius(); + bool isHorizontal = s->isHorizontalWritingMode(); + if (hasBorderRadius && (includeLogicalLeftEdge || includeLogicalRightEdge)) { + IntSize topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius; + s->getBorderRadiiForRect(rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); + + if (includeLogicalLeftEdge) { + if (shadowStyle == Inset) { + topLeftRadius.expand(-borderLeft(), -borderTop()); + topLeftRadius.clampNegativeToZero(); + if (isHorizontal) { + bottomLeftRadius.expand(-borderLeft(), -borderBottom()); + bottomLeftRadius.clampNegativeToZero(); + } else { + topRightRadius.expand(-borderRight(), -borderTop()); + topRightRadius.clampNegativeToZero(); + } + } + topLeft = topLeftRadius; + if (isHorizontal) + bottomLeft = bottomLeftRadius; + else + topRight = topRightRadius; + } + if (includeLogicalRightEdge) { + if (shadowStyle == Inset) { + if (isHorizontal) { + topRightRadius.expand(-borderRight(), -borderTop()); + topRightRadius.clampNegativeToZero(); + } else { + bottomLeftRadius.expand(-borderLeft(), -borderBottom()); + bottomLeftRadius.clampNegativeToZero(); + } + bottomRightRadius.expand(-borderRight(), -borderBottom()); + bottomRightRadius.clampNegativeToZero(); + } + if (isHorizontal) + topRight = topRightRadius; + else + bottomLeft = bottomLeftRadius; + bottomRight = bottomRightRadius; + } + } + + if (shadowStyle == Inset) { + rect.move(includeLogicalLeftEdge || !isHorizontal ? borderLeft() : 0, includeLogicalLeftEdge || isHorizontal ? borderTop() : 0); + rect.setWidth(rect.width() - ((includeLogicalLeftEdge || !isHorizontal) ? borderLeft() : 0) - ((includeLogicalRightEdge || !isHorizontal) ? borderRight() : 0)); + rect.setHeight(rect.height() - ((includeLogicalLeftEdge || isHorizontal) ? borderTop() : 0) - ((includeLogicalRightEdge || isHorizontal) ? borderBottom() : 0)); + } + + bool hasOpaqueBackground = s->visitedDependentColor(CSSPropertyBackgroundColor).isValid() && s->visitedDependentColor(CSSPropertyBackgroundColor).alpha() == 255; + for (const ShadowData* shadow = s->boxShadow(); shadow; shadow = shadow->next()) { + if (shadow->style() != shadowStyle) + continue; + + IntSize shadowOffset(shadow->x(), shadow->y()); + int shadowBlur = shadow->blur(); + int shadowSpread = shadow->spread(); + const Color& shadowColor = shadow->color(); + + if (shadow->style() == Normal) { + IntRect fillRect(rect); + fillRect.inflate(shadowSpread); + if (fillRect.isEmpty()) + continue; + + IntRect shadowRect(rect); + shadowRect.inflate(shadowBlur + shadowSpread); + shadowRect.move(shadowOffset); + + context->save(); + context->clip(shadowRect); + + // Move the fill just outside the clip, adding 1 pixel separation so that the fill does not + // bleed in (due to antialiasing) if the context is transformed. + IntSize extraOffset(w + max(0, shadowOffset.width()) + shadowBlur + 2 * shadowSpread + 1, 0); + shadowOffset -= extraOffset; + fillRect.move(extraOffset); + + context->setShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace()); + if (hasBorderRadius) { + IntRect rectToClipOut = rect; + IntSize topLeftToClipOut = topLeft; + IntSize topRightToClipOut = topRight; + IntSize bottomLeftToClipOut = bottomLeft; + IntSize bottomRightToClipOut = bottomRight; + + IntSize topLeftToFill = topLeft; + IntSize topRightToFill = topRight; + IntSize bottomLeftToFill = bottomLeft; + IntSize bottomRightToFill = bottomRight; + if (shadowSpread < 0) + uniformlyExpandBorderRadii(shadowSpread, topLeftToFill, topRightToFill, bottomLeftToFill, bottomRightToFill); + + // If the box is opaque, it is unnecessary to clip it out. However, doing so saves time + // when painting the shadow. On the other hand, it introduces subpixel gaps along the + // corners. Those are avoided by insetting the clipping path by one pixel. + if (hasOpaqueBackground) { + rectToClipOut.inflate(-1); + uniformlyExpandBorderRadii(-1, topLeftToClipOut, topRightToClipOut, bottomLeftToClipOut, bottomRightToClipOut); + } + + if (!rectToClipOut.isEmpty()) + context->clipOutRoundedRect(rectToClipOut, topLeftToClipOut, topRightToClipOut, bottomLeftToClipOut, bottomRightToClipOut); + context->fillRoundedRect(fillRect, topLeftToFill, topRightToFill, bottomLeftToFill, bottomRightToFill, Color::black, s->colorSpace()); + } else { + IntRect rectToClipOut = rect; + + // If the box is opaque, it is unnecessary to clip it out. However, doing so saves time + // when painting the shadow. On the other hand, it introduces subpixel gaps along the + // edges if they are not pixel-aligned. Those are avoided by insetting the clipping path + // by one pixel. + if (hasOpaqueBackground) { + AffineTransform currentTransformation = context->getCTM(); + if (currentTransformation.a() != 1 || (currentTransformation.d() != 1 && currentTransformation.d() != -1) + || currentTransformation.b() || currentTransformation.c()) + rectToClipOut.inflate(-1); + } + + if (!rectToClipOut.isEmpty()) + context->clipOut(rectToClipOut); + context->fillRect(fillRect, Color::black, s->colorSpace()); + } + + context->restore(); + } else { + // Inset shadow. + IntRect holeRect(rect); + holeRect.inflate(-shadowSpread); + + if (holeRect.isEmpty()) { + if (hasBorderRadius) + context->fillRoundedRect(rect, topLeft, topRight, bottomLeft, bottomRight, shadowColor, s->colorSpace()); + else + context->fillRect(rect, shadowColor, s->colorSpace()); + continue; + } + + if (!includeLogicalLeftEdge) { + if (isHorizontal) { + holeRect.move(-max(shadowOffset.width(), 0) - shadowBlur, 0); + holeRect.setWidth(holeRect.width() + max(shadowOffset.width(), 0) + shadowBlur); + } else { + holeRect.move(0, -max(shadowOffset.height(), 0) - shadowBlur); + holeRect.setHeight(holeRect.height() + max(shadowOffset.height(), 0) + shadowBlur); + } + } + if (!includeLogicalRightEdge) { + if (isHorizontal) + holeRect.setWidth(holeRect.width() - min(shadowOffset.width(), 0) + shadowBlur); + else + holeRect.setHeight(holeRect.height() - min(shadowOffset.height(), 0) + shadowBlur); + } + + Color fillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), 255); + + IntRect outerRect(rect); + outerRect.inflateX(w - 2 * shadowSpread); + outerRect.inflateY(h - 2 * shadowSpread); + + context->save(); + + Path path; + if (hasBorderRadius) { + path.addRoundedRect(rect, topLeft, topRight, bottomLeft, bottomRight); + context->clip(path); + path.clear(); + } else + context->clip(rect); + + IntSize extraOffset(2 * w + max(0, shadowOffset.width()) + shadowBlur - 2 * shadowSpread + 1, 0); + context->translate(extraOffset.width(), extraOffset.height()); + shadowOffset -= extraOffset; + + path.addRect(outerRect); + + if (hasBorderRadius) { + if (shadowSpread > 0) + uniformlyExpandBorderRadii(-shadowSpread, topLeft, topRight, bottomLeft, bottomRight); + path.addRoundedRect(holeRect, topLeft, topRight, bottomLeft, bottomRight); + } else + path.addRect(holeRect); + + context->setFillRule(RULE_EVENODD); + context->setFillColor(fillColor, s->colorSpace()); + context->setShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace()); + context->fillPath(path); + + context->restore(); + } + } +} + +int RenderBoxModelObject::containingBlockLogicalWidthForContent() const +{ + return containingBlock()->availableLogicalWidth(); +} + +RenderBoxModelObject* RenderBoxModelObject::continuation() const +{ + if (!continuationMap) + return 0; + return continuationMap->get(this); +} + +void RenderBoxModelObject::setContinuation(RenderBoxModelObject* continuation) +{ + if (continuation) { + if (!continuationMap) + continuationMap = new ContinuationMap; + continuationMap->set(this, continuation); + } else { + if (continuationMap) + continuationMap->remove(this); + } +} + +} // namespace WebCore |