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