1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2005 Allan Sandfeld Jensen (kde (at) carewolf.com) 5 * (C) 2005, 2006 Samuel Weinig (sam.weinig (at) gmail.com) 6 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. 7 * Copyright (C) 2010 Google Inc. All rights reserved. 8 * 9 * This library is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU Library General Public 11 * License as published by the Free Software Foundation; either 12 * version 2 of the License, or (at your option) any later version. 13 * 14 * This library is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * Library General Public License for more details. 18 * 19 * You should have received a copy of the GNU Library General Public License 20 * along with this library; see the file COPYING.LIB. If not, write to 21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 * Boston, MA 02110-1301, USA. 23 * 24 */ 25 26 #include "config.h" 27 #include "core/rendering/RenderBoxModelObject.h" 28 29 #include "HTMLNames.h" 30 #include "core/html/HTMLFrameOwnerElement.h" 31 #include "core/page/Settings.h" 32 #include "core/page/scrolling/ScrollingConstraints.h" 33 #include "core/platform/graphics/DrawLooper.h" 34 #include "core/platform/graphics/GraphicsContextStateSaver.h" 35 #include "core/platform/graphics/Path.h" 36 #include "core/platform/graphics/transforms/TransformState.h" 37 #include "core/rendering/ImageQualityController.h" 38 #include "core/rendering/RenderBlock.h" 39 #include "core/rendering/RenderGeometryMap.h" 40 #include "core/rendering/RenderInline.h" 41 #include "core/rendering/RenderLayer.h" 42 #include "core/rendering/RenderLayerBacking.h" 43 #include "core/rendering/RenderLayerCompositor.h" 44 #include "core/rendering/RenderNamedFlowThread.h" 45 #include "core/rendering/RenderRegion.h" 46 #include "core/rendering/RenderView.h" 47 #include "wtf/CurrentTime.h" 48 49 using namespace std; 50 51 namespace WebCore { 52 53 using namespace HTMLNames; 54 55 // The HashMap for storing continuation pointers. 56 // An inline can be split with blocks occuring in between the inline content. 57 // When this occurs we need a pointer to the next object. We can basically be 58 // split into a sequence of inlines and blocks. The continuation will either be 59 // an anonymous block (that houses other blocks) or it will be an inline flow. 60 // <b><i><p>Hello</p></i></b>. In this example the <i> will have a block as 61 // its continuation but the <b> will just have an inline as its continuation. 62 typedef HashMap<const RenderBoxModelObject*, RenderBoxModelObject*> ContinuationMap; 63 static ContinuationMap* continuationMap = 0; 64 65 // This HashMap is similar to the continuation map, but connects first-letter 66 // renderers to their remaining text fragments. 67 typedef HashMap<const RenderBoxModelObject*, RenderObject*> FirstLetterRemainingTextMap; 68 static FirstLetterRemainingTextMap* firstLetterRemainingTextMap = 0; 69 70 void RenderBoxModelObject::setSelectionState(SelectionState state) 71 { 72 if (state == SelectionInside && selectionState() != SelectionNone) 73 return; 74 75 if ((state == SelectionStart && selectionState() == SelectionEnd) 76 || (state == SelectionEnd && selectionState() == SelectionStart)) 77 RenderObject::setSelectionState(SelectionBoth); 78 else 79 RenderObject::setSelectionState(state); 80 81 // FIXME: We should consider whether it is OK propagating to ancestor RenderInlines. 82 // This is a workaround for http://webkit.org/b/32123 83 // The containing block can be null in case of an orphaned tree. 84 RenderBlock* containingBlock = this->containingBlock(); 85 if (containingBlock && !containingBlock->isRenderView()) 86 containingBlock->setSelectionState(state); 87 } 88 89 void RenderBoxModelObject::contentChanged(ContentChangeType changeType) 90 { 91 if (!hasLayer()) 92 return; 93 94 layer()->contentChanged(changeType); 95 } 96 97 bool RenderBoxModelObject::hasAcceleratedCompositing() const 98 { 99 return view()->compositor()->hasAcceleratedCompositing(); 100 } 101 102 bool RenderBoxModelObject::startTransition(double timeOffset, CSSPropertyID propertyId, const RenderStyle* fromStyle, const RenderStyle* toStyle) 103 { 104 ASSERT(hasLayer()); 105 ASSERT(isComposited()); 106 return layer()->backing()->startTransition(timeOffset, propertyId, fromStyle, toStyle); 107 } 108 109 void RenderBoxModelObject::transitionPaused(double timeOffset, CSSPropertyID propertyId) 110 { 111 ASSERT(hasLayer()); 112 ASSERT(isComposited()); 113 layer()->backing()->transitionPaused(timeOffset, propertyId); 114 } 115 116 void RenderBoxModelObject::transitionFinished(CSSPropertyID propertyId) 117 { 118 ASSERT(hasLayer()); 119 ASSERT(isComposited()); 120 layer()->backing()->transitionFinished(propertyId); 121 } 122 123 bool RenderBoxModelObject::startAnimation(double timeOffset, const CSSAnimationData* animation, const KeyframeList& keyframes) 124 { 125 ASSERT(hasLayer()); 126 ASSERT(isComposited()); 127 return layer()->backing()->startAnimation(timeOffset, animation, keyframes); 128 } 129 130 void RenderBoxModelObject::animationPaused(double timeOffset, const String& name) 131 { 132 ASSERT(hasLayer()); 133 ASSERT(isComposited()); 134 layer()->backing()->animationPaused(timeOffset, name); 135 } 136 137 void RenderBoxModelObject::animationFinished(const String& name) 138 { 139 ASSERT(hasLayer()); 140 ASSERT(isComposited()); 141 layer()->backing()->animationFinished(name); 142 } 143 144 void RenderBoxModelObject::suspendAnimations(double time) 145 { 146 ASSERT(hasLayer()); 147 ASSERT(isComposited()); 148 layer()->backing()->suspendAnimations(time); 149 } 150 151 bool RenderBoxModelObject::shouldPaintAtLowQuality(GraphicsContext* context, Image* image, const void* layer, const LayoutSize& size) 152 { 153 return ImageQualityController::imageQualityController()->shouldPaintAtLowQuality(context, this, image, layer, size); 154 } 155 156 RenderBoxModelObject::RenderBoxModelObject(ContainerNode* node) 157 : RenderLayerModelObject(node) 158 { 159 } 160 161 RenderBoxModelObject::~RenderBoxModelObject() 162 { 163 ImageQualityController::remove(this); 164 } 165 166 void RenderBoxModelObject::willBeDestroyed() 167 { 168 // A continuation of this RenderObject should be destroyed at subclasses. 169 ASSERT(!continuation()); 170 171 // If this is a first-letter object with a remaining text fragment then the 172 // entry needs to be cleared from the map. 173 if (firstLetterRemainingText()) 174 setFirstLetterRemainingText(0); 175 176 RenderLayerModelObject::willBeDestroyed(); 177 } 178 179 void RenderBoxModelObject::updateFromStyle() 180 { 181 RenderLayerModelObject::updateFromStyle(); 182 183 // Set the appropriate bits for a box model object. Since all bits are cleared in styleWillChange, 184 // we only check for bits that could possibly be set to true. 185 RenderStyle* styleToUse = style(); 186 setHasBoxDecorations(hasBackground() || styleToUse->hasBorder() || styleToUse->hasAppearance() || styleToUse->boxShadow()); 187 setInline(styleToUse->isDisplayInlineType()); 188 setPositionState(styleToUse->position()); 189 setHorizontalWritingMode(styleToUse->isHorizontalWritingMode()); 190 } 191 192 static LayoutSize accumulateInFlowPositionOffsets(const RenderObject* child) 193 { 194 if (!child->isAnonymousBlock() || !child->isInFlowPositioned()) 195 return LayoutSize(); 196 LayoutSize offset; 197 RenderObject* p = toRenderBlock(child)->inlineElementContinuation(); 198 while (p && p->isRenderInline()) { 199 if (p->isInFlowPositioned()) { 200 RenderInline* renderInline = toRenderInline(p); 201 offset += renderInline->offsetForInFlowPosition(); 202 } 203 p = p->parent(); 204 } 205 return offset; 206 } 207 208 bool RenderBoxModelObject::hasAutoHeightOrContainingBlockWithAutoHeight() const 209 { 210 Length logicalHeightLength = style()->logicalHeight(); 211 if (logicalHeightLength.isAuto()) 212 return true; 213 214 // For percentage heights: The percentage is calculated with respect to the height of the generated box's 215 // containing block. If the height of the containing block is not specified explicitly (i.e., it depends 216 // on content height), and this element is not absolutely positioned, the value computes to 'auto'. 217 if (!logicalHeightLength.isPercent() || isOutOfFlowPositioned() || document()->inQuirksMode()) 218 return false; 219 220 // Anonymous block boxes are ignored when resolving percentage values that would refer to it: 221 // the closest non-anonymous ancestor box is used instead. 222 RenderBlock* cb = containingBlock(); 223 while (cb->isAnonymous()) 224 cb = cb->containingBlock(); 225 226 // Matching RenderBox::percentageLogicalHeightIsResolvableFromBlock() by 227 // ignoring table cell's attribute value, where it says that table cells violate 228 // what the CSS spec says to do with heights. Basically we 229 // don't care if the cell specified a height or not. 230 if (cb->isTableCell()) 231 return false; 232 233 if (!cb->style()->logicalHeight().isAuto() || (!cb->style()->logicalTop().isAuto() && !cb->style()->logicalBottom().isAuto())) 234 return false; 235 236 return true; 237 } 238 239 LayoutSize RenderBoxModelObject::relativePositionOffset() const 240 { 241 LayoutSize offset = accumulateInFlowPositionOffsets(this); 242 243 RenderBlock* containingBlock = this->containingBlock(); 244 245 // Objects that shrink to avoid floats normally use available line width when computing containing block width. However 246 // in the case of relative positioning using percentages, we can't do this. The offset should always be resolved using the 247 // available width of the containing block. Therefore we don't use containingBlockLogicalWidthForContent() here, but instead explicitly 248 // call availableWidth on our containing block. 249 if (!style()->left().isAuto()) { 250 if (!style()->right().isAuto() && !containingBlock->style()->isLeftToRightDirection()) 251 offset.setWidth(-valueForLength(style()->right(), containingBlock->availableWidth(), view())); 252 else 253 offset.expand(valueForLength(style()->left(), containingBlock->availableWidth(), view()), 0); 254 } else if (!style()->right().isAuto()) { 255 offset.expand(-valueForLength(style()->right(), containingBlock->availableWidth(), view()), 0); 256 } 257 258 // If the containing block of a relatively positioned element does not 259 // specify a height, a percentage top or bottom offset should be resolved as 260 // auto. An exception to this is if the containing block has the WinIE quirk 261 // where <html> and <body> assume the size of the viewport. In this case, 262 // calculate the percent offset based on this height. 263 // See <https://bugs.webkit.org/show_bug.cgi?id=26396>. 264 if (!style()->top().isAuto() 265 && (!containingBlock->hasAutoHeightOrContainingBlockWithAutoHeight() 266 || !style()->top().isPercent() 267 || containingBlock->stretchesToViewport())) 268 offset.expand(0, valueForLength(style()->top(), containingBlock->availableHeight(), view())); 269 270 else if (!style()->bottom().isAuto() 271 && (!containingBlock->hasAutoHeightOrContainingBlockWithAutoHeight() 272 || !style()->bottom().isPercent() 273 || containingBlock->stretchesToViewport())) 274 offset.expand(0, -valueForLength(style()->bottom(), containingBlock->availableHeight(), view())); 275 276 return offset; 277 } 278 279 LayoutPoint RenderBoxModelObject::adjustedPositionRelativeToOffsetParent(const LayoutPoint& startPoint) const 280 { 281 // If the element is the HTML body element or doesn't have a parent 282 // return 0 and stop this algorithm. 283 if (isBody() || !parent()) 284 return LayoutPoint(); 285 286 LayoutPoint referencePoint = startPoint; 287 referencePoint.move(parent()->offsetForColumns(referencePoint)); 288 289 // If the offsetParent of the element is null, or is the HTML body element, 290 // return the distance between the canvas origin and the left border edge 291 // of the element and stop this algorithm. 292 Element* element = offsetParent(); 293 if (!element) 294 return referencePoint; 295 296 if (const RenderBoxModelObject* offsetParent = element->renderBoxModelObject()) { 297 if (offsetParent->isBox() && !offsetParent->isBody()) 298 referencePoint.move(-toRenderBox(offsetParent)->borderLeft(), -toRenderBox(offsetParent)->borderTop()); 299 if (!isOutOfFlowPositioned() || flowThreadContainingBlock()) { 300 if (isRelPositioned()) 301 referencePoint.move(relativePositionOffset()); 302 else if (isStickyPositioned()) 303 referencePoint.move(stickyPositionOffset()); 304 305 // CSS regions specification says that region flows should return the body element as their offsetParent. 306 // Since we will bypass the bodys renderer anyway, just end the loop if we encounter a region flow (named flow thread). 307 // See http://dev.w3.org/csswg/css-regions/#cssomview-offset-attributes 308 RenderObject* current; 309 for (current = parent(); current != offsetParent && !current->isRenderNamedFlowThread() && current->parent(); current = current->parent()) { 310 // FIXME: What are we supposed to do inside SVG content? 311 if (!isOutOfFlowPositioned()) { 312 if (current->isBox() && !current->isTableRow()) 313 referencePoint.moveBy(toRenderBox(current)->topLeftLocation()); 314 referencePoint.move(current->parent()->offsetForColumns(referencePoint)); 315 } 316 } 317 318 // Compute the offset position for elements inside named flow threads for which the offsetParent was the body. 319 // See https://code.google.com/p/chromium/issues/detail?id=242168 320 if (current->isRenderNamedFlowThread()) 321 referencePoint = toRenderNamedFlowThread(current)->adjustedPositionRelativeToOffsetParent(*this, referencePoint); 322 else if (offsetParent->isBox() && offsetParent->isBody() && !offsetParent->isPositioned()) 323 referencePoint.moveBy(toRenderBox(offsetParent)->topLeftLocation()); 324 } 325 } 326 327 return referencePoint; 328 } 329 330 void RenderBoxModelObject::computeStickyPositionConstraints(StickyPositionViewportConstraints& constraints, const FloatRect& viewportRect) const 331 { 332 RenderBlock* containingBlock = this->containingBlock(); 333 334 LayoutRect containerContentRect = containingBlock->contentBoxRect(); 335 LayoutUnit maxWidth = containingBlock->availableLogicalWidth(); 336 337 // Sticky positioned element ignore any override logical width on the containing block (as they don't call 338 // containingBlockLogicalWidthForContent). It's unclear whether this is totally fine. 339 LayoutBoxExtent minMargin(minimumValueForLength(style()->marginTop(), maxWidth, view()), 340 minimumValueForLength(style()->marginRight(), maxWidth, view()), 341 minimumValueForLength(style()->marginBottom(), maxWidth, view()), 342 minimumValueForLength(style()->marginLeft(), maxWidth, view())); 343 344 // Compute the container-relative area within which the sticky element is allowed to move. 345 containerContentRect.contract(minMargin); 346 // Map to the view to avoid including page scale factor. 347 constraints.setAbsoluteContainingBlockRect(containingBlock->localToContainerQuad(FloatRect(containerContentRect), view()).boundingBox()); 348 349 LayoutRect stickyBoxRect = frameRectForStickyPositioning(); 350 LayoutRect flippedStickyBoxRect = stickyBoxRect; 351 containingBlock->flipForWritingMode(flippedStickyBoxRect); 352 LayoutPoint stickyLocation = flippedStickyBoxRect.location(); 353 354 // FIXME: sucks to call localToAbsolute again, but we can't just offset from the previously computed rect if there are transforms. 355 // Map to the view to avoid including page scale factor. 356 FloatRect absContainerFrame = containingBlock->localToContainerQuad(FloatRect(FloatPoint(), containingBlock->size()), view()).boundingBox(); 357 358 // We can't call localToAbsolute on |this| because that will recur. FIXME: For now, assume that |this| is not transformed. 359 FloatRect absoluteStickyBoxRect(absContainerFrame.location() + stickyLocation, flippedStickyBoxRect.size()); 360 constraints.setAbsoluteStickyBoxRect(absoluteStickyBoxRect); 361 362 if (!style()->left().isAuto()) { 363 constraints.setLeftOffset(valueForLength(style()->left(), viewportRect.width(), view())); 364 constraints.addAnchorEdge(ViewportConstraints::AnchorEdgeLeft); 365 } 366 367 if (!style()->right().isAuto()) { 368 constraints.setRightOffset(valueForLength(style()->right(), viewportRect.width(), view())); 369 constraints.addAnchorEdge(ViewportConstraints::AnchorEdgeRight); 370 } 371 372 if (!style()->top().isAuto()) { 373 constraints.setTopOffset(valueForLength(style()->top(), viewportRect.height(), view())); 374 constraints.addAnchorEdge(ViewportConstraints::AnchorEdgeTop); 375 } 376 377 if (!style()->bottom().isAuto()) { 378 constraints.setBottomOffset(valueForLength(style()->bottom(), viewportRect.height(), view())); 379 constraints.addAnchorEdge(ViewportConstraints::AnchorEdgeBottom); 380 } 381 } 382 383 LayoutSize RenderBoxModelObject::stickyPositionOffset() const 384 { 385 LayoutRect viewportRect = view()->frameView()->viewportConstrainedVisibleContentRect(); 386 387 StickyPositionViewportConstraints constraints; 388 computeStickyPositionConstraints(constraints, viewportRect); 389 390 // The sticky offset is physical, so we can just return the delta computed in absolute coords (though it may be wrong with transforms). 391 return LayoutSize(constraints.computeStickyOffset(viewportRect)); 392 } 393 394 LayoutSize RenderBoxModelObject::offsetForInFlowPosition() const 395 { 396 if (isRelPositioned()) 397 return relativePositionOffset(); 398 399 if (isStickyPositioned()) 400 return stickyPositionOffset(); 401 402 return LayoutSize(); 403 } 404 405 LayoutUnit RenderBoxModelObject::offsetLeft() const 406 { 407 // Note that RenderInline and RenderBox override this to pass a different 408 // startPoint to adjustedPositionRelativeToOffsetParent. 409 return adjustedPositionRelativeToOffsetParent(LayoutPoint()).x(); 410 } 411 412 LayoutUnit RenderBoxModelObject::offsetTop() const 413 { 414 // Note that RenderInline and RenderBox override this to pass a different 415 // startPoint to adjustedPositionRelativeToOffsetParent. 416 return adjustedPositionRelativeToOffsetParent(LayoutPoint()).y(); 417 } 418 419 int RenderBoxModelObject::pixelSnappedOffsetWidth() const 420 { 421 return snapSizeToPixel(offsetWidth(), offsetLeft()); 422 } 423 424 int RenderBoxModelObject::pixelSnappedOffsetHeight() const 425 { 426 return snapSizeToPixel(offsetHeight(), offsetTop()); 427 } 428 429 LayoutUnit RenderBoxModelObject::computedCSSPadding(Length padding) const 430 { 431 LayoutUnit w = 0; 432 RenderView* renderView = 0; 433 if (padding.isPercent()) 434 w = containingBlockLogicalWidthForContent(); 435 else if (padding.isViewportPercentage()) 436 renderView = view(); 437 return minimumValueForLength(padding, w, renderView); 438 } 439 440 RoundedRect RenderBoxModelObject::getBackgroundRoundedRect(const LayoutRect& borderRect, InlineFlowBox* box, LayoutUnit inlineBoxWidth, LayoutUnit inlineBoxHeight, 441 bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const 442 { 443 RenderView* renderView = view(); 444 RoundedRect border = style()->getRoundedBorderFor(borderRect, renderView, includeLogicalLeftEdge, includeLogicalRightEdge); 445 if (box && (box->nextLineBox() || box->prevLineBox())) { 446 RoundedRect segmentBorder = style()->getRoundedBorderFor(LayoutRect(0, 0, inlineBoxWidth, inlineBoxHeight), renderView, includeLogicalLeftEdge, includeLogicalRightEdge); 447 border.setRadii(segmentBorder.radii()); 448 } 449 450 return border; 451 } 452 453 void RenderBoxModelObject::clipRoundedInnerRect(GraphicsContext * context, const LayoutRect& rect, const RoundedRect& clipRect) 454 { 455 if (clipRect.isRenderable()) 456 context->clipRoundedRect(clipRect); 457 else { 458 // We create a rounded rect for each of the corners and clip it, while making sure we clip opposing corners together. 459 if (!clipRect.radii().topLeft().isEmpty() || !clipRect.radii().bottomRight().isEmpty()) { 460 IntRect topCorner(clipRect.rect().x(), clipRect.rect().y(), rect.maxX() - clipRect.rect().x(), rect.maxY() - clipRect.rect().y()); 461 RoundedRect::Radii topCornerRadii; 462 topCornerRadii.setTopLeft(clipRect.radii().topLeft()); 463 context->clipRoundedRect(RoundedRect(topCorner, topCornerRadii)); 464 465 IntRect bottomCorner(rect.x(), rect.y(), clipRect.rect().maxX() - rect.x(), clipRect.rect().maxY() - rect.y()); 466 RoundedRect::Radii bottomCornerRadii; 467 bottomCornerRadii.setBottomRight(clipRect.radii().bottomRight()); 468 context->clipRoundedRect(RoundedRect(bottomCorner, bottomCornerRadii)); 469 } 470 471 if (!clipRect.radii().topRight().isEmpty() || !clipRect.radii().bottomLeft().isEmpty()) { 472 IntRect topCorner(rect.x(), clipRect.rect().y(), clipRect.rect().maxX() - rect.x(), rect.maxY() - clipRect.rect().y()); 473 RoundedRect::Radii topCornerRadii; 474 topCornerRadii.setTopRight(clipRect.radii().topRight()); 475 context->clipRoundedRect(RoundedRect(topCorner, topCornerRadii)); 476 477 IntRect bottomCorner(clipRect.rect().x(), rect.y(), rect.maxX() - clipRect.rect().x(), clipRect.rect().maxY() - rect.y()); 478 RoundedRect::Radii bottomCornerRadii; 479 bottomCornerRadii.setBottomLeft(clipRect.radii().bottomLeft()); 480 context->clipRoundedRect(RoundedRect(bottomCorner, bottomCornerRadii)); 481 } 482 } 483 } 484 485 static LayoutRect shrinkRectByOnePixel(GraphicsContext* context, const LayoutRect& rect) 486 { 487 LayoutRect shrunkRect = rect; 488 AffineTransform transform = context->getCTM(); 489 shrunkRect.inflateX(-static_cast<LayoutUnit>(ceil(1 / transform.xScale()))); 490 shrunkRect.inflateY(-static_cast<LayoutUnit>(ceil(1 / transform.yScale()))); 491 return shrunkRect; 492 } 493 494 LayoutRect RenderBoxModelObject::borderInnerRectAdjustedForBleedAvoidance(GraphicsContext* context, const LayoutRect& rect, BackgroundBleedAvoidance bleedAvoidance) const 495 { 496 // We shrink the rectangle by one pixel on each side to make it fully overlap the anti-aliased background border 497 return (bleedAvoidance == BackgroundBleedBackgroundOverBorder) ? shrinkRectByOnePixel(context, rect) : rect; 498 } 499 500 RoundedRect RenderBoxModelObject::backgroundRoundedRectAdjustedForBleedAvoidance(GraphicsContext* context, const LayoutRect& borderRect, BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox* box, const LayoutSize& boxSize, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const 501 { 502 if (bleedAvoidance == BackgroundBleedShrinkBackground) { 503 // We shrink the rectangle by one pixel on each side because the bleed is one pixel maximum. 504 return getBackgroundRoundedRect(shrinkRectByOnePixel(context, borderRect), box, boxSize.width(), boxSize.height(), includeLogicalLeftEdge, includeLogicalRightEdge); 505 } 506 if (bleedAvoidance == BackgroundBleedBackgroundOverBorder) 507 return style()->getRoundedInnerBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge); 508 509 return getBackgroundRoundedRect(borderRect, box, boxSize.width(), boxSize.height(), includeLogicalLeftEdge, includeLogicalRightEdge); 510 } 511 512 static void applyBoxShadowForBackground(GraphicsContext* context, const RenderObject* renderer) 513 { 514 const ShadowData* boxShadow = renderer->style()->boxShadow(); 515 while (boxShadow->style() != Normal) 516 boxShadow = boxShadow->next(); 517 518 FloatSize shadowOffset(boxShadow->x(), boxShadow->y()); 519 context->setShadow(shadowOffset, boxShadow->blur(), renderer->resolveColor(boxShadow->color()), 520 DrawLooper::ShadowRespectsTransforms, DrawLooper::ShadowIgnoresAlpha); 521 } 522 523 void RenderBoxModelObject::paintFillLayerExtended(const PaintInfo& paintInfo, const Color& color, const FillLayer* bgLayer, const LayoutRect& rect, 524 BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox* box, const LayoutSize& boxSize, CompositeOperator op, RenderObject* backgroundObject) 525 { 526 GraphicsContext* context = paintInfo.context; 527 if (context->paintingDisabled() || rect.isEmpty()) 528 return; 529 530 bool includeLeftEdge = box ? box->includeLogicalLeftEdge() : true; 531 bool includeRightEdge = box ? box->includeLogicalRightEdge() : true; 532 533 bool hasRoundedBorder = style()->hasBorderRadius() && (includeLeftEdge || includeRightEdge); 534 bool clippedWithLocalScrolling = hasOverflowClip() && bgLayer->attachment() == LocalBackgroundAttachment; 535 bool isBorderFill = bgLayer->clip() == BorderFillBox; 536 bool isRoot = this->isRoot(); 537 538 Color bgColor = color; 539 StyleImage* bgImage = bgLayer->image(); 540 bool shouldPaintBackgroundImage = bgImage && bgImage->canRender(this, style()->effectiveZoom()); 541 542 bool forceBackgroundToWhite = false; 543 if (document()->printing()) { 544 if (style()->printColorAdjust() == PrintColorAdjustEconomy) 545 forceBackgroundToWhite = true; 546 if (document()->settings() && document()->settings()->shouldPrintBackgrounds()) 547 forceBackgroundToWhite = false; 548 } 549 550 // When printing backgrounds is disabled or using economy mode, 551 // change existing background colors and images to a solid white background. 552 // If there's no bg color or image, leave it untouched to avoid affecting transparency. 553 // We don't try to avoid loading the background images, because this style flag is only set 554 // when printing, and at that point we've already loaded the background images anyway. (To avoid 555 // loading the background images we'd have to do this check when applying styles rather than 556 // while rendering.) 557 if (forceBackgroundToWhite) { 558 // Note that we can't reuse this variable below because the bgColor might be changed 559 bool shouldPaintBackgroundColor = !bgLayer->next() && bgColor.alpha(); 560 if (shouldPaintBackgroundImage || shouldPaintBackgroundColor) { 561 bgColor = Color::white; 562 shouldPaintBackgroundImage = false; 563 } 564 } 565 566 bool colorVisible = bgColor.alpha(); 567 568 // Fast path for drawing simple color backgrounds. 569 if (!isRoot && !clippedWithLocalScrolling && !shouldPaintBackgroundImage && isBorderFill && !bgLayer->next()) { 570 if (!colorVisible) 571 return; 572 573 bool boxShadowShouldBeAppliedToBackground = this->boxShadowShouldBeAppliedToBackground(bleedAvoidance, box); 574 GraphicsContextStateSaver shadowStateSaver(*context, boxShadowShouldBeAppliedToBackground); 575 if (boxShadowShouldBeAppliedToBackground) 576 applyBoxShadowForBackground(context, this); 577 578 if (hasRoundedBorder && bleedAvoidance != BackgroundBleedUseTransparencyLayer) { 579 RoundedRect border = backgroundRoundedRectAdjustedForBleedAvoidance(context, rect, bleedAvoidance, box, boxSize, includeLeftEdge, includeRightEdge); 580 if (border.isRenderable()) 581 context->fillRoundedRect(border, bgColor); 582 else { 583 context->save(); 584 clipRoundedInnerRect(context, rect, border); 585 context->fillRect(border.rect(), bgColor); 586 context->restore(); 587 } 588 } else { 589 context->fillRect(pixelSnappedIntRect(rect), bgColor); 590 } 591 592 return; 593 } 594 595 // BorderFillBox radius clipping is taken care of by BackgroundBleedUseTransparencyLayer 596 bool clipToBorderRadius = hasRoundedBorder && !(isBorderFill && bleedAvoidance == BackgroundBleedUseTransparencyLayer); 597 GraphicsContextStateSaver clipToBorderStateSaver(*context, clipToBorderRadius); 598 if (clipToBorderRadius) { 599 RoundedRect border = isBorderFill ? backgroundRoundedRectAdjustedForBleedAvoidance(context, rect, bleedAvoidance, box, boxSize, includeLeftEdge, includeRightEdge) : getBackgroundRoundedRect(rect, box, boxSize.width(), boxSize.height(), includeLeftEdge, includeRightEdge); 600 601 // Clip to the padding or content boxes as necessary. 602 if (bgLayer->clip() == ContentFillBox) { 603 border = style()->getRoundedInnerBorderFor(border.rect(), 604 paddingTop() + borderTop(), paddingBottom() + borderBottom(), paddingLeft() + borderLeft(), paddingRight() + borderRight(), includeLeftEdge, includeRightEdge); 605 } else if (bgLayer->clip() == PaddingFillBox) 606 border = style()->getRoundedInnerBorderFor(border.rect(), includeLeftEdge, includeRightEdge); 607 608 clipRoundedInnerRect(context, rect, border); 609 } 610 611 int bLeft = includeLeftEdge ? borderLeft() : 0; 612 int bRight = includeRightEdge ? borderRight() : 0; 613 LayoutUnit pLeft = includeLeftEdge ? paddingLeft() : LayoutUnit(); 614 LayoutUnit pRight = includeRightEdge ? paddingRight() : LayoutUnit(); 615 616 GraphicsContextStateSaver clipWithScrollingStateSaver(*context, clippedWithLocalScrolling); 617 LayoutRect scrolledPaintRect = rect; 618 if (clippedWithLocalScrolling) { 619 // Clip to the overflow area. 620 RenderBox* thisBox = toRenderBox(this); 621 context->clip(thisBox->overflowClipRect(rect.location(), paintInfo.renderRegion)); 622 623 // Adjust the paint rect to reflect a scrolled content box with borders at the ends. 624 IntSize offset = thisBox->scrolledContentOffset(); 625 scrolledPaintRect.move(-offset); 626 scrolledPaintRect.setWidth(bLeft + layer()->scrollWidth() + bRight); 627 scrolledPaintRect.setHeight(borderTop() + layer()->scrollHeight() + borderBottom()); 628 } 629 630 GraphicsContextStateSaver backgroundClipStateSaver(*context, false); 631 IntRect maskRect; 632 633 switch (bgLayer->clip()) { 634 case PaddingFillBox: 635 case ContentFillBox: { 636 if (clipToBorderRadius) 637 break; 638 639 // Clip to the padding or content boxes as necessary. 640 bool includePadding = bgLayer->clip() == ContentFillBox; 641 LayoutRect clipRect = LayoutRect(scrolledPaintRect.x() + bLeft + (includePadding ? pLeft : LayoutUnit()), 642 scrolledPaintRect.y() + borderTop() + (includePadding ? paddingTop() : LayoutUnit()), 643 scrolledPaintRect.width() - bLeft - bRight - (includePadding ? pLeft + pRight : LayoutUnit()), 644 scrolledPaintRect.height() - borderTop() - borderBottom() - (includePadding ? paddingTop() + paddingBottom() : LayoutUnit())); 645 backgroundClipStateSaver.save(); 646 context->clip(clipRect); 647 648 break; 649 } 650 case TextFillBox: { 651 // First figure out how big the mask has to be. It should be no bigger than what we need 652 // to actually render, so we should intersect the dirty rect with the border box of the background. 653 maskRect = pixelSnappedIntRect(rect); 654 maskRect.intersect(paintInfo.rect); 655 656 // We draw the background into a separate layer, to be later masked with yet another layer 657 // holding the text content. 658 backgroundClipStateSaver.save(); 659 context->clip(maskRect); 660 context->beginTransparencyLayer(1); 661 662 break; 663 } 664 case BorderFillBox: 665 break; 666 default: 667 ASSERT_NOT_REACHED(); 668 break; 669 } 670 671 // Only fill with a base color (e.g., white) if we're the root document, since iframes/frames with 672 // no background in the child document should show the parent's background. 673 bool isOpaqueRoot = false; 674 if (isRoot) { 675 isOpaqueRoot = true; 676 if (!bgLayer->next() && !(bgColor.alpha() == 255) && view()->frameView()) { 677 Element* ownerElement = document()->ownerElement(); 678 if (ownerElement) { 679 if (!ownerElement->hasTagName(frameTag)) { 680 // Locate the <body> element using the DOM. This is easier than trying 681 // to crawl around a render tree with potential :before/:after content and 682 // anonymous blocks created by inline <body> tags etc. We can locate the <body> 683 // render object very easily via the DOM. 684 HTMLElement* body = document()->body(); 685 if (body) { 686 // Can't scroll a frameset document anyway. 687 isOpaqueRoot = body->hasLocalName(framesetTag); 688 } else { 689 // SVG documents and XML documents with SVG root nodes are transparent. 690 isOpaqueRoot = !document()->hasSVGRootNode(); 691 } 692 } 693 } else 694 isOpaqueRoot = !view()->frameView()->isTransparent(); 695 } 696 view()->frameView()->setContentIsOpaque(isOpaqueRoot); 697 } 698 699 // Paint the color first underneath all images, culled if background image occludes it. 700 // FIXME: In the bgLayer->hasFiniteBounds() case, we could improve the culling test 701 // by verifying whether the background image covers the entire layout rect. 702 if (!bgLayer->next()) { 703 IntRect backgroundRect(pixelSnappedIntRect(scrolledPaintRect)); 704 bool boxShadowShouldBeAppliedToBackground = this->boxShadowShouldBeAppliedToBackground(bleedAvoidance, box); 705 if (boxShadowShouldBeAppliedToBackground || !shouldPaintBackgroundImage || !bgLayer->hasOpaqueImage(this) || !bgLayer->hasRepeatXY()) { 706 if (!boxShadowShouldBeAppliedToBackground) 707 backgroundRect.intersect(paintInfo.rect); 708 709 GraphicsContextStateSaver shadowStateSaver(*context, boxShadowShouldBeAppliedToBackground); 710 if (boxShadowShouldBeAppliedToBackground) 711 applyBoxShadowForBackground(context, this); 712 713 if (isOpaqueRoot) { 714 // If we have an alpha and we are painting the root element, go ahead and blend with the base background color. 715 Color baseColor = view()->frameView()->baseBackgroundColor(); 716 bool shouldClearDocumentBackground = document()->settings() && document()->settings()->shouldClearDocumentBackground(); 717 CompositeOperator operation = shouldClearDocumentBackground ? CompositeCopy : context->compositeOperation(); 718 719 if (baseColor.alpha()) { 720 if (bgColor.alpha()) 721 baseColor = baseColor.blend(bgColor); 722 context->fillRect(backgroundRect, baseColor, operation); 723 } else if (bgColor.alpha()) { 724 context->fillRect(backgroundRect, bgColor, operation); 725 } else if (shouldClearDocumentBackground) { 726 context->clearRect(backgroundRect); 727 } 728 } else if (bgColor.alpha()) { 729 context->fillRect(backgroundRect, bgColor, context->compositeOperation()); 730 } 731 } 732 } 733 734 // no progressive loading of the background image 735 if (shouldPaintBackgroundImage) { 736 BackgroundImageGeometry geometry; 737 calculateBackgroundImageGeometry(bgLayer, scrolledPaintRect, geometry, backgroundObject); 738 geometry.clip(paintInfo.rect); 739 if (!geometry.destRect().isEmpty()) { 740 CompositeOperator compositeOp = op == CompositeSourceOver ? bgLayer->composite() : op; 741 RenderObject* clientForBackgroundImage = backgroundObject ? backgroundObject : this; 742 RefPtr<Image> image = bgImage->image(clientForBackgroundImage, geometry.tileSize()); 743 bool useLowQualityScaling = shouldPaintAtLowQuality(context, image.get(), bgLayer, geometry.tileSize()); 744 context->drawTiledImage(image.get(), geometry.destRect(), geometry.relativePhase(), geometry.tileSize(), 745 compositeOp, useLowQualityScaling, bgLayer->blendMode()); 746 } 747 } 748 749 if (bgLayer->clip() == TextFillBox) { 750 // Create the text mask layer. 751 context->setCompositeOperation(CompositeDestinationIn); 752 context->beginTransparencyLayer(1); 753 754 // FIXME: Workaround for https://code.google.com/p/skia/issues/detail?id=1291. 755 context->clearRect(maskRect); 756 757 // Now draw the text into the mask. We do this by painting using a special paint phase that signals to 758 // InlineTextBoxes that they should just add their contents to the clip. 759 PaintInfo info(context, maskRect, PaintPhaseTextClip, PaintBehaviorForceBlackText, 0, paintInfo.renderRegion); 760 context->setCompositeOperation(CompositeSourceOver); 761 if (box) { 762 RootInlineBox* root = box->root(); 763 box->paint(info, LayoutPoint(scrolledPaintRect.x() - box->x(), scrolledPaintRect.y() - box->y()), root->lineTop(), root->lineBottom()); 764 } else { 765 LayoutSize localOffset = isBox() ? toRenderBox(this)->locationOffset() : LayoutSize(); 766 paint(info, scrolledPaintRect.location() - localOffset); 767 } 768 769 context->endTransparencyLayer(); 770 context->endTransparencyLayer(); 771 } 772 } 773 774 static inline int resolveWidthForRatio(int height, const FloatSize& intrinsicRatio) 775 { 776 return ceilf(height * intrinsicRatio.width() / intrinsicRatio.height()); 777 } 778 779 static inline int resolveHeightForRatio(int width, const FloatSize& intrinsicRatio) 780 { 781 return ceilf(width * intrinsicRatio.height() / intrinsicRatio.width()); 782 } 783 784 static inline IntSize resolveAgainstIntrinsicWidthOrHeightAndRatio(const IntSize& size, const FloatSize& intrinsicRatio, int useWidth, int useHeight) 785 { 786 if (intrinsicRatio.isEmpty()) { 787 if (useWidth) 788 return IntSize(useWidth, size.height()); 789 return IntSize(size.width(), useHeight); 790 } 791 792 if (useWidth) 793 return IntSize(useWidth, resolveHeightForRatio(useWidth, intrinsicRatio)); 794 return IntSize(resolveWidthForRatio(useHeight, intrinsicRatio), useHeight); 795 } 796 797 static inline IntSize resolveAgainstIntrinsicRatio(const IntSize& size, const FloatSize& intrinsicRatio) 798 { 799 // Two possible solutions: (size.width(), solutionHeight) or (solutionWidth, size.height()) 800 // "... must be assumed to be the largest dimensions..." = easiest answer: the rect with the largest surface area. 801 802 int solutionWidth = resolveWidthForRatio(size.height(), intrinsicRatio); 803 int solutionHeight = resolveHeightForRatio(size.width(), intrinsicRatio); 804 if (solutionWidth <= size.width()) { 805 if (solutionHeight <= size.height()) { 806 // If both solutions fit, choose the one covering the larger area. 807 int areaOne = solutionWidth * size.height(); 808 int areaTwo = size.width() * solutionHeight; 809 if (areaOne < areaTwo) 810 return IntSize(size.width(), solutionHeight); 811 return IntSize(solutionWidth, size.height()); 812 } 813 814 // Only the first solution fits. 815 return IntSize(solutionWidth, size.height()); 816 } 817 818 // Only the second solution fits, assert that. 819 ASSERT(solutionHeight <= size.height()); 820 return IntSize(size.width(), solutionHeight); 821 } 822 823 IntSize RenderBoxModelObject::calculateImageIntrinsicDimensions(StyleImage* image, const IntSize& positioningAreaSize, ScaleByEffectiveZoomOrNot shouldScaleOrNot) const 824 { 825 // A generated image without a fixed size, will always return the container size as intrinsic size. 826 if (image->isGeneratedImage() && image->usesImageContainerSize()) 827 return IntSize(positioningAreaSize.width(), positioningAreaSize.height()); 828 829 Length intrinsicWidth; 830 Length intrinsicHeight; 831 FloatSize intrinsicRatio; 832 image->computeIntrinsicDimensions(this, intrinsicWidth, intrinsicHeight, intrinsicRatio); 833 834 // Intrinsic dimensions expressed as percentages must be resolved relative to the dimensions of the rectangle 835 // that establishes the coordinate system for the 'background-position' property. 836 837 // FIXME: Remove unnecessary rounding when layout is off ints: webkit.org/b/63656 838 if (intrinsicWidth.isPercent() && intrinsicHeight.isPercent() && intrinsicRatio.isEmpty()) { 839 // Resolve width/height percentages against positioningAreaSize, only if no intrinsic ratio is provided. 840 int resolvedWidth = static_cast<int>(round(positioningAreaSize.width() * intrinsicWidth.percent() / 100)); 841 int resolvedHeight = static_cast<int>(round(positioningAreaSize.height() * intrinsicHeight.percent() / 100)); 842 return IntSize(resolvedWidth, resolvedHeight); 843 } 844 845 IntSize resolvedSize(intrinsicWidth.isFixed() ? intrinsicWidth.value() : 0, intrinsicHeight.isFixed() ? intrinsicHeight.value() : 0); 846 IntSize minimumSize(resolvedSize.width() > 0 ? 1 : 0, resolvedSize.height() > 0 ? 1 : 0); 847 if (shouldScaleOrNot == ScaleByEffectiveZoom) 848 resolvedSize.scale(style()->effectiveZoom()); 849 resolvedSize.clampToMinimumSize(minimumSize); 850 851 if (!resolvedSize.isEmpty()) 852 return resolvedSize; 853 854 // If the image has one of either an intrinsic width or an intrinsic height: 855 // * and an intrinsic aspect ratio, then the missing dimension is calculated from the given dimension and the ratio. 856 // * and no intrinsic aspect ratio, then the missing dimension is assumed to be the size of the rectangle that 857 // establishes the coordinate system for the 'background-position' property. 858 if (resolvedSize.width() > 0 || resolvedSize.height() > 0) 859 return resolveAgainstIntrinsicWidthOrHeightAndRatio(positioningAreaSize, intrinsicRatio, resolvedSize.width(), resolvedSize.height()); 860 861 // If the image has no intrinsic dimensions and has an intrinsic ratio the dimensions must be assumed to be the 862 // largest dimensions at that ratio such that neither dimension exceeds the dimensions of the rectangle that 863 // establishes the coordinate system for the 'background-position' property. 864 if (!intrinsicRatio.isEmpty()) 865 return resolveAgainstIntrinsicRatio(positioningAreaSize, intrinsicRatio); 866 867 // If the image has no intrinsic ratio either, then the dimensions must be assumed to be the rectangle that 868 // establishes the coordinate system for the 'background-position' property. 869 return positioningAreaSize; 870 } 871 872 static inline void applySubPixelHeuristicForTileSize(LayoutSize& tileSize, const IntSize& positioningAreaSize) 873 { 874 tileSize.setWidth(positioningAreaSize.width() - tileSize.width() <= 1 ? tileSize.width().ceil() : tileSize.width().floor()); 875 tileSize.setHeight(positioningAreaSize.height() - tileSize.height() <= 1 ? tileSize.height().ceil() : tileSize.height().floor()); 876 } 877 878 IntSize RenderBoxModelObject::calculateFillTileSize(const FillLayer* fillLayer, const IntSize& positioningAreaSize) const 879 { 880 StyleImage* image = fillLayer->image(); 881 EFillSizeType type = fillLayer->size().type; 882 883 IntSize imageIntrinsicSize = calculateImageIntrinsicDimensions(image, positioningAreaSize, ScaleByEffectiveZoom); 884 imageIntrinsicSize.scale(1 / image->imageScaleFactor(), 1 / image->imageScaleFactor()); 885 RenderView* renderView = view(); 886 switch (type) { 887 case SizeLength: { 888 LayoutSize tileSize = positioningAreaSize; 889 890 Length layerWidth = fillLayer->size().size.width(); 891 Length layerHeight = fillLayer->size().size.height(); 892 893 if (layerWidth.isFixed()) 894 tileSize.setWidth(layerWidth.value()); 895 else if (layerWidth.isPercent() || layerWidth.isViewportPercentage()) 896 tileSize.setWidth(valueForLength(layerWidth, positioningAreaSize.width(), renderView)); 897 898 if (layerHeight.isFixed()) 899 tileSize.setHeight(layerHeight.value()); 900 else if (layerHeight.isPercent() || layerHeight.isViewportPercentage()) 901 tileSize.setHeight(valueForLength(layerHeight, positioningAreaSize.height(), renderView)); 902 903 applySubPixelHeuristicForTileSize(tileSize, positioningAreaSize); 904 905 // If one of the values is auto we have to use the appropriate 906 // scale to maintain our aspect ratio. 907 if (layerWidth.isAuto() && !layerHeight.isAuto()) { 908 if (imageIntrinsicSize.height()) 909 tileSize.setWidth(imageIntrinsicSize.width() * tileSize.height() / imageIntrinsicSize.height()); 910 } else if (!layerWidth.isAuto() && layerHeight.isAuto()) { 911 if (imageIntrinsicSize.width()) 912 tileSize.setHeight(imageIntrinsicSize.height() * tileSize.width() / imageIntrinsicSize.width()); 913 } else if (layerWidth.isAuto() && layerHeight.isAuto()) { 914 // If both width and height are auto, use the image's intrinsic size. 915 tileSize = imageIntrinsicSize; 916 } 917 918 tileSize.clampNegativeToZero(); 919 return flooredIntSize(tileSize); 920 } 921 case SizeNone: { 922 // If both values are auto then the intrinsic width and/or height of the image should be used, if any. 923 if (!imageIntrinsicSize.isEmpty()) 924 return imageIntrinsicSize; 925 926 // If the image has neither an intrinsic width nor an intrinsic height, its size is determined as for contain. 927 type = Contain; 928 } 929 case Contain: 930 case Cover: { 931 float horizontalScaleFactor = imageIntrinsicSize.width() 932 ? static_cast<float>(positioningAreaSize.width()) / imageIntrinsicSize.width() : 1; 933 float verticalScaleFactor = imageIntrinsicSize.height() 934 ? static_cast<float>(positioningAreaSize.height()) / imageIntrinsicSize.height() : 1; 935 float scaleFactor = type == Contain ? min(horizontalScaleFactor, verticalScaleFactor) : max(horizontalScaleFactor, verticalScaleFactor); 936 return IntSize(max(1, static_cast<int>(imageIntrinsicSize.width() * scaleFactor)), max(1, static_cast<int>(imageIntrinsicSize.height() * scaleFactor))); 937 } 938 } 939 940 ASSERT_NOT_REACHED(); 941 return IntSize(); 942 } 943 944 void RenderBoxModelObject::BackgroundImageGeometry::setNoRepeatX(int xOffset) 945 { 946 m_destRect.move(max(xOffset, 0), 0); 947 m_phase.setX(-min(xOffset, 0)); 948 m_destRect.setWidth(m_tileSize.width() + min(xOffset, 0)); 949 } 950 void RenderBoxModelObject::BackgroundImageGeometry::setNoRepeatY(int yOffset) 951 { 952 m_destRect.move(0, max(yOffset, 0)); 953 m_phase.setY(-min(yOffset, 0)); 954 m_destRect.setHeight(m_tileSize.height() + min(yOffset, 0)); 955 } 956 957 void RenderBoxModelObject::BackgroundImageGeometry::useFixedAttachment(const IntPoint& attachmentPoint) 958 { 959 IntPoint alignedPoint = attachmentPoint; 960 m_phase.move(max(alignedPoint.x() - m_destRect.x(), 0), max(alignedPoint.y() - m_destRect.y(), 0)); 961 } 962 963 void RenderBoxModelObject::BackgroundImageGeometry::clip(const IntRect& clipRect) 964 { 965 m_destRect.intersect(clipRect); 966 } 967 968 IntPoint RenderBoxModelObject::BackgroundImageGeometry::relativePhase() const 969 { 970 IntPoint phase = m_phase; 971 phase += m_destRect.location() - m_destOrigin; 972 return phase; 973 } 974 975 bool RenderBoxModelObject::fixedBackgroundPaintsInLocalCoordinates() const 976 { 977 if (!isRoot()) 978 return false; 979 980 if (view()->frameView() && view()->frameView()->paintBehavior() & PaintBehaviorFlattenCompositingLayers) 981 return false; 982 983 RenderLayer* rootLayer = view()->layer(); 984 if (!rootLayer || !rootLayer->isComposited()) 985 return false; 986 987 return rootLayer->backing()->backgroundLayerPaintsFixedRootBackground(); 988 } 989 990 void RenderBoxModelObject::calculateBackgroundImageGeometry(const FillLayer* fillLayer, const LayoutRect& paintRect, 991 BackgroundImageGeometry& geometry, RenderObject* backgroundObject) 992 { 993 LayoutUnit left = 0; 994 LayoutUnit top = 0; 995 IntSize positioningAreaSize; 996 IntRect snappedPaintRect = pixelSnappedIntRect(paintRect); 997 998 // Determine the background positioning area and set destRect to the background painting area. 999 // destRect will be adjusted later if the background is non-repeating. 1000 bool fixedAttachment = fillLayer->attachment() == FixedBackgroundAttachment; 1001 1002 #if ENABLE(FAST_MOBILE_SCROLLING) 1003 if (view()->frameView() && view()->frameView()->canBlitOnScroll()) { 1004 // As a side effect of an optimization to blit on scroll, we do not honor the CSS 1005 // property "background-attachment: fixed" because it may result in rendering 1006 // artifacts. Note, these artifacts only appear if we are blitting on scroll of 1007 // a page that has fixed background images. 1008 fixedAttachment = false; 1009 } 1010 #endif 1011 1012 if (!fixedAttachment) { 1013 geometry.setDestRect(snappedPaintRect); 1014 1015 LayoutUnit right = 0; 1016 LayoutUnit bottom = 0; 1017 // Scroll and Local. 1018 if (fillLayer->origin() != BorderFillBox) { 1019 left = borderLeft(); 1020 right = borderRight(); 1021 top = borderTop(); 1022 bottom = borderBottom(); 1023 if (fillLayer->origin() == ContentFillBox) { 1024 left += paddingLeft(); 1025 right += paddingRight(); 1026 top += paddingTop(); 1027 bottom += paddingBottom(); 1028 } 1029 } 1030 1031 // The background of the box generated by the root element covers the entire canvas including 1032 // its margins. Since those were added in already, we have to factor them out when computing 1033 // the background positioning area. 1034 if (isRoot()) { 1035 positioningAreaSize = pixelSnappedIntSize(toRenderBox(this)->size() - LayoutSize(left + right, top + bottom), toRenderBox(this)->location()); 1036 left += marginLeft(); 1037 top += marginTop(); 1038 } else 1039 positioningAreaSize = pixelSnappedIntSize(paintRect.size() - LayoutSize(left + right, top + bottom), paintRect.location()); 1040 } else { 1041 IntRect viewportRect = pixelSnappedIntRect(viewRect()); 1042 if (fixedBackgroundPaintsInLocalCoordinates()) 1043 viewportRect.setLocation(IntPoint()); 1044 else if (FrameView* frameView = view()->frameView()) 1045 viewportRect.setLocation(IntPoint(frameView->scrollOffsetForFixedPosition())); 1046 1047 geometry.setDestRect(pixelSnappedIntRect(viewportRect)); 1048 positioningAreaSize = geometry.destRect().size(); 1049 } 1050 1051 RenderObject* clientForBackgroundImage = backgroundObject ? backgroundObject : this; 1052 IntSize fillTileSize = calculateFillTileSize(fillLayer, positioningAreaSize); 1053 fillLayer->image()->setContainerSizeForRenderer(clientForBackgroundImage, fillTileSize, style()->effectiveZoom()); 1054 geometry.setTileSize(fillTileSize); 1055 1056 EFillRepeat backgroundRepeatX = fillLayer->repeatX(); 1057 EFillRepeat backgroundRepeatY = fillLayer->repeatY(); 1058 RenderView* renderView = view(); 1059 int availableWidth = positioningAreaSize.width() - geometry.tileSize().width(); 1060 int availableHeight = positioningAreaSize.height() - geometry.tileSize().height(); 1061 1062 LayoutUnit computedXPosition = minimumValueForLength(fillLayer->xPosition(), availableWidth, renderView, true); 1063 if (backgroundRepeatX == RepeatFill) 1064 geometry.setPhaseX(geometry.tileSize().width() ? geometry.tileSize().width() - roundToInt(computedXPosition + left) % geometry.tileSize().width() : 0); 1065 else { 1066 int xOffset = fillLayer->backgroundXOrigin() == RightEdge ? availableWidth - computedXPosition : computedXPosition; 1067 geometry.setNoRepeatX(left + xOffset); 1068 } 1069 LayoutUnit computedYPosition = minimumValueForLength(fillLayer->yPosition(), availableHeight, renderView, true); 1070 if (backgroundRepeatY == RepeatFill) 1071 geometry.setPhaseY(geometry.tileSize().height() ? geometry.tileSize().height() - roundToInt(computedYPosition + top) % geometry.tileSize().height() : 0); 1072 else { 1073 int yOffset = fillLayer->backgroundYOrigin() == BottomEdge ? availableHeight - computedYPosition : computedYPosition; 1074 geometry.setNoRepeatY(top + yOffset); 1075 } 1076 1077 if (fixedAttachment) 1078 geometry.useFixedAttachment(snappedPaintRect.location()); 1079 1080 geometry.clip(snappedPaintRect); 1081 geometry.setDestOrigin(geometry.destRect().location()); 1082 } 1083 1084 static LayoutUnit computeBorderImageSide(Length borderSlice, LayoutUnit borderSide, LayoutUnit imageSide, LayoutUnit boxExtent, RenderView* renderView) 1085 { 1086 if (borderSlice.isRelative()) 1087 return borderSlice.value() * borderSide; 1088 if (borderSlice.isAuto()) 1089 return imageSide; 1090 return valueForLength(borderSlice, boxExtent, renderView); 1091 } 1092 1093 bool RenderBoxModelObject::paintNinePieceImage(GraphicsContext* graphicsContext, const LayoutRect& rect, const RenderStyle* style, 1094 const NinePieceImage& ninePieceImage, CompositeOperator op) 1095 { 1096 StyleImage* styleImage = ninePieceImage.image(); 1097 if (!styleImage) 1098 return false; 1099 1100 if (!styleImage->isLoaded()) 1101 return true; // Never paint a nine-piece image incrementally, but don't paint the fallback borders either. 1102 1103 if (!styleImage->canRender(this, style->effectiveZoom())) 1104 return false; 1105 1106 // FIXME: border-image is broken with full page zooming when tiling has to happen, since the tiling function 1107 // doesn't have any understanding of the zoom that is in effect on the tile. 1108 LayoutRect rectWithOutsets = rect; 1109 rectWithOutsets.expand(style->imageOutsets(ninePieceImage)); 1110 IntRect borderImageRect = pixelSnappedIntRect(rectWithOutsets); 1111 1112 IntSize imageSize = calculateImageIntrinsicDimensions(styleImage, borderImageRect.size(), DoNotScaleByEffectiveZoom); 1113 1114 // If both values are auto then the intrinsic width and/or height of the image should be used, if any. 1115 styleImage->setContainerSizeForRenderer(this, imageSize, style->effectiveZoom()); 1116 1117 int imageWidth = imageSize.width(); 1118 int imageHeight = imageSize.height(); 1119 RenderView* renderView = view(); 1120 1121 float imageScaleFactor = styleImage->imageScaleFactor(); 1122 int topSlice = min<int>(imageHeight, valueForLength(ninePieceImage.imageSlices().top(), imageHeight, renderView)) * imageScaleFactor; 1123 int rightSlice = min<int>(imageWidth, valueForLength(ninePieceImage.imageSlices().right(), imageWidth, renderView)) * imageScaleFactor; 1124 int bottomSlice = min<int>(imageHeight, valueForLength(ninePieceImage.imageSlices().bottom(), imageHeight, renderView)) * imageScaleFactor; 1125 int leftSlice = min<int>(imageWidth, valueForLength(ninePieceImage.imageSlices().left(), imageWidth, renderView)) * imageScaleFactor; 1126 1127 ENinePieceImageRule hRule = ninePieceImage.horizontalRule(); 1128 ENinePieceImageRule vRule = ninePieceImage.verticalRule(); 1129 1130 int topWidth = computeBorderImageSide(ninePieceImage.borderSlices().top(), style->borderTopWidth(), topSlice, borderImageRect.height(), renderView); 1131 int rightWidth = computeBorderImageSide(ninePieceImage.borderSlices().right(), style->borderRightWidth(), rightSlice, borderImageRect.width(), renderView); 1132 int bottomWidth = computeBorderImageSide(ninePieceImage.borderSlices().bottom(), style->borderBottomWidth(), bottomSlice, borderImageRect.height(), renderView); 1133 int leftWidth = computeBorderImageSide(ninePieceImage.borderSlices().left(), style->borderLeftWidth(), leftSlice, borderImageRect.width(), renderView); 1134 1135 // Reduce the widths if they're too large. 1136 // The spec says: Given Lwidth as the width of the border image area, Lheight as its height, and Wside as the border image width 1137 // offset for the side, let f = min(Lwidth/(Wleft+Wright), Lheight/(Wtop+Wbottom)). If f < 1, then all W are reduced by 1138 // multiplying them by f. 1139 int borderSideWidth = max(1, leftWidth + rightWidth); 1140 int borderSideHeight = max(1, topWidth + bottomWidth); 1141 float borderSideScaleFactor = min((float)borderImageRect.width() / borderSideWidth, (float)borderImageRect.height() / borderSideHeight); 1142 if (borderSideScaleFactor < 1) { 1143 topWidth *= borderSideScaleFactor; 1144 rightWidth *= borderSideScaleFactor; 1145 bottomWidth *= borderSideScaleFactor; 1146 leftWidth *= borderSideScaleFactor; 1147 } 1148 1149 bool drawLeft = leftSlice > 0 && leftWidth > 0; 1150 bool drawTop = topSlice > 0 && topWidth > 0; 1151 bool drawRight = rightSlice > 0 && rightWidth > 0; 1152 bool drawBottom = bottomSlice > 0 && bottomWidth > 0; 1153 bool drawMiddle = ninePieceImage.fill() && (imageWidth - leftSlice - rightSlice) > 0 && (borderImageRect.width() - leftWidth - rightWidth) > 0 1154 && (imageHeight - topSlice - bottomSlice) > 0 && (borderImageRect.height() - topWidth - bottomWidth) > 0; 1155 1156 RefPtr<Image> image = styleImage->image(this, imageSize); 1157 1158 float destinationWidth = borderImageRect.width() - leftWidth - rightWidth; 1159 float destinationHeight = borderImageRect.height() - topWidth - bottomWidth; 1160 1161 float sourceWidth = imageWidth - leftSlice - rightSlice; 1162 float sourceHeight = imageHeight - topSlice - bottomSlice; 1163 1164 float leftSideScale = drawLeft ? (float)leftWidth / leftSlice : 1; 1165 float rightSideScale = drawRight ? (float)rightWidth / rightSlice : 1; 1166 float topSideScale = drawTop ? (float)topWidth / topSlice : 1; 1167 float bottomSideScale = drawBottom ? (float)bottomWidth / bottomSlice : 1; 1168 1169 if (drawLeft) { 1170 // Paint the top and bottom left corners. 1171 1172 // The top left corner rect is (tx, ty, leftWidth, topWidth) 1173 // The rect to use from within the image is obtained from our slice, and is (0, 0, leftSlice, topSlice) 1174 if (drawTop) 1175 graphicsContext->drawImage(image.get(), IntRect(borderImageRect.location(), IntSize(leftWidth, topWidth)), 1176 LayoutRect(0, 0, leftSlice, topSlice), op); 1177 1178 // The bottom left corner rect is (tx, ty + h - bottomWidth, leftWidth, bottomWidth) 1179 // The rect to use from within the image is (0, imageHeight - bottomSlice, leftSlice, botomSlice) 1180 if (drawBottom) 1181 graphicsContext->drawImage(image.get(), IntRect(borderImageRect.x(), borderImageRect.maxY() - bottomWidth, leftWidth, bottomWidth), 1182 LayoutRect(0, imageHeight - bottomSlice, leftSlice, bottomSlice), op); 1183 1184 // Paint the left edge. 1185 // Have to scale and tile into the border rect. 1186 if (sourceHeight > 0) 1187 graphicsContext->drawTiledImage(image.get(), IntRect(borderImageRect.x(), borderImageRect.y() + topWidth, leftWidth, destinationHeight), 1188 IntRect(0, topSlice, leftSlice, sourceHeight), 1189 FloatSize(leftSideScale, leftSideScale), Image::StretchTile, (Image::TileRule)vRule, op); 1190 } 1191 1192 if (drawRight) { 1193 // Paint the top and bottom right corners 1194 // The top right corner rect is (tx + w - rightWidth, ty, rightWidth, topWidth) 1195 // The rect to use from within the image is obtained from our slice, and is (imageWidth - rightSlice, 0, rightSlice, topSlice) 1196 if (drawTop) 1197 graphicsContext->drawImage(image.get(), IntRect(borderImageRect.maxX() - rightWidth, borderImageRect.y(), rightWidth, topWidth), 1198 LayoutRect(imageWidth - rightSlice, 0, rightSlice, topSlice), op); 1199 1200 // The bottom right corner rect is (tx + w - rightWidth, ty + h - bottomWidth, rightWidth, bottomWidth) 1201 // The rect to use from within the image is (imageWidth - rightSlice, imageHeight - bottomSlice, rightSlice, bottomSlice) 1202 if (drawBottom) 1203 graphicsContext->drawImage(image.get(), IntRect(borderImageRect.maxX() - rightWidth, borderImageRect.maxY() - bottomWidth, rightWidth, bottomWidth), 1204 LayoutRect(imageWidth - rightSlice, imageHeight - bottomSlice, rightSlice, bottomSlice), op); 1205 1206 // Paint the right edge. 1207 if (sourceHeight > 0) 1208 graphicsContext->drawTiledImage(image.get(), IntRect(borderImageRect.maxX() - rightWidth, borderImageRect.y() + topWidth, rightWidth, 1209 destinationHeight), 1210 IntRect(imageWidth - rightSlice, topSlice, rightSlice, sourceHeight), 1211 FloatSize(rightSideScale, rightSideScale), 1212 Image::StretchTile, (Image::TileRule)vRule, op); 1213 } 1214 1215 // Paint the top edge. 1216 if (drawTop && sourceWidth > 0) 1217 graphicsContext->drawTiledImage(image.get(), IntRect(borderImageRect.x() + leftWidth, borderImageRect.y(), destinationWidth, topWidth), 1218 IntRect(leftSlice, 0, sourceWidth, topSlice), 1219 FloatSize(topSideScale, topSideScale), (Image::TileRule)hRule, Image::StretchTile, op); 1220 1221 // Paint the bottom edge. 1222 if (drawBottom && sourceWidth > 0) 1223 graphicsContext->drawTiledImage(image.get(), IntRect(borderImageRect.x() + leftWidth, borderImageRect.maxY() - bottomWidth, 1224 destinationWidth, bottomWidth), 1225 IntRect(leftSlice, imageHeight - bottomSlice, sourceWidth, bottomSlice), 1226 FloatSize(bottomSideScale, bottomSideScale), 1227 (Image::TileRule)hRule, Image::StretchTile, op); 1228 1229 // Paint the middle. 1230 if (drawMiddle) { 1231 FloatSize middleScaleFactor(1, 1); 1232 if (drawTop) 1233 middleScaleFactor.setWidth(topSideScale); 1234 else if (drawBottom) 1235 middleScaleFactor.setWidth(bottomSideScale); 1236 if (drawLeft) 1237 middleScaleFactor.setHeight(leftSideScale); 1238 else if (drawRight) 1239 middleScaleFactor.setHeight(rightSideScale); 1240 1241 // For "stretch" rules, just override the scale factor and replace. We only had to do this for the 1242 // center tile, since sides don't even use the scale factor unless they have a rule other than "stretch". 1243 // The middle however can have "stretch" specified in one axis but not the other, so we have to 1244 // correct the scale here. 1245 if (hRule == StretchImageRule) 1246 middleScaleFactor.setWidth(destinationWidth / sourceWidth); 1247 1248 if (vRule == StretchImageRule) 1249 middleScaleFactor.setHeight(destinationHeight / sourceHeight); 1250 1251 graphicsContext->drawTiledImage(image.get(), 1252 IntRect(borderImageRect.x() + leftWidth, borderImageRect.y() + topWidth, destinationWidth, destinationHeight), 1253 IntRect(leftSlice, topSlice, sourceWidth, sourceHeight), 1254 middleScaleFactor, (Image::TileRule)hRule, (Image::TileRule)vRule, op); 1255 } 1256 1257 return true; 1258 } 1259 1260 class BorderEdge { 1261 public: 1262 BorderEdge(int edgeWidth, const Color& edgeColor, EBorderStyle edgeStyle, bool edgeIsTransparent, bool edgeIsPresent = true) 1263 : width(edgeWidth) 1264 , color(edgeColor) 1265 , style(edgeStyle) 1266 , isTransparent(edgeIsTransparent) 1267 , isPresent(edgeIsPresent) 1268 { 1269 if (style == DOUBLE && edgeWidth < 3) 1270 style = SOLID; 1271 } 1272 1273 BorderEdge() 1274 : width(0) 1275 , style(BHIDDEN) 1276 , isTransparent(false) 1277 , isPresent(false) 1278 { 1279 } 1280 1281 bool hasVisibleColorAndStyle() const { return style > BHIDDEN && !isTransparent; } 1282 bool shouldRender() const { return isPresent && width && hasVisibleColorAndStyle(); } 1283 bool presentButInvisible() const { return usedWidth() && !hasVisibleColorAndStyle(); } 1284 bool obscuresBackgroundEdge(float scale) const 1285 { 1286 if (!isPresent || isTransparent || (width * scale) < 2 || color.hasAlpha() || style == BHIDDEN) 1287 return false; 1288 1289 if (style == DOTTED || style == DASHED) 1290 return false; 1291 1292 if (style == DOUBLE) 1293 return width >= 5 * scale; // The outer band needs to be >= 2px wide at unit scale. 1294 1295 return true; 1296 } 1297 bool obscuresBackground() const 1298 { 1299 if (!isPresent || isTransparent || color.hasAlpha() || style == BHIDDEN) 1300 return false; 1301 1302 if (style == DOTTED || style == DASHED || style == DOUBLE) 1303 return false; 1304 1305 return true; 1306 } 1307 1308 int usedWidth() const { return isPresent ? width : 0; } 1309 1310 void getDoubleBorderStripeWidths(int& outerWidth, int& innerWidth) const 1311 { 1312 int fullWidth = usedWidth(); 1313 outerWidth = fullWidth / 3; 1314 innerWidth = fullWidth * 2 / 3; 1315 1316 // We need certain integer rounding results 1317 if (fullWidth % 3 == 2) 1318 outerWidth += 1; 1319 1320 if (fullWidth % 3 == 1) 1321 innerWidth += 1; 1322 } 1323 1324 int width; 1325 Color color; 1326 EBorderStyle style; 1327 bool isTransparent; 1328 bool isPresent; 1329 }; 1330 1331 static bool allCornersClippedOut(const RoundedRect& border, const LayoutRect& clipRect) 1332 { 1333 LayoutRect boundingRect = border.rect(); 1334 if (clipRect.contains(boundingRect)) 1335 return false; 1336 1337 RoundedRect::Radii radii = border.radii(); 1338 1339 LayoutRect topLeftRect(boundingRect.location(), radii.topLeft()); 1340 if (clipRect.intersects(topLeftRect)) 1341 return false; 1342 1343 LayoutRect topRightRect(boundingRect.location(), radii.topRight()); 1344 topRightRect.setX(boundingRect.maxX() - topRightRect.width()); 1345 if (clipRect.intersects(topRightRect)) 1346 return false; 1347 1348 LayoutRect bottomLeftRect(boundingRect.location(), radii.bottomLeft()); 1349 bottomLeftRect.setY(boundingRect.maxY() - bottomLeftRect.height()); 1350 if (clipRect.intersects(bottomLeftRect)) 1351 return false; 1352 1353 LayoutRect bottomRightRect(boundingRect.location(), radii.bottomRight()); 1354 bottomRightRect.setX(boundingRect.maxX() - bottomRightRect.width()); 1355 bottomRightRect.setY(boundingRect.maxY() - bottomRightRect.height()); 1356 if (clipRect.intersects(bottomRightRect)) 1357 return false; 1358 1359 return true; 1360 } 1361 1362 static bool borderWillArcInnerEdge(const LayoutSize& firstRadius, const FloatSize& secondRadius) 1363 { 1364 return !firstRadius.isZero() || !secondRadius.isZero(); 1365 } 1366 1367 enum BorderEdgeFlag { 1368 TopBorderEdge = 1 << BSTop, 1369 RightBorderEdge = 1 << BSRight, 1370 BottomBorderEdge = 1 << BSBottom, 1371 LeftBorderEdge = 1 << BSLeft, 1372 AllBorderEdges = TopBorderEdge | BottomBorderEdge | LeftBorderEdge | RightBorderEdge 1373 }; 1374 1375 static inline BorderEdgeFlag edgeFlagForSide(BoxSide side) 1376 { 1377 return static_cast<BorderEdgeFlag>(1 << side); 1378 } 1379 1380 static inline bool includesEdge(BorderEdgeFlags flags, BoxSide side) 1381 { 1382 return flags & edgeFlagForSide(side); 1383 } 1384 1385 static inline bool includesAdjacentEdges(BorderEdgeFlags flags) 1386 { 1387 return (flags & (TopBorderEdge | RightBorderEdge)) == (TopBorderEdge | RightBorderEdge) 1388 || (flags & (RightBorderEdge | BottomBorderEdge)) == (RightBorderEdge | BottomBorderEdge) 1389 || (flags & (BottomBorderEdge | LeftBorderEdge)) == (BottomBorderEdge | LeftBorderEdge) 1390 || (flags & (LeftBorderEdge | TopBorderEdge)) == (LeftBorderEdge | TopBorderEdge); 1391 } 1392 1393 inline bool edgesShareColor(const BorderEdge& firstEdge, const BorderEdge& secondEdge) 1394 { 1395 return firstEdge.color == secondEdge.color; 1396 } 1397 1398 inline bool styleRequiresClipPolygon(EBorderStyle style) 1399 { 1400 return style == DOTTED || style == DASHED; // These are drawn with a stroke, so we have to clip to get corner miters. 1401 } 1402 1403 static bool borderStyleFillsBorderArea(EBorderStyle style) 1404 { 1405 return !(style == DOTTED || style == DASHED || style == DOUBLE); 1406 } 1407 1408 static bool borderStyleHasInnerDetail(EBorderStyle style) 1409 { 1410 return style == GROOVE || style == RIDGE || style == DOUBLE; 1411 } 1412 1413 static bool borderStyleIsDottedOrDashed(EBorderStyle style) 1414 { 1415 return style == DOTTED || style == DASHED; 1416 } 1417 1418 // OUTSET darkens the bottom and right (and maybe lightens the top and left) 1419 // INSET darkens the top and left (and maybe lightens the bottom and right) 1420 static inline bool borderStyleHasUnmatchedColorsAtCorner(EBorderStyle style, BoxSide side, BoxSide adjacentSide) 1421 { 1422 // These styles match at the top/left and bottom/right. 1423 if (style == INSET || style == GROOVE || style == RIDGE || style == OUTSET) { 1424 const BorderEdgeFlags topRightFlags = edgeFlagForSide(BSTop) | edgeFlagForSide(BSRight); 1425 const BorderEdgeFlags bottomLeftFlags = edgeFlagForSide(BSBottom) | edgeFlagForSide(BSLeft); 1426 1427 BorderEdgeFlags flags = edgeFlagForSide(side) | edgeFlagForSide(adjacentSide); 1428 return flags == topRightFlags || flags == bottomLeftFlags; 1429 } 1430 return false; 1431 } 1432 1433 static inline bool colorsMatchAtCorner(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[]) 1434 { 1435 if (edges[side].shouldRender() != edges[adjacentSide].shouldRender()) 1436 return false; 1437 1438 if (!edgesShareColor(edges[side], edges[adjacentSide])) 1439 return false; 1440 1441 return !borderStyleHasUnmatchedColorsAtCorner(edges[side].style, side, adjacentSide); 1442 } 1443 1444 1445 static inline bool colorNeedsAntiAliasAtCorner(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[]) 1446 { 1447 if (!edges[side].color.hasAlpha()) 1448 return false; 1449 1450 if (edges[side].shouldRender() != edges[adjacentSide].shouldRender()) 1451 return false; 1452 1453 if (!edgesShareColor(edges[side], edges[adjacentSide])) 1454 return true; 1455 1456 return borderStyleHasUnmatchedColorsAtCorner(edges[side].style, side, adjacentSide); 1457 } 1458 1459 // This assumes that we draw in order: top, bottom, left, right. 1460 static inline bool willBeOverdrawn(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[]) 1461 { 1462 switch (side) { 1463 case BSTop: 1464 case BSBottom: 1465 if (edges[adjacentSide].presentButInvisible()) 1466 return false; 1467 1468 if (!edgesShareColor(edges[side], edges[adjacentSide]) && edges[adjacentSide].color.hasAlpha()) 1469 return false; 1470 1471 if (!borderStyleFillsBorderArea(edges[adjacentSide].style)) 1472 return false; 1473 1474 return true; 1475 1476 case BSLeft: 1477 case BSRight: 1478 // These draw last, so are never overdrawn. 1479 return false; 1480 } 1481 return false; 1482 } 1483 1484 static inline bool borderStylesRequireMitre(BoxSide side, BoxSide adjacentSide, EBorderStyle style, EBorderStyle adjacentStyle) 1485 { 1486 if (style == DOUBLE || adjacentStyle == DOUBLE || adjacentStyle == GROOVE || adjacentStyle == RIDGE) 1487 return true; 1488 1489 if (borderStyleIsDottedOrDashed(style) != borderStyleIsDottedOrDashed(adjacentStyle)) 1490 return true; 1491 1492 if (style != adjacentStyle) 1493 return true; 1494 1495 return borderStyleHasUnmatchedColorsAtCorner(style, side, adjacentSide); 1496 } 1497 1498 static bool joinRequiresMitre(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[], bool allowOverdraw) 1499 { 1500 if ((edges[side].isTransparent && edges[adjacentSide].isTransparent) || !edges[adjacentSide].isPresent) 1501 return false; 1502 1503 if (allowOverdraw && willBeOverdrawn(side, adjacentSide, edges)) 1504 return false; 1505 1506 if (!edgesShareColor(edges[side], edges[adjacentSide])) 1507 return true; 1508 1509 if (borderStylesRequireMitre(side, adjacentSide, edges[side].style, edges[adjacentSide].style)) 1510 return true; 1511 1512 return false; 1513 } 1514 1515 void RenderBoxModelObject::paintOneBorderSide(GraphicsContext* graphicsContext, const RenderStyle* style, const RoundedRect& outerBorder, const RoundedRect& innerBorder, 1516 const IntRect& sideRect, BoxSide side, BoxSide adjacentSide1, BoxSide adjacentSide2, const BorderEdge edges[], const Path* path, 1517 BackgroundBleedAvoidance bleedAvoidance, bool includeLogicalLeftEdge, bool includeLogicalRightEdge, bool antialias, const Color* overrideColor) 1518 { 1519 const BorderEdge& edgeToRender = edges[side]; 1520 ASSERT(edgeToRender.width); 1521 const BorderEdge& adjacentEdge1 = edges[adjacentSide1]; 1522 const BorderEdge& adjacentEdge2 = edges[adjacentSide2]; 1523 1524 bool mitreAdjacentSide1 = joinRequiresMitre(side, adjacentSide1, edges, !antialias); 1525 bool mitreAdjacentSide2 = joinRequiresMitre(side, adjacentSide2, edges, !antialias); 1526 1527 bool adjacentSide1StylesMatch = colorsMatchAtCorner(side, adjacentSide1, edges); 1528 bool adjacentSide2StylesMatch = colorsMatchAtCorner(side, adjacentSide2, edges); 1529 1530 const Color& colorToPaint = overrideColor ? *overrideColor : edgeToRender.color; 1531 1532 if (path) { 1533 GraphicsContextStateSaver stateSaver(*graphicsContext); 1534 if (innerBorder.isRenderable()) 1535 clipBorderSidePolygon(graphicsContext, outerBorder, innerBorder, side, adjacentSide1StylesMatch, adjacentSide2StylesMatch); 1536 else 1537 clipBorderSideForComplexInnerPath(graphicsContext, outerBorder, innerBorder, side, edges); 1538 float thickness = max(max(edgeToRender.width, adjacentEdge1.width), adjacentEdge2.width); 1539 drawBoxSideFromPath(graphicsContext, outerBorder.rect(), *path, edges, edgeToRender.width, thickness, side, style, 1540 colorToPaint, edgeToRender.style, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge); 1541 } else { 1542 bool clipForStyle = styleRequiresClipPolygon(edgeToRender.style) && (mitreAdjacentSide1 || mitreAdjacentSide2); 1543 bool clipAdjacentSide1 = colorNeedsAntiAliasAtCorner(side, adjacentSide1, edges) && mitreAdjacentSide1; 1544 bool clipAdjacentSide2 = colorNeedsAntiAliasAtCorner(side, adjacentSide2, edges) && mitreAdjacentSide2; 1545 bool shouldClip = clipForStyle || clipAdjacentSide1 || clipAdjacentSide2; 1546 1547 GraphicsContextStateSaver clipStateSaver(*graphicsContext, shouldClip); 1548 if (shouldClip) { 1549 bool aliasAdjacentSide1 = clipAdjacentSide1 || (clipForStyle && mitreAdjacentSide1); 1550 bool aliasAdjacentSide2 = clipAdjacentSide2 || (clipForStyle && mitreAdjacentSide2); 1551 clipBorderSidePolygon(graphicsContext, outerBorder, innerBorder, side, !aliasAdjacentSide1, !aliasAdjacentSide2); 1552 // Since we clipped, no need to draw with a mitre. 1553 mitreAdjacentSide1 = false; 1554 mitreAdjacentSide2 = false; 1555 } 1556 1557 drawLineForBoxSide(graphicsContext, sideRect.x(), sideRect.y(), sideRect.maxX(), sideRect.maxY(), side, colorToPaint, edgeToRender.style, 1558 mitreAdjacentSide1 ? adjacentEdge1.width : 0, mitreAdjacentSide2 ? adjacentEdge2.width : 0, antialias); 1559 } 1560 } 1561 1562 static IntRect calculateSideRect(const RoundedRect& outerBorder, const BorderEdge edges[], int side) 1563 { 1564 IntRect sideRect = outerBorder.rect(); 1565 int width = edges[side].width; 1566 1567 if (side == BSTop) 1568 sideRect.setHeight(width); 1569 else if (side == BSBottom) 1570 sideRect.shiftYEdgeTo(sideRect.maxY() - width); 1571 else if (side == BSLeft) 1572 sideRect.setWidth(width); 1573 else 1574 sideRect.shiftXEdgeTo(sideRect.maxX() - width); 1575 1576 return sideRect; 1577 } 1578 1579 void RenderBoxModelObject::paintBorderSides(GraphicsContext* graphicsContext, const RenderStyle* style, const RoundedRect& outerBorder, const RoundedRect& innerBorder, 1580 const IntPoint& innerBorderAdjustment, const BorderEdge edges[], BorderEdgeFlags edgeSet, BackgroundBleedAvoidance bleedAvoidance, 1581 bool includeLogicalLeftEdge, bool includeLogicalRightEdge, bool antialias, const Color* overrideColor) 1582 { 1583 bool renderRadii = outerBorder.isRounded(); 1584 1585 Path roundedPath; 1586 if (renderRadii) 1587 roundedPath.addRoundedRect(outerBorder); 1588 1589 // The inner border adjustment for bleed avoidance mode BackgroundBleedBackgroundOverBorder 1590 // is only applied to sideRect, which is okay since BackgroundBleedBackgroundOverBorder 1591 // is only to be used for solid borders and the shape of the border painted by drawBoxSideFromPath 1592 // only depends on sideRect when painting solid borders. 1593 1594 if (edges[BSTop].shouldRender() && includesEdge(edgeSet, BSTop)) { 1595 IntRect sideRect = outerBorder.rect(); 1596 sideRect.setHeight(edges[BSTop].width + innerBorderAdjustment.y()); 1597 1598 bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSTop].style) || borderWillArcInnerEdge(innerBorder.radii().topLeft(), innerBorder.radii().topRight())); 1599 paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSTop, BSLeft, BSRight, edges, usePath ? &roundedPath : 0, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor); 1600 } 1601 1602 if (edges[BSBottom].shouldRender() && includesEdge(edgeSet, BSBottom)) { 1603 IntRect sideRect = outerBorder.rect(); 1604 sideRect.shiftYEdgeTo(sideRect.maxY() - edges[BSBottom].width - innerBorderAdjustment.y()); 1605 1606 bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSBottom].style) || borderWillArcInnerEdge(innerBorder.radii().bottomLeft(), innerBorder.radii().bottomRight())); 1607 paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSBottom, BSLeft, BSRight, edges, usePath ? &roundedPath : 0, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor); 1608 } 1609 1610 if (edges[BSLeft].shouldRender() && includesEdge(edgeSet, BSLeft)) { 1611 IntRect sideRect = outerBorder.rect(); 1612 sideRect.setWidth(edges[BSLeft].width + innerBorderAdjustment.x()); 1613 1614 bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSLeft].style) || borderWillArcInnerEdge(innerBorder.radii().bottomLeft(), innerBorder.radii().topLeft())); 1615 paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSLeft, BSTop, BSBottom, edges, usePath ? &roundedPath : 0, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor); 1616 } 1617 1618 if (edges[BSRight].shouldRender() && includesEdge(edgeSet, BSRight)) { 1619 IntRect sideRect = outerBorder.rect(); 1620 sideRect.shiftXEdgeTo(sideRect.maxX() - edges[BSRight].width - innerBorderAdjustment.x()); 1621 1622 bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSRight].style) || borderWillArcInnerEdge(innerBorder.radii().bottomRight(), innerBorder.radii().topRight())); 1623 paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSRight, BSTop, BSBottom, edges, usePath ? &roundedPath : 0, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor); 1624 } 1625 } 1626 1627 void RenderBoxModelObject::paintTranslucentBorderSides(GraphicsContext* graphicsContext, const RenderStyle* style, const RoundedRect& outerBorder, const RoundedRect& innerBorder, const IntPoint& innerBorderAdjustment, 1628 const BorderEdge edges[], BorderEdgeFlags edgesToDraw, BackgroundBleedAvoidance bleedAvoidance, bool includeLogicalLeftEdge, bool includeLogicalRightEdge, bool antialias) 1629 { 1630 // willBeOverdrawn assumes that we draw in order: top, bottom, left, right. 1631 // This is different from BoxSide enum order. 1632 static const BoxSide paintOrder[] = { BSTop, BSBottom, BSLeft, BSRight }; 1633 1634 while (edgesToDraw) { 1635 // Find undrawn edges sharing a color. 1636 Color commonColor; 1637 1638 BorderEdgeFlags commonColorEdgeSet = 0; 1639 for (size_t i = 0; i < sizeof(paintOrder) / sizeof(paintOrder[0]); ++i) { 1640 BoxSide currSide = paintOrder[i]; 1641 if (!includesEdge(edgesToDraw, currSide)) 1642 continue; 1643 1644 bool includeEdge; 1645 if (!commonColorEdgeSet) { 1646 commonColor = edges[currSide].color; 1647 includeEdge = true; 1648 } else 1649 includeEdge = edges[currSide].color == commonColor; 1650 1651 if (includeEdge) 1652 commonColorEdgeSet |= edgeFlagForSide(currSide); 1653 } 1654 1655 bool useTransparencyLayer = includesAdjacentEdges(commonColorEdgeSet) && commonColor.hasAlpha(); 1656 if (useTransparencyLayer) { 1657 graphicsContext->beginTransparencyLayer(static_cast<float>(commonColor.alpha()) / 255); 1658 commonColor = Color(commonColor.red(), commonColor.green(), commonColor.blue()); 1659 } 1660 1661 paintBorderSides(graphicsContext, style, outerBorder, innerBorder, innerBorderAdjustment, edges, commonColorEdgeSet, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, &commonColor); 1662 1663 if (useTransparencyLayer) 1664 graphicsContext->endTransparencyLayer(); 1665 1666 edgesToDraw &= ~commonColorEdgeSet; 1667 } 1668 } 1669 1670 void RenderBoxModelObject::paintBorder(const PaintInfo& info, const LayoutRect& rect, const RenderStyle* style, 1671 BackgroundBleedAvoidance bleedAvoidance, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) 1672 { 1673 GraphicsContext* graphicsContext = info.context; 1674 // border-image is not affected by border-radius. 1675 if (paintNinePieceImage(graphicsContext, rect, style, style->borderImage())) 1676 return; 1677 1678 if (graphicsContext->paintingDisabled()) 1679 return; 1680 1681 BorderEdge edges[4]; 1682 getBorderEdgeInfo(edges, style, includeLogicalLeftEdge, includeLogicalRightEdge); 1683 RoundedRect outerBorder = style->getRoundedBorderFor(rect, view(), includeLogicalLeftEdge, includeLogicalRightEdge); 1684 RoundedRect innerBorder = style->getRoundedInnerBorderFor(borderInnerRectAdjustedForBleedAvoidance(graphicsContext, rect, bleedAvoidance), includeLogicalLeftEdge, includeLogicalRightEdge); 1685 1686 bool haveAlphaColor = false; 1687 bool haveAllSolidEdges = true; 1688 bool haveAllDoubleEdges = true; 1689 int numEdgesVisible = 4; 1690 bool allEdgesShareColor = true; 1691 int firstVisibleEdge = -1; 1692 BorderEdgeFlags edgesToDraw = 0; 1693 1694 for (int i = BSTop; i <= BSLeft; ++i) { 1695 const BorderEdge& currEdge = edges[i]; 1696 1697 if (edges[i].shouldRender()) 1698 edgesToDraw |= edgeFlagForSide(static_cast<BoxSide>(i)); 1699 1700 if (currEdge.presentButInvisible()) { 1701 --numEdgesVisible; 1702 allEdgesShareColor = false; 1703 continue; 1704 } 1705 1706 if (!currEdge.width) { 1707 --numEdgesVisible; 1708 continue; 1709 } 1710 1711 if (firstVisibleEdge == -1) 1712 firstVisibleEdge = i; 1713 else if (currEdge.color != edges[firstVisibleEdge].color) 1714 allEdgesShareColor = false; 1715 1716 if (currEdge.color.hasAlpha()) 1717 haveAlphaColor = true; 1718 1719 if (currEdge.style != SOLID) 1720 haveAllSolidEdges = false; 1721 1722 if (currEdge.style != DOUBLE) 1723 haveAllDoubleEdges = false; 1724 } 1725 1726 // If no corner intersects the clip region, we can pretend outerBorder is 1727 // rectangular to improve performance. 1728 if (haveAllSolidEdges && outerBorder.isRounded() && allCornersClippedOut(outerBorder, info.rect)) 1729 outerBorder.setRadii(RoundedRect::Radii()); 1730 1731 // isRenderable() check avoids issue described in https://bugs.webkit.org/show_bug.cgi?id=38787 1732 if ((haveAllSolidEdges || haveAllDoubleEdges) && allEdgesShareColor && innerBorder.isRenderable()) { 1733 // Fast path for drawing all solid edges and all unrounded double edges 1734 if (numEdgesVisible == 4 && (outerBorder.isRounded() || haveAlphaColor) 1735 && (haveAllSolidEdges || (!outerBorder.isRounded() && !innerBorder.isRounded()))) { 1736 Path path; 1737 1738 if (outerBorder.isRounded() && bleedAvoidance != BackgroundBleedUseTransparencyLayer) 1739 path.addRoundedRect(outerBorder); 1740 else 1741 path.addRect(outerBorder.rect()); 1742 1743 if (haveAllDoubleEdges) { 1744 IntRect innerThirdRect = outerBorder.rect(); 1745 IntRect outerThirdRect = outerBorder.rect(); 1746 for (int side = BSTop; side <= BSLeft; ++side) { 1747 int outerWidth; 1748 int innerWidth; 1749 edges[side].getDoubleBorderStripeWidths(outerWidth, innerWidth); 1750 1751 if (side == BSTop) { 1752 innerThirdRect.shiftYEdgeTo(innerThirdRect.y() + innerWidth); 1753 outerThirdRect.shiftYEdgeTo(outerThirdRect.y() + outerWidth); 1754 } else if (side == BSBottom) { 1755 innerThirdRect.setHeight(innerThirdRect.height() - innerWidth); 1756 outerThirdRect.setHeight(outerThirdRect.height() - outerWidth); 1757 } else if (side == BSLeft) { 1758 innerThirdRect.shiftXEdgeTo(innerThirdRect.x() + innerWidth); 1759 outerThirdRect.shiftXEdgeTo(outerThirdRect.x() + outerWidth); 1760 } else { 1761 innerThirdRect.setWidth(innerThirdRect.width() - innerWidth); 1762 outerThirdRect.setWidth(outerThirdRect.width() - outerWidth); 1763 } 1764 } 1765 1766 RoundedRect outerThird = outerBorder; 1767 RoundedRect innerThird = innerBorder; 1768 innerThird.setRect(innerThirdRect); 1769 outerThird.setRect(outerThirdRect); 1770 1771 if (outerThird.isRounded() && bleedAvoidance != BackgroundBleedUseTransparencyLayer) 1772 path.addRoundedRect(outerThird); 1773 else 1774 path.addRect(outerThird.rect()); 1775 1776 if (innerThird.isRounded() && bleedAvoidance != BackgroundBleedUseTransparencyLayer) 1777 path.addRoundedRect(innerThird); 1778 else 1779 path.addRect(innerThird.rect()); 1780 } 1781 1782 if (innerBorder.isRounded()) 1783 path.addRoundedRect(innerBorder); 1784 else 1785 path.addRect(innerBorder.rect()); 1786 1787 graphicsContext->setFillRule(RULE_EVENODD); 1788 graphicsContext->setFillColor(edges[firstVisibleEdge].color); 1789 graphicsContext->fillPath(path); 1790 return; 1791 } 1792 // Avoid creating transparent layers 1793 if (haveAllSolidEdges && numEdgesVisible != 4 && !outerBorder.isRounded() && haveAlphaColor) { 1794 Path path; 1795 1796 for (int i = BSTop; i <= BSLeft; ++i) { 1797 const BorderEdge& currEdge = edges[i]; 1798 if (currEdge.shouldRender()) { 1799 IntRect sideRect = calculateSideRect(outerBorder, edges, i); 1800 path.addRect(sideRect); 1801 } 1802 } 1803 1804 graphicsContext->setFillRule(RULE_NONZERO); 1805 graphicsContext->setFillColor(edges[firstVisibleEdge].color); 1806 graphicsContext->fillPath(path); 1807 return; 1808 } 1809 } 1810 1811 bool clipToOuterBorder = outerBorder.isRounded(); 1812 GraphicsContextStateSaver stateSaver(*graphicsContext, clipToOuterBorder); 1813 if (clipToOuterBorder) { 1814 // Clip to the inner and outer radii rects. 1815 if (bleedAvoidance != BackgroundBleedUseTransparencyLayer) 1816 graphicsContext->clipRoundedRect(outerBorder); 1817 // isRenderable() check avoids issue described in https://bugs.webkit.org/show_bug.cgi?id=38787 1818 // The inside will be clipped out later (in clipBorderSideForComplexInnerPath) 1819 if (innerBorder.isRenderable()) 1820 graphicsContext->clipOutRoundedRect(innerBorder); 1821 } 1822 1823 // If only one edge visible antialiasing doesn't create seams 1824 bool antialias = shouldAntialiasLines(graphicsContext) || numEdgesVisible == 1; 1825 RoundedRect unadjustedInnerBorder = (bleedAvoidance == BackgroundBleedBackgroundOverBorder) ? style->getRoundedInnerBorderFor(rect, includeLogicalLeftEdge, includeLogicalRightEdge) : innerBorder; 1826 IntPoint innerBorderAdjustment(innerBorder.rect().x() - unadjustedInnerBorder.rect().x(), innerBorder.rect().y() - unadjustedInnerBorder.rect().y()); 1827 if (haveAlphaColor) 1828 paintTranslucentBorderSides(graphicsContext, style, outerBorder, unadjustedInnerBorder, innerBorderAdjustment, edges, edgesToDraw, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias); 1829 else 1830 paintBorderSides(graphicsContext, style, outerBorder, unadjustedInnerBorder, innerBorderAdjustment, edges, edgesToDraw, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias); 1831 } 1832 1833 void RenderBoxModelObject::drawBoxSideFromPath(GraphicsContext* graphicsContext, const LayoutRect& borderRect, const Path& borderPath, const BorderEdge edges[], 1834 float thickness, float drawThickness, BoxSide side, const RenderStyle* style, Color color, EBorderStyle borderStyle, BackgroundBleedAvoidance bleedAvoidance, 1835 bool includeLogicalLeftEdge, bool includeLogicalRightEdge) 1836 { 1837 if (thickness <= 0) 1838 return; 1839 1840 if (borderStyle == DOUBLE && thickness < 3) 1841 borderStyle = SOLID; 1842 1843 switch (borderStyle) { 1844 case BNONE: 1845 case BHIDDEN: 1846 return; 1847 case DOTTED: 1848 case DASHED: { 1849 graphicsContext->setStrokeColor(color); 1850 1851 // The stroke is doubled here because the provided path is the 1852 // outside edge of the border so half the stroke is clipped off. 1853 // The extra multiplier is so that the clipping mask can antialias 1854 // the edges to prevent jaggies. 1855 graphicsContext->setStrokeThickness(drawThickness * 2 * 1.1f); 1856 graphicsContext->setStrokeStyle(borderStyle == DASHED ? DashedStroke : DottedStroke); 1857 1858 // If the number of dashes that fit in the path is odd and non-integral then we 1859 // will have an awkwardly-sized dash at the end of the path. To try to avoid that 1860 // here, we simply make the whitespace dashes ever so slightly bigger. 1861 // FIXME: This could be even better if we tried to manipulate the dash offset 1862 // and possibly the gapLength to get the corners dash-symmetrical. 1863 float dashLength = thickness * ((borderStyle == DASHED) ? 3.0f : 1.0f); 1864 float gapLength = dashLength; 1865 float numberOfDashes = borderPath.length() / dashLength; 1866 // Don't try to show dashes if we have less than 2 dashes + 2 gaps. 1867 // FIXME: should do this test per side. 1868 if (numberOfDashes >= 4) { 1869 bool evenNumberOfFullDashes = !((int)numberOfDashes % 2); 1870 bool integralNumberOfDashes = !(numberOfDashes - (int)numberOfDashes); 1871 if (!evenNumberOfFullDashes && !integralNumberOfDashes) { 1872 float numberOfGaps = numberOfDashes / 2; 1873 gapLength += (dashLength / numberOfGaps); 1874 } 1875 1876 DashArray lineDash; 1877 lineDash.append(dashLength); 1878 lineDash.append(gapLength); 1879 graphicsContext->setLineDash(lineDash, dashLength); 1880 } 1881 1882 // FIXME: stroking the border path causes issues with tight corners: 1883 // https://bugs.webkit.org/show_bug.cgi?id=58711 1884 // Also, to get the best appearance we should stroke a path between the two borders. 1885 graphicsContext->strokePath(borderPath); 1886 return; 1887 } 1888 case DOUBLE: { 1889 // Get the inner border rects for both the outer border line and the inner border line 1890 int outerBorderTopWidth; 1891 int innerBorderTopWidth; 1892 edges[BSTop].getDoubleBorderStripeWidths(outerBorderTopWidth, innerBorderTopWidth); 1893 1894 int outerBorderRightWidth; 1895 int innerBorderRightWidth; 1896 edges[BSRight].getDoubleBorderStripeWidths(outerBorderRightWidth, innerBorderRightWidth); 1897 1898 int outerBorderBottomWidth; 1899 int innerBorderBottomWidth; 1900 edges[BSBottom].getDoubleBorderStripeWidths(outerBorderBottomWidth, innerBorderBottomWidth); 1901 1902 int outerBorderLeftWidth; 1903 int innerBorderLeftWidth; 1904 edges[BSLeft].getDoubleBorderStripeWidths(outerBorderLeftWidth, innerBorderLeftWidth); 1905 1906 // Draw inner border line 1907 { 1908 GraphicsContextStateSaver stateSaver(*graphicsContext); 1909 RoundedRect innerClip = style->getRoundedInnerBorderFor(borderRect, 1910 innerBorderTopWidth, innerBorderBottomWidth, innerBorderLeftWidth, innerBorderRightWidth, 1911 includeLogicalLeftEdge, includeLogicalRightEdge); 1912 1913 graphicsContext->clipRoundedRect(innerClip); 1914 drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, SOLID, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge); 1915 } 1916 1917 // Draw outer border line 1918 { 1919 GraphicsContextStateSaver stateSaver(*graphicsContext); 1920 LayoutRect outerRect = borderRect; 1921 if (bleedAvoidance == BackgroundBleedUseTransparencyLayer) { 1922 outerRect.inflate(1); 1923 ++outerBorderTopWidth; 1924 ++outerBorderBottomWidth; 1925 ++outerBorderLeftWidth; 1926 ++outerBorderRightWidth; 1927 } 1928 1929 RoundedRect outerClip = style->getRoundedInnerBorderFor(outerRect, 1930 outerBorderTopWidth, outerBorderBottomWidth, outerBorderLeftWidth, outerBorderRightWidth, 1931 includeLogicalLeftEdge, includeLogicalRightEdge); 1932 graphicsContext->clipOutRoundedRect(outerClip); 1933 drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, SOLID, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge); 1934 } 1935 return; 1936 } 1937 case RIDGE: 1938 case GROOVE: 1939 { 1940 EBorderStyle s1; 1941 EBorderStyle s2; 1942 if (borderStyle == GROOVE) { 1943 s1 = INSET; 1944 s2 = OUTSET; 1945 } else { 1946 s1 = OUTSET; 1947 s2 = INSET; 1948 } 1949 1950 // Paint full border 1951 drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, s1, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge); 1952 1953 // Paint inner only 1954 GraphicsContextStateSaver stateSaver(*graphicsContext); 1955 LayoutUnit topWidth = edges[BSTop].usedWidth() / 2; 1956 LayoutUnit bottomWidth = edges[BSBottom].usedWidth() / 2; 1957 LayoutUnit leftWidth = edges[BSLeft].usedWidth() / 2; 1958 LayoutUnit rightWidth = edges[BSRight].usedWidth() / 2; 1959 1960 RoundedRect clipRect = style->getRoundedInnerBorderFor(borderRect, 1961 topWidth, bottomWidth, leftWidth, rightWidth, 1962 includeLogicalLeftEdge, includeLogicalRightEdge); 1963 1964 graphicsContext->clipRoundedRect(clipRect); 1965 drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, s2, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge); 1966 return; 1967 } 1968 case INSET: 1969 if (side == BSTop || side == BSLeft) 1970 color = color.dark(); 1971 break; 1972 case OUTSET: 1973 if (side == BSBottom || side == BSRight) 1974 color = color.dark(); 1975 break; 1976 default: 1977 break; 1978 } 1979 1980 graphicsContext->setStrokeStyle(NoStroke); 1981 graphicsContext->setFillColor(color); 1982 graphicsContext->drawRect(pixelSnappedIntRect(borderRect)); 1983 } 1984 1985 void RenderBoxModelObject::clipBorderSidePolygon(GraphicsContext* graphicsContext, const RoundedRect& outerBorder, const RoundedRect& innerBorder, 1986 BoxSide side, bool firstEdgeMatches, bool secondEdgeMatches) 1987 { 1988 FloatPoint quad[4]; 1989 1990 const LayoutRect& outerRect = outerBorder.rect(); 1991 const LayoutRect& innerRect = innerBorder.rect(); 1992 1993 FloatPoint centerPoint(innerRect.location().x() + static_cast<float>(innerRect.width()) / 2, innerRect.location().y() + static_cast<float>(innerRect.height()) / 2); 1994 1995 // For each side, create a quad that encompasses all parts of that side that may draw, 1996 // including areas inside the innerBorder. 1997 // 1998 // 0----------------3 1999 // 0 \ / 0 2000 // |\ 1----------- 2 /| 2001 // | 1 1 | 2002 // | | | | 2003 // | | | | 2004 // | 2 2 | 2005 // |/ 1------------2 \| 2006 // 3 / \ 3 2007 // 0----------------3 2008 // 2009 switch (side) { 2010 case BSTop: 2011 quad[0] = outerRect.minXMinYCorner(); 2012 quad[1] = innerRect.minXMinYCorner(); 2013 quad[2] = innerRect.maxXMinYCorner(); 2014 quad[3] = outerRect.maxXMinYCorner(); 2015 2016 if (!innerBorder.radii().topLeft().isZero()) { 2017 findIntersection(quad[0], quad[1], 2018 FloatPoint( 2019 quad[1].x() + innerBorder.radii().topLeft().width(), 2020 quad[1].y()), 2021 FloatPoint( 2022 quad[1].x(), 2023 quad[1].y() + innerBorder.radii().topLeft().height()), 2024 quad[1]); 2025 } 2026 2027 if (!innerBorder.radii().topRight().isZero()) { 2028 findIntersection(quad[3], quad[2], 2029 FloatPoint( 2030 quad[2].x() - innerBorder.radii().topRight().width(), 2031 quad[2].y()), 2032 FloatPoint( 2033 quad[2].x(), 2034 quad[2].y() + innerBorder.radii().topRight().height()), 2035 quad[2]); 2036 } 2037 break; 2038 2039 case BSLeft: 2040 quad[0] = outerRect.minXMinYCorner(); 2041 quad[1] = innerRect.minXMinYCorner(); 2042 quad[2] = innerRect.minXMaxYCorner(); 2043 quad[3] = outerRect.minXMaxYCorner(); 2044 2045 if (!innerBorder.radii().topLeft().isZero()) { 2046 findIntersection(quad[0], quad[1], 2047 FloatPoint( 2048 quad[1].x() + innerBorder.radii().topLeft().width(), 2049 quad[1].y()), 2050 FloatPoint( 2051 quad[1].x(), 2052 quad[1].y() + innerBorder.radii().topLeft().height()), 2053 quad[1]); 2054 } 2055 2056 if (!innerBorder.radii().bottomLeft().isZero()) { 2057 findIntersection(quad[3], quad[2], 2058 FloatPoint( 2059 quad[2].x() + innerBorder.radii().bottomLeft().width(), 2060 quad[2].y()), 2061 FloatPoint( 2062 quad[2].x(), 2063 quad[2].y() - innerBorder.radii().bottomLeft().height()), 2064 quad[2]); 2065 } 2066 break; 2067 2068 case BSBottom: 2069 quad[0] = outerRect.minXMaxYCorner(); 2070 quad[1] = innerRect.minXMaxYCorner(); 2071 quad[2] = innerRect.maxXMaxYCorner(); 2072 quad[3] = outerRect.maxXMaxYCorner(); 2073 2074 if (!innerBorder.radii().bottomLeft().isZero()) { 2075 findIntersection(quad[0], quad[1], 2076 FloatPoint( 2077 quad[1].x() + innerBorder.radii().bottomLeft().width(), 2078 quad[1].y()), 2079 FloatPoint( 2080 quad[1].x(), 2081 quad[1].y() - innerBorder.radii().bottomLeft().height()), 2082 quad[1]); 2083 } 2084 2085 if (!innerBorder.radii().bottomRight().isZero()) { 2086 findIntersection(quad[3], quad[2], 2087 FloatPoint( 2088 quad[2].x() - innerBorder.radii().bottomRight().width(), 2089 quad[2].y()), 2090 FloatPoint( 2091 quad[2].x(), 2092 quad[2].y() - innerBorder.radii().bottomRight().height()), 2093 quad[2]); 2094 } 2095 break; 2096 2097 case BSRight: 2098 quad[0] = outerRect.maxXMinYCorner(); 2099 quad[1] = innerRect.maxXMinYCorner(); 2100 quad[2] = innerRect.maxXMaxYCorner(); 2101 quad[3] = outerRect.maxXMaxYCorner(); 2102 2103 if (!innerBorder.radii().topRight().isZero()) { 2104 findIntersection(quad[0], quad[1], 2105 FloatPoint( 2106 quad[1].x() - innerBorder.radii().topRight().width(), 2107 quad[1].y()), 2108 FloatPoint( 2109 quad[1].x(), 2110 quad[1].y() + innerBorder.radii().topRight().height()), 2111 quad[1]); 2112 } 2113 2114 if (!innerBorder.radii().bottomRight().isZero()) { 2115 findIntersection(quad[3], quad[2], 2116 FloatPoint( 2117 quad[2].x() - innerBorder.radii().bottomRight().width(), 2118 quad[2].y()), 2119 FloatPoint( 2120 quad[2].x(), 2121 quad[2].y() - innerBorder.radii().bottomRight().height()), 2122 quad[2]); 2123 } 2124 break; 2125 } 2126 2127 // If the border matches both of its adjacent sides, don't anti-alias the clip, and 2128 // if neither side matches, anti-alias the clip. 2129 if (firstEdgeMatches == secondEdgeMatches) { 2130 graphicsContext->clipConvexPolygon(4, quad, !firstEdgeMatches); 2131 return; 2132 } 2133 2134 // If antialiasing settings for the first edge and second edge is different, 2135 // they have to be addressed separately. We do this by breaking the quad into 2136 // two parallelograms, made by moving quad[1] and quad[2]. 2137 float ax = quad[1].x() - quad[0].x(); 2138 float ay = quad[1].y() - quad[0].y(); 2139 float bx = quad[2].x() - quad[1].x(); 2140 float by = quad[2].y() - quad[1].y(); 2141 float cx = quad[3].x() - quad[2].x(); 2142 float cy = quad[3].y() - quad[2].y(); 2143 2144 const static float kEpsilon = 1e-2f; 2145 float r1, r2; 2146 if (fabsf(bx) < kEpsilon && fabsf(by) < kEpsilon) { 2147 // The quad was actually a triangle. 2148 r1 = r2 = 1.0f; 2149 } else { 2150 // Extend parallelogram a bit to hide calculation error 2151 const static float kExtendFill = 1e-2f; 2152 2153 r1 = (-ax * by + ay * bx) / (cx * by - cy * bx) + kExtendFill; 2154 r2 = (-cx * by + cy * bx) / (ax * by - ay * bx) + kExtendFill; 2155 } 2156 2157 FloatPoint firstQuad[4]; 2158 firstQuad[0] = quad[0]; 2159 firstQuad[1] = quad[1]; 2160 firstQuad[2] = FloatPoint(quad[3].x() + r2 * ax, quad[3].y() + r2 * ay); 2161 firstQuad[3] = quad[3]; 2162 graphicsContext->clipConvexPolygon(4, firstQuad, !firstEdgeMatches); 2163 2164 FloatPoint secondQuad[4]; 2165 secondQuad[0] = quad[0]; 2166 secondQuad[1] = FloatPoint(quad[0].x() - r1 * cx, quad[0].y() - r1 * cy); 2167 secondQuad[2] = quad[2]; 2168 secondQuad[3] = quad[3]; 2169 graphicsContext->clipConvexPolygon(4, secondQuad, !secondEdgeMatches); 2170 } 2171 2172 static IntRect calculateSideRectIncludingInner(const RoundedRect& outerBorder, const BorderEdge edges[], BoxSide side) 2173 { 2174 IntRect sideRect = outerBorder.rect(); 2175 int width; 2176 2177 switch (side) { 2178 case BSTop: 2179 width = sideRect.height() - edges[BSBottom].width; 2180 sideRect.setHeight(width); 2181 break; 2182 case BSBottom: 2183 width = sideRect.height() - edges[BSTop].width; 2184 sideRect.shiftYEdgeTo(sideRect.maxY() - width); 2185 break; 2186 case BSLeft: 2187 width = sideRect.width() - edges[BSRight].width; 2188 sideRect.setWidth(width); 2189 break; 2190 case BSRight: 2191 width = sideRect.width() - edges[BSLeft].width; 2192 sideRect.shiftXEdgeTo(sideRect.maxX() - width); 2193 break; 2194 } 2195 2196 return sideRect; 2197 } 2198 2199 static RoundedRect calculateAdjustedInnerBorder(const RoundedRect&innerBorder, BoxSide side) 2200 { 2201 // Expand the inner border as necessary to make it a rounded rect (i.e. radii contained within each edge). 2202 // This function relies on the fact we only get radii not contained within each edge if one of the radii 2203 // for an edge is zero, so we can shift the arc towards the zero radius corner. 2204 RoundedRect::Radii newRadii = innerBorder.radii(); 2205 IntRect newRect = innerBorder.rect(); 2206 2207 float overshoot; 2208 float maxRadii; 2209 2210 switch (side) { 2211 case BSTop: 2212 overshoot = newRadii.topLeft().width() + newRadii.topRight().width() - newRect.width(); 2213 if (overshoot > 0) { 2214 ASSERT(!(newRadii.topLeft().width() && newRadii.topRight().width())); 2215 newRect.setWidth(newRect.width() + overshoot); 2216 if (!newRadii.topLeft().width()) 2217 newRect.move(-overshoot, 0); 2218 } 2219 newRadii.setBottomLeft(IntSize(0, 0)); 2220 newRadii.setBottomRight(IntSize(0, 0)); 2221 maxRadii = max(newRadii.topLeft().height(), newRadii.topRight().height()); 2222 if (maxRadii > newRect.height()) 2223 newRect.setHeight(maxRadii); 2224 break; 2225 2226 case BSBottom: 2227 overshoot = newRadii.bottomLeft().width() + newRadii.bottomRight().width() - newRect.width(); 2228 if (overshoot > 0) { 2229 ASSERT(!(newRadii.bottomLeft().width() && newRadii.bottomRight().width())); 2230 newRect.setWidth(newRect.width() + overshoot); 2231 if (!newRadii.bottomLeft().width()) 2232 newRect.move(-overshoot, 0); 2233 } 2234 newRadii.setTopLeft(IntSize(0, 0)); 2235 newRadii.setTopRight(IntSize(0, 0)); 2236 maxRadii = max(newRadii.bottomLeft().height(), newRadii.bottomRight().height()); 2237 if (maxRadii > newRect.height()) { 2238 newRect.move(0, newRect.height() - maxRadii); 2239 newRect.setHeight(maxRadii); 2240 } 2241 break; 2242 2243 case BSLeft: 2244 overshoot = newRadii.topLeft().height() + newRadii.bottomLeft().height() - newRect.height(); 2245 if (overshoot > 0) { 2246 ASSERT(!(newRadii.topLeft().height() && newRadii.bottomLeft().height())); 2247 newRect.setHeight(newRect.height() + overshoot); 2248 if (!newRadii.topLeft().height()) 2249 newRect.move(0, -overshoot); 2250 } 2251 newRadii.setTopRight(IntSize(0, 0)); 2252 newRadii.setBottomRight(IntSize(0, 0)); 2253 maxRadii = max(newRadii.topLeft().width(), newRadii.bottomLeft().width()); 2254 if (maxRadii > newRect.width()) 2255 newRect.setWidth(maxRadii); 2256 break; 2257 2258 case BSRight: 2259 overshoot = newRadii.topRight().height() + newRadii.bottomRight().height() - newRect.height(); 2260 if (overshoot > 0) { 2261 ASSERT(!(newRadii.topRight().height() && newRadii.bottomRight().height())); 2262 newRect.setHeight(newRect.height() + overshoot); 2263 if (!newRadii.topRight().height()) 2264 newRect.move(0, -overshoot); 2265 } 2266 newRadii.setTopLeft(IntSize(0, 0)); 2267 newRadii.setBottomLeft(IntSize(0, 0)); 2268 maxRadii = max(newRadii.topRight().width(), newRadii.bottomRight().width()); 2269 if (maxRadii > newRect.width()) { 2270 newRect.move(newRect.width() - maxRadii, 0); 2271 newRect.setWidth(maxRadii); 2272 } 2273 break; 2274 } 2275 2276 return RoundedRect(newRect, newRadii); 2277 } 2278 2279 void RenderBoxModelObject::clipBorderSideForComplexInnerPath(GraphicsContext* graphicsContext, const RoundedRect& outerBorder, const RoundedRect& innerBorder, 2280 BoxSide side, const class BorderEdge edges[]) 2281 { 2282 graphicsContext->clip(calculateSideRectIncludingInner(outerBorder, edges, side)); 2283 graphicsContext->clipOutRoundedRect(calculateAdjustedInnerBorder(innerBorder, side)); 2284 } 2285 2286 void RenderBoxModelObject::getBorderEdgeInfo(BorderEdge edges[], const RenderStyle* style, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const 2287 { 2288 bool horizontal = style->isHorizontalWritingMode(); 2289 2290 edges[BSTop] = BorderEdge(style->borderTopWidth(), 2291 resolveColor(style, CSSPropertyBorderTopColor), 2292 style->borderTopStyle(), 2293 style->borderTopIsTransparent(), 2294 horizontal || includeLogicalLeftEdge); 2295 2296 edges[BSRight] = BorderEdge(style->borderRightWidth(), 2297 resolveColor(style, CSSPropertyBorderRightColor), 2298 style->borderRightStyle(), 2299 style->borderRightIsTransparent(), 2300 !horizontal || includeLogicalRightEdge); 2301 2302 edges[BSBottom] = BorderEdge(style->borderBottomWidth(), 2303 resolveColor(style, CSSPropertyBorderBottomColor), 2304 style->borderBottomStyle(), 2305 style->borderBottomIsTransparent(), 2306 horizontal || includeLogicalRightEdge); 2307 2308 edges[BSLeft] = BorderEdge(style->borderLeftWidth(), 2309 resolveColor(style, CSSPropertyBorderLeftColor), 2310 style->borderLeftStyle(), 2311 style->borderLeftIsTransparent(), 2312 !horizontal || includeLogicalLeftEdge); 2313 } 2314 2315 bool RenderBoxModelObject::borderObscuresBackgroundEdge(const FloatSize& contextScale) const 2316 { 2317 BorderEdge edges[4]; 2318 getBorderEdgeInfo(edges, style()); 2319 2320 for (int i = BSTop; i <= BSLeft; ++i) { 2321 const BorderEdge& currEdge = edges[i]; 2322 // FIXME: for vertical text 2323 float axisScale = (i == BSTop || i == BSBottom) ? contextScale.height() : contextScale.width(); 2324 if (!currEdge.obscuresBackgroundEdge(axisScale)) 2325 return false; 2326 } 2327 2328 return true; 2329 } 2330 2331 bool RenderBoxModelObject::borderObscuresBackground() const 2332 { 2333 if (!style()->hasBorder()) 2334 return false; 2335 2336 // Bail if we have any border-image for now. We could look at the image alpha to improve this. 2337 if (style()->borderImage().image()) 2338 return false; 2339 2340 BorderEdge edges[4]; 2341 getBorderEdgeInfo(edges, style()); 2342 2343 for (int i = BSTop; i <= BSLeft; ++i) { 2344 const BorderEdge& currEdge = edges[i]; 2345 if (!currEdge.obscuresBackground()) 2346 return false; 2347 } 2348 2349 return true; 2350 } 2351 2352 bool RenderBoxModelObject::boxShadowShouldBeAppliedToBackground(BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox* inlineFlowBox) const 2353 { 2354 if (bleedAvoidance != BackgroundBleedNone) 2355 return false; 2356 2357 if (style()->hasAppearance()) 2358 return false; 2359 2360 bool hasOneNormalBoxShadow = false; 2361 for (const ShadowData* currentShadow = style()->boxShadow(); currentShadow; currentShadow = currentShadow->next()) { 2362 if (currentShadow->style() != Normal) 2363 continue; 2364 2365 if (hasOneNormalBoxShadow) 2366 return false; 2367 hasOneNormalBoxShadow = true; 2368 2369 if (currentShadow->spread()) 2370 return false; 2371 } 2372 2373 if (!hasOneNormalBoxShadow) 2374 return false; 2375 2376 Color backgroundColor = resolveColor(CSSPropertyBackgroundColor); 2377 if (backgroundColor.hasAlpha()) 2378 return false; 2379 2380 const FillLayer* lastBackgroundLayer = style()->backgroundLayers(); 2381 for (const FillLayer* next = lastBackgroundLayer->next(); next; next = lastBackgroundLayer->next()) 2382 lastBackgroundLayer = next; 2383 2384 if (lastBackgroundLayer->clip() != BorderFillBox) 2385 return false; 2386 2387 if (lastBackgroundLayer->image() && style()->hasBorderRadius()) 2388 return false; 2389 2390 if (inlineFlowBox && !inlineFlowBox->boxShadowCanBeAppliedToBackground(*lastBackgroundLayer)) 2391 return false; 2392 2393 if (hasOverflowClip() && lastBackgroundLayer->attachment() == LocalBackgroundAttachment) 2394 return false; 2395 2396 return true; 2397 } 2398 2399 static inline IntRect areaCastingShadowInHole(const IntRect& holeRect, int shadowBlur, int shadowSpread, const IntSize& shadowOffset) 2400 { 2401 IntRect bounds(holeRect); 2402 2403 bounds.inflate(shadowBlur); 2404 2405 if (shadowSpread < 0) 2406 bounds.inflate(-shadowSpread); 2407 2408 IntRect offsetBounds = bounds; 2409 offsetBounds.move(-shadowOffset); 2410 return unionRect(bounds, offsetBounds); 2411 } 2412 2413 void RenderBoxModelObject::paintBoxShadow(const PaintInfo& info, const LayoutRect& paintRect, const RenderStyle* s, ShadowStyle shadowStyle, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) 2414 { 2415 // FIXME: Deal with border-image. Would be great to use border-image as a mask. 2416 GraphicsContext* context = info.context; 2417 if (context->paintingDisabled() || !s->boxShadow()) 2418 return; 2419 2420 RoundedRect border = (shadowStyle == Inset) ? s->getRoundedInnerBorderFor(paintRect, includeLogicalLeftEdge, includeLogicalRightEdge) 2421 : s->getRoundedBorderFor(paintRect, view(), includeLogicalLeftEdge, includeLogicalRightEdge); 2422 2423 bool hasBorderRadius = s->hasBorderRadius(); 2424 bool isHorizontal = s->isHorizontalWritingMode(); 2425 2426 bool hasOpaqueBackground = s->visitedDependentColor(CSSPropertyBackgroundColor).isValid() && s->visitedDependentColor(CSSPropertyBackgroundColor).alpha() == 255; 2427 for (const ShadowData* shadow = s->boxShadow(); shadow; shadow = shadow->next()) { 2428 if (shadow->style() != shadowStyle) 2429 continue; 2430 2431 IntSize shadowOffset(shadow->x(), shadow->y()); 2432 int shadowBlur = shadow->blur(); 2433 int shadowSpread = shadow->spread(); 2434 2435 if (shadowOffset.isZero() && !shadowBlur && !shadowSpread) 2436 continue; 2437 2438 const Color& shadowColor = resolveColor(shadow->color(), Color::stdShadowColor); 2439 2440 if (shadow->style() == Normal) { 2441 RoundedRect fillRect = border; 2442 fillRect.inflate(shadowSpread); 2443 if (fillRect.isEmpty()) 2444 continue; 2445 2446 IntRect shadowRect(border.rect()); 2447 shadowRect.inflate(shadowBlur + shadowSpread); 2448 shadowRect.move(shadowOffset); 2449 2450 // Draw only the shadow. 2451 DrawLooper drawLooper; 2452 drawLooper.addShadow(shadowOffset, shadowBlur, shadowColor, 2453 DrawLooper::ShadowRespectsTransforms, DrawLooper::ShadowIgnoresAlpha); 2454 context->setDrawLooper(drawLooper); 2455 2456 context->save(); 2457 if (hasBorderRadius) { 2458 RoundedRect rectToClipOut = border; 2459 2460 // If the box is opaque, it is unnecessary to clip it out. However, doing so saves time 2461 // when painting the shadow. On the other hand, it introduces subpixel gaps along the 2462 // corners. Those are avoided by insetting the clipping path by one pixel. 2463 if (hasOpaqueBackground) 2464 rectToClipOut.inflateWithRadii(-1); 2465 2466 if (!rectToClipOut.isEmpty()) { 2467 context->clipOutRoundedRect(rectToClipOut); 2468 } 2469 2470 RoundedRect influenceRect(shadowRect, border.radii()); 2471 influenceRect.expandRadii(2 * shadowBlur + shadowSpread); 2472 if (allCornersClippedOut(influenceRect, info.rect)) 2473 context->fillRect(fillRect.rect(), Color::black); 2474 else { 2475 fillRect.expandRadii(shadowSpread); 2476 if (!fillRect.isRenderable()) 2477 fillRect.adjustRadii(); 2478 context->fillRoundedRect(fillRect, Color::black); 2479 } 2480 } else { 2481 IntRect rectToClipOut = border.rect(); 2482 2483 // If the box is opaque, it is unnecessary to clip it out. However, doing so saves time 2484 // when painting the shadow. On the other hand, it introduces subpixel gaps along the 2485 // edges if they are not pixel-aligned. Those are avoided by insetting the clipping path 2486 // by one pixel. 2487 if (hasOpaqueBackground) { 2488 // FIXME: The function to decide on the policy based on the transform should be a named function. 2489 // FIXME: It's not clear if this check is right. What about integral scale factors? 2490 AffineTransform transform = context->getCTM(); 2491 if (transform.a() != 1 || (transform.d() != 1 && transform.d() != -1) || transform.b() || transform.c()) 2492 rectToClipOut.inflate(-1); 2493 } 2494 2495 if (!rectToClipOut.isEmpty()) { 2496 context->clipOut(rectToClipOut); 2497 } 2498 context->fillRect(fillRect.rect(), Color::black); 2499 } 2500 context->restore(); 2501 context->clearDrawLooper(); 2502 } else { 2503 GraphicsContext::Edges clippedEdges = GraphicsContext::NoEdge; 2504 if (!includeLogicalLeftEdge) { 2505 if (isHorizontal) 2506 clippedEdges |= GraphicsContext::LeftEdge; 2507 else 2508 clippedEdges |= GraphicsContext::TopEdge; 2509 } 2510 if (!includeLogicalRightEdge) { 2511 if (isHorizontal) 2512 clippedEdges |= GraphicsContext::RightEdge; 2513 else 2514 clippedEdges |= GraphicsContext::BottomEdge; 2515 } 2516 context->drawInnerShadow(border, shadowColor, shadowOffset, shadowBlur, shadowSpread, clippedEdges); 2517 } 2518 } 2519 } 2520 2521 LayoutUnit RenderBoxModelObject::containingBlockLogicalWidthForContent() const 2522 { 2523 return containingBlock()->availableLogicalWidth(); 2524 } 2525 2526 RenderBoxModelObject* RenderBoxModelObject::continuation() const 2527 { 2528 if (!continuationMap) 2529 return 0; 2530 return continuationMap->get(this); 2531 } 2532 2533 void RenderBoxModelObject::setContinuation(RenderBoxModelObject* continuation) 2534 { 2535 if (continuation) { 2536 if (!continuationMap) 2537 continuationMap = new ContinuationMap; 2538 continuationMap->set(this, continuation); 2539 } else { 2540 if (continuationMap) 2541 continuationMap->remove(this); 2542 } 2543 } 2544 2545 void RenderBoxModelObject::computeLayerHitTestRects(LayerHitTestRects& rects) const 2546 { 2547 RenderLayerModelObject::computeLayerHitTestRects(rects); 2548 2549 // If there is a continuation then we need to consult it here, since this is 2550 // the root of the tree walk and it wouldn't otherwise get picked up. 2551 // Continuations should always be siblings in the tree, so any others should 2552 // get picked up already by the tree walk. 2553 if (continuation()) 2554 continuation()->computeLayerHitTestRects(rects); 2555 } 2556 2557 RenderObject* RenderBoxModelObject::firstLetterRemainingText() const 2558 { 2559 if (!firstLetterRemainingTextMap) 2560 return 0; 2561 return firstLetterRemainingTextMap->get(this); 2562 } 2563 2564 void RenderBoxModelObject::setFirstLetterRemainingText(RenderObject* remainingText) 2565 { 2566 if (remainingText) { 2567 if (!firstLetterRemainingTextMap) 2568 firstLetterRemainingTextMap = new FirstLetterRemainingTextMap; 2569 firstLetterRemainingTextMap->set(this, remainingText); 2570 } else if (firstLetterRemainingTextMap) 2571 firstLetterRemainingTextMap->remove(this); 2572 } 2573 2574 LayoutRect RenderBoxModelObject::localCaretRectForEmptyElement(LayoutUnit width, LayoutUnit textIndentOffset) 2575 { 2576 ASSERT(!firstChild()); 2577 2578 // FIXME: This does not take into account either :first-line or :first-letter 2579 // However, as soon as some content is entered, the line boxes will be 2580 // constructed and this kludge is not called any more. So only the caret size 2581 // of an empty :first-line'd block is wrong. I think we can live with that. 2582 RenderStyle* currentStyle = firstLineStyle(); 2583 LayoutUnit height = lineHeight(true, currentStyle->isHorizontalWritingMode() ? HorizontalLine : VerticalLine); 2584 2585 enum CaretAlignment { alignLeft, alignRight, alignCenter }; 2586 2587 CaretAlignment alignment = alignLeft; 2588 2589 switch (currentStyle->textAlign()) { 2590 case LEFT: 2591 case WEBKIT_LEFT: 2592 break; 2593 case CENTER: 2594 case WEBKIT_CENTER: 2595 alignment = alignCenter; 2596 break; 2597 case RIGHT: 2598 case WEBKIT_RIGHT: 2599 alignment = alignRight; 2600 break; 2601 case JUSTIFY: 2602 case TASTART: 2603 if (!currentStyle->isLeftToRightDirection()) 2604 alignment = alignRight; 2605 break; 2606 case TAEND: 2607 if (currentStyle->isLeftToRightDirection()) 2608 alignment = alignRight; 2609 break; 2610 } 2611 2612 LayoutUnit x = borderLeft() + paddingLeft(); 2613 LayoutUnit maxX = width - borderRight() - paddingRight(); 2614 2615 switch (alignment) { 2616 case alignLeft: 2617 if (currentStyle->isLeftToRightDirection()) 2618 x += textIndentOffset; 2619 break; 2620 case alignCenter: 2621 x = (x + maxX) / 2; 2622 if (currentStyle->isLeftToRightDirection()) 2623 x += textIndentOffset / 2; 2624 else 2625 x -= textIndentOffset / 2; 2626 break; 2627 case alignRight: 2628 x = maxX - caretWidth; 2629 if (!currentStyle->isLeftToRightDirection()) 2630 x -= textIndentOffset; 2631 break; 2632 } 2633 x = min(x, max<LayoutUnit>(maxX - caretWidth, 0)); 2634 2635 LayoutUnit y = paddingTop() + borderTop(); 2636 2637 return currentStyle->isHorizontalWritingMode() ? LayoutRect(x, y, caretWidth, height) : LayoutRect(y, x, height, caretWidth); 2638 } 2639 2640 bool RenderBoxModelObject::shouldAntialiasLines(GraphicsContext* context) 2641 { 2642 // FIXME: We may want to not antialias when scaled by an integral value, 2643 // and we may want to antialias when translated by a non-integral value. 2644 return !context->getCTM().isIdentityOrTranslationOrFlipped(); 2645 } 2646 2647 void RenderBoxModelObject::mapAbsoluteToLocalPoint(MapCoordinatesFlags mode, TransformState& transformState) const 2648 { 2649 // We don't expect to be called during layout. 2650 ASSERT(!view() || !view()->layoutStateEnabled()); 2651 2652 RenderObject* o = container(); 2653 if (!o) 2654 return; 2655 2656 // The point inside a box that's inside a region has its coordinates relative to the region, 2657 // not the FlowThread that is its container in the RenderObject tree. 2658 if (o->isRenderFlowThread() && isRenderBlock()) { 2659 // FIXME: switch to Box instead of Block when we'll have range information for boxes as well, not just for blocks. 2660 RenderRegion* startRegion; 2661 RenderRegion* ignoredEndRegion; 2662 toRenderFlowThread(o)->getRegionRangeForBox(toRenderBlock(this), startRegion, ignoredEndRegion); 2663 // If there is no region to use the FlowThread, then there's no region range for the content in that FlowThread. 2664 // An API like elementFromPoint might crash without this check. 2665 if (startRegion) 2666 o = startRegion; 2667 } 2668 2669 o->mapAbsoluteToLocalPoint(mode, transformState); 2670 2671 LayoutSize containerOffset = offsetFromContainer(o, LayoutPoint()); 2672 2673 if (!style()->hasOutOfFlowPosition() && o->hasColumns()) { 2674 RenderBlock* block = toRenderBlock(o); 2675 LayoutPoint point(roundedLayoutPoint(transformState.mappedPoint())); 2676 point -= containerOffset; 2677 block->adjustForColumnRect(containerOffset, point); 2678 } 2679 2680 bool preserve3D = mode & UseTransforms && (o->style()->preserves3D() || style()->preserves3D()); 2681 if (mode & UseTransforms && shouldUseTransformFromContainer(o)) { 2682 TransformationMatrix t; 2683 getTransformFromContainer(o, containerOffset, t); 2684 transformState.applyTransform(t, preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); 2685 } else 2686 transformState.move(containerOffset.width(), containerOffset.height(), preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); 2687 } 2688 2689 const RenderObject* RenderBoxModelObject::pushMappingToContainer(const RenderLayerModelObject* ancestorToStopAt, RenderGeometryMap& geometryMap) const 2690 { 2691 ASSERT(ancestorToStopAt != this); 2692 2693 bool ancestorSkipped; 2694 RenderObject* container = this->container(ancestorToStopAt, &ancestorSkipped); 2695 if (!container) 2696 return 0; 2697 2698 bool isInline = isRenderInline(); 2699 bool isFixedPos = !isInline && style()->position() == FixedPosition; 2700 bool hasTransform = !isInline && hasLayer() && layer()->transform(); 2701 2702 LayoutSize adjustmentForSkippedAncestor; 2703 if (ancestorSkipped) { 2704 // There can't be a transform between repaintContainer and o, because transforms create containers, so it should be safe 2705 // to just subtract the delta between the ancestor and o. 2706 adjustmentForSkippedAncestor = -ancestorToStopAt->offsetFromAncestorContainer(container); 2707 } 2708 2709 bool offsetDependsOnPoint = false; 2710 LayoutSize containerOffset = offsetFromContainer(container, LayoutPoint(), &offsetDependsOnPoint); 2711 2712 bool preserve3D = container->style()->preserves3D() || style()->preserves3D(); 2713 if (shouldUseTransformFromContainer(container)) { 2714 TransformationMatrix t; 2715 getTransformFromContainer(container, containerOffset, t); 2716 t.translateRight(adjustmentForSkippedAncestor.width(), adjustmentForSkippedAncestor.height()); 2717 geometryMap.push(this, t, preserve3D, offsetDependsOnPoint, isFixedPos, hasTransform); 2718 } else { 2719 containerOffset += adjustmentForSkippedAncestor; 2720 geometryMap.push(this, containerOffset, preserve3D, offsetDependsOnPoint, isFixedPos, hasTransform); 2721 } 2722 2723 return ancestorSkipped ? ancestorToStopAt : container; 2724 } 2725 2726 void RenderBoxModelObject::moveChildTo(RenderBoxModelObject* toBoxModelObject, RenderObject* child, RenderObject* beforeChild, bool fullRemoveInsert) 2727 { 2728 // We assume that callers have cleared their positioned objects list for child moves (!fullRemoveInsert) so the 2729 // positioned renderer maps don't become stale. It would be too slow to do the map lookup on each call. 2730 ASSERT(!fullRemoveInsert || !isRenderBlock() || !toRenderBlock(this)->hasPositionedObjects()); 2731 2732 ASSERT(this == child->parent()); 2733 ASSERT(!beforeChild || toBoxModelObject == beforeChild->parent()); 2734 if (fullRemoveInsert && (toBoxModelObject->isRenderBlock() || toBoxModelObject->isRenderInline())) { 2735 // Takes care of adding the new child correctly if toBlock and fromBlock 2736 // have different kind of children (block vs inline). 2737 toBoxModelObject->addChild(virtualChildren()->removeChildNode(this, child), beforeChild); 2738 } else 2739 toBoxModelObject->virtualChildren()->insertChildNode(toBoxModelObject, virtualChildren()->removeChildNode(this, child, fullRemoveInsert), beforeChild, fullRemoveInsert); 2740 } 2741 2742 void RenderBoxModelObject::moveChildrenTo(RenderBoxModelObject* toBoxModelObject, RenderObject* startChild, RenderObject* endChild, RenderObject* beforeChild, bool fullRemoveInsert) 2743 { 2744 // This condition is rarely hit since this function is usually called on 2745 // anonymous blocks which can no longer carry positioned objects (see r120761) 2746 // or when fullRemoveInsert is false. 2747 if (fullRemoveInsert && isRenderBlock()) { 2748 RenderBlock* block = toRenderBlock(this); 2749 block->removePositionedObjects(0); 2750 block->removeFloatingObjects(); 2751 } 2752 2753 ASSERT(!beforeChild || toBoxModelObject == beforeChild->parent()); 2754 for (RenderObject* child = startChild; child && child != endChild; ) { 2755 // Save our next sibling as moveChildTo will clear it. 2756 RenderObject* nextSibling = child->nextSibling(); 2757 moveChildTo(toBoxModelObject, child, beforeChild, fullRemoveInsert); 2758 child = nextSibling; 2759 } 2760 } 2761 2762 } // namespace WebCore 2763