1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2005 Allan Sandfeld Jensen (kde (at) carewolf.com) 5 * (C) 2005, 2006 Samuel Weinig (sam.weinig (at) gmail.com) 6 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. 7 * 8 * This library is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Library General Public 10 * License as published by the Free Software Foundation; either 11 * version 2 of the License, or (at your option) any later version. 12 * 13 * This library is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Library General Public License for more details. 17 * 18 * You should have received a copy of the GNU Library General Public License 19 * along with this library; see the file COPYING.LIB. If not, write to 20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 * Boston, MA 02110-1301, USA. 22 * 23 */ 24 25 #include "config.h" 26 #include "RenderBoxModelObject.h" 27 28 #include "GraphicsContext.h" 29 #include "HTMLElement.h" 30 #include "HTMLNames.h" 31 #include "ImageBuffer.h" 32 #include "RenderBlock.h" 33 #include "RenderInline.h" 34 #include "RenderLayer.h" 35 #include "RenderView.h" 36 #include <wtf/CurrentTime.h> 37 38 using namespace std; 39 40 namespace WebCore { 41 42 using namespace HTMLNames; 43 44 bool RenderBoxModelObject::s_wasFloating = false; 45 bool RenderBoxModelObject::s_hadLayer = false; 46 bool RenderBoxModelObject::s_layerWasSelfPainting = false; 47 48 static const double cInterpolationCutoff = 800. * 800.; 49 static const double cLowQualityTimeThreshold = 0.500; // 500 ms 50 51 class RenderBoxModelScaleData : public Noncopyable { 52 public: 53 RenderBoxModelScaleData(RenderBoxModelObject* object, const IntSize& size, const AffineTransform& transform, double time, bool lowQualityScale) 54 : m_size(size) 55 , m_transform(transform) 56 , m_lastPaintTime(time) 57 , m_lowQualityScale(lowQualityScale) 58 , m_highQualityRepaintTimer(object, &RenderBoxModelObject::highQualityRepaintTimerFired) 59 { 60 } 61 62 ~RenderBoxModelScaleData() 63 { 64 m_highQualityRepaintTimer.stop(); 65 } 66 67 Timer<RenderBoxModelObject>& hiqhQualityRepaintTimer() { return m_highQualityRepaintTimer; } 68 69 const IntSize& size() const { return m_size; } 70 void setSize(const IntSize& s) { m_size = s; } 71 double lastPaintTime() const { return m_lastPaintTime; } 72 void setLastPaintTime(double t) { m_lastPaintTime = t; } 73 bool useLowQualityScale() const { return m_lowQualityScale; } 74 const AffineTransform& transform() const { return m_transform; } 75 void setTransform(const AffineTransform& transform) { m_transform = transform; } 76 void setUseLowQualityScale(bool b) 77 { 78 m_highQualityRepaintTimer.stop(); 79 m_lowQualityScale = b; 80 if (b) 81 m_highQualityRepaintTimer.startOneShot(cLowQualityTimeThreshold); 82 } 83 84 private: 85 IntSize m_size; 86 AffineTransform m_transform; 87 double m_lastPaintTime; 88 bool m_lowQualityScale; 89 Timer<RenderBoxModelObject> m_highQualityRepaintTimer; 90 }; 91 92 class RenderBoxModelScaleObserver { 93 public: 94 static bool shouldPaintBackgroundAtLowQuality(GraphicsContext*, RenderBoxModelObject*, Image*, const IntSize&); 95 96 static void boxModelObjectDestroyed(RenderBoxModelObject* object) 97 { 98 if (gBoxModelObjects) { 99 RenderBoxModelScaleData* data = gBoxModelObjects->take(object); 100 delete data; 101 if (!gBoxModelObjects->size()) { 102 delete gBoxModelObjects; 103 gBoxModelObjects = 0; 104 } 105 } 106 } 107 108 static void highQualityRepaintTimerFired(RenderBoxModelObject* object) 109 { 110 RenderBoxModelScaleObserver::boxModelObjectDestroyed(object); 111 object->repaint(); 112 } 113 114 static HashMap<RenderBoxModelObject*, RenderBoxModelScaleData*>* gBoxModelObjects; 115 }; 116 117 bool RenderBoxModelScaleObserver::shouldPaintBackgroundAtLowQuality(GraphicsContext* context, RenderBoxModelObject* object, Image* image, const IntSize& size) 118 { 119 // If the image is not a bitmap image, then none of this is relevant and we just paint at high 120 // quality. 121 if (!image || !image->isBitmapImage()) 122 return false; 123 124 // Make sure to use the unzoomed image size, since if a full page zoom is in effect, the image 125 // is actually being scaled. 126 IntSize imageSize(image->width(), image->height()); 127 128 // Look ourselves up in the hashtable. 129 RenderBoxModelScaleData* data = 0; 130 if (gBoxModelObjects) 131 data = gBoxModelObjects->get(object); 132 133 const AffineTransform& currentTransform = context->getCTM(); 134 bool contextIsScaled = !currentTransform.isIdentityOrTranslation(); 135 if (!contextIsScaled && imageSize == size) { 136 // There is no scale in effect. If we had a scale in effect before, we can just delete this data. 137 if (data) { 138 gBoxModelObjects->remove(object); 139 delete data; 140 } 141 return false; 142 } 143 144 // There is no need to hash scaled images that always use low quality mode when the page demands it. This is the iChat case. 145 if (object->document()->page()->inLowQualityImageInterpolationMode()) { 146 double totalPixels = static_cast<double>(image->width()) * static_cast<double>(image->height()); 147 if (totalPixels > cInterpolationCutoff) 148 return true; 149 } 150 151 // If there is no data yet, we will paint the first scale at high quality and record the paint time in case a second scale happens 152 // very soon. 153 if (!data) { 154 data = new RenderBoxModelScaleData(object, size, currentTransform, currentTime(), false); 155 if (!gBoxModelObjects) 156 gBoxModelObjects = new HashMap<RenderBoxModelObject*, RenderBoxModelScaleData*>; 157 gBoxModelObjects->set(object, data); 158 return false; 159 } 160 161 // We are scaled, but we painted already at this size, so just keep using whatever mode we last painted with. 162 if ((!contextIsScaled || data->transform() == currentTransform) && data->size() == size) 163 return data->useLowQualityScale(); 164 165 // We have data and our size just changed. If this change happened quickly, go into low quality mode and then set a repaint 166 // timer to paint in high quality mode. Otherwise it is ok to just paint in high quality mode. 167 double newTime = currentTime(); 168 data->setUseLowQualityScale(newTime - data->lastPaintTime() < cLowQualityTimeThreshold); 169 data->setLastPaintTime(newTime); 170 data->setTransform(currentTransform); 171 data->setSize(size); 172 return data->useLowQualityScale(); 173 } 174 175 HashMap<RenderBoxModelObject*, RenderBoxModelScaleData*>* RenderBoxModelScaleObserver::gBoxModelObjects = 0; 176 177 void RenderBoxModelObject::highQualityRepaintTimerFired(Timer<RenderBoxModelObject>*) 178 { 179 RenderBoxModelScaleObserver::highQualityRepaintTimerFired(this); 180 } 181 182 RenderBoxModelObject::RenderBoxModelObject(Node* node) 183 : RenderObject(node) 184 , m_layer(0) 185 { 186 } 187 188 RenderBoxModelObject::~RenderBoxModelObject() 189 { 190 // Our layer should have been destroyed and cleared by now 191 ASSERT(!hasLayer()); 192 ASSERT(!m_layer); 193 RenderBoxModelScaleObserver::boxModelObjectDestroyed(this); 194 } 195 196 void RenderBoxModelObject::destroyLayer() 197 { 198 ASSERT(!hasLayer()); // Callers should have already called setHasLayer(false) 199 ASSERT(m_layer); 200 m_layer->destroy(renderArena()); 201 m_layer = 0; 202 } 203 204 void RenderBoxModelObject::destroy() 205 { 206 // This must be done before we destroy the RenderObject. 207 if (m_layer) 208 m_layer->clearClipRects(); 209 210 // RenderObject::destroy calls back to destroyLayer() for layer destruction 211 RenderObject::destroy(); 212 } 213 214 bool RenderBoxModelObject::hasSelfPaintingLayer() const 215 { 216 return m_layer && m_layer->isSelfPaintingLayer(); 217 } 218 219 void RenderBoxModelObject::styleWillChange(StyleDifference diff, const RenderStyle* newStyle) 220 { 221 s_wasFloating = isFloating(); 222 s_hadLayer = hasLayer(); 223 if (s_hadLayer) 224 s_layerWasSelfPainting = layer()->isSelfPaintingLayer(); 225 226 // If our z-index changes value or our visibility changes, 227 // we need to dirty our stacking context's z-order list. 228 if (style() && newStyle) { 229 if (parent()) { 230 // Do a repaint with the old style first, e.g., for example if we go from 231 // having an outline to not having an outline. 232 if (diff == StyleDifferenceRepaintLayer) { 233 layer()->repaintIncludingDescendants(); 234 if (!(style()->clip() == newStyle->clip())) 235 layer()->clearClipRectsIncludingDescendants(); 236 } else if (diff == StyleDifferenceRepaint || newStyle->outlineSize() < style()->outlineSize()) 237 repaint(); 238 } 239 240 if (diff == StyleDifferenceLayout) { 241 // When a layout hint happens, we go ahead and do a repaint of the layer, since the layer could 242 // end up being destroyed. 243 if (hasLayer()) { 244 if (style()->position() != newStyle->position() || 245 style()->zIndex() != newStyle->zIndex() || 246 style()->hasAutoZIndex() != newStyle->hasAutoZIndex() || 247 !(style()->clip() == newStyle->clip()) || 248 style()->hasClip() != newStyle->hasClip() || 249 style()->opacity() != newStyle->opacity() || 250 style()->transform() != newStyle->transform()) 251 layer()->repaintIncludingDescendants(); 252 } else if (newStyle->hasTransform() || newStyle->opacity() < 1) { 253 // If we don't have a layer yet, but we are going to get one because of transform or opacity, 254 // then we need to repaint the old position of the object. 255 repaint(); 256 } 257 } 258 259 if (hasLayer() && (style()->hasAutoZIndex() != newStyle->hasAutoZIndex() || 260 style()->zIndex() != newStyle->zIndex() || 261 style()->visibility() != newStyle->visibility())) { 262 layer()->dirtyStackingContextZOrderLists(); 263 if (style()->hasAutoZIndex() != newStyle->hasAutoZIndex() || style()->visibility() != newStyle->visibility()) 264 layer()->dirtyZOrderLists(); 265 } 266 } 267 268 RenderObject::styleWillChange(diff, newStyle); 269 } 270 271 void RenderBoxModelObject::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) 272 { 273 RenderObject::styleDidChange(diff, oldStyle); 274 updateBoxModelInfoFromStyle(); 275 276 if (requiresLayer()) { 277 if (!layer()) { 278 if (s_wasFloating && isFloating()) 279 setChildNeedsLayout(true); 280 m_layer = new (renderArena()) RenderLayer(this); 281 setHasLayer(true); 282 m_layer->insertOnlyThisLayer(); 283 if (parent() && !needsLayout() && containingBlock()) 284 m_layer->updateLayerPositions(); 285 } 286 } else if (layer() && layer()->parent()) { 287 setHasTransform(false); // Either a transform wasn't specified or the object doesn't support transforms, so just null out the bit. 288 setHasReflection(false); 289 m_layer->removeOnlyThisLayer(); // calls destroyLayer() which clears m_layer 290 if (s_wasFloating && isFloating()) 291 setChildNeedsLayout(true); 292 } 293 294 if (layer()) { 295 layer()->styleChanged(diff, oldStyle); 296 if (s_hadLayer && layer()->isSelfPaintingLayer() != s_layerWasSelfPainting) 297 setChildNeedsLayout(true); 298 } 299 } 300 301 void RenderBoxModelObject::updateBoxModelInfoFromStyle() 302 { 303 // Set the appropriate bits for a box model object. Since all bits are cleared in styleWillChange, 304 // we only check for bits that could possibly be set to true. 305 setHasBoxDecorations(style()->hasBorder() || style()->hasBackground() || style()->hasAppearance() || style()->boxShadow()); 306 setInline(style()->isDisplayInlineType()); 307 setRelPositioned(style()->position() == RelativePosition); 308 } 309 310 int RenderBoxModelObject::relativePositionOffsetX() const 311 { 312 // Objects that shrink to avoid floats normally use available line width when computing containing block width. However 313 // in the case of relative positioning using percentages, we can't do this. The offset should always be resolved using the 314 // available width of the containing block. Therefore we don't use containingBlockWidthForContent() here, but instead explicitly 315 // call availableWidth on our containing block. 316 if (!style()->left().isAuto()) { 317 RenderBlock* cb = containingBlock(); 318 if (!style()->right().isAuto() && containingBlock()->style()->direction() == RTL) 319 return -style()->right().calcValue(cb->availableWidth()); 320 return style()->left().calcValue(cb->availableWidth()); 321 } 322 if (!style()->right().isAuto()) { 323 RenderBlock* cb = containingBlock(); 324 return -style()->right().calcValue(cb->availableWidth()); 325 } 326 return 0; 327 } 328 329 int RenderBoxModelObject::relativePositionOffsetY() const 330 { 331 if (!style()->top().isAuto()) 332 return style()->top().calcValue(containingBlock()->availableHeight()); 333 else if (!style()->bottom().isAuto()) 334 return -style()->bottom().calcValue(containingBlock()->availableHeight()); 335 336 return 0; 337 } 338 339 int RenderBoxModelObject::offsetLeft() const 340 { 341 // If the element is the HTML body element or does not have an associated box 342 // return 0 and stop this algorithm. 343 if (isBody()) 344 return 0; 345 346 RenderBoxModelObject* offsetPar = offsetParent(); 347 int xPos = (isBox() ? toRenderBox(this)->x() : 0); 348 349 // If the offsetParent of the element is null, or is the HTML body element, 350 // return the distance between the canvas origin and the left border edge 351 // of the element and stop this algorithm. 352 if (offsetPar) { 353 if (offsetPar->isBox() && !offsetPar->isBody()) 354 xPos -= toRenderBox(offsetPar)->borderLeft(); 355 if (!isPositioned()) { 356 if (isRelPositioned()) 357 xPos += relativePositionOffsetX(); 358 RenderObject* curr = parent(); 359 while (curr && curr != offsetPar) { 360 // FIXME: What are we supposed to do inside SVG content? 361 if (curr->isBox() && !curr->isTableRow()) 362 xPos += toRenderBox(curr)->x(); 363 curr = curr->parent(); 364 } 365 if (offsetPar->isBox() && offsetPar->isBody() && !offsetPar->isRelPositioned() && !offsetPar->isPositioned()) 366 xPos += toRenderBox(offsetPar)->x(); 367 } 368 } 369 370 return xPos; 371 } 372 373 int RenderBoxModelObject::offsetTop() const 374 { 375 // If the element is the HTML body element or does not have an associated box 376 // return 0 and stop this algorithm. 377 if (isBody()) 378 return 0; 379 380 RenderBoxModelObject* offsetPar = offsetParent(); 381 int yPos = (isBox() ? toRenderBox(this)->y() : 0); 382 383 // If the offsetParent of the element is null, or is the HTML body element, 384 // return the distance between the canvas origin and the top border edge 385 // of the element and stop this algorithm. 386 if (offsetPar) { 387 if (offsetPar->isBox() && !offsetPar->isBody()) 388 yPos -= toRenderBox(offsetPar)->borderTop(); 389 if (!isPositioned()) { 390 if (isRelPositioned()) 391 yPos += relativePositionOffsetY(); 392 RenderObject* curr = parent(); 393 while (curr && curr != offsetPar) { 394 // FIXME: What are we supposed to do inside SVG content? 395 if (curr->isBox() && !curr->isTableRow()) 396 yPos += toRenderBox(curr)->y(); 397 curr = curr->parent(); 398 } 399 if (offsetPar->isBox() && offsetPar->isBody() && !offsetPar->isRelPositioned() && !offsetPar->isPositioned()) 400 yPos += toRenderBox(offsetPar)->y(); 401 } 402 } 403 return yPos; 404 } 405 406 int RenderBoxModelObject::paddingTop(bool) const 407 { 408 int w = 0; 409 Length padding = style()->paddingTop(); 410 if (padding.isPercent()) 411 w = containingBlock()->availableWidth(); 412 return padding.calcMinValue(w); 413 } 414 415 int RenderBoxModelObject::paddingBottom(bool) const 416 { 417 int w = 0; 418 Length padding = style()->paddingBottom(); 419 if (padding.isPercent()) 420 w = containingBlock()->availableWidth(); 421 return padding.calcMinValue(w); 422 } 423 424 int RenderBoxModelObject::paddingLeft(bool) const 425 { 426 int w = 0; 427 Length padding = style()->paddingLeft(); 428 if (padding.isPercent()) 429 w = containingBlock()->availableWidth(); 430 return padding.calcMinValue(w); 431 } 432 433 int RenderBoxModelObject::paddingRight(bool) const 434 { 435 int w = 0; 436 Length padding = style()->paddingRight(); 437 if (padding.isPercent()) 438 w = containingBlock()->availableWidth(); 439 return padding.calcMinValue(w); 440 } 441 442 443 void RenderBoxModelObject::paintFillLayerExtended(const PaintInfo& paintInfo, const Color& c, const FillLayer* bgLayer, int tx, int ty, int w, int h, InlineFlowBox* box, CompositeOperator op, RenderObject* backgroundObject) 444 { 445 GraphicsContext* context = paintInfo.context; 446 if (context->paintingDisabled()) 447 return; 448 449 bool includeLeftEdge = box ? box->includeLeftEdge() : true; 450 bool includeRightEdge = box ? box->includeRightEdge() : true; 451 int bLeft = includeLeftEdge ? borderLeft() : 0; 452 int bRight = includeRightEdge ? borderRight() : 0; 453 int pLeft = includeLeftEdge ? paddingLeft() : 0; 454 int pRight = includeRightEdge ? paddingRight() : 0; 455 456 bool clippedToBorderRadius = false; 457 if (style()->hasBorderRadius() && (includeLeftEdge || includeRightEdge)) { 458 IntRect borderRect(tx, ty, w, h); 459 460 if (borderRect.isEmpty()) 461 return; 462 463 context->save(); 464 465 IntSize topLeft, topRight, bottomLeft, bottomRight; 466 style()->getBorderRadiiForRect(borderRect, topLeft, topRight, bottomLeft, bottomRight); 467 468 context->addRoundedRectClip(borderRect, includeLeftEdge ? topLeft : IntSize(), 469 includeRightEdge ? topRight : IntSize(), 470 includeLeftEdge ? bottomLeft : IntSize(), 471 includeRightEdge ? bottomRight : IntSize()); 472 clippedToBorderRadius = true; 473 } 474 475 bool clippedWithLocalScrolling = hasOverflowClip() && bgLayer->attachment() == LocalBackgroundAttachment; 476 if (clippedWithLocalScrolling) { 477 // Clip to the overflow area. 478 context->save(); 479 context->clip(toRenderBox(this)->overflowClipRect(tx, ty)); 480 481 // Now adjust our tx, ty, w, h to reflect a scrolled content box with borders at the ends. 482 layer()->subtractScrolledContentOffset(tx, ty); 483 w = bLeft + layer()->scrollWidth() + bRight; 484 h = borderTop() + layer()->scrollHeight() + borderBottom(); 485 } 486 487 if (bgLayer->clip() == PaddingFillBox || bgLayer->clip() == ContentFillBox) { 488 // Clip to the padding or content boxes as necessary. 489 bool includePadding = bgLayer->clip() == ContentFillBox; 490 int x = tx + bLeft + (includePadding ? pLeft : 0); 491 int y = ty + borderTop() + (includePadding ? paddingTop() : 0); 492 int width = w - bLeft - bRight - (includePadding ? pLeft + pRight : 0); 493 int height = h - borderTop() - borderBottom() - (includePadding ? paddingTop() + paddingBottom() : 0); 494 context->save(); 495 context->clip(IntRect(x, y, width, height)); 496 } else if (bgLayer->clip() == TextFillBox) { 497 // We have to draw our text into a mask that can then be used to clip background drawing. 498 // First figure out how big the mask has to be. It should be no bigger than what we need 499 // to actually render, so we should intersect the dirty rect with the border box of the background. 500 IntRect maskRect(tx, ty, w, h); 501 maskRect.intersect(paintInfo.rect); 502 503 // Now create the mask. 504 OwnPtr<ImageBuffer> maskImage = ImageBuffer::create(maskRect.size()); 505 if (!maskImage) 506 return; 507 508 GraphicsContext* maskImageContext = maskImage->context(); 509 maskImageContext->translate(-maskRect.x(), -maskRect.y()); 510 511 // Now add the text to the clip. We do this by painting using a special paint phase that signals to 512 // InlineTextBoxes that they should just add their contents to the clip. 513 PaintInfo info(maskImageContext, maskRect, PaintPhaseTextClip, true, 0, 0); 514 if (box) 515 box->paint(info, tx - box->x(), ty - box->y()); 516 else { 517 int x = isBox() ? toRenderBox(this)->x() : 0; 518 int y = isBox() ? toRenderBox(this)->y() : 0; 519 paint(info, tx - x, ty - y); 520 } 521 522 // The mask has been created. Now we just need to clip to it. 523 context->save(); 524 context->clipToImageBuffer(maskRect, maskImage.get()); 525 } 526 527 StyleImage* bg = bgLayer->image(); 528 bool shouldPaintBackgroundImage = bg && bg->canRender(style()->effectiveZoom()); 529 Color bgColor = c; 530 531 // When this style flag is set, change existing background colors and images to a solid white background. 532 // If there's no bg color or image, leave it untouched to avoid affecting transparency. 533 // We don't try to avoid loading the background images, because this style flag is only set 534 // when printing, and at that point we've already loaded the background images anyway. (To avoid 535 // loading the background images we'd have to do this check when applying styles rather than 536 // while rendering.) 537 if (style()->forceBackgroundsToWhite()) { 538 // Note that we can't reuse this variable below because the bgColor might be changed 539 bool shouldPaintBackgroundColor = !bgLayer->next() && bgColor.isValid() && bgColor.alpha() > 0; 540 if (shouldPaintBackgroundImage || shouldPaintBackgroundColor) { 541 bgColor = Color::white; 542 shouldPaintBackgroundImage = false; 543 } 544 } 545 546 bool isRoot = this->isRoot(); 547 548 // Only fill with a base color (e.g., white) if we're the root document, since iframes/frames with 549 // no background in the child document should show the parent's background. 550 bool isOpaqueRoot = false; 551 if (isRoot) { 552 isOpaqueRoot = true; 553 if (!bgLayer->next() && !(bgColor.isValid() && bgColor.alpha() == 255) && view()->frameView()) { 554 Element* ownerElement = document()->ownerElement(); 555 if (ownerElement) { 556 if (!ownerElement->hasTagName(frameTag)) { 557 // Locate the <body> element using the DOM. This is easier than trying 558 // to crawl around a render tree with potential :before/:after content and 559 // anonymous blocks created by inline <body> tags etc. We can locate the <body> 560 // render object very easily via the DOM. 561 HTMLElement* body = document()->body(); 562 if (body) { 563 // Can't scroll a frameset document anyway. 564 isOpaqueRoot = body->hasLocalName(framesetTag); 565 } 566 } 567 } else 568 isOpaqueRoot = !view()->frameView()->isTransparent(); 569 } 570 view()->frameView()->setContentIsOpaque(isOpaqueRoot); 571 } 572 573 // Paint the color first underneath all images. 574 if (!bgLayer->next()) { 575 IntRect rect(tx, ty, w, h); 576 rect.intersect(paintInfo.rect); 577 // If we have an alpha and we are painting the root element, go ahead and blend with the base background color. 578 if (isOpaqueRoot) { 579 Color baseColor = view()->frameView()->baseBackgroundColor(); 580 if (baseColor.alpha() > 0) { 581 context->save(); 582 context->setCompositeOperation(CompositeCopy); 583 context->fillRect(rect, baseColor, style()->colorSpace()); 584 context->restore(); 585 } else 586 context->clearRect(rect); 587 } 588 589 if (bgColor.isValid() && bgColor.alpha() > 0) 590 context->fillRect(rect, bgColor, style()->colorSpace()); 591 } 592 593 // no progressive loading of the background image 594 if (shouldPaintBackgroundImage) { 595 IntRect destRect; 596 IntPoint phase; 597 IntSize tileSize; 598 599 calculateBackgroundImageGeometry(bgLayer, tx, ty, w, h, destRect, phase, tileSize); 600 IntPoint destOrigin = destRect.location(); 601 destRect.intersect(paintInfo.rect); 602 if (!destRect.isEmpty()) { 603 phase += destRect.location() - destOrigin; 604 CompositeOperator compositeOp = op == CompositeSourceOver ? bgLayer->composite() : op; 605 RenderObject* clientForBackgroundImage = backgroundObject ? backgroundObject : this; 606 Image* image = bg->image(clientForBackgroundImage, tileSize); 607 bool useLowQualityScaling = RenderBoxModelScaleObserver::shouldPaintBackgroundAtLowQuality(context, this, image, destRect.size()); 608 context->drawTiledImage(image, style()->colorSpace(), destRect, phase, tileSize, compositeOp, useLowQualityScaling); 609 } 610 } 611 612 if (bgLayer->clip() != BorderFillBox) 613 // Undo the background clip 614 context->restore(); 615 616 if (clippedToBorderRadius) 617 // Undo the border radius clip 618 context->restore(); 619 620 if (clippedWithLocalScrolling) // Undo the clip for local background attachments. 621 context->restore(); 622 } 623 624 IntSize RenderBoxModelObject::calculateFillTileSize(const FillLayer* fillLayer, IntSize positioningAreaSize) const 625 { 626 StyleImage* image = fillLayer->image(); 627 image->setImageContainerSize(positioningAreaSize); // Use the box established by background-origin. 628 629 EFillSizeType type = fillLayer->size().type; 630 631 switch (type) { 632 case SizeLength: { 633 int w = positioningAreaSize.width(); 634 int h = positioningAreaSize.height(); 635 Length layerWidth = fillLayer->size().size.width(); 636 Length layerHeight = fillLayer->size().size.height(); 637 638 if (layerWidth.isFixed()) 639 w = layerWidth.value(); 640 else if (layerWidth.isPercent()) 641 w = layerWidth.calcValue(positioningAreaSize.width()); 642 643 if (layerHeight.isFixed()) 644 h = layerHeight.value(); 645 else if (layerHeight.isPercent()) 646 h = layerHeight.calcValue(positioningAreaSize.height()); 647 648 // If one of the values is auto we have to use the appropriate 649 // scale to maintain our aspect ratio. 650 if (layerWidth.isAuto() && !layerHeight.isAuto()) 651 w = image->imageSize(this, style()->effectiveZoom()).width() * h / image->imageSize(this, style()->effectiveZoom()).height(); 652 else if (!layerWidth.isAuto() && layerHeight.isAuto()) 653 h = image->imageSize(this, style()->effectiveZoom()).height() * w / image->imageSize(this, style()->effectiveZoom()).width(); 654 else if (layerWidth.isAuto() && layerHeight.isAuto()) { 655 // If both width and height are auto, we just want to use the image's 656 // intrinsic size. 657 w = image->imageSize(this, style()->effectiveZoom()).width(); 658 h = image->imageSize(this, style()->effectiveZoom()).height(); 659 } 660 661 return IntSize(max(1, w), max(1, h)); 662 } 663 case Contain: 664 case Cover: { 665 IntSize imageIntrinsicSize = image->imageSize(this, 1); 666 float horizontalScaleFactor = static_cast<float>(positioningAreaSize.width()) / imageIntrinsicSize.width(); 667 float verticalScaleFactor = static_cast<float>(positioningAreaSize.height()) / imageIntrinsicSize.height(); 668 float scaleFactor = type == Contain ? min(horizontalScaleFactor, verticalScaleFactor) : max(horizontalScaleFactor, verticalScaleFactor); 669 670 return IntSize(max<int>(1, imageIntrinsicSize.width() * scaleFactor), max<int>(1, imageIntrinsicSize.height() * scaleFactor)); 671 } 672 case SizeNone: 673 break; 674 } 675 return image->imageSize(this, style()->effectiveZoom()); 676 } 677 678 void RenderBoxModelObject::calculateBackgroundImageGeometry(const FillLayer* fillLayer, int tx, int ty, int w, int h, 679 IntRect& destRect, IntPoint& phase, IntSize& tileSize) 680 { 681 int left = 0; 682 int top = 0; 683 IntSize positioningAreaSize; 684 685 // Determine the background positioning area and set destRect to the background painting area. 686 // destRect will be adjusted later if the background is non-repeating. 687 bool fixedAttachment = fillLayer->attachment() == FixedBackgroundAttachment; 688 689 #if ENABLE(FAST_MOBILE_SCROLLING) 690 if (view()->frameView() && view()->frameView()->canBlitOnScroll()) { 691 // As a side effect of an optimization to blit on scroll, we do not honor the CSS 692 // property "background-attachment: fixed" because it may result in rendering 693 // artifacts. Note, these artifacts only appear if we are blitting on scroll of 694 // a page that has fixed background images. 695 fixedAttachment = false; 696 } 697 #endif 698 699 if (!fixedAttachment) { 700 destRect = IntRect(tx, ty, w, h); 701 702 int right = 0; 703 int bottom = 0; 704 // Scroll and Local. 705 if (fillLayer->origin() != BorderFillBox) { 706 left = borderLeft(); 707 right = borderRight(); 708 top = borderTop(); 709 bottom = borderBottom(); 710 if (fillLayer->origin() == ContentFillBox) { 711 left += paddingLeft(); 712 right += paddingRight(); 713 top += paddingTop(); 714 bottom += paddingBottom(); 715 } 716 } 717 718 // The background of the box generated by the root element covers the entire canvas including 719 // its margins. Since those were added in already, we have to factor them out when computing 720 // the background positioning area. 721 if (isRoot()) { 722 positioningAreaSize = IntSize(toRenderBox(this)->width() - left - right, toRenderBox(this)->height() - top - bottom); 723 left += marginLeft(); 724 top += marginTop(); 725 } else 726 positioningAreaSize = IntSize(w - left - right, h - top - bottom); 727 } else { 728 destRect = viewRect(); 729 positioningAreaSize = destRect.size(); 730 } 731 732 tileSize = calculateFillTileSize(fillLayer, positioningAreaSize); 733 734 EFillRepeat backgroundRepeatX = fillLayer->repeatX(); 735 EFillRepeat backgroundRepeatY = fillLayer->repeatY(); 736 737 int xPosition = fillLayer->xPosition().calcMinValue(positioningAreaSize.width() - tileSize.width(), true); 738 if (backgroundRepeatX == RepeatFill) 739 phase.setX(tileSize.width() ? tileSize.width() - (xPosition + left) % tileSize.width() : 0); 740 else { 741 destRect.move(max(xPosition + left, 0), 0); 742 phase.setX(-min(xPosition + left, 0)); 743 destRect.setWidth(tileSize.width() + min(xPosition + left, 0)); 744 } 745 746 int yPosition = fillLayer->yPosition().calcMinValue(positioningAreaSize.height() - tileSize.height(), true); 747 if (backgroundRepeatY == RepeatFill) 748 phase.setY(tileSize.height() ? tileSize.height() - (yPosition + top) % tileSize.height() : 0); 749 else { 750 destRect.move(0, max(yPosition + top, 0)); 751 phase.setY(-min(yPosition + top, 0)); 752 destRect.setHeight(tileSize.height() + min(yPosition + top, 0)); 753 } 754 755 if (fixedAttachment) 756 phase.move(max(tx - destRect.x(), 0), max(ty - destRect.y(), 0)); 757 758 destRect.intersect(IntRect(tx, ty, w, h)); 759 } 760 761 int RenderBoxModelObject::verticalPosition(bool firstLine) const 762 { 763 // This method determines the vertical position for inline elements. 764 ASSERT(isInline()); 765 if (!isInline()) 766 return 0; 767 768 int vpos = 0; 769 EVerticalAlign va = style()->verticalAlign(); 770 if (va == TOP) 771 vpos = PositionTop; 772 else if (va == BOTTOM) 773 vpos = PositionBottom; 774 else { 775 bool checkParent = parent()->isRenderInline() && parent()->style()->verticalAlign() != TOP && parent()->style()->verticalAlign() != BOTTOM; 776 vpos = checkParent ? toRenderInline(parent())->verticalPositionFromCache(firstLine) : 0; 777 // don't allow elements nested inside text-top to have a different valignment. 778 if (va == BASELINE) 779 return vpos; 780 781 const Font& f = parent()->style(firstLine)->font(); 782 int fontsize = f.pixelSize(); 783 784 if (va == SUB) 785 vpos += fontsize / 5 + 1; 786 else if (va == SUPER) 787 vpos -= fontsize / 3 + 1; 788 else if (va == TEXT_TOP) 789 vpos += baselinePosition(firstLine) - f.ascent(); 790 else if (va == MIDDLE) 791 vpos += -static_cast<int>(f.xHeight() / 2) - lineHeight(firstLine) / 2 + baselinePosition(firstLine); 792 else if (va == TEXT_BOTTOM) { 793 vpos += f.descent(); 794 if (!isReplaced()) // lineHeight - baselinePosition is always 0 for replaced elements, so don't bother wasting time in that case. 795 vpos -= (lineHeight(firstLine) - baselinePosition(firstLine)); 796 } else if (va == BASELINE_MIDDLE) 797 vpos += -lineHeight(firstLine) / 2 + baselinePosition(firstLine); 798 else if (va == LENGTH) 799 vpos -= style()->verticalAlignLength().calcValue(lineHeight(firstLine)); 800 } 801 802 return vpos; 803 } 804 805 bool RenderBoxModelObject::paintNinePieceImage(GraphicsContext* graphicsContext, int tx, int ty, int w, int h, const RenderStyle* style, 806 const NinePieceImage& ninePieceImage, CompositeOperator op) 807 { 808 StyleImage* styleImage = ninePieceImage.image(); 809 if (!styleImage) 810 return false; 811 812 if (!styleImage->isLoaded()) 813 return true; // Never paint a nine-piece image incrementally, but don't paint the fallback borders either. 814 815 if (!styleImage->canRender(style->effectiveZoom())) 816 return false; 817 818 // FIXME: border-image is broken with full page zooming when tiling has to happen, since the tiling function 819 // doesn't have any understanding of the zoom that is in effect on the tile. 820 styleImage->setImageContainerSize(IntSize(w, h)); 821 IntSize imageSize = styleImage->imageSize(this, 1.0f); 822 int imageWidth = imageSize.width(); 823 int imageHeight = imageSize.height(); 824 825 int topSlice = min(imageHeight, ninePieceImage.m_slices.top().calcValue(imageHeight)); 826 int bottomSlice = min(imageHeight, ninePieceImage.m_slices.bottom().calcValue(imageHeight)); 827 int leftSlice = min(imageWidth, ninePieceImage.m_slices.left().calcValue(imageWidth)); 828 int rightSlice = min(imageWidth, ninePieceImage.m_slices.right().calcValue(imageWidth)); 829 830 ENinePieceImageRule hRule = ninePieceImage.horizontalRule(); 831 ENinePieceImageRule vRule = ninePieceImage.verticalRule(); 832 833 bool fitToBorder = style->borderImage() == ninePieceImage; 834 835 int leftWidth = fitToBorder ? style->borderLeftWidth() : leftSlice; 836 int topWidth = fitToBorder ? style->borderTopWidth() : topSlice; 837 int rightWidth = fitToBorder ? style->borderRightWidth() : rightSlice; 838 int bottomWidth = fitToBorder ? style->borderBottomWidth() : bottomSlice; 839 840 bool drawLeft = leftSlice > 0 && leftWidth > 0; 841 bool drawTop = topSlice > 0 && topWidth > 0; 842 bool drawRight = rightSlice > 0 && rightWidth > 0; 843 bool drawBottom = bottomSlice > 0 && bottomWidth > 0; 844 bool drawMiddle = (imageWidth - leftSlice - rightSlice) > 0 && (w - leftWidth - rightWidth) > 0 && 845 (imageHeight - topSlice - bottomSlice) > 0 && (h - topWidth - bottomWidth) > 0; 846 847 Image* image = styleImage->image(this, imageSize); 848 ColorSpace colorSpace = style->colorSpace(); 849 850 if (drawLeft) { 851 // Paint the top and bottom left corners. 852 853 // The top left corner rect is (tx, ty, leftWidth, topWidth) 854 // The rect to use from within the image is obtained from our slice, and is (0, 0, leftSlice, topSlice) 855 if (drawTop) 856 graphicsContext->drawImage(image, colorSpace, IntRect(tx, ty, leftWidth, topWidth), 857 IntRect(0, 0, leftSlice, topSlice), op); 858 859 // The bottom left corner rect is (tx, ty + h - bottomWidth, leftWidth, bottomWidth) 860 // The rect to use from within the image is (0, imageHeight - bottomSlice, leftSlice, botomSlice) 861 if (drawBottom) 862 graphicsContext->drawImage(image, colorSpace, IntRect(tx, ty + h - bottomWidth, leftWidth, bottomWidth), 863 IntRect(0, imageHeight - bottomSlice, leftSlice, bottomSlice), op); 864 865 // Paint the left edge. 866 // Have to scale and tile into the border rect. 867 graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx, ty + topWidth, leftWidth, 868 h - topWidth - bottomWidth), 869 IntRect(0, topSlice, leftSlice, imageHeight - topSlice - bottomSlice), 870 Image::StretchTile, (Image::TileRule)vRule, op); 871 } 872 873 if (drawRight) { 874 // Paint the top and bottom right corners 875 // The top right corner rect is (tx + w - rightWidth, ty, rightWidth, topWidth) 876 // The rect to use from within the image is obtained from our slice, and is (imageWidth - rightSlice, 0, rightSlice, topSlice) 877 if (drawTop) 878 graphicsContext->drawImage(image, colorSpace, IntRect(tx + w - rightWidth, ty, rightWidth, topWidth), 879 IntRect(imageWidth - rightSlice, 0, rightSlice, topSlice), op); 880 881 // The bottom right corner rect is (tx + w - rightWidth, ty + h - bottomWidth, rightWidth, bottomWidth) 882 // The rect to use from within the image is (imageWidth - rightSlice, imageHeight - bottomSlice, rightSlice, bottomSlice) 883 if (drawBottom) 884 graphicsContext->drawImage(image, colorSpace, IntRect(tx + w - rightWidth, ty + h - bottomWidth, rightWidth, bottomWidth), 885 IntRect(imageWidth - rightSlice, imageHeight - bottomSlice, rightSlice, bottomSlice), op); 886 887 // Paint the right edge. 888 graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx + w - rightWidth, ty + topWidth, rightWidth, 889 h - topWidth - bottomWidth), 890 IntRect(imageWidth - rightSlice, topSlice, rightSlice, imageHeight - topSlice - bottomSlice), 891 Image::StretchTile, (Image::TileRule)vRule, op); 892 } 893 894 // Paint the top edge. 895 if (drawTop) 896 graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx + leftWidth, ty, w - leftWidth - rightWidth, topWidth), 897 IntRect(leftSlice, 0, imageWidth - rightSlice - leftSlice, topSlice), 898 (Image::TileRule)hRule, Image::StretchTile, op); 899 900 // Paint the bottom edge. 901 if (drawBottom) 902 graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx + leftWidth, ty + h - bottomWidth, 903 w - leftWidth - rightWidth, bottomWidth), 904 IntRect(leftSlice, imageHeight - bottomSlice, imageWidth - rightSlice - leftSlice, bottomSlice), 905 (Image::TileRule)hRule, Image::StretchTile, op); 906 907 // Paint the middle. 908 if (drawMiddle) 909 graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx + leftWidth, ty + topWidth, w - leftWidth - rightWidth, 910 h - topWidth - bottomWidth), 911 IntRect(leftSlice, topSlice, imageWidth - rightSlice - leftSlice, imageHeight - topSlice - bottomSlice), 912 (Image::TileRule)hRule, (Image::TileRule)vRule, op); 913 914 return true; 915 } 916 917 void RenderBoxModelObject::paintBorder(GraphicsContext* graphicsContext, int tx, int ty, int w, int h, 918 const RenderStyle* style, bool begin, bool end) 919 { 920 if (paintNinePieceImage(graphicsContext, tx, ty, w, h, style, style->borderImage())) 921 return; 922 923 const Color& topColor = style->borderTopColor(); 924 const Color& bottomColor = style->borderBottomColor(); 925 const Color& leftColor = style->borderLeftColor(); 926 const Color& rightColor = style->borderRightColor(); 927 928 bool topTransparent = style->borderTopIsTransparent(); 929 bool bottomTransparent = style->borderBottomIsTransparent(); 930 bool rightTransparent = style->borderRightIsTransparent(); 931 bool leftTransparent = style->borderLeftIsTransparent(); 932 933 EBorderStyle topStyle = style->borderTopStyle(); 934 EBorderStyle bottomStyle = style->borderBottomStyle(); 935 EBorderStyle leftStyle = style->borderLeftStyle(); 936 EBorderStyle rightStyle = style->borderRightStyle(); 937 938 bool renderTop = topStyle > BHIDDEN && !topTransparent; 939 bool renderLeft = leftStyle > BHIDDEN && begin && !leftTransparent; 940 bool renderRight = rightStyle > BHIDDEN && end && !rightTransparent; 941 bool renderBottom = bottomStyle > BHIDDEN && !bottomTransparent; 942 943 bool renderRadii = false; 944 IntSize topLeft, topRight, bottomLeft, bottomRight; 945 946 if (style->hasBorderRadius()) { 947 IntRect borderRect = IntRect(tx, ty, w, h); 948 949 IntSize topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius; 950 style->getBorderRadiiForRect(borderRect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); 951 952 if (begin) { 953 topLeft = topLeftRadius; 954 bottomLeft = bottomLeftRadius; 955 } 956 if (end) { 957 topRight = topRightRadius; 958 bottomRight = bottomRightRadius; 959 } 960 961 renderRadii = true; 962 963 // Clip to the rounded rectangle. 964 graphicsContext->save(); 965 graphicsContext->addRoundedRectClip(borderRect, topLeft, topRight, bottomLeft, bottomRight); 966 } 967 968 int firstAngleStart, secondAngleStart, firstAngleSpan, secondAngleSpan; 969 float thickness; 970 bool upperLeftBorderStylesMatch = renderLeft && (topStyle == leftStyle) && (topColor == leftColor); 971 bool upperRightBorderStylesMatch = renderRight && (topStyle == rightStyle) && (topColor == rightColor) && (topStyle != OUTSET) && (topStyle != RIDGE) && (topStyle != INSET) && (topStyle != GROOVE); 972 bool lowerLeftBorderStylesMatch = renderLeft && (bottomStyle == leftStyle) && (bottomColor == leftColor) && (bottomStyle != OUTSET) && (bottomStyle != RIDGE) && (bottomStyle != INSET) && (bottomStyle != GROOVE); 973 bool lowerRightBorderStylesMatch = renderRight && (bottomStyle == rightStyle) && (bottomColor == rightColor); 974 975 if (renderTop) { 976 bool ignore_left = (renderRadii && topLeft.width() > 0) || 977 (topColor == leftColor && topTransparent == leftTransparent && topStyle >= OUTSET && 978 (leftStyle == DOTTED || leftStyle == DASHED || leftStyle == SOLID || leftStyle == OUTSET)); 979 980 bool ignore_right = (renderRadii && topRight.width() > 0) || 981 (topColor == rightColor && topTransparent == rightTransparent && topStyle >= OUTSET && 982 (rightStyle == DOTTED || rightStyle == DASHED || rightStyle == SOLID || rightStyle == INSET)); 983 984 int x = tx; 985 int x2 = tx + w; 986 if (renderRadii) { 987 x += topLeft.width(); 988 x2 -= topRight.width(); 989 } 990 991 drawLineForBoxSide(graphicsContext, x, ty, x2, ty + style->borderTopWidth(), BSTop, topColor, style->color(), topStyle, 992 ignore_left ? 0 : style->borderLeftWidth(), ignore_right ? 0 : style->borderRightWidth()); 993 994 if (renderRadii) { 995 int leftY = ty; 996 997 // We make the arc double thick and let the clip rect take care of clipping the extra off. 998 // We're doing this because it doesn't seem possible to match the curve of the clip exactly 999 // with the arc-drawing function. 1000 thickness = style->borderTopWidth() * 2; 1001 1002 if (topLeft.width()) { 1003 int leftX = tx; 1004 // The inner clip clips inside the arc. This is especially important for 1px borders. 1005 bool applyLeftInnerClip = (style->borderLeftWidth() < topLeft.width()) 1006 && (style->borderTopWidth() < topLeft.height()) 1007 && (topStyle != DOUBLE || style->borderTopWidth() > 6); 1008 if (applyLeftInnerClip) { 1009 graphicsContext->save(); 1010 graphicsContext->addInnerRoundedRectClip(IntRect(leftX, leftY, topLeft.width() * 2, topLeft.height() * 2), 1011 style->borderTopWidth()); 1012 } 1013 1014 firstAngleStart = 90; 1015 firstAngleSpan = upperLeftBorderStylesMatch ? 90 : 45; 1016 1017 // Draw upper left arc 1018 drawArcForBoxSide(graphicsContext, leftX, leftY, thickness, topLeft, firstAngleStart, firstAngleSpan, 1019 BSTop, topColor, style->color(), topStyle, true); 1020 if (applyLeftInnerClip) 1021 graphicsContext->restore(); 1022 } 1023 1024 if (topRight.width()) { 1025 int rightX = tx + w - topRight.width() * 2; 1026 bool applyRightInnerClip = (style->borderRightWidth() < topRight.width()) 1027 && (style->borderTopWidth() < topRight.height()) 1028 && (topStyle != DOUBLE || style->borderTopWidth() > 6); 1029 if (applyRightInnerClip) { 1030 graphicsContext->save(); 1031 graphicsContext->addInnerRoundedRectClip(IntRect(rightX, leftY, topRight.width() * 2, topRight.height() * 2), 1032 style->borderTopWidth()); 1033 } 1034 1035 if (upperRightBorderStylesMatch) { 1036 secondAngleStart = 0; 1037 secondAngleSpan = 90; 1038 } else { 1039 secondAngleStart = 45; 1040 secondAngleSpan = 45; 1041 } 1042 1043 // Draw upper right arc 1044 drawArcForBoxSide(graphicsContext, rightX, leftY, thickness, topRight, secondAngleStart, secondAngleSpan, 1045 BSTop, topColor, style->color(), topStyle, false); 1046 if (applyRightInnerClip) 1047 graphicsContext->restore(); 1048 } 1049 } 1050 } 1051 1052 if (renderBottom) { 1053 bool ignore_left = (renderRadii && bottomLeft.width() > 0) || 1054 (bottomColor == leftColor && bottomTransparent == leftTransparent && bottomStyle >= OUTSET && 1055 (leftStyle == DOTTED || leftStyle == DASHED || leftStyle == SOLID || leftStyle == OUTSET)); 1056 1057 bool ignore_right = (renderRadii && bottomRight.width() > 0) || 1058 (bottomColor == rightColor && bottomTransparent == rightTransparent && bottomStyle >= OUTSET && 1059 (rightStyle == DOTTED || rightStyle == DASHED || rightStyle == SOLID || rightStyle == INSET)); 1060 1061 int x = tx; 1062 int x2 = tx + w; 1063 if (renderRadii) { 1064 x += bottomLeft.width(); 1065 x2 -= bottomRight.width(); 1066 } 1067 1068 drawLineForBoxSide(graphicsContext, x, ty + h - style->borderBottomWidth(), x2, ty + h, BSBottom, bottomColor, style->color(), bottomStyle, 1069 ignore_left ? 0 : style->borderLeftWidth(), ignore_right ? 0 : style->borderRightWidth()); 1070 1071 if (renderRadii) { 1072 thickness = style->borderBottomWidth() * 2; 1073 1074 if (bottomLeft.width()) { 1075 int leftX = tx; 1076 int leftY = ty + h - bottomLeft.height() * 2; 1077 bool applyLeftInnerClip = (style->borderLeftWidth() < bottomLeft.width()) 1078 && (style->borderBottomWidth() < bottomLeft.height()) 1079 && (bottomStyle != DOUBLE || style->borderBottomWidth() > 6); 1080 if (applyLeftInnerClip) { 1081 graphicsContext->save(); 1082 graphicsContext->addInnerRoundedRectClip(IntRect(leftX, leftY, bottomLeft.width() * 2, bottomLeft.height() * 2), 1083 style->borderBottomWidth()); 1084 } 1085 1086 if (lowerLeftBorderStylesMatch) { 1087 firstAngleStart = 180; 1088 firstAngleSpan = 90; 1089 } else { 1090 firstAngleStart = 225; 1091 firstAngleSpan = 45; 1092 } 1093 1094 // Draw lower left arc 1095 drawArcForBoxSide(graphicsContext, leftX, leftY, thickness, bottomLeft, firstAngleStart, firstAngleSpan, 1096 BSBottom, bottomColor, style->color(), bottomStyle, true); 1097 if (applyLeftInnerClip) 1098 graphicsContext->restore(); 1099 } 1100 1101 if (bottomRight.width()) { 1102 int rightY = ty + h - bottomRight.height() * 2; 1103 int rightX = tx + w - bottomRight.width() * 2; 1104 bool applyRightInnerClip = (style->borderRightWidth() < bottomRight.width()) 1105 && (style->borderBottomWidth() < bottomRight.height()) 1106 && (bottomStyle != DOUBLE || style->borderBottomWidth() > 6); 1107 if (applyRightInnerClip) { 1108 graphicsContext->save(); 1109 graphicsContext->addInnerRoundedRectClip(IntRect(rightX, rightY, bottomRight.width() * 2, bottomRight.height() * 2), 1110 style->borderBottomWidth()); 1111 } 1112 1113 secondAngleStart = 270; 1114 secondAngleSpan = lowerRightBorderStylesMatch ? 90 : 45; 1115 1116 // Draw lower right arc 1117 drawArcForBoxSide(graphicsContext, rightX, rightY, thickness, bottomRight, secondAngleStart, secondAngleSpan, 1118 BSBottom, bottomColor, style->color(), bottomStyle, false); 1119 if (applyRightInnerClip) 1120 graphicsContext->restore(); 1121 } 1122 } 1123 } 1124 1125 if (renderLeft) { 1126 bool ignore_top = (renderRadii && topLeft.height() > 0) || 1127 (topColor == leftColor && topTransparent == leftTransparent && leftStyle >= OUTSET && 1128 (topStyle == DOTTED || topStyle == DASHED || topStyle == SOLID || topStyle == OUTSET)); 1129 1130 bool ignore_bottom = (renderRadii && bottomLeft.height() > 0) || 1131 (bottomColor == leftColor && bottomTransparent == leftTransparent && leftStyle >= OUTSET && 1132 (bottomStyle == DOTTED || bottomStyle == DASHED || bottomStyle == SOLID || bottomStyle == INSET)); 1133 1134 int y = ty; 1135 int y2 = ty + h; 1136 if (renderRadii) { 1137 y += topLeft.height(); 1138 y2 -= bottomLeft.height(); 1139 } 1140 1141 drawLineForBoxSide(graphicsContext, tx, y, tx + style->borderLeftWidth(), y2, BSLeft, leftColor, style->color(), leftStyle, 1142 ignore_top ? 0 : style->borderTopWidth(), ignore_bottom ? 0 : style->borderBottomWidth()); 1143 1144 if (renderRadii && (!upperLeftBorderStylesMatch || !lowerLeftBorderStylesMatch)) { 1145 int topX = tx; 1146 thickness = style->borderLeftWidth() * 2; 1147 1148 if (!upperLeftBorderStylesMatch && topLeft.width()) { 1149 int topY = ty; 1150 bool applyTopInnerClip = (style->borderLeftWidth() < topLeft.width()) 1151 && (style->borderTopWidth() < topLeft.height()) 1152 && (leftStyle != DOUBLE || style->borderLeftWidth() > 6); 1153 if (applyTopInnerClip) { 1154 graphicsContext->save(); 1155 graphicsContext->addInnerRoundedRectClip(IntRect(topX, topY, topLeft.width() * 2, topLeft.height() * 2), 1156 style->borderLeftWidth()); 1157 } 1158 1159 firstAngleStart = 135; 1160 firstAngleSpan = 45; 1161 1162 // Draw top left arc 1163 drawArcForBoxSide(graphicsContext, topX, topY, thickness, topLeft, firstAngleStart, firstAngleSpan, 1164 BSLeft, leftColor, style->color(), leftStyle, true); 1165 if (applyTopInnerClip) 1166 graphicsContext->restore(); 1167 } 1168 1169 if (!lowerLeftBorderStylesMatch && bottomLeft.width()) { 1170 int bottomY = ty + h - bottomLeft.height() * 2; 1171 bool applyBottomInnerClip = (style->borderLeftWidth() < bottomLeft.width()) 1172 && (style->borderBottomWidth() < bottomLeft.height()) 1173 && (leftStyle != DOUBLE || style->borderLeftWidth() > 6); 1174 if (applyBottomInnerClip) { 1175 graphicsContext->save(); 1176 graphicsContext->addInnerRoundedRectClip(IntRect(topX, bottomY, bottomLeft.width() * 2, bottomLeft.height() * 2), 1177 style->borderLeftWidth()); 1178 } 1179 1180 secondAngleStart = 180; 1181 secondAngleSpan = 45; 1182 1183 // Draw bottom left arc 1184 drawArcForBoxSide(graphicsContext, topX, bottomY, thickness, bottomLeft, secondAngleStart, secondAngleSpan, 1185 BSLeft, leftColor, style->color(), leftStyle, false); 1186 if (applyBottomInnerClip) 1187 graphicsContext->restore(); 1188 } 1189 } 1190 } 1191 1192 if (renderRight) { 1193 bool ignore_top = (renderRadii && topRight.height() > 0) || 1194 ((topColor == rightColor) && (topTransparent == rightTransparent) && 1195 (rightStyle >= DOTTED || rightStyle == INSET) && 1196 (topStyle == DOTTED || topStyle == DASHED || topStyle == SOLID || topStyle == OUTSET)); 1197 1198 bool ignore_bottom = (renderRadii && bottomRight.height() > 0) || 1199 ((bottomColor == rightColor) && (bottomTransparent == rightTransparent) && 1200 (rightStyle >= DOTTED || rightStyle == INSET) && 1201 (bottomStyle == DOTTED || bottomStyle == DASHED || bottomStyle == SOLID || bottomStyle == INSET)); 1202 1203 int y = ty; 1204 int y2 = ty + h; 1205 if (renderRadii) { 1206 y += topRight.height(); 1207 y2 -= bottomRight.height(); 1208 } 1209 1210 drawLineForBoxSide(graphicsContext, tx + w - style->borderRightWidth(), y, tx + w, y2, BSRight, rightColor, style->color(), rightStyle, 1211 ignore_top ? 0 : style->borderTopWidth(), ignore_bottom ? 0 : style->borderBottomWidth()); 1212 1213 if (renderRadii && (!upperRightBorderStylesMatch || !lowerRightBorderStylesMatch)) { 1214 thickness = style->borderRightWidth() * 2; 1215 1216 if (!upperRightBorderStylesMatch && topRight.width()) { 1217 int topX = tx + w - topRight.width() * 2; 1218 int topY = ty; 1219 bool applyTopInnerClip = (style->borderRightWidth() < topRight.width()) 1220 && (style->borderTopWidth() < topRight.height()) 1221 && (rightStyle != DOUBLE || style->borderRightWidth() > 6); 1222 if (applyTopInnerClip) { 1223 graphicsContext->save(); 1224 graphicsContext->addInnerRoundedRectClip(IntRect(topX, topY, topRight.width() * 2, topRight.height() * 2), 1225 style->borderRightWidth()); 1226 } 1227 1228 firstAngleStart = 0; 1229 firstAngleSpan = 45; 1230 1231 // Draw top right arc 1232 drawArcForBoxSide(graphicsContext, topX, topY, thickness, topRight, firstAngleStart, firstAngleSpan, 1233 BSRight, rightColor, style->color(), rightStyle, true); 1234 if (applyTopInnerClip) 1235 graphicsContext->restore(); 1236 } 1237 1238 if (!lowerRightBorderStylesMatch && bottomRight.width()) { 1239 int bottomX = tx + w - bottomRight.width() * 2; 1240 int bottomY = ty + h - bottomRight.height() * 2; 1241 bool applyBottomInnerClip = (style->borderRightWidth() < bottomRight.width()) 1242 && (style->borderBottomWidth() < bottomRight.height()) 1243 && (rightStyle != DOUBLE || style->borderRightWidth() > 6); 1244 if (applyBottomInnerClip) { 1245 graphicsContext->save(); 1246 graphicsContext->addInnerRoundedRectClip(IntRect(bottomX, bottomY, bottomRight.width() * 2, bottomRight.height() * 2), 1247 style->borderRightWidth()); 1248 } 1249 1250 secondAngleStart = 315; 1251 secondAngleSpan = 45; 1252 1253 // Draw bottom right arc 1254 drawArcForBoxSide(graphicsContext, bottomX, bottomY, thickness, bottomRight, secondAngleStart, secondAngleSpan, 1255 BSRight, rightColor, style->color(), rightStyle, false); 1256 if (applyBottomInnerClip) 1257 graphicsContext->restore(); 1258 } 1259 } 1260 } 1261 1262 if (renderRadii) 1263 graphicsContext->restore(); 1264 } 1265 1266 void RenderBoxModelObject::paintBoxShadow(GraphicsContext* context, int tx, int ty, int w, int h, const RenderStyle* s, ShadowStyle shadowStyle, bool begin, bool end) 1267 { 1268 // FIXME: Deal with border-image. Would be great to use border-image as a mask. 1269 1270 if (context->paintingDisabled()) 1271 return; 1272 1273 IntRect rect(tx, ty, w, h); 1274 IntSize topLeft; 1275 IntSize topRight; 1276 IntSize bottomLeft; 1277 IntSize bottomRight; 1278 1279 bool hasBorderRadius = s->hasBorderRadius(); 1280 if (hasBorderRadius && (begin || end)) { 1281 IntSize topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius; 1282 s->getBorderRadiiForRect(rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); 1283 1284 if (begin) { 1285 if (shadowStyle == Inset) { 1286 topLeftRadius.expand(-borderLeft(), -borderTop()); 1287 topLeftRadius.clampNegativeToZero(); 1288 bottomLeftRadius.expand(-borderLeft(), -borderBottom()); 1289 bottomLeftRadius.clampNegativeToZero(); 1290 } 1291 topLeft = topLeftRadius; 1292 bottomLeft = bottomLeftRadius; 1293 } 1294 if (end) { 1295 if (shadowStyle == Inset) { 1296 topRightRadius.expand(-borderRight(), -borderTop()); 1297 topRightRadius.clampNegativeToZero(); 1298 bottomRightRadius.expand(-borderRight(), -borderBottom()); 1299 bottomRightRadius.clampNegativeToZero(); 1300 } 1301 topRight = topRightRadius; 1302 bottomRight = bottomRightRadius; 1303 } 1304 } 1305 1306 if (shadowStyle == Inset) { 1307 rect.move(begin ? borderLeft() : 0, borderTop()); 1308 rect.setWidth(rect.width() - (begin ? borderLeft() : 0) - (end ? borderRight() : 0)); 1309 rect.setHeight(rect.height() - borderTop() - borderBottom()); 1310 } 1311 1312 bool hasOpaqueBackground = s->backgroundColor().isValid() && s->backgroundColor().alpha() == 255; 1313 for (ShadowData* shadow = s->boxShadow(); shadow; shadow = shadow->next) { 1314 if (shadow->style != shadowStyle) 1315 continue; 1316 1317 IntSize shadowOffset(shadow->x, shadow->y); 1318 int shadowBlur = shadow->blur; 1319 int shadowSpread = shadow->spread; 1320 Color& shadowColor = shadow->color; 1321 1322 if (shadow->style == Normal) { 1323 IntRect fillRect(rect); 1324 fillRect.inflate(shadowSpread); 1325 if (fillRect.isEmpty()) 1326 continue; 1327 1328 IntRect shadowRect(rect); 1329 shadowRect.inflate(shadowBlur + shadowSpread); 1330 shadowRect.move(shadowOffset); 1331 1332 context->save(); 1333 context->clip(shadowRect); 1334 1335 // Move the fill just outside the clip, adding 1 pixel separation so that the fill does not 1336 // bleed in (due to antialiasing) if the context is transformed. 1337 IntSize extraOffset(w + max(0, shadowOffset.width()) + shadowBlur + 2 * shadowSpread + 1, 0); 1338 shadowOffset -= extraOffset; 1339 fillRect.move(extraOffset); 1340 1341 context->setShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace()); 1342 if (hasBorderRadius) { 1343 IntRect rectToClipOut = rect; 1344 IntSize topLeftToClipOut = topLeft; 1345 IntSize topRightToClipOut = topRight; 1346 IntSize bottomLeftToClipOut = bottomLeft; 1347 IntSize bottomRightToClipOut = bottomRight; 1348 1349 if (shadowSpread < 0) { 1350 topLeft.expand(shadowSpread, shadowSpread); 1351 topLeft.clampNegativeToZero(); 1352 1353 topRight.expand(shadowSpread, shadowSpread); 1354 topRight.clampNegativeToZero(); 1355 1356 bottomLeft.expand(shadowSpread, shadowSpread); 1357 bottomLeft.clampNegativeToZero(); 1358 1359 bottomRight.expand(shadowSpread, shadowSpread); 1360 bottomRight.clampNegativeToZero(); 1361 } 1362 1363 // If the box is opaque, it is unnecessary to clip it out. However, doing so saves time 1364 // when painting the shadow. On the other hand, it introduces subpixel gaps along the 1365 // corners. Those are avoided by insetting the clipping path by one pixel. 1366 if (hasOpaqueBackground) { 1367 rectToClipOut.inflate(-1); 1368 1369 topLeftToClipOut.expand(-1, -1); 1370 topLeftToClipOut.clampNegativeToZero(); 1371 1372 topRightToClipOut.expand(-1, -1); 1373 topRightToClipOut.clampNegativeToZero(); 1374 1375 bottomLeftToClipOut.expand(-1, -1); 1376 bottomLeftToClipOut.clampNegativeToZero(); 1377 1378 bottomRightToClipOut.expand(-1, -1); 1379 bottomRightToClipOut.clampNegativeToZero(); 1380 } 1381 1382 if (!rectToClipOut.isEmpty()) 1383 context->clipOutRoundedRect(rectToClipOut, topLeftToClipOut, topRightToClipOut, bottomLeftToClipOut, bottomRightToClipOut); 1384 context->fillRoundedRect(fillRect, topLeft, topRight, bottomLeft, bottomRight, Color::black, s->colorSpace()); 1385 } else { 1386 IntRect rectToClipOut = rect; 1387 1388 // If the box is opaque, it is unnecessary to clip it out. However, doing so saves time 1389 // when painting the shadow. On the other hand, it introduces subpixel gaps along the 1390 // edges if they are not pixel-aligned. Those are avoided by insetting the clipping path 1391 // by one pixel. 1392 if (hasOpaqueBackground) { 1393 AffineTransform currentTransformation = context->getCTM(); 1394 if (currentTransformation.a() != 1 || (currentTransformation.d() != 1 && currentTransformation.d() != -1) 1395 || currentTransformation.b() || currentTransformation.c()) 1396 rectToClipOut.inflate(-1); 1397 } 1398 1399 if (!rectToClipOut.isEmpty()) 1400 context->clipOut(rectToClipOut); 1401 context->fillRect(fillRect, Color::black, s->colorSpace()); 1402 } 1403 1404 context->restore(); 1405 } else { 1406 // Inset shadow. 1407 IntRect holeRect(rect); 1408 holeRect.inflate(-shadowSpread); 1409 1410 if (holeRect.isEmpty()) { 1411 if (hasBorderRadius) 1412 context->fillRoundedRect(rect, topLeft, topRight, bottomLeft, bottomRight, shadowColor, s->colorSpace()); 1413 else 1414 context->fillRect(rect, shadowColor, s->colorSpace()); 1415 continue; 1416 } 1417 if (!begin) { 1418 holeRect.move(-max(shadowOffset.width(), 0) - shadowBlur, 0); 1419 holeRect.setWidth(holeRect.width() + max(shadowOffset.width(), 0) + shadowBlur); 1420 } 1421 if (!end) 1422 holeRect.setWidth(holeRect.width() - min(shadowOffset.width(), 0) + shadowBlur); 1423 1424 Color fillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), 255); 1425 1426 IntRect outerRect(rect); 1427 outerRect.inflateX(w - 2 * shadowSpread); 1428 outerRect.inflateY(h - 2 * shadowSpread); 1429 1430 context->save(); 1431 1432 if (hasBorderRadius) 1433 context->clip(Path::createRoundedRectangle(rect, topLeft, topRight, bottomLeft, bottomRight)); 1434 else 1435 context->clip(rect); 1436 1437 IntSize extraOffset(2 * w + max(0, shadowOffset.width()) + shadowBlur - 2 * shadowSpread + 1, 0); 1438 context->translate(extraOffset.width(), extraOffset.height()); 1439 shadowOffset -= extraOffset; 1440 1441 context->beginPath(); 1442 context->addPath(Path::createRectangle(outerRect)); 1443 1444 if (hasBorderRadius) { 1445 if (shadowSpread > 0) { 1446 topLeft.expand(-shadowSpread, -shadowSpread); 1447 topLeft.clampNegativeToZero(); 1448 1449 topRight.expand(-shadowSpread, -shadowSpread); 1450 topRight.clampNegativeToZero(); 1451 1452 bottomLeft.expand(-shadowSpread, -shadowSpread); 1453 bottomLeft.clampNegativeToZero(); 1454 1455 bottomRight.expand(-shadowSpread, -shadowSpread); 1456 bottomRight.clampNegativeToZero(); 1457 } 1458 context->addPath(Path::createRoundedRectangle(holeRect, topLeft, topRight, bottomLeft, bottomRight)); 1459 } else 1460 context->addPath(Path::createRectangle(holeRect)); 1461 1462 context->setFillRule(RULE_EVENODD); 1463 context->setFillColor(fillColor, s->colorSpace()); 1464 context->setShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace()); 1465 context->fillPath(); 1466 1467 context->restore(); 1468 } 1469 } 1470 } 1471 1472 int RenderBoxModelObject::containingBlockWidthForContent() const 1473 { 1474 return containingBlock()->availableWidth(); 1475 } 1476 1477 } // namespace WebCore 1478