| Index: Source/core/paint/BoxPainter.cpp
|
| diff --git a/Source/core/paint/BoxPainter.cpp b/Source/core/paint/BoxPainter.cpp
|
| index 684e70fba3c259d815c22e3b588e37f092ba9a78..e393f24ce02dc610f1303fbfc00604d642ce6ad8 100644
|
| --- a/Source/core/paint/BoxPainter.cpp
|
| +++ b/Source/core/paint/BoxPainter.cpp
|
| @@ -5,13 +5,22 @@
|
| #include "config.h"
|
| #include "core/paint/BoxPainter.h"
|
|
|
| +#include "core/HTMLNames.h"
|
| +#include "core/frame/Settings.h"
|
| +#include "core/html/HTMLFrameOwnerElement.h"
|
| +#include "core/paint/BackgroundImageGeometry.h"
|
| #include "core/paint/BoxDecorationData.h"
|
| +#include "core/rendering/ImageQualityController.h"
|
| #include "core/rendering/PaintInfo.h"
|
| #include "core/rendering/RenderBox.h"
|
| +#include "core/rendering/RenderBoxModelObject.h"
|
| #include "core/rendering/RenderLayer.h"
|
| #include "core/rendering/RenderTable.h"
|
| #include "core/rendering/RenderTheme.h"
|
| #include "core/rendering/RenderView.h"
|
| +#include "core/rendering/compositing/CompositedLayerMapping.h"
|
| +#include "core/rendering/style/ShadowList.h"
|
| +#include "platform/LengthFunctions.h"
|
| #include "platform/geometry/LayoutPoint.h"
|
| #include "platform/graphics/GraphicsContextStateSaver.h"
|
|
|
| @@ -157,8 +166,8 @@ void BoxPainter::paintFillLayers(const PaintInfo& paintInfo, const Color& c, con
|
|
|
| // Paint the document's base background color outside the transparency layer,
|
| // so that the background images don't blend with this color: http://crbug.com/389039.
|
| - if (isBaseColorVisible && m_renderBox.isDocumentElementWithOpaqueBackground()) {
|
| - m_renderBox.paintRootBackgroundColor(paintInfo, rect, Color());
|
| + if (isBaseColorVisible && isDocumentElementWithOpaqueBackground()) {
|
| + paintRootBackgroundColor(paintInfo, rect, Color());
|
| skipBaseColor = true;
|
| }
|
| context->beginTransparencyLayer(1);
|
| @@ -175,7 +184,307 @@ void BoxPainter::paintFillLayers(const PaintInfo& paintInfo, const Color& c, con
|
| void BoxPainter::paintFillLayer(const PaintInfo& paintInfo, const Color& c, const FillLayer& fillLayer, const LayoutRect& rect,
|
| BackgroundBleedAvoidance bleedAvoidance, CompositeOperator op, RenderObject* backgroundObject, bool skipBaseColor)
|
| {
|
| - m_renderBox.paintFillLayerExtended(paintInfo, c, fillLayer, rect, bleedAvoidance, 0, LayoutSize(), op, backgroundObject, skipBaseColor);
|
| + paintFillLayerExtended(paintInfo, c, fillLayer, rect, bleedAvoidance, 0, LayoutSize(), op, backgroundObject, skipBaseColor);
|
| +}
|
| +
|
| +void BoxPainter::applyBoxShadowForBackground(GraphicsContext* context)
|
| +{
|
| + const ShadowList* shadowList = m_renderBox.style()->boxShadow();
|
| + ASSERT(shadowList);
|
| + for (size_t i = shadowList->shadows().size(); i--; ) {
|
| + const ShadowData& boxShadow = shadowList->shadows()[i];
|
| + if (boxShadow.style() != Normal)
|
| + continue;
|
| + FloatSize shadowOffset(boxShadow.x(), boxShadow.y());
|
| + context->setShadow(shadowOffset, boxShadow.blur(), boxShadow.color(),
|
| + DrawLooperBuilder::ShadowRespectsTransforms, DrawLooperBuilder::ShadowIgnoresAlpha);
|
| + return;
|
| + }
|
| +}
|
| +
|
| +// FIXME: See crbug.com/382491. The use of getCTM in this context is incorrect because the matrix returned does not
|
| +// include scales applied at raster time, such as the device zoom.
|
| +static LayoutRect shrinkRectByOnePixel(GraphicsContext* context, const LayoutRect& rect)
|
| +{
|
| + LayoutRect shrunkRect = rect;
|
| + AffineTransform transform = context->getCTM();
|
| + shrunkRect.inflateX(-static_cast<LayoutUnit>(ceil(1 / transform.xScale())));
|
| + shrunkRect.inflateY(-static_cast<LayoutUnit>(ceil(1 / transform.yScale())));
|
| + return shrunkRect;
|
| +}
|
| +
|
| +RoundedRect BoxPainter::getBackgroundRoundedRect(const LayoutRect& borderRect, InlineFlowBox* box, LayoutUnit inlineBoxWidth, LayoutUnit inlineBoxHeight,
|
| + bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const
|
| +{
|
| + RoundedRect border = m_renderBox.style()->getRoundedBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge);
|
| + if (box && (box->nextLineBox() || box->prevLineBox())) {
|
| + RoundedRect segmentBorder = m_renderBox.style()->getRoundedBorderFor(LayoutRect(0, 0, inlineBoxWidth, inlineBoxHeight), includeLogicalLeftEdge, includeLogicalRightEdge);
|
| + border.setRadii(segmentBorder.radii());
|
| + }
|
| +
|
| + return border;
|
| +}
|
| +
|
| +RoundedRect BoxPainter::backgroundRoundedRectAdjustedForBleedAvoidance(GraphicsContext* context, const LayoutRect& borderRect, BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox* box, const LayoutSize& boxSize, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const
|
| +{
|
| + if (bleedAvoidance == BackgroundBleedShrinkBackground) {
|
| + // We shrink the rectangle by one pixel on each side because the bleed is one pixel maximum.
|
| + return getBackgroundRoundedRect(shrinkRectByOnePixel(context, borderRect), box, boxSize.width(), boxSize.height(), includeLogicalLeftEdge, includeLogicalRightEdge);
|
| + }
|
| + if (bleedAvoidance == BackgroundBleedBackgroundOverBorder)
|
| + return m_renderBox.style()->getRoundedInnerBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge);
|
| +
|
| + return getBackgroundRoundedRect(borderRect, box, boxSize.width(), boxSize.height(), includeLogicalLeftEdge, includeLogicalRightEdge);
|
| +}
|
| +
|
| +void BoxPainter::clipRoundedInnerRect(GraphicsContext * context, const LayoutRect& rect, const RoundedRect& clipRect)
|
| +{
|
| + if (clipRect.isRenderable()) {
|
| + context->clipRoundedRect(clipRect);
|
| + } else {
|
| + // We create a rounded rect for each of the corners and clip it, while making sure we clip opposing corners together.
|
| + if (!clipRect.radii().topLeft().isEmpty() || !clipRect.radii().bottomRight().isEmpty()) {
|
| + IntRect topCorner(clipRect.rect().x(), clipRect.rect().y(), rect.maxX() - clipRect.rect().x(), rect.maxY() - clipRect.rect().y());
|
| + RoundedRect::Radii topCornerRadii;
|
| + topCornerRadii.setTopLeft(clipRect.radii().topLeft());
|
| + context->clipRoundedRect(RoundedRect(topCorner, topCornerRadii));
|
| +
|
| + IntRect bottomCorner(rect.x(), rect.y(), clipRect.rect().maxX() - rect.x(), clipRect.rect().maxY() - rect.y());
|
| + RoundedRect::Radii bottomCornerRadii;
|
| + bottomCornerRadii.setBottomRight(clipRect.radii().bottomRight());
|
| + context->clipRoundedRect(RoundedRect(bottomCorner, bottomCornerRadii));
|
| + }
|
| +
|
| + if (!clipRect.radii().topRight().isEmpty() || !clipRect.radii().bottomLeft().isEmpty()) {
|
| + IntRect topCorner(rect.x(), clipRect.rect().y(), clipRect.rect().maxX() - rect.x(), rect.maxY() - clipRect.rect().y());
|
| + RoundedRect::Radii topCornerRadii;
|
| + topCornerRadii.setTopRight(clipRect.radii().topRight());
|
| + context->clipRoundedRect(RoundedRect(topCorner, topCornerRadii));
|
| +
|
| + IntRect bottomCorner(clipRect.rect().x(), rect.y(), rect.maxX() - clipRect.rect().x(), clipRect.rect().maxY() - rect.y());
|
| + RoundedRect::Radii bottomCornerRadii;
|
| + bottomCornerRadii.setBottomLeft(clipRect.radii().bottomLeft());
|
| + context->clipRoundedRect(RoundedRect(bottomCorner, bottomCornerRadii));
|
| + }
|
| + }
|
| +}
|
| +
|
| +void BoxPainter::paintFillLayerExtended(const PaintInfo& paintInfo, const Color& color, const FillLayer& bgLayer, const LayoutRect& rect,
|
| + BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox* box, const LayoutSize& boxSize, CompositeOperator op, RenderObject* backgroundObject, bool skipBaseColor)
|
| +{
|
| + GraphicsContext* context = paintInfo.context;
|
| + if (rect.isEmpty())
|
| + return;
|
| +
|
| + bool includeLeftEdge = box ? box->includeLogicalLeftEdge() : true;
|
| + bool includeRightEdge = box ? box->includeLogicalRightEdge() : true;
|
| +
|
| + bool hasRoundedBorder = m_renderBox.style()->hasBorderRadius() && (includeLeftEdge || includeRightEdge);
|
| + bool clippedWithLocalScrolling = m_renderBox.hasOverflowClip() && bgLayer.attachment() == LocalBackgroundAttachment;
|
| + bool isBorderFill = bgLayer.clip() == BorderFillBox;
|
| + bool isDocumentElementRenderer = m_renderBox.isDocumentElement();
|
| + bool isBottomLayer = !bgLayer.next();
|
| +
|
| + Color bgColor = color;
|
| + StyleImage* bgImage = bgLayer.image();
|
| + bool shouldPaintBackgroundImage = bgImage && bgImage->canRender(m_renderBox, m_renderBox.style()->effectiveZoom());
|
| +
|
| + bool forceBackgroundToWhite = false;
|
| + if (m_renderBox.document().printing()) {
|
| + if (m_renderBox.style()->printColorAdjust() == PrintColorAdjustEconomy)
|
| + forceBackgroundToWhite = true;
|
| + if (m_renderBox.document().settings() && m_renderBox.document().settings()->shouldPrintBackgrounds())
|
| + forceBackgroundToWhite = false;
|
| + }
|
| +
|
| + // When printing backgrounds is disabled or using economy mode,
|
| + // 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 (forceBackgroundToWhite) {
|
| + // Note that we can't reuse this variable below because the bgColor might be changed
|
| + bool shouldPaintBackgroundColor = isBottomLayer && bgColor.alpha();
|
| + if (shouldPaintBackgroundImage || shouldPaintBackgroundColor) {
|
| + bgColor = Color::white;
|
| + shouldPaintBackgroundImage = false;
|
| + }
|
| + }
|
| +
|
| + bool colorVisible = bgColor.alpha();
|
| +
|
| + // Fast path for drawing simple color backgrounds.
|
| + if (!isDocumentElementRenderer && !clippedWithLocalScrolling && !shouldPaintBackgroundImage && isBorderFill && isBottomLayer) {
|
| + if (!colorVisible)
|
| + return;
|
| +
|
| + bool boxShadowShouldBeAppliedToBackground = m_renderBox.boxShadowShouldBeAppliedToBackground(bleedAvoidance, box);
|
| + GraphicsContextStateSaver shadowStateSaver(*context, boxShadowShouldBeAppliedToBackground);
|
| + if (boxShadowShouldBeAppliedToBackground)
|
| + applyBoxShadowForBackground(context);
|
| +
|
| + if (hasRoundedBorder && bleedAvoidance != BackgroundBleedClipBackground) {
|
| + RoundedRect border = backgroundRoundedRectAdjustedForBleedAvoidance(context, rect, bleedAvoidance, box, boxSize, includeLeftEdge, includeRightEdge);
|
| + if (border.isRenderable()) {
|
| + context->fillRoundedRect(border, bgColor);
|
| + } else {
|
| + context->save();
|
| + clipRoundedInnerRect(context, rect, border);
|
| + context->fillRect(border.rect(), bgColor);
|
| + context->restore();
|
| + }
|
| + } else {
|
| + context->fillRect(pixelSnappedIntRect(rect), bgColor);
|
| + }
|
| +
|
| + return;
|
| + }
|
| +
|
| + // BorderFillBox radius clipping is taken care of by BackgroundBleedClipBackground
|
| + bool clipToBorderRadius = hasRoundedBorder && !(isBorderFill && bleedAvoidance == BackgroundBleedClipBackground);
|
| + GraphicsContextStateSaver clipToBorderStateSaver(*context, clipToBorderRadius);
|
| + if (clipToBorderRadius) {
|
| + RoundedRect border = isBorderFill ? backgroundRoundedRectAdjustedForBleedAvoidance(context, rect, bleedAvoidance, box, boxSize, includeLeftEdge, includeRightEdge) : getBackgroundRoundedRect(rect, box, boxSize.width(), boxSize.height(), includeLeftEdge, includeRightEdge);
|
| +
|
| + // Clip to the padding or content boxes as necessary.
|
| + if (bgLayer.clip() == ContentFillBox) {
|
| + border = m_renderBox.style()->getRoundedInnerBorderFor(border.rect(),
|
| + m_renderBox.paddingTop() + m_renderBox.borderTop(), m_renderBox.paddingBottom() + m_renderBox.borderBottom(),
|
| + m_renderBox.paddingLeft() + m_renderBox.borderLeft(), m_renderBox.paddingRight() + m_renderBox.borderRight(), includeLeftEdge, includeRightEdge);
|
| + } else if (bgLayer.clip() == PaddingFillBox) {
|
| + border = m_renderBox.style()->getRoundedInnerBorderFor(border.rect(), includeLeftEdge, includeRightEdge);
|
| + }
|
| +
|
| + clipRoundedInnerRect(context, rect, border);
|
| + }
|
| +
|
| + int bLeft = includeLeftEdge ? m_renderBox.borderLeft() : 0;
|
| + int bRight = includeRightEdge ? m_renderBox.borderRight() : 0;
|
| + LayoutUnit pLeft = includeLeftEdge ? m_renderBox.paddingLeft() : LayoutUnit();
|
| + LayoutUnit pRight = includeRightEdge ? m_renderBox.paddingRight() : LayoutUnit();
|
| +
|
| + GraphicsContextStateSaver clipWithScrollingStateSaver(*context, clippedWithLocalScrolling);
|
| + LayoutRect scrolledPaintRect = rect;
|
| + if (clippedWithLocalScrolling) {
|
| + // Clip to the overflow area.
|
| + context->clip(m_renderBox.overflowClipRect(rect.location()));
|
| +
|
| + // Adjust the paint rect to reflect a scrolled content box with borders at the ends.
|
| + IntSize offset = m_renderBox.scrolledContentOffset();
|
| + scrolledPaintRect.move(-offset);
|
| + scrolledPaintRect.setWidth(bLeft + m_renderBox.scrollWidth() + bRight);
|
| + scrolledPaintRect.setHeight(m_renderBox.borderTop() + m_renderBox.scrollHeight() + m_renderBox.borderBottom());
|
| + }
|
| +
|
| + GraphicsContextStateSaver backgroundClipStateSaver(*context, false);
|
| + IntRect maskRect;
|
| +
|
| + switch (bgLayer.clip()) {
|
| + case PaddingFillBox:
|
| + case ContentFillBox: {
|
| + if (clipToBorderRadius)
|
| + break;
|
| +
|
| + // Clip to the padding or content boxes as necessary.
|
| + bool includePadding = bgLayer.clip() == ContentFillBox;
|
| + LayoutRect clipRect = LayoutRect(scrolledPaintRect.x() + bLeft + (includePadding ? pLeft : LayoutUnit()),
|
| + scrolledPaintRect.y() + m_renderBox.borderTop() + (includePadding ? m_renderBox.paddingTop() : LayoutUnit()),
|
| + scrolledPaintRect.width() - bLeft - bRight - (includePadding ? pLeft + pRight : LayoutUnit()),
|
| + scrolledPaintRect.height() - m_renderBox.borderTop() - m_renderBox.borderBottom() - (includePadding ? m_renderBox.paddingTop() + m_renderBox.paddingBottom() : LayoutUnit()));
|
| + backgroundClipStateSaver.save();
|
| + context->clip(clipRect);
|
| +
|
| + break;
|
| + }
|
| + case TextFillBox: {
|
| + // 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.
|
| + maskRect = pixelSnappedIntRect(rect);
|
| + maskRect.intersect(paintInfo.rect);
|
| +
|
| + // We draw the background into a separate layer, to be later masked with yet another layer
|
| + // holding the text content.
|
| + backgroundClipStateSaver.save();
|
| + context->clip(maskRect);
|
| + context->beginTransparencyLayer(1);
|
| +
|
| + break;
|
| + }
|
| + case BorderFillBox:
|
| + break;
|
| + default:
|
| + ASSERT_NOT_REACHED();
|
| + break;
|
| + }
|
| +
|
| + // Paint the color first underneath all images, culled if background image occludes it.
|
| + // FIXME: In the bgLayer->hasFiniteBounds() case, we could improve the culling test
|
| + // by verifying whether the background image covers the entire layout rect.
|
| + if (isBottomLayer) {
|
| + IntRect backgroundRect(pixelSnappedIntRect(scrolledPaintRect));
|
| + bool boxShadowShouldBeAppliedToBackground = m_renderBox.boxShadowShouldBeAppliedToBackground(bleedAvoidance, box);
|
| + bool isOpaqueRoot = (isDocumentElementRenderer && !bgColor.hasAlpha()) || isDocumentElementWithOpaqueBackground();
|
| + if (boxShadowShouldBeAppliedToBackground || !shouldPaintBackgroundImage || !bgLayer.hasOpaqueImage(&m_renderBox) || !bgLayer.hasRepeatXY() || (isOpaqueRoot && m_renderBox.height())) {
|
| + if (!boxShadowShouldBeAppliedToBackground)
|
| + backgroundRect.intersect(paintInfo.rect);
|
| +
|
| + GraphicsContextStateSaver shadowStateSaver(*context, boxShadowShouldBeAppliedToBackground);
|
| + if (boxShadowShouldBeAppliedToBackground)
|
| + applyBoxShadowForBackground(context);
|
| +
|
| + if (isOpaqueRoot && !skipBaseColor) {
|
| + paintRootBackgroundColor(paintInfo, rect, bgColor);
|
| + } else if (bgColor.alpha()) {
|
| + context->fillRect(backgroundRect, bgColor, context->compositeOperation());
|
| + }
|
| + }
|
| + }
|
| +
|
| + // no progressive loading of the background image
|
| + if (shouldPaintBackgroundImage) {
|
| + BackgroundImageGeometry geometry;
|
| + calculateBackgroundImageGeometry(paintInfo.paintContainer(), bgLayer, scrolledPaintRect, geometry, backgroundObject);
|
| + geometry.clip(paintInfo.rect);
|
| + if (!geometry.destRect().isEmpty()) {
|
| + CompositeOperator compositeOp = op == CompositeSourceOver ? bgLayer.composite() : op;
|
| + RenderObject* clientForBackgroundImage = backgroundObject ? backgroundObject : &m_renderBox;
|
| + RefPtr<Image> image = bgImage->image(clientForBackgroundImage, geometry.tileSize());
|
| + InterpolationQuality interpolationQuality = chooseInterpolationQuality(context, image.get(), &bgLayer, geometry.tileSize());
|
| + if (bgLayer.maskSourceType() == MaskLuminance)
|
| + context->setColorFilter(ColorFilterLuminanceToAlpha);
|
| + InterpolationQuality previousInterpolationQuality = context->imageInterpolationQuality();
|
| + context->setImageInterpolationQuality(interpolationQuality);
|
| + context->drawTiledImage(image.get(), geometry.destRect(), geometry.relativePhase(), geometry.tileSize(),
|
| + compositeOp, bgLayer.blendMode(), geometry.spaceSize());
|
| + context->setImageInterpolationQuality(previousInterpolationQuality);
|
| + }
|
| + }
|
| +
|
| + if (bgLayer.clip() == TextFillBox) {
|
| + // Create the text mask layer.
|
| + context->setCompositeOperation(CompositeDestinationIn);
|
| + context->beginTransparencyLayer(1);
|
| +
|
| + // FIXME: Workaround for https://code.google.com/p/skia/issues/detail?id=1291.
|
| + context->clearRect(maskRect);
|
| +
|
| + // Now draw the text into the mask. 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(context, maskRect, PaintPhaseTextClip, PaintBehaviorForceBlackText, 0);
|
| + context->setCompositeOperation(CompositeSourceOver);
|
| + if (box) {
|
| + RootInlineBox& root = box->root();
|
| + box->paint(info, LayoutPoint(scrolledPaintRect.x() - box->x(), scrolledPaintRect.y() - box->y()), root.lineTop(), root.lineBottom());
|
| + } else {
|
| + LayoutSize localOffset = m_renderBox.locationOffset();
|
| + paint(info, scrolledPaintRect.location() - localOffset);
|
| + }
|
| +
|
| + context->endLayer();
|
| + context->endLayer();
|
| + }
|
| }
|
|
|
| void BoxPainter::paintMask(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
|
| @@ -238,4 +547,321 @@ void BoxPainter::paintClippingMask(PaintInfo& paintInfo, const LayoutPoint& pain
|
| paintInfo.context->fillRect(pixelSnappedIntRect(paintRect), Color::black);
|
| }
|
|
|
| +void BoxPainter::paintRootBackgroundColor(const PaintInfo& paintInfo, const LayoutRect& rect, const Color& bgColor)
|
| +{
|
| + GraphicsContext* context = paintInfo.context;
|
| + if (rect.isEmpty())
|
| + return;
|
| +
|
| + ASSERT(isDocumentElement());
|
| +
|
| + IntRect backgroundRect(pixelSnappedIntRect(rect));
|
| + backgroundRect.intersect(paintInfo.rect);
|
| +
|
| + Color baseColor = m_renderBox.view()->frameView()->baseBackgroundColor();
|
| + bool shouldClearDocumentBackground = m_renderBox.document().settings() && m_renderBox.document().settings()->shouldClearDocumentBackground();
|
| + CompositeOperator operation = shouldClearDocumentBackground ? CompositeCopy : context->compositeOperation();
|
| +
|
| + // If we have an alpha go ahead and blend with the base background color.
|
| + if (baseColor.alpha()) {
|
| + if (bgColor.alpha())
|
| + baseColor = baseColor.blend(bgColor);
|
| + context->fillRect(backgroundRect, baseColor, operation);
|
| + } else if (bgColor.alpha()) {
|
| + context->fillRect(backgroundRect, bgColor, operation);
|
| + } else if (shouldClearDocumentBackground) {
|
| + context->clearRect(backgroundRect);
|
| + }
|
| +}
|
| +
|
| +bool BoxPainter::isDocumentElementWithOpaqueBackground() const
|
| +{
|
| + if (!m_renderBox.isDocumentElement())
|
| + return false;
|
| +
|
| + // The background is opaque only if we're the root document, since iframes with
|
| + // no background in the child document should show the parent's background.
|
| + bool isOpaque = true;
|
| + Element* ownerElement = m_renderBox.document().ownerElement();
|
| + if (ownerElement) {
|
| + if (!isHTMLFrameElement(*ownerElement)) {
|
| + // 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 = m_renderBox.document().body();
|
| + if (body) {
|
| + // Can't scroll a frameset document anyway.
|
| + isOpaque = body->hasTagName(HTMLNames::framesetTag);
|
| + } else {
|
| + // FIXME: SVG specific behavior should be in the SVG code.
|
| + // SVG documents and XML documents with SVG root nodes are transparent.
|
| + isOpaque = !m_renderBox.document().hasSVGRootNode();
|
| + }
|
| + }
|
| + } else if (m_renderBox.view()->frameView()) {
|
| + isOpaque = !m_renderBox.view()->frameView()->isTransparent();
|
| + }
|
| +
|
| + return isOpaque;
|
| +}
|
| +
|
| +static inline int getSpace(int areaSize, int tileSize)
|
| +{
|
| + int numberOfTiles = areaSize / tileSize;
|
| + int space = -1;
|
| +
|
| + if (numberOfTiles > 1)
|
| + space = lroundf((float)(areaSize - numberOfTiles * tileSize) / (numberOfTiles - 1));
|
| +
|
| + return space;
|
| +}
|
| +
|
| +void BoxPainter::calculateBackgroundImageGeometry(const RenderLayerModelObject* paintContainer, const FillLayer& fillLayer, const LayoutRect& paintRect,
|
| + BackgroundImageGeometry& geometry, RenderObject* backgroundObject) const
|
| +{
|
| + LayoutUnit left = 0;
|
| + LayoutUnit top = 0;
|
| + IntSize positioningAreaSize;
|
| + IntRect snappedPaintRect = pixelSnappedIntRect(paintRect);
|
| +
|
| + // Determine the background positioning area and set destRect to the background painting area.
|
| + // destRect will be adjusted later if the background is non-repeating.
|
| + // FIXME: transforms spec says that fixed backgrounds behave like scroll inside transforms.
|
| + bool fixedAttachment = fillLayer.attachment() == FixedBackgroundAttachment;
|
| +
|
| + if (RuntimeEnabledFeatures::fastMobileScrollingEnabled()) {
|
| + // 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;
|
| + }
|
| +
|
| + if (!fixedAttachment) {
|
| + geometry.setDestRect(snappedPaintRect);
|
| +
|
| + LayoutUnit right = 0;
|
| + LayoutUnit bottom = 0;
|
| + // Scroll and Local.
|
| + if (fillLayer.origin() != BorderFillBox) {
|
| + left = m_renderBox.borderLeft();
|
| + right = m_renderBox.borderRight();
|
| + top = m_renderBox.borderTop();
|
| + bottom = m_renderBox.borderBottom();
|
| + if (fillLayer.origin() == ContentFillBox) {
|
| + left += m_renderBox.paddingLeft();
|
| + right += m_renderBox.paddingRight();
|
| + top += m_renderBox.paddingTop();
|
| + bottom += m_renderBox.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 (m_renderBox.isDocumentElement()) {
|
| + positioningAreaSize = pixelSnappedIntSize(m_renderBox.size() - LayoutSize(left + right, top + bottom), m_renderBox.location());
|
| + left += m_renderBox.marginLeft();
|
| + top += m_renderBox.marginTop();
|
| + } else {
|
| + positioningAreaSize = pixelSnappedIntSize(paintRect.size() - LayoutSize(left + right, top + bottom), paintRect.location());
|
| + }
|
| + } else {
|
| + geometry.setHasNonLocalGeometry();
|
| +
|
| + IntRect viewportRect = pixelSnappedIntRect(m_renderBox.viewRect());
|
| + if (fixedBackgroundPaintsInLocalCoordinates())
|
| + viewportRect.setLocation(IntPoint());
|
| + else if (FrameView* frameView = m_renderBox.view()->frameView())
|
| + viewportRect.setLocation(IntPoint(frameView->scrollOffsetForFixedPosition()));
|
| +
|
| + if (paintContainer) {
|
| + IntPoint absoluteContainerOffset = roundedIntPoint(paintContainer->localToAbsolute(FloatPoint()));
|
| + viewportRect.moveBy(-absoluteContainerOffset);
|
| + }
|
| +
|
| + geometry.setDestRect(pixelSnappedIntRect(viewportRect));
|
| + positioningAreaSize = geometry.destRect().size();
|
| + }
|
| +
|
| + const RenderObject* clientForBackgroundImage = backgroundObject ? backgroundObject : &m_renderBox;
|
| + IntSize fillTileSize = calculateFillTileSize(fillLayer, positioningAreaSize);
|
| + fillLayer.image()->setContainerSizeForRenderer(clientForBackgroundImage, fillTileSize, m_renderBox.style()->effectiveZoom());
|
| + geometry.setTileSize(fillTileSize);
|
| +
|
| + EFillRepeat backgroundRepeatX = fillLayer.repeatX();
|
| + EFillRepeat backgroundRepeatY = fillLayer.repeatY();
|
| + int availableWidth = positioningAreaSize.width() - geometry.tileSize().width();
|
| + int availableHeight = positioningAreaSize.height() - geometry.tileSize().height();
|
| +
|
| + LayoutUnit computedXPosition = roundedMinimumValueForLength(fillLayer.xPosition(), availableWidth);
|
| + if (backgroundRepeatX == RoundFill && positioningAreaSize.width() > 0 && fillTileSize.width() > 0) {
|
| + long nrTiles = std::max(1l, lroundf((float)positioningAreaSize.width() / fillTileSize.width()));
|
| +
|
| + if (fillLayer.size().size.height().isAuto() && backgroundRepeatY != RoundFill) {
|
| + fillTileSize.setHeight(fillTileSize.height() * positioningAreaSize.width() / (nrTiles * fillTileSize.width()));
|
| + }
|
| +
|
| + fillTileSize.setWidth(positioningAreaSize.width() / nrTiles);
|
| + geometry.setTileSize(fillTileSize);
|
| + geometry.setPhaseX(geometry.tileSize().width() ? geometry.tileSize().width() - roundToInt(computedXPosition + left) % geometry.tileSize().width() : 0);
|
| + geometry.setSpaceSize(IntSize());
|
| + }
|
| +
|
| + LayoutUnit computedYPosition = roundedMinimumValueForLength(fillLayer.yPosition(), availableHeight);
|
| + if (backgroundRepeatY == RoundFill && positioningAreaSize.height() > 0 && fillTileSize.height() > 0) {
|
| + long nrTiles = std::max(1l, lroundf((float)positioningAreaSize.height() / fillTileSize.height()));
|
| +
|
| + if (fillLayer.size().size.width().isAuto() && backgroundRepeatX != RoundFill) {
|
| + fillTileSize.setWidth(fillTileSize.width() * positioningAreaSize.height() / (nrTiles * fillTileSize.height()));
|
| + }
|
| +
|
| + fillTileSize.setHeight(positioningAreaSize.height() / nrTiles);
|
| + geometry.setTileSize(fillTileSize);
|
| + geometry.setPhaseY(geometry.tileSize().height() ? geometry.tileSize().height() - roundToInt(computedYPosition + top) % geometry.tileSize().height() : 0);
|
| + geometry.setSpaceSize(IntSize());
|
| + }
|
| +
|
| + if (backgroundRepeatX == RepeatFill) {
|
| + geometry.setPhaseX(geometry.tileSize().width() ? geometry.tileSize().width() - roundToInt(computedXPosition + left) % geometry.tileSize().width() : 0);
|
| + geometry.setSpaceSize(IntSize());
|
| + } else if (backgroundRepeatX == SpaceFill && fillTileSize.width() > 0) {
|
| + int space = getSpace(positioningAreaSize.width(), geometry.tileSize().width());
|
| + int actualWidth = geometry.tileSize().width() + space;
|
| +
|
| + if (space >= 0) {
|
| + computedXPosition = roundedMinimumValueForLength(Length(), availableWidth);
|
| + geometry.setSpaceSize(IntSize(space, 0));
|
| + geometry.setPhaseX(actualWidth ? actualWidth - roundToInt(computedXPosition + left) % actualWidth : 0);
|
| + } else {
|
| + backgroundRepeatX = NoRepeatFill;
|
| + }
|
| + }
|
| + if (backgroundRepeatX == NoRepeatFill) {
|
| + int xOffset = fillLayer.backgroundXOrigin() == RightEdge ? availableWidth - computedXPosition : computedXPosition;
|
| + geometry.setNoRepeatX(left + xOffset);
|
| + geometry.setSpaceSize(IntSize(0, geometry.spaceSize().height()));
|
| + }
|
| +
|
| + if (backgroundRepeatY == RepeatFill) {
|
| + geometry.setPhaseY(geometry.tileSize().height() ? geometry.tileSize().height() - roundToInt(computedYPosition + top) % geometry.tileSize().height() : 0);
|
| + geometry.setSpaceSize(IntSize(geometry.spaceSize().width(), 0));
|
| + } else if (backgroundRepeatY == SpaceFill && fillTileSize.height() > 0) {
|
| + int space = getSpace(positioningAreaSize.height(), geometry.tileSize().height());
|
| + int actualHeight = geometry.tileSize().height() + space;
|
| +
|
| + if (space >= 0) {
|
| + computedYPosition = roundedMinimumValueForLength(Length(), availableHeight);
|
| + geometry.setSpaceSize(IntSize(geometry.spaceSize().width(), space));
|
| + geometry.setPhaseY(actualHeight ? actualHeight - roundToInt(computedYPosition + top) % actualHeight : 0);
|
| + } else {
|
| + backgroundRepeatY = NoRepeatFill;
|
| + }
|
| + }
|
| + if (backgroundRepeatY == NoRepeatFill) {
|
| + int yOffset = fillLayer.backgroundYOrigin() == BottomEdge ? availableHeight - computedYPosition : computedYPosition;
|
| + geometry.setNoRepeatY(top + yOffset);
|
| + geometry.setSpaceSize(IntSize(geometry.spaceSize().width(), 0));
|
| + }
|
| +
|
| + if (fixedAttachment)
|
| + geometry.useFixedAttachment(snappedPaintRect.location());
|
| +
|
| + geometry.clip(snappedPaintRect);
|
| + geometry.setDestOrigin(geometry.destRect().location());
|
| +}
|
| +
|
| +
|
| +InterpolationQuality BoxPainter::chooseInterpolationQuality(GraphicsContext* context, Image* image, const void* layer, const LayoutSize& size)
|
| +{
|
| + return ImageQualityController::imageQualityController()->chooseInterpolationQuality(context, &m_renderBox, image, layer, size);
|
| +}
|
| +
|
| +bool BoxPainter::fixedBackgroundPaintsInLocalCoordinates() const
|
| +{
|
| + if (!m_renderBox.isDocumentElement())
|
| + return false;
|
| +
|
| + if (m_renderBox.view()->frameView() && m_renderBox.view()->frameView()->paintBehavior() & PaintBehaviorFlattenCompositingLayers)
|
| + return false;
|
| +
|
| + RenderLayer* rootLayer = m_renderBox.view()->layer();
|
| + if (!rootLayer || rootLayer->compositingState() == NotComposited)
|
| + return false;
|
| +
|
| + return rootLayer->compositedLayerMapping()->backgroundLayerPaintsFixedRootBackground();
|
| +}
|
| +
|
| +static inline void applySubPixelHeuristicForTileSize(LayoutSize& tileSize, const IntSize& positioningAreaSize)
|
| +{
|
| + tileSize.setWidth(positioningAreaSize.width() - tileSize.width() <= 1 ? tileSize.width().ceil() : tileSize.width().floor());
|
| + tileSize.setHeight(positioningAreaSize.height() - tileSize.height() <= 1 ? tileSize.height().ceil() : tileSize.height().floor());
|
| +}
|
| +
|
| +IntSize BoxPainter::calculateFillTileSize(const FillLayer& fillLayer, const IntSize& positioningAreaSize) const
|
| +{
|
| + StyleImage* image = fillLayer.image();
|
| + EFillSizeType type = fillLayer.size().type;
|
| +
|
| + IntSize imageIntrinsicSize = m_renderBox.calculateImageIntrinsicDimensions(image, positioningAreaSize, RenderBoxModelObject::ScaleByEffectiveZoom);
|
| + imageIntrinsicSize.scale(1 / image->imageScaleFactor(), 1 / image->imageScaleFactor());
|
| + switch (type) {
|
| + case SizeLength: {
|
| + LayoutSize tileSize = positioningAreaSize;
|
| +
|
| + Length layerWidth = fillLayer.size().size.width();
|
| + Length layerHeight = fillLayer.size().size.height();
|
| +
|
| + if (layerWidth.isFixed())
|
| + tileSize.setWidth(layerWidth.value());
|
| + else if (layerWidth.isPercent())
|
| + tileSize.setWidth(valueForLength(layerWidth, positioningAreaSize.width()));
|
| +
|
| + if (layerHeight.isFixed())
|
| + tileSize.setHeight(layerHeight.value());
|
| + else if (layerHeight.isPercent())
|
| + tileSize.setHeight(valueForLength(layerHeight, positioningAreaSize.height()));
|
| +
|
| + applySubPixelHeuristicForTileSize(tileSize, positioningAreaSize);
|
| +
|
| + // If one of the values is auto we have to use the appropriate
|
| + // scale to maintain our aspect ratio.
|
| + if (layerWidth.isAuto() && !layerHeight.isAuto()) {
|
| + if (imageIntrinsicSize.height())
|
| + tileSize.setWidth(imageIntrinsicSize.width() * tileSize.height() / imageIntrinsicSize.height());
|
| + } else if (!layerWidth.isAuto() && layerHeight.isAuto()) {
|
| + if (imageIntrinsicSize.width())
|
| + tileSize.setHeight(imageIntrinsicSize.height() * tileSize.width() / imageIntrinsicSize.width());
|
| + } else if (layerWidth.isAuto() && layerHeight.isAuto()) {
|
| + // If both width and height are auto, use the image's intrinsic size.
|
| + tileSize = imageIntrinsicSize;
|
| + }
|
| +
|
| + tileSize.clampNegativeToZero();
|
| + return flooredIntSize(tileSize);
|
| + }
|
| + case SizeNone: {
|
| + // If both values are ‘auto’ then the intrinsic width and/or height of the image should be used, if any.
|
| + if (!imageIntrinsicSize.isEmpty())
|
| + return imageIntrinsicSize;
|
| +
|
| + // If the image has neither an intrinsic width nor an intrinsic height, its size is determined as for ‘contain’.
|
| + type = Contain;
|
| + }
|
| + case Contain:
|
| + case Cover: {
|
| + 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 ? std::min(horizontalScaleFactor, verticalScaleFactor) : std::max(horizontalScaleFactor, verticalScaleFactor);
|
| + return IntSize(std::max(1l, lround(imageIntrinsicSize.width() * scaleFactor)), std::max(1l, lround(imageIntrinsicSize.height() * scaleFactor)));
|
| + }
|
| + }
|
| +
|
| + ASSERT_NOT_REACHED();
|
| + return IntSize();
|
| +}
|
| +
|
| } // namespace blink
|
|
|