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