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