nsCSSRendering.cpp (196431B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* utility functions for drawing borders and backgrounds */ 8 9 #include "nsCSSRendering.h" 10 11 #include <algorithm> 12 #include <ctime> 13 14 #include "BorderConsts.h" 15 #include "ImageContainer.h" 16 #include "ImageOps.h" 17 #include "ScaledFontBase.h" 18 #include "TextDrawTarget.h" 19 #include "gfx2DGlue.h" 20 #include "gfxContext.h" 21 #include "gfxDrawable.h" 22 #include "gfxFont.h" 23 #include "gfxGradientCache.h" 24 #include "gfxUtils.h" 25 #include "imgIContainer.h" 26 #include "mozilla/ComputedStyle.h" 27 #include "mozilla/PresShell.h" 28 #include "mozilla/ProfilerLabels.h" 29 #include "mozilla/SVGImageContext.h" 30 #include "mozilla/ScrollContainerFrame.h" 31 #include "mozilla/StaticPtr.h" 32 #include "mozilla/css/ImageLoader.h" 33 #include "mozilla/dom/DocumentInlines.h" 34 #include "mozilla/gfx/2D.h" 35 #include "mozilla/gfx/Helpers.h" 36 #include "mozilla/gfx/Logging.h" 37 #include "mozilla/gfx/PathHelpers.h" 38 #include "nsBlockFrame.h" 39 #include "nsCSSAnonBoxes.h" 40 #include "nsCSSColorUtils.h" 41 #include "nsCSSFrameConstructor.h" 42 #include "nsCSSProps.h" 43 #include "nsCSSRenderingBorders.h" 44 #include "nsCanvasFrame.h" 45 #include "nsContentUtils.h" 46 #include "nsFrameManager.h" 47 #include "nsGkAtoms.h" 48 #include "nsIContent.h" 49 #include "nsIFrame.h" 50 #include "nsIFrameInlines.h" 51 #include "nsITheme.h" 52 #include "nsInlineFrame.h" 53 #include "nsLayoutUtils.h" 54 #include "nsPageSequenceFrame.h" 55 #include "nsPoint.h" 56 #include "nsPresContext.h" 57 #include "nsRect.h" 58 #include "nsRubyTextContainerFrame.h" 59 #include "nsStyleConsts.h" 60 #include "nsStyleStructInlines.h" 61 #include "skia/include/core/SkTextBlob.h" 62 63 using namespace mozilla; 64 using namespace mozilla::css; 65 using namespace mozilla::gfx; 66 using namespace mozilla::image; 67 using mozilla::CSSSizeOrRatio; 68 using mozilla::dom::Document; 69 70 static int gFrameTreeLockCount = 0; 71 72 // To avoid storing this data on nsInlineFrame (bloat) and to avoid 73 // recalculating this for each frame in a continuation (perf), hold 74 // a cache of various coordinate information that we need in order 75 // to paint inline backgrounds. 76 struct InlineBackgroundData { 77 InlineBackgroundData() 78 : mFrame(nullptr), 79 mLineContainer(nullptr), 80 mContinuationPoint(0), 81 mUnbrokenMeasure(0), 82 mLineContinuationPoint(0), 83 mPIStartBorderData{}, 84 mBidiEnabled(false), 85 mVertical(false) {} 86 87 ~InlineBackgroundData() = default; 88 89 void Reset() { 90 mBoundingBox.SetRect(0, 0, 0, 0); 91 mContinuationPoint = mLineContinuationPoint = mUnbrokenMeasure = 0; 92 mFrame = mLineContainer = nullptr; 93 mPIStartBorderData.Reset(); 94 } 95 96 /** 97 * Return a continuous rect for (an inline) aFrame relative to the 98 * continuation that draws the left-most part of the background. 99 * This is used when painting backgrounds. 100 */ 101 nsRect GetContinuousRect(nsIFrame* aFrame) { 102 MOZ_ASSERT(static_cast<nsInlineFrame*>(do_QueryFrame(aFrame))); 103 104 SetFrame(aFrame); 105 106 nscoord pos; // an x coordinate if writing-mode is horizontal; 107 // y coordinate if vertical 108 if (mBidiEnabled) { 109 pos = mLineContinuationPoint; 110 111 // Scan continuations on the same line as aFrame and accumulate the widths 112 // of frames that are to the left (if this is an LTR block) or right 113 // (if it's RTL) of the current one. 114 bool isRtlBlock = (mLineContainer->StyleVisibility()->mDirection == 115 StyleDirection::Rtl); 116 nscoord curOffset = mVertical ? aFrame->GetOffsetTo(mLineContainer).y 117 : aFrame->GetOffsetTo(mLineContainer).x; 118 119 // If the continuation is fluid we know inlineFrame is not on the same 120 // line. If it's not fluid, we need to test further to be sure. 121 nsIFrame* inlineFrame = aFrame->GetPrevContinuation(); 122 while (inlineFrame && !inlineFrame->GetNextInFlow() && 123 AreOnSameLine(aFrame, inlineFrame)) { 124 nscoord frameOffset = mVertical 125 ? inlineFrame->GetOffsetTo(mLineContainer).y 126 : inlineFrame->GetOffsetTo(mLineContainer).x; 127 if (isRtlBlock == (frameOffset >= curOffset)) { 128 pos += mVertical ? inlineFrame->GetSize().height 129 : inlineFrame->GetSize().width; 130 } 131 inlineFrame = inlineFrame->GetPrevContinuation(); 132 } 133 134 inlineFrame = aFrame->GetNextContinuation(); 135 while (inlineFrame && !inlineFrame->GetPrevInFlow() && 136 AreOnSameLine(aFrame, inlineFrame)) { 137 nscoord frameOffset = mVertical 138 ? inlineFrame->GetOffsetTo(mLineContainer).y 139 : inlineFrame->GetOffsetTo(mLineContainer).x; 140 if (isRtlBlock == (frameOffset >= curOffset)) { 141 pos += mVertical ? inlineFrame->GetSize().height 142 : inlineFrame->GetSize().width; 143 } 144 inlineFrame = inlineFrame->GetNextContinuation(); 145 } 146 if (isRtlBlock) { 147 // aFrame itself is also to the right of its left edge, so add its 148 // width. 149 pos += mVertical ? aFrame->GetSize().height : aFrame->GetSize().width; 150 // pos is now the distance from the left [top] edge of aFrame to the 151 // right [bottom] edge of the unbroken content. Change it to indicate 152 // the distance from the left [top] edge of the unbroken content to the 153 // left [top] edge of aFrame. 154 pos = mUnbrokenMeasure - pos; 155 } 156 } else { 157 pos = mContinuationPoint; 158 } 159 160 // Assume background-origin: border and return a rect with offsets 161 // relative to (0,0). If we have a different background-origin, 162 // then our rect should be deflated appropriately by our caller. 163 return mVertical 164 ? nsRect(0, -pos, mFrame->GetSize().width, mUnbrokenMeasure) 165 : nsRect(-pos, 0, mUnbrokenMeasure, mFrame->GetSize().height); 166 } 167 168 /** 169 * Return a continuous rect for (an inline) aFrame relative to the 170 * continuation that should draw the left[top]-border. This is used when 171 * painting borders and clipping backgrounds. This may NOT be the same 172 * continuous rect as for drawing backgrounds; the continuation with the 173 * left[top]-border might be somewhere in the middle of that rect (e.g. BIDI), 174 * in those cases we need the reverse background order starting at the 175 * left[top]-border continuation. 176 */ 177 nsRect GetBorderContinuousRect(nsIFrame* aFrame, nsRect aBorderArea) { 178 // Calling GetContinuousRect(aFrame) here may lead to Reset/Init which 179 // resets our mPIStartBorderData so we save it ... 180 PhysicalInlineStartBorderData saved(mPIStartBorderData); 181 nsRect joinedBorderArea = GetContinuousRect(aFrame); 182 if (!saved.mIsValid || saved.mFrame != mPIStartBorderData.mFrame) { 183 if (aFrame == mPIStartBorderData.mFrame) { 184 if (mVertical) { 185 mPIStartBorderData.SetCoord(joinedBorderArea.y); 186 } else { 187 mPIStartBorderData.SetCoord(joinedBorderArea.x); 188 } 189 } else if (mPIStartBorderData.mFrame) { 190 // Copy data to a temporary object so that computing the 191 // continous rect here doesn't clobber our normal state. 192 InlineBackgroundData temp = *this; 193 if (mVertical) { 194 mPIStartBorderData.SetCoord( 195 temp.GetContinuousRect(mPIStartBorderData.mFrame).y); 196 } else { 197 mPIStartBorderData.SetCoord( 198 temp.GetContinuousRect(mPIStartBorderData.mFrame).x); 199 } 200 } 201 } else { 202 // ... and restore it when possible. 203 mPIStartBorderData.SetCoord(saved.mCoord); 204 } 205 if (mVertical) { 206 if (joinedBorderArea.y > mPIStartBorderData.mCoord) { 207 joinedBorderArea.y = 208 -(mUnbrokenMeasure + joinedBorderArea.y - aBorderArea.height); 209 } else { 210 joinedBorderArea.y -= mPIStartBorderData.mCoord; 211 } 212 } else { 213 if (joinedBorderArea.x > mPIStartBorderData.mCoord) { 214 joinedBorderArea.x = 215 -(mUnbrokenMeasure + joinedBorderArea.x - aBorderArea.width); 216 } else { 217 joinedBorderArea.x -= mPIStartBorderData.mCoord; 218 } 219 } 220 return joinedBorderArea; 221 } 222 223 nsRect GetBoundingRect(nsIFrame* aFrame) { 224 SetFrame(aFrame); 225 226 // Move the offsets relative to (0,0) which puts the bounding box into 227 // our coordinate system rather than our parent's. We do this by 228 // moving it the back distance from us to the bounding box. 229 // This also assumes background-origin: border, so our caller will 230 // need to deflate us if needed. 231 nsRect boundingBox(mBoundingBox); 232 nsPoint point = mFrame->GetPosition(); 233 boundingBox.MoveBy(-point.x, -point.y); 234 235 return boundingBox; 236 } 237 238 protected: 239 // This is a coordinate on the inline axis, but is not a true logical inline- 240 // coord because it is always measured from left to right (if horizontal) or 241 // from top to bottom (if vertical), ignoring any bidi RTL directionality. 242 // We'll call this "physical inline start", or PIStart for short. 243 struct PhysicalInlineStartBorderData { 244 nsIFrame* mFrame; // the continuation that may have a left-border 245 nscoord mCoord; // cached GetContinuousRect(mFrame).x or .y 246 bool mIsValid; // true if mCoord is valid 247 void Reset() { 248 mFrame = nullptr; 249 mIsValid = false; 250 } 251 void SetCoord(nscoord aCoord) { 252 mCoord = aCoord; 253 mIsValid = true; 254 } 255 }; 256 257 nsIFrame* mFrame; 258 nsIFrame* mLineContainer; 259 nsRect mBoundingBox; 260 nscoord mContinuationPoint; 261 nscoord mUnbrokenMeasure; 262 nscoord mLineContinuationPoint; 263 PhysicalInlineStartBorderData mPIStartBorderData; 264 bool mBidiEnabled; 265 bool mVertical; 266 267 void SetFrame(nsIFrame* aFrame) { 268 MOZ_ASSERT(aFrame, "Need a frame"); 269 NS_ASSERTION(gFrameTreeLockCount > 0, 270 "Can't call this when frame tree is not locked"); 271 272 if (aFrame == mFrame) { 273 return; 274 } 275 276 nsIFrame* prevContinuation = GetPrevContinuation(aFrame); 277 278 if (!prevContinuation || mFrame != prevContinuation) { 279 // Ok, we've got the wrong frame. We have to start from scratch. 280 Reset(); 281 Init(aFrame); 282 return; 283 } 284 285 // Get our last frame's size and add its width to our continuation 286 // point before we cache the new frame. 287 mContinuationPoint += 288 mVertical ? mFrame->GetSize().height : mFrame->GetSize().width; 289 290 // If this a new line, update mLineContinuationPoint. 291 if (mBidiEnabled && 292 (aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) { 293 mLineContinuationPoint = mContinuationPoint; 294 } 295 296 mFrame = aFrame; 297 } 298 299 nsIFrame* GetPrevContinuation(nsIFrame* aFrame) { 300 nsIFrame* prevCont = aFrame->GetPrevContinuation(); 301 if (!prevCont && aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) { 302 nsIFrame* block = aFrame->GetProperty(nsIFrame::IBSplitPrevSibling()); 303 if (block) { 304 // The {ib} properties are only stored on first continuations 305 NS_ASSERTION(!block->GetPrevContinuation(), 306 "Incorrect value for IBSplitPrevSibling"); 307 prevCont = block->GetProperty(nsIFrame::IBSplitPrevSibling()); 308 NS_ASSERTION(prevCont, "How did that happen?"); 309 } 310 } 311 return prevCont; 312 } 313 314 nsIFrame* GetNextContinuation(nsIFrame* aFrame) { 315 nsIFrame* nextCont = aFrame->GetNextContinuation(); 316 if (!nextCont && aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) { 317 // The {ib} properties are only stored on first continuations 318 aFrame = aFrame->FirstContinuation(); 319 nsIFrame* block = aFrame->GetProperty(nsIFrame::IBSplitSibling()); 320 if (block) { 321 nextCont = block->GetProperty(nsIFrame::IBSplitSibling()); 322 NS_ASSERTION(nextCont, "How did that happen?"); 323 } 324 } 325 return nextCont; 326 } 327 328 void Init(nsIFrame* aFrame) { 329 mPIStartBorderData.Reset(); 330 mBidiEnabled = aFrame->PresContext()->BidiEnabled(); 331 if (mBidiEnabled) { 332 // Find the line container frame 333 mLineContainer = aFrame; 334 while (mLineContainer && mLineContainer->IsLineParticipant()) { 335 mLineContainer = mLineContainer->GetParent(); 336 } 337 338 MOZ_ASSERT(mLineContainer, "Cannot find line containing frame."); 339 MOZ_ASSERT(mLineContainer != aFrame, 340 "line container frame " 341 "should be an ancestor of the target frame."); 342 } 343 344 mVertical = aFrame->GetWritingMode().IsVertical(); 345 346 // Start with the previous flow frame as our continuation point 347 // is the total of the widths of the previous frames. 348 nsIFrame* inlineFrame = GetPrevContinuation(aFrame); 349 bool changedLines = false; 350 while (inlineFrame) { 351 if (!mPIStartBorderData.mFrame && 352 !(mVertical ? inlineFrame->GetSkipSides().Top() 353 : inlineFrame->GetSkipSides().Left())) { 354 mPIStartBorderData.mFrame = inlineFrame; 355 } 356 nsRect rect = inlineFrame->GetRect(); 357 mContinuationPoint += mVertical ? rect.height : rect.width; 358 if (mBidiEnabled && 359 (changedLines || !AreOnSameLine(aFrame, inlineFrame))) { 360 mLineContinuationPoint += mVertical ? rect.height : rect.width; 361 changedLines = true; 362 } 363 mUnbrokenMeasure += mVertical ? rect.height : rect.width; 364 mBoundingBox.UnionRect(mBoundingBox, rect); 365 inlineFrame = GetPrevContinuation(inlineFrame); 366 } 367 368 // Next add this frame and subsequent frames to the bounding box and 369 // unbroken width. 370 inlineFrame = aFrame; 371 while (inlineFrame) { 372 if (!mPIStartBorderData.mFrame && 373 !(mVertical ? inlineFrame->GetSkipSides().Top() 374 : inlineFrame->GetSkipSides().Left())) { 375 mPIStartBorderData.mFrame = inlineFrame; 376 } 377 nsRect rect = inlineFrame->GetRect(); 378 mUnbrokenMeasure += mVertical ? rect.height : rect.width; 379 mBoundingBox.UnionRect(mBoundingBox, rect); 380 inlineFrame = GetNextContinuation(inlineFrame); 381 } 382 383 mFrame = aFrame; 384 } 385 386 bool AreOnSameLine(nsIFrame* aFrame1, nsIFrame* aFrame2) { 387 if (nsBlockFrame* blockFrame = do_QueryFrame(mLineContainer)) { 388 bool isValid1, isValid2; 389 nsBlockInFlowLineIterator it1(blockFrame, aFrame1, &isValid1); 390 nsBlockInFlowLineIterator it2(blockFrame, aFrame2, &isValid2); 391 return isValid1 && isValid2 && 392 // Make sure aFrame1 and aFrame2 are in the same continuation of 393 // blockFrame. 394 it1.GetContainer() == it2.GetContainer() && 395 // And on the same line in it 396 it1.GetLine().get() == it2.GetLine().get(); 397 } 398 if (nsRubyTextContainerFrame* rtcFrame = do_QueryFrame(mLineContainer)) { 399 nsBlockFrame* block = nsLayoutUtils::FindNearestBlockAncestor(rtcFrame); 400 // Ruby text container can only hold one line of text, so if they 401 // are in the same continuation, they are in the same line. Since 402 // ruby text containers are bidi isolate, they are never split for 403 // bidi reordering, which means being in different continuation 404 // indicates being in different lines. 405 for (nsIFrame* frame = rtcFrame->FirstContinuation(); frame; 406 frame = frame->GetNextContinuation()) { 407 bool isDescendant1 = 408 nsLayoutUtils::IsProperAncestorFrame(frame, aFrame1, block); 409 bool isDescendant2 = 410 nsLayoutUtils::IsProperAncestorFrame(frame, aFrame2, block); 411 if (isDescendant1 && isDescendant2) { 412 return true; 413 } 414 if (isDescendant1 || isDescendant2) { 415 return false; 416 } 417 } 418 MOZ_ASSERT_UNREACHABLE("None of the frames is a descendant of this rtc?"); 419 } 420 MOZ_ASSERT_UNREACHABLE("Do we have any other type of line container?"); 421 return false; 422 } 423 }; 424 425 static StaticAutoPtr<InlineBackgroundData> gInlineBGData; 426 427 // Initialize any static variables used by nsCSSRendering. 428 void nsCSSRendering::Init() { 429 NS_ASSERTION(!gInlineBGData, "Init called twice"); 430 gInlineBGData = new InlineBackgroundData(); 431 } 432 433 // Clean up any global variables used by nsCSSRendering. 434 void nsCSSRendering::Shutdown() { gInlineBGData = nullptr; } 435 436 /** 437 * Make a bevel color 438 */ 439 static nscolor MakeBevelColor(mozilla::Side whichSide, StyleBorderStyle style, 440 nscolor aBorderColor) { 441 nscolor colors[2]; 442 nscolor theColor; 443 444 // Given a background color and a border color 445 // calculate the color used for the shading 446 NS_GetSpecial3DColors(colors, aBorderColor); 447 448 if ((style == StyleBorderStyle::Outset) || 449 (style == StyleBorderStyle::Ridge)) { 450 // Flip colors for these two border styles 451 switch (whichSide) { 452 case eSideBottom: 453 whichSide = eSideTop; 454 break; 455 case eSideRight: 456 whichSide = eSideLeft; 457 break; 458 case eSideTop: 459 whichSide = eSideBottom; 460 break; 461 case eSideLeft: 462 whichSide = eSideRight; 463 break; 464 } 465 } 466 467 switch (whichSide) { 468 case eSideBottom: 469 theColor = colors[1]; 470 break; 471 case eSideRight: 472 theColor = colors[1]; 473 break; 474 case eSideTop: 475 theColor = colors[0]; 476 break; 477 case eSideLeft: 478 default: 479 theColor = colors[0]; 480 break; 481 } 482 return theColor; 483 } 484 485 static bool GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder, 486 const nsRect& aOrigBorderArea, const nsRect& aBorderArea, 487 nsRectCornerRadii& aRadii) { 488 bool haveRoundedCorners; 489 nsSize sz = aBorderArea.Size(); 490 nsSize frameSize = aForFrame->GetSize(); 491 if (&aBorder == aForFrame->StyleBorder() && 492 frameSize == aOrigBorderArea.Size()) { 493 haveRoundedCorners = aForFrame->GetBorderRadii(sz, sz, Sides(), aRadii); 494 } else { 495 haveRoundedCorners = nsIFrame::ComputeBorderRadii( 496 aBorder.mBorderRadius, frameSize, sz, Sides(), aRadii); 497 } 498 499 return haveRoundedCorners; 500 } 501 502 static bool GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder, 503 const nsRect& aOrigBorderArea, const nsRect& aBorderArea, 504 RectCornerRadii* aBgRadii) { 505 nsRectCornerRadii radii; 506 bool haveRoundedCorners = 507 GetRadii(aForFrame, aBorder, aOrigBorderArea, aBorderArea, radii); 508 509 if (haveRoundedCorners) { 510 auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel(); 511 nsCSSRendering::ComputePixelRadii(radii, d2a, aBgRadii); 512 } 513 return haveRoundedCorners; 514 } 515 516 static nsRect JoinBoxesForBlockAxisSlice(nsIFrame* aFrame, 517 const nsRect& aBorderArea) { 518 // Inflate the block-axis size as if our continuations were laid out 519 // adjacent in that axis. Note that we don't touch the inline size. 520 const auto wm = aFrame->GetWritingMode(); 521 const nsSize dummyContainerSize; 522 LogicalRect borderArea(wm, aBorderArea, dummyContainerSize); 523 nscoord bSize = 0; 524 nsIFrame* f = aFrame->GetNextContinuation(); 525 for (; f; f = f->GetNextContinuation()) { 526 bSize += f->BSize(wm); 527 } 528 borderArea.BSize(wm) += bSize; 529 bSize = 0; 530 f = aFrame->GetPrevContinuation(); 531 for (; f; f = f->GetPrevContinuation()) { 532 bSize += f->BSize(wm); 533 } 534 borderArea.BStart(wm) -= bSize; 535 borderArea.BSize(wm) += bSize; 536 return borderArea.GetPhysicalRect(wm, dummyContainerSize); 537 } 538 539 /** 540 * Inflate aBorderArea which is relative to aFrame's origin to calculate 541 * a hypothetical non-split frame area for all the continuations. 542 * See "Joining Boxes for 'slice'" in 543 * http://dev.w3.org/csswg/css-break/#break-decoration 544 */ 545 enum InlineBoxOrder { eForBorder, eForBackground }; 546 static nsRect JoinBoxesForSlice(nsIFrame* aFrame, const nsRect& aBorderArea, 547 InlineBoxOrder aOrder) { 548 if (static_cast<nsInlineFrame*>(do_QueryFrame(aFrame))) { 549 return (aOrder == eForBorder 550 ? gInlineBGData->GetBorderContinuousRect(aFrame, aBorderArea) 551 : gInlineBGData->GetContinuousRect(aFrame)) + 552 aBorderArea.TopLeft(); 553 } 554 return JoinBoxesForBlockAxisSlice(aFrame, aBorderArea); 555 } 556 557 /* static */ 558 bool nsCSSRendering::IsBoxDecorationSlice(const nsStyleBorder& aStyleBorder) { 559 return aStyleBorder.mBoxDecorationBreak == StyleBoxDecorationBreak::Slice; 560 } 561 562 /* static */ 563 nsRect nsCSSRendering::BoxDecorationRectForBorder( 564 nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides, 565 const nsStyleBorder* aStyleBorder) { 566 if (!aStyleBorder) { 567 aStyleBorder = aFrame->StyleBorder(); 568 } 569 // If aSkipSides.IsEmpty() then there are no continuations, or it's 570 // a ::first-letter that wants all border sides on the first continuation. 571 return IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty() 572 ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBorder) 573 : aBorderArea; 574 } 575 576 /* static */ 577 nsRect nsCSSRendering::BoxDecorationRectForBackground( 578 nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides, 579 const nsStyleBorder* aStyleBorder) { 580 if (!aStyleBorder) { 581 aStyleBorder = aFrame->StyleBorder(); 582 } 583 // If aSkipSides.IsEmpty() then there are no continuations, or it's 584 // a ::first-letter that wants all border sides on the first continuation. 585 return IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty() 586 ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBackground) 587 : aBorderArea; 588 } 589 590 //---------------------------------------------------------------------- 591 // Thebes Border Rendering Code Start 592 593 /* 594 * Compute the float-pixel radii that should be used for drawing 595 * this border/outline, given the various input bits. 596 */ 597 /* static */ 598 void nsCSSRendering::ComputePixelRadii(const nsRectCornerRadii& aRadii, 599 nscoord aAppUnitsPerPixel, 600 RectCornerRadii* oBorderRadii) { 601 for (const auto corner : mozilla::AllPhysicalCorners()) { 602 (*oBorderRadii)[corner] = 603 LayoutDeviceSize::FromAppUnits(aRadii[corner], aAppUnitsPerPixel) 604 .ToUnknownSize(); 605 } 606 } 607 608 static Maybe<nsStyleBorder> GetBorderIfVisited(const ComputedStyle& aStyle) { 609 Maybe<nsStyleBorder> result; 610 // Don't check RelevantLinkVisited here, since we want to take the 611 // same amount of time whether or not it's true. 612 const ComputedStyle* styleIfVisited = aStyle.GetStyleIfVisited(); 613 if (MOZ_LIKELY(!styleIfVisited)) { 614 return result; 615 } 616 617 result.emplace(*aStyle.StyleBorder()); 618 auto& newBorder = result.ref(); 619 for (const auto side : mozilla::AllPhysicalSides()) { 620 nscolor color = aStyle.GetVisitedDependentColor( 621 nsStyleBorder::BorderColorFieldFor(side)); 622 newBorder.BorderColorFor(side) = StyleColor::FromColor(color); 623 } 624 625 return result; 626 } 627 628 ImgDrawResult nsCSSRendering::PaintBorder( 629 nsPresContext* aPresContext, gfxContext& aRenderingContext, 630 nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea, 631 ComputedStyle* aStyle, PaintBorderFlags aFlags, Sides aSkipSides) { 632 AUTO_PROFILER_LABEL("nsCSSRendering::PaintBorder", GRAPHICS); 633 634 Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle); 635 return PaintBorderWithStyleBorder( 636 aPresContext, aRenderingContext, aForFrame, aDirtyRect, aBorderArea, 637 visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aFlags, aSkipSides); 638 } 639 640 Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRenderer( 641 nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, 642 const nsRect& aDirtyRect, const nsRect& aBorderArea, ComputedStyle* aStyle, 643 bool* aOutBorderIsEmpty, Sides aSkipSides) { 644 Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle); 645 return CreateBorderRendererWithStyleBorder( 646 aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea, 647 visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aOutBorderIsEmpty, 648 aSkipSides); 649 } 650 651 ImgDrawResult nsCSSRendering::CreateWebRenderCommandsForBorder( 652 nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea, 653 mozilla::wr::DisplayListBuilder& aBuilder, 654 mozilla::wr::IpcResourceUpdateQueue& aResources, 655 const mozilla::layers::StackingContextHelper& aSc, 656 mozilla::layers::RenderRootStateManager* aManager, 657 nsDisplayListBuilder* aDisplayListBuilder) { 658 const auto* style = aForFrame->Style(); 659 Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*style); 660 return nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder( 661 aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aManager, 662 aDisplayListBuilder, visitedBorder.refOr(*style->StyleBorder())); 663 } 664 665 void nsCSSRendering::CreateWebRenderCommandsForNullBorder( 666 nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea, 667 mozilla::wr::DisplayListBuilder& aBuilder, 668 mozilla::wr::IpcResourceUpdateQueue& aResources, 669 const mozilla::layers::StackingContextHelper& aSc, 670 const nsStyleBorder& aStyleBorder) { 671 bool borderIsEmpty = false; 672 Maybe<nsCSSBorderRenderer> br = 673 nsCSSRendering::CreateNullBorderRendererWithStyleBorder( 674 aForFrame->PresContext(), nullptr, aForFrame, nsRect(), aBorderArea, 675 aStyleBorder, aForFrame->Style(), &borderIsEmpty, 676 aForFrame->GetSkipSides()); 677 if (!borderIsEmpty && br) { 678 br->CreateWebRenderCommands(aItem, aBuilder, aResources, aSc); 679 } 680 } 681 682 ImgDrawResult nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder( 683 nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea, 684 mozilla::wr::DisplayListBuilder& aBuilder, 685 mozilla::wr::IpcResourceUpdateQueue& aResources, 686 const mozilla::layers::StackingContextHelper& aSc, 687 mozilla::layers::RenderRootStateManager* aManager, 688 nsDisplayListBuilder* aDisplayListBuilder, 689 const nsStyleBorder& aStyleBorder) { 690 auto& borderImage = aStyleBorder.mBorderImageSource; 691 // First try to create commands for simple borders. 692 if (borderImage.IsNone()) { 693 CreateWebRenderCommandsForNullBorder( 694 aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aStyleBorder); 695 return ImgDrawResult::SUCCESS; 696 } 697 698 // Next we try image and gradient borders. Gradients are not supported at 699 // this very moment. 700 if (!borderImage.IsImageRequestType()) { 701 return ImgDrawResult::NOT_SUPPORTED; 702 } 703 704 if (aStyleBorder.mBorderImageRepeat._0 == 705 StyleBorderImageRepeatKeyword::Space || 706 aStyleBorder.mBorderImageRepeat._1 == 707 StyleBorderImageRepeatKeyword::Space) { 708 return ImgDrawResult::NOT_SUPPORTED; 709 } 710 711 uint32_t flags = 0; 712 if (aDisplayListBuilder->IsPaintingToWindow()) { 713 flags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW; 714 } 715 if (aDisplayListBuilder->ShouldSyncDecodeImages()) { 716 flags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES; 717 } 718 719 bool dummy; 720 image::ImgDrawResult result; 721 Maybe<nsCSSBorderImageRenderer> bir = 722 nsCSSBorderImageRenderer::CreateBorderImageRenderer( 723 aForFrame->PresContext(), aForFrame, aBorderArea, aStyleBorder, 724 aItem->GetBounds(aDisplayListBuilder, &dummy), 725 aForFrame->GetSkipSides(), flags, &result); 726 727 if (!bir) { 728 // We aren't ready. Try to fallback to the null border image if present but 729 // return the draw result for the border image renderer. 730 CreateWebRenderCommandsForNullBorder( 731 aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aStyleBorder); 732 return result; 733 } 734 735 return bir->CreateWebRenderCommands(aItem, aForFrame, aBuilder, aResources, 736 aSc, aManager, aDisplayListBuilder); 737 } 738 739 static nsCSSBorderRenderer ConstructBorderRenderer( 740 nsPresContext* aPresContext, ComputedStyle* aStyle, DrawTarget* aDrawTarget, 741 nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea, 742 const nsStyleBorder& aStyleBorder, Sides aSkipSides, bool* aNeedsClip) { 743 nsMargin border = aStyleBorder.GetComputedBorder(); 744 745 // Compute the outermost boundary of the area that might be painted. 746 // Same coordinate space as aBorderArea & aBGClipRect. 747 nsRect joinedBorderArea = nsCSSRendering::BoxDecorationRectForBorder( 748 aForFrame, aBorderArea, aSkipSides, &aStyleBorder); 749 RectCornerRadii bgRadii; 750 ::GetRadii(aForFrame, aStyleBorder, aBorderArea, joinedBorderArea, &bgRadii); 751 752 PrintAsFormatString(" joinedBorderArea: %d %d %d %d\n", joinedBorderArea.x, 753 joinedBorderArea.y, joinedBorderArea.width, 754 joinedBorderArea.height); 755 756 // start drawing 757 if (nsCSSRendering::IsBoxDecorationSlice(aStyleBorder)) { 758 if (joinedBorderArea.IsEqualEdges(aBorderArea)) { 759 // No need for a clip, just skip the sides we don't want. 760 border.ApplySkipSides(aSkipSides); 761 } else { 762 // We're drawing borders around the joined continuation boxes so we need 763 // to clip that to the slice that we want for this frame. 764 *aNeedsClip = true; 765 } 766 } else { 767 MOZ_ASSERT(joinedBorderArea.IsEqualEdges(aBorderArea), 768 "Should use aBorderArea for box-decoration-break:clone"); 769 MOZ_ASSERT( 770 aForFrame->GetSkipSides().IsEmpty() || 771 aForFrame->IsTrueOverflowContainer() || 772 aForFrame->IsColumnSetFrame(), // a little broader than column-rule 773 "Should not skip sides for box-decoration-break:clone except " 774 "::first-letter/line continuations or other frame types that " 775 "don't have borders but those shouldn't reach this point. " 776 "Overflow containers do reach this point though, as does " 777 "column-rule drawing (which always involves a columnset)."); 778 border.ApplySkipSides(aSkipSides); 779 } 780 781 // Convert to dev pixels. 782 nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1); 783 Rect joinedBorderAreaPx = NSRectToRect(joinedBorderArea, oneDevPixel); 784 Margin borderWidths( 785 Float(border.top) / oneDevPixel, Float(border.right) / oneDevPixel, 786 Float(border.bottom) / oneDevPixel, Float(border.left) / oneDevPixel); 787 Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel); 788 789 StyleBorderStyle borderStyles[4]; 790 nscolor borderColors[4]; 791 792 // pull out styles, colors 793 for (const auto i : mozilla::AllPhysicalSides()) { 794 borderStyles[i] = aStyleBorder.GetBorderStyle(i); 795 borderColors[i] = aStyleBorder.BorderColorFor(i).CalcColor(*aStyle); 796 } 797 798 PrintAsFormatString( 799 " borderStyles: %d %d %d %d\n", static_cast<int>(borderStyles[0]), 800 static_cast<int>(borderStyles[1]), static_cast<int>(borderStyles[2]), 801 static_cast<int>(borderStyles[3])); 802 803 return nsCSSBorderRenderer( 804 aPresContext, aDrawTarget, dirtyRect, joinedBorderAreaPx, borderStyles, 805 borderWidths, bgRadii, borderColors, !aForFrame->BackfaceIsHidden(), 806 *aNeedsClip ? Some(NSRectToRect(aBorderArea, oneDevPixel)) : Nothing()); 807 } 808 809 ImgDrawResult nsCSSRendering::PaintBorderWithStyleBorder( 810 nsPresContext* aPresContext, gfxContext& aRenderingContext, 811 nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea, 812 const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle, 813 PaintBorderFlags aFlags, Sides aSkipSides) { 814 DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget(); 815 816 PrintAsStringNewline("++ PaintBorder"); 817 818 // Check to see if we have an appearance defined. If so, we let the theme 819 // renderer draw the border. DO not get the data from aForFrame, since the 820 // passed in ComputedStyle may be different! Always use |aStyle|! 821 StyleAppearance appearance = aStyle->StyleDisplay()->EffectiveAppearance(); 822 if (appearance != StyleAppearance::None) { 823 nsITheme* theme = aPresContext->Theme(); 824 if (theme->ThemeSupportsWidget(aPresContext, aForFrame, appearance)) { 825 return ImgDrawResult::SUCCESS; // Let the theme handle it. 826 } 827 } 828 829 if (!aStyleBorder.mBorderImageSource.IsNone()) { 830 ImgDrawResult result = ImgDrawResult::SUCCESS; 831 832 uint32_t irFlags = 0; 833 if (aFlags & PaintBorderFlags::SyncDecodeImages) { 834 irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES; 835 } 836 837 // Creating the border image renderer will request a decode, and we rely on 838 // that happening. 839 Maybe<nsCSSBorderImageRenderer> renderer = 840 nsCSSBorderImageRenderer::CreateBorderImageRenderer( 841 aPresContext, aForFrame, aBorderArea, aStyleBorder, aDirtyRect, 842 aSkipSides, irFlags, &result); 843 // renderer was created successfully, which means border image is ready to 844 // be used. 845 if (renderer) { 846 MOZ_ASSERT(result == ImgDrawResult::SUCCESS); 847 return renderer->DrawBorderImage(aPresContext, aRenderingContext, 848 aForFrame, aDirtyRect); 849 } 850 } 851 852 ImgDrawResult result = ImgDrawResult::SUCCESS; 853 854 // If we had a border-image, but it wasn't loaded, then we should return 855 // ImgDrawResult::NOT_READY; we'll want to try again if we do a paint with 856 // sync decoding enabled. 857 if (!aStyleBorder.mBorderImageSource.IsNone()) { 858 result = ImgDrawResult::NOT_READY; 859 } 860 861 nsMargin border = aStyleBorder.GetComputedBorder(); 862 if (0 == border.left && 0 == border.right && 0 == border.top && 863 0 == border.bottom) { 864 // Empty border area 865 return result; 866 } 867 868 bool needsClip = false; 869 nsCSSBorderRenderer br = ConstructBorderRenderer( 870 aPresContext, aStyle, &aDrawTarget, aForFrame, aDirtyRect, aBorderArea, 871 aStyleBorder, aSkipSides, &needsClip); 872 if (needsClip) { 873 aDrawTarget.PushClipRect(NSRectToSnappedRect( 874 aBorderArea, aForFrame->PresContext()->AppUnitsPerDevPixel(), 875 aDrawTarget)); 876 } 877 878 br.DrawBorders(); 879 880 if (needsClip) { 881 aDrawTarget.PopClip(); 882 } 883 884 PrintAsStringNewline(); 885 886 return result; 887 } 888 889 Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRendererWithStyleBorder( 890 nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, 891 const nsRect& aDirtyRect, const nsRect& aBorderArea, 892 const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle, 893 bool* aOutBorderIsEmpty, Sides aSkipSides) { 894 if (!aStyleBorder.mBorderImageSource.IsNone()) { 895 return Nothing(); 896 } 897 return CreateNullBorderRendererWithStyleBorder( 898 aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea, 899 aStyleBorder, aStyle, aOutBorderIsEmpty, aSkipSides); 900 } 901 902 Maybe<nsCSSBorderRenderer> 903 nsCSSRendering::CreateNullBorderRendererWithStyleBorder( 904 nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, 905 const nsRect& aDirtyRect, const nsRect& aBorderArea, 906 const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle, 907 bool* aOutBorderIsEmpty, Sides aSkipSides) { 908 StyleAppearance appearance = aStyle->StyleDisplay()->EffectiveAppearance(); 909 if (appearance != StyleAppearance::None) { 910 nsITheme* theme = aPresContext->Theme(); 911 if (theme->ThemeSupportsWidget(aPresContext, aForFrame, appearance)) { 912 // The border will be draw as part of the themed background item created 913 // for this same frame. If no themed background item was created then not 914 // drawing also matches that we do without webrender and what 915 // nsDisplayBorder does for themed borders. 916 if (aOutBorderIsEmpty) { 917 *aOutBorderIsEmpty = true; 918 } 919 return Nothing(); 920 } 921 } 922 923 nsMargin border = aStyleBorder.GetComputedBorder(); 924 if (0 == border.left && 0 == border.right && 0 == border.top && 925 0 == border.bottom) { 926 // Empty border area 927 if (aOutBorderIsEmpty) { 928 *aOutBorderIsEmpty = true; 929 } 930 return Nothing(); 931 } 932 933 bool needsClip = false; 934 nsCSSBorderRenderer br = ConstructBorderRenderer( 935 aPresContext, aStyle, aDrawTarget, aForFrame, aDirtyRect, aBorderArea, 936 aStyleBorder, aSkipSides, &needsClip); 937 return Some(br); 938 } 939 940 Maybe<nsCSSBorderRenderer> 941 nsCSSRendering::CreateBorderRendererForNonThemedOutline( 942 nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, 943 const nsRect& aDirtyRect, const nsRect& aInnerRect, ComputedStyle* aStyle) { 944 // Get our ComputedStyle's color struct. 945 const nsStyleOutline* ourOutline = aStyle->StyleOutline(); 946 if (!ourOutline->ShouldPaintOutline()) { 947 // Empty outline 948 return Nothing(); 949 } 950 951 nsRect innerRect = aInnerRect; 952 953 const nsSize effectiveOffset = ourOutline->EffectiveOffsetFor(innerRect); 954 innerRect.Inflate(effectiveOffset); 955 956 // If the dirty rect is completely inside the border area (e.g., only the 957 // content is being painted), then we can skip out now 958 // XXX this isn't exactly true for rounded borders, where the inside curves 959 // may encroach into the content area. A safer calculation would be to 960 // shorten insideRect by the radius one each side before performing this test. 961 if (innerRect.Contains(aDirtyRect)) { 962 return Nothing(); 963 } 964 965 const nscoord width = ourOutline->mOutlineWidth; 966 967 StyleBorderStyle outlineStyle; 968 // Themed outlines are handled by our callers, if supported. 969 if (ourOutline->mOutlineStyle.IsAuto()) { 970 if (width == 0) { 971 return Nothing(); // empty outline 972 } 973 // http://dev.w3.org/csswg/css-ui/#outline 974 // "User agents may treat 'auto' as 'solid'." 975 outlineStyle = StyleBorderStyle::Solid; 976 } else { 977 outlineStyle = ourOutline->mOutlineStyle.AsBorderStyle(); 978 } 979 980 RectCornerRadii outlineRadii; 981 nsRect outerRect = innerRect; 982 outerRect.Inflate(width); 983 984 const nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel(); 985 Rect oRect(NSRectToRect(outerRect, oneDevPixel)); 986 987 const Margin outlineWidths( 988 Float(width) / oneDevPixel, Float(width) / oneDevPixel, 989 Float(width) / oneDevPixel, Float(width) / oneDevPixel); 990 991 // convert the radii 992 nsRectCornerRadii twipsRadii; 993 994 // get the radius for our outline 995 if (aForFrame->GetBorderRadii(twipsRadii)) { 996 RectCornerRadii innerRadii; 997 ComputePixelRadii(twipsRadii, oneDevPixel, &innerRadii); 998 999 const auto devPxOffset = LayoutDeviceSize::FromAppUnits( 1000 effectiveOffset, aPresContext->AppUnitsPerDevPixel()); 1001 1002 const Margin widths(outlineWidths.top + devPxOffset.Height(), 1003 outlineWidths.right + devPxOffset.Width(), 1004 outlineWidths.bottom + devPxOffset.Height(), 1005 outlineWidths.left + devPxOffset.Width()); 1006 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outlineRadii); 1007 } 1008 1009 StyleBorderStyle outlineStyles[4] = {outlineStyle, outlineStyle, outlineStyle, 1010 outlineStyle}; 1011 1012 // This handles treating the initial color as 'currentColor'; if we 1013 // ever want 'invert' back we'll need to do a bit of work here too. 1014 nscolor outlineColor = 1015 aStyle->GetVisitedDependentColor(&nsStyleOutline::mOutlineColor); 1016 nscolor outlineColors[4] = {outlineColor, outlineColor, outlineColor, 1017 outlineColor}; 1018 1019 Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel); 1020 1021 return Some(nsCSSBorderRenderer( 1022 aPresContext, aDrawTarget, dirtyRect, oRect, outlineStyles, outlineWidths, 1023 outlineRadii, outlineColors, !aForFrame->BackfaceIsHidden(), Nothing())); 1024 } 1025 1026 void nsCSSRendering::PaintNonThemedOutline(nsPresContext* aPresContext, 1027 gfxContext& aRenderingContext, 1028 nsIFrame* aForFrame, 1029 const nsRect& aDirtyRect, 1030 const nsRect& aInnerRect, 1031 ComputedStyle* aStyle) { 1032 Maybe<nsCSSBorderRenderer> br = CreateBorderRendererForNonThemedOutline( 1033 aPresContext, aRenderingContext.GetDrawTarget(), aForFrame, aDirtyRect, 1034 aInnerRect, aStyle); 1035 if (!br) { 1036 return; 1037 } 1038 1039 // start drawing 1040 br->DrawBorders(); 1041 1042 PrintAsStringNewline(); 1043 } 1044 1045 nsCSSBorderRenderer nsCSSRendering::GetBorderRendererForFocus( 1046 nsIFrame* aForFrame, DrawTarget* aDrawTarget, const nsRect& aFocusRect, 1047 nscolor aColor) { 1048 auto* pc = aForFrame->PresContext(); 1049 nscoord oneCSSPixel = nsPresContext::CSSPixelsToAppUnits(1); 1050 nscoord oneDevPixel = pc->DevPixelsToAppUnits(1); 1051 1052 Rect focusRect(NSRectToRect(aFocusRect, oneDevPixel)); 1053 1054 RectCornerRadii focusRadii; 1055 Margin focusWidths( 1056 Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel, 1057 Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel); 1058 1059 StyleBorderStyle focusStyles[4] = { 1060 StyleBorderStyle::Dotted, StyleBorderStyle::Dotted, 1061 StyleBorderStyle::Dotted, StyleBorderStyle::Dotted}; 1062 nscolor focusColors[4] = {aColor, aColor, aColor, aColor}; 1063 1064 // Because this renders a dotted border, the background color 1065 // should not be used. Therefore, we provide a value that will 1066 // be blatantly wrong if it ever does get used. (If this becomes 1067 // something that CSS can style, this function will then have access 1068 // to a ComputedStyle and can use the same logic that PaintBorder 1069 // and PaintOutline do.) 1070 return nsCSSBorderRenderer(pc, aDrawTarget, focusRect, focusRect, focusStyles, 1071 focusWidths, focusRadii, focusColors, 1072 !aForFrame->BackfaceIsHidden(), Nothing()); 1073 } 1074 1075 // Thebes Border Rendering Code End 1076 //---------------------------------------------------------------------- 1077 1078 //---------------------------------------------------------------------- 1079 1080 /** 1081 * Helper for ComputeObjectAnchorPoint; parameters are the same as for 1082 * that function, except they're for a single coordinate / a single size 1083 * dimension. (so, x/width vs. y/height) 1084 */ 1085 static void ComputeObjectAnchorCoord(const LengthPercentage& aCoord, 1086 const nscoord aOriginBounds, 1087 const nscoord aImageSize, 1088 nscoord* aTopLeftCoord, 1089 nscoord* aAnchorPointCoord) { 1090 nscoord extraSpace = aOriginBounds - aImageSize; 1091 1092 // The anchor-point doesn't care about our image's size; just the size 1093 // of the region we're rendering into. 1094 *aAnchorPointCoord = aCoord.Resolve( 1095 aOriginBounds, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp)); 1096 // Adjust aTopLeftCoord by the specified % of the extra space. 1097 *aTopLeftCoord = aCoord.Resolve( 1098 extraSpace, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp)); 1099 } 1100 1101 void nsImageRenderer::ComputeObjectAnchorPoint(const Position& aPos, 1102 const nsSize& aOriginBounds, 1103 const nsSize& aImageSize, 1104 nsPoint* aTopLeft, 1105 nsPoint* aAnchorPoint) { 1106 ComputeObjectAnchorCoord(aPos.horizontal, aOriginBounds.width, 1107 aImageSize.width, &aTopLeft->x, &aAnchorPoint->x); 1108 1109 ComputeObjectAnchorCoord(aPos.vertical, aOriginBounds.height, 1110 aImageSize.height, &aTopLeft->y, &aAnchorPoint->y); 1111 } 1112 1113 // In print / print preview we have multiple canvas frames (one for each page, 1114 // and one for the document as a whole). For the topmost one, we really want the 1115 // page sequence page background, not the root or body's background. 1116 static nsIFrame* GetPageSequenceForCanvas(const nsIFrame* aCanvasFrame) { 1117 MOZ_ASSERT(aCanvasFrame->IsCanvasFrame(), "not a canvas frame"); 1118 nsPresContext* pc = aCanvasFrame->PresContext(); 1119 if (!pc->IsRootPaginatedDocument()) { 1120 return nullptr; 1121 } 1122 auto* ps = pc->PresShell()->GetPageSequenceFrame(); 1123 if (NS_WARN_IF(!ps)) { 1124 return nullptr; 1125 } 1126 if (ps->GetParent() != aCanvasFrame) { 1127 return nullptr; 1128 } 1129 return ps; 1130 } 1131 1132 auto nsCSSRendering::FindEffectiveBackgroundColor(nsIFrame* aFrame, 1133 bool aStopAtThemed, 1134 bool aPreferBodyToCanvas) 1135 -> EffectiveBackgroundColor { 1136 MOZ_ASSERT(aFrame); 1137 nsPresContext* pc = aFrame->PresContext(); 1138 auto BgColorIfNotTransparent = [&](nsIFrame* aFrame) -> Maybe<nscolor> { 1139 nscolor c = 1140 aFrame->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor); 1141 if (NS_GET_A(c) == 255) { 1142 return Some(c); 1143 } 1144 if (NS_GET_A(c)) { 1145 // TODO(emilio): We should maybe just blend with ancestor bg colors and 1146 // such, but this is probably good enough for now, matches pre-existing 1147 // behavior. 1148 const nscolor defaultBg = pc->DefaultBackgroundColor(); 1149 MOZ_ASSERT(NS_GET_A(defaultBg) == 255, "PreferenceSheet guarantees this"); 1150 return Some(NS_ComposeColors(defaultBg, c)); 1151 } 1152 return Nothing(); 1153 }; 1154 1155 for (nsIFrame* frame = aFrame; frame; 1156 frame = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame)) { 1157 if (auto bg = BgColorIfNotTransparent(frame)) { 1158 return {*bg}; 1159 } 1160 1161 if (aStopAtThemed && frame->IsThemed()) { 1162 return {NS_TRANSPARENT, true}; 1163 } 1164 1165 if (frame->IsCanvasFrame()) { 1166 if (aPreferBodyToCanvas && !GetPageSequenceForCanvas(frame)) { 1167 if (auto* body = pc->Document()->GetBodyElement()) { 1168 if (nsIFrame* f = body->GetPrimaryFrame()) { 1169 if (auto bg = BgColorIfNotTransparent(f)) { 1170 return {*bg}; 1171 } 1172 } 1173 } 1174 } 1175 if (nsIFrame* bgFrame = FindBackgroundFrame(frame)) { 1176 if (auto bg = BgColorIfNotTransparent(bgFrame)) { 1177 return {*bg}; 1178 } 1179 } 1180 } 1181 } 1182 1183 return {pc->DefaultBackgroundColor()}; 1184 } 1185 1186 nsIFrame* nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame) { 1187 const nsStyleBackground* result = aForFrame->StyleBackground(); 1188 1189 // Check if we need to do propagation from BODY rather than HTML. 1190 if (!result->IsTransparent(aForFrame)) { 1191 return aForFrame; 1192 } 1193 1194 nsIContent* content = aForFrame->GetContent(); 1195 // The root element content can't be null. We wouldn't know what 1196 // frame to create for aFrame. 1197 // Use |OwnerDoc| so it works during destruction. 1198 if (!content) { 1199 return aForFrame; 1200 } 1201 1202 Document* document = content->OwnerDoc(); 1203 1204 dom::Element* bodyContent = document->GetBodyElement(); 1205 // We need to null check the body node (bug 118829) since 1206 // there are cases, thanks to the fix for bug 5569, where we 1207 // will reflow a document with no body. In particular, if a 1208 // SCRIPT element in the head blocks the parser and then has a 1209 // SCRIPT that does "document.location.href = 'foo'", then 1210 // nsParser::Terminate will call |DidBuildModel| methods 1211 // through to the content sink, which will call |StartLayout| 1212 // and thus |Initialize| on the pres shell. See bug 119351 1213 // for the ugly details. 1214 if (!bodyContent || aForFrame->StyleDisplay()->IsContainAny()) { 1215 return aForFrame; 1216 } 1217 1218 nsIFrame* bodyFrame = bodyContent->GetPrimaryFrame(); 1219 if (!bodyFrame || bodyFrame->StyleDisplay()->IsContainAny()) { 1220 return aForFrame; 1221 } 1222 1223 return nsLayoutUtils::GetStyleFrame(bodyFrame); 1224 } 1225 1226 /** 1227 * |FindBackground| finds the correct style data to use to paint the 1228 * background. It is responsible for handling the following two 1229 * statements in section 14.2 of CSS2: 1230 * 1231 * The background of the box generated by the root element covers the 1232 * entire canvas. 1233 * 1234 * For HTML documents, however, we recommend that authors specify the 1235 * background for the BODY element rather than the HTML element. User 1236 * agents should observe the following precedence rules to fill in the 1237 * background: if the value of the 'background' property for the HTML 1238 * element is different from 'transparent' then use it, else use the 1239 * value of the 'background' property for the BODY element. If the 1240 * resulting value is 'transparent', the rendering is undefined. 1241 * 1242 * Thus, in our implementation, it is responsible for ensuring that: 1243 * + we paint the correct background on the |nsCanvasFrame| or |nsPageFrame|, 1244 * + we don't paint the background on the root element, and 1245 * + we don't paint the background on the BODY element in *some* cases, 1246 * and for SGML-based HTML documents only. 1247 * 1248 * |FindBackground| checks whether a background should be painted. If yes, it 1249 * returns the resulting ComputedStyle to use for the background information; 1250 * Otherwise, it returns nullptr. 1251 */ 1252 ComputedStyle* nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame) { 1253 return FindBackgroundStyleFrame(aForFrame)->Style(); 1254 } 1255 1256 static nsIFrame* FindCanvasBackgroundFrame(const nsIFrame* aForFrame, 1257 nsIFrame* aRootElementFrame) { 1258 MOZ_ASSERT(aForFrame->IsCanvasFrame(), "not a canvas frame"); 1259 if (auto* ps = GetPageSequenceForCanvas(aForFrame)) { 1260 return ps; 1261 } 1262 if (aRootElementFrame) { 1263 return nsCSSRendering::FindBackgroundStyleFrame(aRootElementFrame); 1264 } 1265 // This should always give transparent, so we'll fill it in with the default 1266 // color if needed. This seems to happen a bit while a page is being loaded. 1267 return const_cast<nsIFrame*>(aForFrame); 1268 } 1269 1270 // Helper for FindBackgroundFrame. Returns true if aForFrame has a meaningful 1271 // background that it should draw (i.e. that it hasn't propagated to another 1272 // frame). See documentation for FindBackground. 1273 inline bool FrameHasMeaningfulBackground(const nsIFrame* aForFrame, 1274 nsIFrame* aRootElementFrame) { 1275 MOZ_ASSERT(!aForFrame->IsCanvasFrame(), 1276 "FindBackgroundFrame handles canvas frames before calling us, " 1277 "so we don't need to consider them here"); 1278 1279 if (aForFrame == aRootElementFrame) { 1280 // We must have propagated our background to the viewport or canvas. Abort. 1281 return false; 1282 } 1283 1284 // Return true unless the frame is for a BODY element whose background 1285 // was propagated to the viewport. 1286 1287 nsIContent* content = aForFrame->GetContent(); 1288 if (!content || content->NodeInfo()->NameAtom() != nsGkAtoms::body) { 1289 return true; // not frame for a "body" element 1290 } 1291 // It could be a non-HTML "body" element but that's OK, we'd fail the 1292 // bodyContent check below 1293 1294 if (aForFrame->Style()->GetPseudoType() != PseudoStyleType::NotPseudo || 1295 aForFrame->StyleDisplay()->IsContainAny()) { 1296 return true; // A pseudo-element frame, or contained. 1297 } 1298 1299 // We should only look at the <html> background if we're in an HTML document 1300 Document* document = content->OwnerDoc(); 1301 1302 dom::Element* bodyContent = document->GetBodyElement(); 1303 if (bodyContent != content) { 1304 return true; // this wasn't the background that was propagated 1305 } 1306 1307 // This can be called even when there's no root element yet, during frame 1308 // construction, via nsLayoutUtils::FrameHasTransparency and 1309 // nsContainerFrame::SyncFrameViewProperties. 1310 if (!aRootElementFrame || aRootElementFrame->StyleDisplay()->IsContainAny()) { 1311 return true; 1312 } 1313 1314 const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground(); 1315 return !htmlBG->IsTransparent(aRootElementFrame); 1316 } 1317 1318 nsIFrame* nsCSSRendering::FindBackgroundFrame(const nsIFrame* aForFrame) { 1319 nsIFrame* rootElementFrame = 1320 aForFrame->PresShell()->FrameConstructor()->GetRootElementStyleFrame(); 1321 if (aForFrame->IsCanvasFrame()) { 1322 return FindCanvasBackgroundFrame(aForFrame, rootElementFrame); 1323 } 1324 1325 if (FrameHasMeaningfulBackground(aForFrame, rootElementFrame)) { 1326 return const_cast<nsIFrame*>(aForFrame); 1327 } 1328 1329 return nullptr; 1330 } 1331 1332 ComputedStyle* nsCSSRendering::FindBackground(const nsIFrame* aForFrame) { 1333 if (auto* backgroundFrame = FindBackgroundFrame(aForFrame)) { 1334 return backgroundFrame->Style(); 1335 } 1336 return nullptr; 1337 } 1338 1339 void nsCSSRendering::BeginFrameTreesLocked() { ++gFrameTreeLockCount; } 1340 1341 void nsCSSRendering::EndFrameTreesLocked() { 1342 NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked"); 1343 --gFrameTreeLockCount; 1344 if (gFrameTreeLockCount == 0) { 1345 gInlineBGData->Reset(); 1346 } 1347 } 1348 1349 bool nsCSSRendering::HasBoxShadowNativeTheme(nsIFrame* aFrame, 1350 bool& aMaybeHasBorderRadius) { 1351 const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay(); 1352 nsITheme::Transparency transparency; 1353 if (aFrame->IsThemed(styleDisplay, &transparency)) { 1354 aMaybeHasBorderRadius = false; 1355 // For opaque (rectangular) theme widgets we can take the generic 1356 // border-box path with border-radius disabled. 1357 return transparency != nsITheme::eOpaque; 1358 } 1359 1360 aMaybeHasBorderRadius = true; 1361 return false; 1362 } 1363 1364 gfx::sRGBColor nsCSSRendering::GetShadowColor(const StyleSimpleShadow& aShadow, 1365 nsIFrame* aFrame, 1366 float aOpacity) { 1367 // Get the shadow color; if not specified, use the foreground color 1368 nscolor shadowColor = aShadow.color.CalcColor(aFrame); 1369 sRGBColor color = sRGBColor::FromABGR(shadowColor); 1370 color.a *= aOpacity; 1371 return color; 1372 } 1373 1374 nsRect nsCSSRendering::GetShadowRect(const nsRect& aFrameArea, 1375 bool aNativeTheme, nsIFrame* aForFrame) { 1376 nsRect frameRect = aNativeTheme ? aForFrame->InkOverflowRectRelativeToSelf() + 1377 aFrameArea.TopLeft() 1378 : aFrameArea; 1379 Sides skipSides = aForFrame->GetSkipSides(); 1380 frameRect = BoxDecorationRectForBorder(aForFrame, frameRect, skipSides); 1381 1382 // Explicitly do not need to account for the spread radius here 1383 // Webrender does it for us or PaintBoxShadow will for non-WR 1384 return frameRect; 1385 } 1386 1387 bool nsCSSRendering::GetBorderRadii(const nsRect& aFrameRect, 1388 const nsRect& aBorderRect, nsIFrame* aFrame, 1389 RectCornerRadii& aOutRadii) { 1390 const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1); 1391 nsRectCornerRadii twipsRadii; 1392 NS_ASSERTION( 1393 aBorderRect.Size() == aFrame->VisualBorderRectRelativeToSelf().Size(), 1394 "unexpected size"); 1395 nsSize sz = aFrameRect.Size(); 1396 bool hasBorderRadius = aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii); 1397 if (hasBorderRadius) { 1398 ComputePixelRadii(twipsRadii, oneDevPixel, &aOutRadii); 1399 } 1400 1401 return hasBorderRadius; 1402 } 1403 1404 void nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext, 1405 gfxContext& aRenderingContext, 1406 nsIFrame* aForFrame, 1407 const nsRect& aFrameArea, 1408 const nsRect& aDirtyRect, 1409 float aOpacity) { 1410 DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget(); 1411 auto shadows = aForFrame->StyleEffects()->mBoxShadow.AsSpan(); 1412 if (shadows.IsEmpty()) { 1413 return; 1414 } 1415 1416 bool hasBorderRadius; 1417 // mutually exclusive with hasBorderRadius 1418 bool nativeTheme = HasBoxShadowNativeTheme(aForFrame, hasBorderRadius); 1419 const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay(); 1420 1421 nsRect frameRect = GetShadowRect(aFrameArea, nativeTheme, aForFrame); 1422 1423 // Get any border radius, since box-shadow must also have rounded corners if 1424 // the frame does. 1425 RectCornerRadii borderRadii; 1426 const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1); 1427 if (hasBorderRadius) { 1428 nsRectCornerRadii twipsRadii; 1429 NS_ASSERTION( 1430 aFrameArea.Size() == aForFrame->VisualBorderRectRelativeToSelf().Size(), 1431 "unexpected size"); 1432 nsSize sz = frameRect.Size(); 1433 hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii); 1434 if (hasBorderRadius) { 1435 ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii); 1436 } 1437 } 1438 1439 // We don't show anything that intersects with the frame we're blurring on. So 1440 // tell the blurrer not to do unnecessary work there. 1441 gfxRect skipGfxRect = ThebesRect(NSRectToRect(frameRect, oneDevPixel)); 1442 skipGfxRect.Round(); 1443 bool useSkipGfxRect = true; 1444 if (nativeTheme) { 1445 // Optimize non-leaf native-themed frames by skipping computing pixels 1446 // in the padding-box. We assume the padding-box is going to be painted 1447 // opaquely for non-leaf frames. 1448 // XXX this may not be a safe assumption; we should make this go away 1449 // by optimizing box-shadow drawing more for the cases where we don't have a 1450 // skip-rect. 1451 useSkipGfxRect = !aForFrame->IsLeaf(); 1452 nsRect paddingRect = 1453 aForFrame->GetPaddingRectRelativeToSelf() + aFrameArea.TopLeft(); 1454 skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, oneDevPixel); 1455 } else if (hasBorderRadius) { 1456 skipGfxRect.Deflate(gfxMargin( 1457 std::max(borderRadii[C_TL].height, borderRadii[C_TR].height), 0, 1458 std::max(borderRadii[C_BL].height, borderRadii[C_BR].height), 0)); 1459 } 1460 1461 for (const StyleBoxShadow& shadow : Reversed(shadows)) { 1462 if (shadow.inset) { 1463 continue; 1464 } 1465 1466 nsRect shadowRect = frameRect; 1467 nsPoint shadowOffset(shadow.base.horizontal.ToAppUnits(), 1468 shadow.base.vertical.ToAppUnits()); 1469 shadowRect.MoveBy(shadowOffset); 1470 nscoord shadowSpread = shadow.spread.ToAppUnits(); 1471 if (!nativeTheme) { 1472 shadowRect.Inflate(shadowSpread); 1473 } 1474 1475 // shadowRect won't include the blur, so make an extra rect here that 1476 // includes the blur for use in the even-odd rule below. 1477 nsRect shadowRectPlusBlur = shadowRect; 1478 nscoord blurRadius = shadow.base.blur.ToAppUnits(); 1479 shadowRectPlusBlur.Inflate( 1480 nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel)); 1481 1482 Rect shadowGfxRectPlusBlur = NSRectToRect(shadowRectPlusBlur, oneDevPixel); 1483 shadowGfxRectPlusBlur.RoundOut(); 1484 MaybeSnapToDevicePixels(shadowGfxRectPlusBlur, aDrawTarget, true); 1485 1486 sRGBColor gfxShadowColor = GetShadowColor(shadow.base, aForFrame, aOpacity); 1487 1488 if (nativeTheme) { 1489 nsContextBoxBlur blurringArea; 1490 1491 // When getting the widget shape from the native theme, we're going 1492 // to draw the widget into the shadow surface to create a mask. 1493 // We need to ensure that there actually *is* a shadow surface 1494 // and that we're not going to draw directly into aRenderingContext. 1495 gfxContext* shadowContext = blurringArea.Init( 1496 shadowRect, shadowSpread, blurRadius, oneDevPixel, &aRenderingContext, 1497 aDirtyRect, useSkipGfxRect ? &skipGfxRect : nullptr, 1498 nsContextBoxBlur::FORCE_MASK); 1499 if (!shadowContext) { 1500 continue; 1501 } 1502 1503 MOZ_ASSERT(shadowContext == blurringArea.GetContext()); 1504 1505 aRenderingContext.Save(); 1506 aRenderingContext.SetColor(gfxShadowColor); 1507 1508 // Draw the shape of the frame so it can be blurred. Recall how 1509 // nsContextBoxBlur doesn't make any temporary surfaces if blur is 0 and 1510 // it just returns the original surface? If we have no blur, we're 1511 // painting this fill on the actual content surface (aRenderingContext == 1512 // shadowContext) which is why we set up the color and clip before doing 1513 // this. 1514 1515 // We don't clip the border-box from the shadow, nor any other box. 1516 // We assume that the native theme is going to paint over the shadow. 1517 1518 // Draw the widget shape 1519 gfxContextMatrixAutoSaveRestore save(shadowContext); 1520 gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint( 1521 shadowOffset, aPresContext->AppUnitsPerDevPixel()); 1522 shadowContext->SetMatrixDouble( 1523 shadowContext->CurrentMatrixDouble().PreTranslate(devPixelOffset)); 1524 1525 nsRect nativeRect = aDirtyRect; 1526 nativeRect.MoveBy(-shadowOffset); 1527 nativeRect.IntersectRect(frameRect, nativeRect); 1528 aPresContext->Theme()->DrawWidgetBackground( 1529 shadowContext, aForFrame, styleDisplay->EffectiveAppearance(), 1530 aFrameArea, nativeRect, nsITheme::DrawOverflow::No); 1531 1532 blurringArea.DoPaint(); 1533 aRenderingContext.Restore(); 1534 } else { 1535 aRenderingContext.Save(); 1536 1537 { 1538 Rect innerClipRect = NSRectToRect(frameRect, oneDevPixel); 1539 if (!MaybeSnapToDevicePixels(innerClipRect, aDrawTarget, true)) { 1540 innerClipRect.Round(); 1541 } 1542 1543 // Clip out the interior of the frame's border edge so that the shadow 1544 // is only painted outside that area. 1545 RefPtr<PathBuilder> builder = 1546 aDrawTarget.CreatePathBuilder(FillRule::FILL_EVEN_ODD); 1547 AppendRectToPath(builder, shadowGfxRectPlusBlur); 1548 if (hasBorderRadius) { 1549 AppendRoundedRectToPath(builder, innerClipRect, borderRadii); 1550 } else { 1551 AppendRectToPath(builder, innerClipRect); 1552 } 1553 RefPtr<Path> path = builder->Finish(); 1554 aRenderingContext.Clip(path); 1555 } 1556 1557 // Clip the shadow so that we only get the part that applies to aForFrame. 1558 nsRect fragmentClip = shadowRectPlusBlur; 1559 Sides skipSides = aForFrame->GetSkipSides(); 1560 if (!skipSides.IsEmpty()) { 1561 if (skipSides.Left()) { 1562 nscoord xmost = fragmentClip.XMost(); 1563 fragmentClip.x = aFrameArea.x; 1564 fragmentClip.width = xmost - fragmentClip.x; 1565 } 1566 if (skipSides.Right()) { 1567 nscoord xmost = fragmentClip.XMost(); 1568 nscoord overflow = xmost - aFrameArea.XMost(); 1569 if (overflow > 0) { 1570 fragmentClip.width -= overflow; 1571 } 1572 } 1573 if (skipSides.Top()) { 1574 nscoord ymost = fragmentClip.YMost(); 1575 fragmentClip.y = aFrameArea.y; 1576 fragmentClip.height = ymost - fragmentClip.y; 1577 } 1578 if (skipSides.Bottom()) { 1579 nscoord ymost = fragmentClip.YMost(); 1580 nscoord overflow = ymost - aFrameArea.YMost(); 1581 if (overflow > 0) { 1582 fragmentClip.height -= overflow; 1583 } 1584 } 1585 } 1586 fragmentClip = fragmentClip.Intersect(aDirtyRect); 1587 aRenderingContext.Clip(NSRectToSnappedRect( 1588 fragmentClip, aForFrame->PresContext()->AppUnitsPerDevPixel(), 1589 aDrawTarget)); 1590 1591 RectCornerRadii clipRectRadii; 1592 if (hasBorderRadius) { 1593 Float spreadDistance = Float(shadowSpread / oneDevPixel); 1594 Margin borderSizes(spreadDistance, spreadDistance, spreadDistance, 1595 spreadDistance); 1596 nsCSSBorderRenderer::ComputeOuterRadii(borderRadii, borderSizes, 1597 &clipRectRadii); 1598 } 1599 nsContextBoxBlur::BlurRectangle( 1600 &aRenderingContext, shadowRect, oneDevPixel, 1601 hasBorderRadius ? &clipRectRadii : nullptr, blurRadius, 1602 gfxShadowColor, aDirtyRect, skipGfxRect); 1603 aRenderingContext.Restore(); 1604 } 1605 } 1606 } 1607 1608 nsRect nsCSSRendering::GetBoxShadowInnerPaddingRect(nsIFrame* aFrame, 1609 const nsRect& aFrameArea) { 1610 Sides skipSides = aFrame->GetSkipSides(); 1611 nsRect frameRect = BoxDecorationRectForBorder(aFrame, aFrameArea, skipSides); 1612 1613 nsRect paddingRect = frameRect; 1614 nsMargin border = aFrame->GetUsedBorder(); 1615 paddingRect.Deflate(border); 1616 return paddingRect; 1617 } 1618 1619 bool nsCSSRendering::ShouldPaintBoxShadowInner(nsIFrame* aFrame) { 1620 const Span<const StyleBoxShadow> shadows = 1621 aFrame->StyleEffects()->mBoxShadow.AsSpan(); 1622 if (shadows.IsEmpty()) { 1623 return false; 1624 } 1625 1626 if (aFrame->IsThemed() && aFrame->GetContent() && 1627 !nsContentUtils::IsChromeDoc(aFrame->GetContent()->GetComposedDoc())) { 1628 // There's no way of getting hold of a shape corresponding to a 1629 // "padding-box" for native-themed widgets, so just don't draw 1630 // inner box-shadows for them. But we allow chrome to paint inner 1631 // box shadows since chrome can be aware of the platform theme. 1632 return false; 1633 } 1634 1635 return true; 1636 } 1637 1638 bool nsCSSRendering::GetShadowInnerRadii(nsIFrame* aFrame, 1639 const nsRect& aFrameArea, 1640 RectCornerRadii& aOutInnerRadii) { 1641 // Get any border radius, since box-shadow must also have rounded corners 1642 // if the frame does. 1643 nsRectCornerRadii twipsRadii; 1644 nsRect frameRect = 1645 BoxDecorationRectForBorder(aFrame, aFrameArea, aFrame->GetSkipSides()); 1646 nsSize sz = frameRect.Size(); 1647 nsMargin border = aFrame->GetUsedBorder(); 1648 aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii); 1649 const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1); 1650 1651 RectCornerRadii borderRadii; 1652 1653 const bool hasBorderRadius = 1654 GetBorderRadii(frameRect, aFrameArea, aFrame, borderRadii); 1655 1656 if (hasBorderRadius) { 1657 ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii); 1658 1659 Margin borderSizes( 1660 Float(border.top) / oneDevPixel, Float(border.right) / oneDevPixel, 1661 Float(border.bottom) / oneDevPixel, Float(border.left) / oneDevPixel); 1662 nsCSSBorderRenderer::ComputeInnerRadii(borderRadii, borderSizes, 1663 &aOutInnerRadii); 1664 } 1665 1666 return hasBorderRadius; 1667 } 1668 1669 void nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext, 1670 gfxContext& aRenderingContext, 1671 nsIFrame* aForFrame, 1672 const nsRect& aFrameArea) { 1673 if (!ShouldPaintBoxShadowInner(aForFrame)) { 1674 return; 1675 } 1676 1677 const Span<const StyleBoxShadow> shadows = 1678 aForFrame->StyleEffects()->mBoxShadow.AsSpan(); 1679 NS_ASSERTION( 1680 aForFrame->IsFieldSetFrame() || aFrameArea.Size() == aForFrame->GetSize(), 1681 "unexpected size"); 1682 1683 nsRect paddingRect = GetBoxShadowInnerPaddingRect(aForFrame, aFrameArea); 1684 1685 RectCornerRadii innerRadii; 1686 bool hasBorderRadius = GetShadowInnerRadii(aForFrame, aFrameArea, innerRadii); 1687 1688 const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1); 1689 1690 for (const StyleBoxShadow& shadow : Reversed(shadows)) { 1691 if (!shadow.inset) { 1692 continue; 1693 } 1694 1695 // shadowPaintRect: the area to paint on the temp surface 1696 // shadowClipRect: the area on the temporary surface within shadowPaintRect 1697 // that we will NOT paint in 1698 nscoord blurRadius = shadow.base.blur.ToAppUnits(); 1699 nsMargin blurMargin = 1700 nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel); 1701 nsRect shadowPaintRect = paddingRect; 1702 shadowPaintRect.Inflate(blurMargin); 1703 1704 // Round the spread radius to device pixels (by truncation). 1705 // This mostly matches what we do for borders, except that we don't round 1706 // up values between zero and one device pixels to one device pixel. 1707 // This way of rounding is symmetric around zero, which makes sense for 1708 // the spread radius. 1709 int32_t spreadDistance = shadow.spread.ToAppUnits() / oneDevPixel; 1710 nscoord spreadDistanceAppUnits = 1711 aPresContext->DevPixelsToAppUnits(spreadDistance); 1712 1713 nsRect shadowClipRect = paddingRect; 1714 shadowClipRect.MoveBy(shadow.base.horizontal.ToAppUnits(), 1715 shadow.base.vertical.ToAppUnits()); 1716 shadowClipRect.Deflate(spreadDistanceAppUnits, spreadDistanceAppUnits); 1717 1718 Rect shadowClipGfxRect = NSRectToRect(shadowClipRect, oneDevPixel); 1719 shadowClipGfxRect.Round(); 1720 1721 RectCornerRadii clipRectRadii; 1722 if (hasBorderRadius) { 1723 // Calculate the radii the inner clipping rect will have 1724 Margin borderSizes; 1725 1726 // See PaintBoxShadowOuter and bug 514670 1727 if (innerRadii[C_TL].width > 0 || innerRadii[C_BL].width > 0) { 1728 borderSizes.left = spreadDistance; 1729 } 1730 1731 if (innerRadii[C_TL].height > 0 || innerRadii[C_TR].height > 0) { 1732 borderSizes.top = spreadDistance; 1733 } 1734 1735 if (innerRadii[C_TR].width > 0 || innerRadii[C_BR].width > 0) { 1736 borderSizes.right = spreadDistance; 1737 } 1738 1739 if (innerRadii[C_BL].height > 0 || innerRadii[C_BR].height > 0) { 1740 borderSizes.bottom = spreadDistance; 1741 } 1742 1743 nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes, 1744 &clipRectRadii); 1745 } 1746 1747 // Set the "skip rect" to the area within the frame that we don't paint in, 1748 // including after blurring. 1749 nsRect skipRect = shadowClipRect; 1750 skipRect.Deflate(blurMargin); 1751 gfxRect skipGfxRect = nsLayoutUtils::RectToGfxRect(skipRect, oneDevPixel); 1752 if (hasBorderRadius) { 1753 skipGfxRect.Deflate(gfxMargin( 1754 std::max(clipRectRadii[C_TL].height, clipRectRadii[C_TR].height), 0, 1755 std::max(clipRectRadii[C_BL].height, clipRectRadii[C_BR].height), 0)); 1756 } 1757 1758 // When there's a blur radius, gfxGaussianBlur leaves the skiprect area 1759 // unchanged. And by construction the gfxSkipRect is not touched by the 1760 // rendered shadow (even after blurring), so those pixels must be completely 1761 // transparent in the shadow, so drawing them changes nothing. 1762 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); 1763 1764 // Clip the context to the area of the frame's padding rect, so no part of 1765 // the shadow is painted outside. Also cut out anything beyond where the 1766 // inset shadow will be. 1767 Rect shadowGfxRect = NSRectToRect(paddingRect, oneDevPixel); 1768 shadowGfxRect.Round(); 1769 1770 sRGBColor shadowColor = GetShadowColor(shadow.base, aForFrame, 1.0); 1771 aRenderingContext.Save(); 1772 1773 // This clips the outside border radius. 1774 // clipRectRadii is the border radius inside the inset shadow. 1775 if (hasBorderRadius) { 1776 RefPtr<Path> roundedRect = 1777 MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii); 1778 aRenderingContext.Clip(roundedRect); 1779 } else { 1780 aRenderingContext.Clip(shadowGfxRect); 1781 } 1782 1783 nsContextBoxBlur insetBoxBlur; 1784 gfxRect destRect = 1785 nsLayoutUtils::RectToGfxRect(shadowPaintRect, oneDevPixel); 1786 Point shadowOffset(shadow.base.horizontal.ToAppUnits() / oneDevPixel, 1787 shadow.base.vertical.ToAppUnits() / oneDevPixel); 1788 1789 insetBoxBlur.InsetBoxBlur( 1790 &aRenderingContext, ToRect(destRect), shadowClipGfxRect, shadowColor, 1791 blurRadius, spreadDistanceAppUnits, oneDevPixel, hasBorderRadius, 1792 clipRectRadii, ToRect(skipGfxRect), shadowOffset); 1793 aRenderingContext.Restore(); 1794 } 1795 } 1796 1797 /* static */ 1798 nsCSSRendering::PaintBGParams nsCSSRendering::PaintBGParams::ForAllLayers( 1799 nsPresContext& aPresCtx, const nsRect& aDirtyRect, 1800 const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags, 1801 float aOpacity) { 1802 MOZ_ASSERT(aFrame); 1803 1804 PaintBGParams result(aPresCtx, aDirtyRect, aBorderArea, aFrame, aPaintFlags, 1805 -1, CompositionOp::OP_OVER, aOpacity); 1806 1807 return result; 1808 } 1809 1810 /* static */ 1811 nsCSSRendering::PaintBGParams nsCSSRendering::PaintBGParams::ForSingleLayer( 1812 nsPresContext& aPresCtx, const nsRect& aDirtyRect, 1813 const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags, 1814 int32_t aLayer, CompositionOp aCompositionOp, float aOpacity) { 1815 MOZ_ASSERT(aFrame && (aLayer != -1)); 1816 1817 PaintBGParams result(aPresCtx, aDirtyRect, aBorderArea, aFrame, aPaintFlags, 1818 aLayer, aCompositionOp, aOpacity); 1819 1820 return result; 1821 } 1822 1823 ImgDrawResult nsCSSRendering::PaintStyleImageLayer(const PaintBGParams& aParams, 1824 gfxContext& aRenderingCtx) { 1825 AUTO_PROFILER_LABEL("nsCSSRendering::PaintStyleImageLayer", GRAPHICS); 1826 1827 MOZ_ASSERT(aParams.frame, 1828 "Frame is expected to be provided to PaintStyleImageLayer"); 1829 1830 const ComputedStyle* sc = FindBackground(aParams.frame); 1831 if (!sc) { 1832 // We don't want to bail out if moz-appearance is set on a root 1833 // node. If it has a parent content node, bail because it's not 1834 // a root, otherwise keep going in order to let the theme stuff 1835 // draw the background. The canvas really should be drawing the 1836 // bg, but there's no way to hook that up via css. 1837 if (!aParams.frame->StyleDisplay()->HasAppearance()) { 1838 return ImgDrawResult::SUCCESS; 1839 } 1840 1841 nsIContent* content = aParams.frame->GetContent(); 1842 if (!content || content->GetParent()) { 1843 return ImgDrawResult::SUCCESS; 1844 } 1845 1846 sc = aParams.frame->Style(); 1847 } 1848 1849 return PaintStyleImageLayerWithSC(aParams, aRenderingCtx, sc, 1850 *aParams.frame->StyleBorder()); 1851 } 1852 1853 bool nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer( 1854 WebRenderLayerManager* aManager, nsPresContext& aPresCtx, nsIFrame* aFrame, 1855 const nsStyleBackground* aBackgroundStyle, int32_t aLayer, 1856 uint32_t aPaintFlags) { 1857 if (!aBackgroundStyle) { 1858 return false; 1859 } 1860 1861 MOZ_ASSERT(aFrame && aLayer >= 0 && 1862 (uint32_t)aLayer < aBackgroundStyle->mImage.mLayers.Length()); 1863 1864 // We cannot draw native themed backgrounds 1865 StyleAppearance appearance = aFrame->StyleDisplay()->EffectiveAppearance(); 1866 if (appearance != StyleAppearance::None) { 1867 nsITheme* theme = aPresCtx.Theme(); 1868 if (theme->ThemeSupportsWidget(&aPresCtx, aFrame, appearance)) { 1869 return false; 1870 } 1871 } 1872 1873 // We only support painting gradients and image for a single style image 1874 // layer, and we don't support crop-rects. 1875 const auto& styleImage = 1876 aBackgroundStyle->mImage.mLayers[aLayer].mImage.FinalImage(); 1877 if (styleImage.IsImageRequestType()) { 1878 imgRequestProxy* requestProxy = styleImage.GetImageRequest(); 1879 if (!requestProxy) { 1880 return false; 1881 } 1882 1883 uint32_t imageFlags = imgIContainer::FLAG_NONE; 1884 if (aPaintFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) { 1885 imageFlags |= imgIContainer::FLAG_SYNC_DECODE; 1886 } 1887 1888 nsCOMPtr<imgIContainer> srcImage; 1889 requestProxy->GetImage(getter_AddRefs(srcImage)); 1890 if (!srcImage || 1891 !srcImage->IsImageContainerAvailable(aManager, imageFlags)) { 1892 return false; 1893 } 1894 1895 return true; 1896 } 1897 1898 if (styleImage.IsGradient()) { 1899 return true; 1900 } 1901 1902 return false; 1903 } 1904 1905 ImgDrawResult nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer( 1906 const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder, 1907 mozilla::wr::IpcResourceUpdateQueue& aResources, 1908 const mozilla::layers::StackingContextHelper& aSc, 1909 mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem) { 1910 MOZ_ASSERT(aParams.frame, 1911 "Frame is expected to be provided to " 1912 "BuildWebRenderDisplayItemsForStyleImageLayer"); 1913 1914 ComputedStyle* sc = FindBackground(aParams.frame); 1915 if (!sc) { 1916 // We don't want to bail out if moz-appearance is set on a root 1917 // node. If it has a parent content node, bail because it's not 1918 // a root, otherwise keep going in order to let the theme stuff 1919 // draw the background. The canvas really should be drawing the 1920 // bg, but there's no way to hook that up via css. 1921 if (!aParams.frame->StyleDisplay()->HasAppearance()) { 1922 return ImgDrawResult::SUCCESS; 1923 } 1924 1925 nsIContent* content = aParams.frame->GetContent(); 1926 if (!content || content->GetParent()) { 1927 return ImgDrawResult::SUCCESS; 1928 } 1929 1930 sc = aParams.frame->Style(); 1931 } 1932 return BuildWebRenderDisplayItemsForStyleImageLayerWithSC( 1933 aParams, aBuilder, aResources, aSc, aManager, aItem, sc, 1934 *aParams.frame->StyleBorder()); 1935 } 1936 1937 static bool IsOpaqueBorderEdge(const nsStyleBorder& aBorder, 1938 mozilla::Side aSide, const nsIFrame* aForFrame) { 1939 if (aBorder.GetComputedBorder().Side(aSide) == 0) { 1940 return true; 1941 } 1942 switch (aBorder.GetBorderStyle(aSide)) { 1943 case StyleBorderStyle::Solid: 1944 case StyleBorderStyle::Groove: 1945 case StyleBorderStyle::Ridge: 1946 case StyleBorderStyle::Inset: 1947 case StyleBorderStyle::Outset: 1948 break; 1949 default: 1950 return false; 1951 } 1952 1953 // If we're using a border image, assume it's not fully opaque, 1954 // because we may not even have the image loaded at this point, and 1955 // even if we did, checking whether the relevant tile is fully 1956 // opaque would be too much work. 1957 if (!aBorder.mBorderImageSource.IsNone()) { 1958 return false; 1959 } 1960 return NS_GET_A(aBorder.BorderColorFor(aSide).CalcColor(aForFrame)) == 255; 1961 } 1962 1963 /** 1964 * Returns true if all border edges are either missing or opaque. 1965 */ 1966 static bool IsOpaqueBorder(const nsStyleBorder& aBorder, 1967 const nsIFrame* aForFrame) { 1968 for (const auto i : mozilla::AllPhysicalSides()) { 1969 if (!IsOpaqueBorderEdge(aBorder, i, aForFrame)) { 1970 return false; 1971 } 1972 } 1973 return true; 1974 } 1975 1976 static inline void SetupDirtyRects(const nsRect& aBGClipArea, 1977 const nsRect& aCallerDirtyRect, 1978 nscoord aAppUnitsPerPixel, 1979 /* OUT: */ 1980 nsRect* aDirtyRect, gfxRect* aDirtyRectGfx) { 1981 aDirtyRect->IntersectRect(aBGClipArea, aCallerDirtyRect); 1982 1983 // Compute the Thebes equivalent of the dirtyRect. 1984 *aDirtyRectGfx = nsLayoutUtils::RectToGfxRect(*aDirtyRect, aAppUnitsPerPixel); 1985 NS_WARNING_ASSERTION(aDirtyRect->IsEmpty() || !aDirtyRectGfx->IsEmpty(), 1986 "converted dirty rect should not be empty"); 1987 MOZ_ASSERT(!aDirtyRect->IsEmpty() || aDirtyRectGfx->IsEmpty(), 1988 "second should be empty if first is"); 1989 } 1990 1991 static bool IsSVGStyleGeometryBox(StyleGeometryBox aBox) { 1992 return (aBox == StyleGeometryBox::FillBox || 1993 aBox == StyleGeometryBox::StrokeBox || 1994 aBox == StyleGeometryBox::ViewBox); 1995 } 1996 1997 static bool IsHTMLStyleGeometryBox(StyleGeometryBox aBox) { 1998 return (aBox == StyleGeometryBox::ContentBox || 1999 aBox == StyleGeometryBox::PaddingBox || 2000 aBox == StyleGeometryBox::BorderBox || 2001 aBox == StyleGeometryBox::MarginBox); 2002 } 2003 2004 static StyleGeometryBox ComputeBoxValueForOrigin(nsIFrame* aForFrame, 2005 StyleGeometryBox aBox) { 2006 // The mapping for mask-origin is from 2007 // https://drafts.fxtf.org/css-masking/#the-mask-origin 2008 if (!aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { 2009 // For elements with associated CSS layout box, the values fill-box, 2010 // stroke-box and view-box compute to the initial value of mask-origin. 2011 if (IsSVGStyleGeometryBox(aBox)) { 2012 return StyleGeometryBox::BorderBox; 2013 } 2014 } else { 2015 // For SVG elements without associated CSS layout box, the values 2016 // content-box, padding-box, border-box compute to fill-box. 2017 if (IsHTMLStyleGeometryBox(aBox)) { 2018 return StyleGeometryBox::FillBox; 2019 } 2020 } 2021 2022 return aBox; 2023 } 2024 2025 static StyleGeometryBox ComputeBoxValueForClip(const nsIFrame* aForFrame, 2026 StyleGeometryBox aBox) { 2027 // The mapping for mask-clip is from 2028 // https://drafts.fxtf.org/css-masking/#the-mask-clip 2029 if (aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { 2030 // For SVG elements without associated CSS layout box, the used values for 2031 // content-box and padding-box compute to fill-box and for border-box and 2032 // margin-box compute to stroke-box. 2033 switch (aBox) { 2034 case StyleGeometryBox::ContentBox: 2035 case StyleGeometryBox::PaddingBox: 2036 return StyleGeometryBox::FillBox; 2037 case StyleGeometryBox::BorderBox: 2038 case StyleGeometryBox::MarginBox: 2039 return StyleGeometryBox::StrokeBox; 2040 default: 2041 return aBox; 2042 } 2043 } 2044 2045 // For elements with associated CSS layout box, the used values for fill-box 2046 // compute to content-box and for stroke-box and view-box compute to 2047 // border-box. 2048 switch (aBox) { 2049 case StyleGeometryBox::FillBox: 2050 return StyleGeometryBox::ContentBox; 2051 case StyleGeometryBox::StrokeBox: 2052 case StyleGeometryBox::ViewBox: 2053 return StyleGeometryBox::BorderBox; 2054 default: 2055 return aBox; 2056 } 2057 } 2058 2059 bool nsCSSRendering::ImageLayerClipState::IsValid() const { 2060 // mDirtyRectInDevPx comes from mDirtyRectInAppUnits. mDirtyRectInAppUnits 2061 // can not be empty if mDirtyRectInDevPx is not. 2062 if (!mDirtyRectInDevPx.IsEmpty() && mDirtyRectInAppUnits.IsEmpty()) { 2063 return false; 2064 } 2065 2066 if (mHasRoundedCorners == mClippedRadii.IsEmpty()) { 2067 return false; 2068 } 2069 2070 return true; 2071 } 2072 2073 /* static */ 2074 void nsCSSRendering::GetImageLayerClip( 2075 const nsStyleImageLayers::Layer& aLayer, nsIFrame* aForFrame, 2076 const nsStyleBorder& aBorder, const nsRect& aBorderArea, 2077 const nsRect& aCallerDirtyRect, bool aWillPaintBorder, 2078 nscoord aAppUnitsPerPixel, 2079 /* out */ ImageLayerClipState* aClipState) { 2080 StyleGeometryBox layerClip = ComputeBoxValueForClip(aForFrame, aLayer.mClip); 2081 if (IsSVGStyleGeometryBox(layerClip)) { 2082 MOZ_ASSERT(aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)); 2083 2084 // The coordinate space of clipArea is svg user space. 2085 nsRect clipArea = 2086 nsLayoutUtils::ComputeSVGReferenceRect(aForFrame, layerClip); 2087 2088 nsRect strokeBox = (layerClip == StyleGeometryBox::StrokeBox) 2089 ? clipArea 2090 : nsLayoutUtils::ComputeSVGReferenceRect( 2091 aForFrame, StyleGeometryBox::StrokeBox); 2092 nsRect clipAreaRelativeToStrokeBox = clipArea - strokeBox.TopLeft(); 2093 2094 // aBorderArea is the stroke-box area in a coordinate space defined by 2095 // the caller. This coordinate space can be svg user space of aForFrame, 2096 // the space of aForFrame's reference-frame, or anything else. 2097 // 2098 // Which coordinate space chosen for aBorderArea is not matter. What 2099 // matter is to ensure returning aClipState->mBGClipArea in the consistent 2100 // coordiante space with aBorderArea. So we evaluate the position of clip 2101 // area base on the position of aBorderArea here. 2102 aClipState->mBGClipArea = 2103 clipAreaRelativeToStrokeBox + aBorderArea.TopLeft(); 2104 2105 SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, 2106 aAppUnitsPerPixel, &aClipState->mDirtyRectInAppUnits, 2107 &aClipState->mDirtyRectInDevPx); 2108 MOZ_ASSERT(aClipState->IsValid()); 2109 return; 2110 } 2111 2112 if (layerClip == StyleGeometryBox::NoClip) { 2113 aClipState->mBGClipArea = aCallerDirtyRect; 2114 2115 SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, 2116 aAppUnitsPerPixel, &aClipState->mDirtyRectInAppUnits, 2117 &aClipState->mDirtyRectInDevPx); 2118 MOZ_ASSERT(aClipState->IsValid()); 2119 return; 2120 } 2121 2122 MOZ_ASSERT(!aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)); 2123 2124 // Compute the outermost boundary of the area that might be painted. 2125 // Same coordinate space as aBorderArea. 2126 Sides skipSides = aForFrame->GetSkipSides(); 2127 nsRect clipBorderArea = 2128 BoxDecorationRectForBorder(aForFrame, aBorderArea, skipSides, &aBorder); 2129 2130 bool haveRoundedCorners = false; 2131 LayoutFrameType fType = aForFrame->Type(); 2132 if (fType != LayoutFrameType::TableColGroup && 2133 fType != LayoutFrameType::TableCol && 2134 fType != LayoutFrameType::TableRow && 2135 fType != LayoutFrameType::TableRowGroup) { 2136 haveRoundedCorners = GetRadii(aForFrame, aBorder, aBorderArea, 2137 clipBorderArea, aClipState->mRadii); 2138 } 2139 const bool isSolidBorder = 2140 aWillPaintBorder && IsOpaqueBorder(aBorder, aForFrame); 2141 if (isSolidBorder && layerClip == StyleGeometryBox::BorderBox) { 2142 // If we have rounded corners, we need to inflate the background 2143 // drawing area a bit to avoid seams between the border and 2144 // background. 2145 layerClip = haveRoundedCorners ? StyleGeometryBox::MozAlmostPadding 2146 : StyleGeometryBox::PaddingBox; 2147 } 2148 2149 aClipState->mBGClipArea = clipBorderArea; 2150 2151 if (aForFrame->IsScrollContainerFrame() && 2152 StyleImageLayerAttachment::Local == aLayer.mAttachment) { 2153 // As of this writing, this is still in discussion in the CSS Working Group 2154 // http://lists.w3.org/Archives/Public/www-style/2013Jul/0250.html 2155 2156 // The rectangle for 'background-clip' scrolls with the content, 2157 // but the background is also clipped at a non-scrolling 'padding-box' 2158 // like the content. (See below.) 2159 // Therefore, only 'content-box' makes a difference here. 2160 if (layerClip == StyleGeometryBox::ContentBox) { 2161 ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(aForFrame); 2162 // Clip at a rectangle attached to the scrolled content. 2163 aClipState->mHasAdditionalBGClipArea = true; 2164 aClipState->mAdditionalBGClipArea = 2165 nsRect(aClipState->mBGClipArea.TopLeft() + 2166 scrollContainerFrame->GetScrolledFrame()->GetPosition() 2167 // For the dir=rtl case: 2168 + scrollContainerFrame->GetScrollRange().TopLeft(), 2169 scrollContainerFrame->GetScrolledRect().Size()); 2170 nsMargin padding = aForFrame->GetUsedPadding(); 2171 // padding-bottom is ignored on scrollable frames: 2172 // https://bugzilla.mozilla.org/show_bug.cgi?id=748518 2173 padding.bottom = 0; 2174 padding.ApplySkipSides(skipSides); 2175 aClipState->mAdditionalBGClipArea.Deflate(padding); 2176 } 2177 2178 // Also clip at a non-scrolling, rounded-corner 'padding-box', 2179 // same as the scrolled content because of the 'overflow' property. 2180 layerClip = StyleGeometryBox::PaddingBox; 2181 } 2182 2183 // See the comment of StyleGeometryBox::Margin. 2184 // Hitting this assertion means we decide to turn on margin-box support for 2185 // positioned mask from CSS parser and style system. In this case, you 2186 // should *inflate* mBGClipArea by the margin returning from 2187 // aForFrame->GetUsedMargin() in the code chunk bellow. 2188 MOZ_ASSERT(layerClip != StyleGeometryBox::MarginBox, 2189 "StyleGeometryBox::MarginBox rendering is not supported yet.\n"); 2190 2191 if (layerClip != StyleGeometryBox::BorderBox && 2192 layerClip != StyleGeometryBox::Text) { 2193 nsMargin border = aForFrame->GetUsedBorder(); 2194 if (layerClip == StyleGeometryBox::MozAlmostPadding) { 2195 // Reduce |border| by 1px (device pixels) on all sides, if 2196 // possible, so that we don't get antialiasing seams between the 2197 // {background|mask} and border. 2198 border.top = std::max(0, border.top - aAppUnitsPerPixel); 2199 border.right = std::max(0, border.right - aAppUnitsPerPixel); 2200 border.bottom = std::max(0, border.bottom - aAppUnitsPerPixel); 2201 border.left = std::max(0, border.left - aAppUnitsPerPixel); 2202 } else if (layerClip != StyleGeometryBox::PaddingBox) { 2203 NS_ASSERTION(layerClip == StyleGeometryBox::ContentBox, 2204 "unexpected background-clip"); 2205 border += aForFrame->GetUsedPadding(); 2206 } 2207 border.ApplySkipSides(skipSides); 2208 aClipState->mBGClipArea.Deflate(border); 2209 2210 if (haveRoundedCorners) { 2211 aClipState->mRadii.AdjustInwards(border); 2212 } 2213 } 2214 2215 if (haveRoundedCorners) { 2216 auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel(); 2217 nsCSSRendering::ComputePixelRadii(aClipState->mRadii, d2a, 2218 &aClipState->mClippedRadii); 2219 aClipState->mHasRoundedCorners = !aClipState->mClippedRadii.IsEmpty(); 2220 } 2221 2222 if (!haveRoundedCorners && aClipState->mHasAdditionalBGClipArea) { 2223 // Do the intersection here to account for the fast path(?) below. 2224 aClipState->mBGClipArea = 2225 aClipState->mBGClipArea.Intersect(aClipState->mAdditionalBGClipArea); 2226 aClipState->mHasAdditionalBGClipArea = false; 2227 } 2228 2229 SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, aAppUnitsPerPixel, 2230 &aClipState->mDirtyRectInAppUnits, 2231 &aClipState->mDirtyRectInDevPx); 2232 2233 MOZ_ASSERT(aClipState->IsValid()); 2234 } 2235 2236 static void SetupImageLayerClip(nsCSSRendering::ImageLayerClipState& aClipState, 2237 gfxContext* aCtx, nscoord aAppUnitsPerPixel, 2238 gfxContextAutoSaveRestore* aAutoSR) { 2239 if (aClipState.mDirtyRectInDevPx.IsEmpty()) { 2240 // Our caller won't draw anything under this condition, so no need 2241 // to set more up. 2242 return; 2243 } 2244 2245 if (aClipState.mCustomClip) { 2246 // We don't support custom clips and rounded corners, arguably a bug, but 2247 // table painting seems to depend on it. 2248 return; 2249 } 2250 2251 // If we have rounded corners, clip all subsequent drawing to the 2252 // rounded rectangle defined by bgArea and bgRadii (we don't know 2253 // whether the rounded corners intrude on the dirtyRect or not). 2254 // Do not do this if we have a caller-provided clip rect -- 2255 // as above with bgArea, arguably a bug, but table painting seems 2256 // to depend on it. 2257 2258 if (aClipState.mHasAdditionalBGClipArea) { 2259 gfxRect bgAreaGfx = nsLayoutUtils::RectToGfxRect( 2260 aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel); 2261 bgAreaGfx.Round(); 2262 gfxUtils::ConditionRect(bgAreaGfx); 2263 2264 aAutoSR->EnsureSaved(aCtx); 2265 aCtx->SnappedClip(bgAreaGfx); 2266 } 2267 2268 if (aClipState.mHasRoundedCorners) { 2269 Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel); 2270 bgAreaGfx.Round(); 2271 2272 if (bgAreaGfx.IsEmpty()) { 2273 // I think it's become possible to hit this since 2274 // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed. 2275 NS_WARNING("converted background area should not be empty"); 2276 // Make our caller not do anything. 2277 aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0)); 2278 return; 2279 } 2280 2281 aAutoSR->EnsureSaved(aCtx); 2282 2283 RefPtr<Path> roundedRect = MakePathForRoundedRect( 2284 *aCtx->GetDrawTarget(), bgAreaGfx, aClipState.mClippedRadii); 2285 aCtx->Clip(roundedRect); 2286 } 2287 } 2288 2289 static void DrawBackgroundColor(nsCSSRendering::ImageLayerClipState& aClipState, 2290 gfxContext* aCtx, nscoord aAppUnitsPerPixel) { 2291 if (aClipState.mDirtyRectInDevPx.IsEmpty()) { 2292 // Our caller won't draw anything under this condition, so no need 2293 // to set more up. 2294 return; 2295 } 2296 2297 DrawTarget* drawTarget = aCtx->GetDrawTarget(); 2298 2299 // We don't support custom clips and rounded corners, arguably a bug, but 2300 // table painting seems to depend on it. 2301 if (!aClipState.mHasRoundedCorners || aClipState.mCustomClip) { 2302 aCtx->NewPath(); 2303 aCtx->SnappedRectangle(aClipState.mDirtyRectInDevPx); 2304 aCtx->Fill(); 2305 return; 2306 } 2307 2308 Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel); 2309 bgAreaGfx.Round(); 2310 2311 if (bgAreaGfx.IsEmpty()) { 2312 // I think it's become possible to hit this since 2313 // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed. 2314 NS_WARNING("converted background area should not be empty"); 2315 // Make our caller not do anything. 2316 aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0)); 2317 return; 2318 } 2319 2320 aCtx->Save(); 2321 gfxRect dirty = ThebesRect(bgAreaGfx).Intersect(aClipState.mDirtyRectInDevPx); 2322 2323 aCtx->SnappedClip(dirty); 2324 2325 if (aClipState.mHasAdditionalBGClipArea) { 2326 gfxRect bgAdditionalAreaGfx = nsLayoutUtils::RectToGfxRect( 2327 aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel); 2328 bgAdditionalAreaGfx.Round(); 2329 gfxUtils::ConditionRect(bgAdditionalAreaGfx); 2330 aCtx->SnappedClip(bgAdditionalAreaGfx); 2331 } 2332 2333 RefPtr<Path> roundedRect = 2334 MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii); 2335 aCtx->SetPath(roundedRect); 2336 aCtx->Fill(); 2337 aCtx->Restore(); 2338 } 2339 2340 enum class ScrollbarColorKind { 2341 Thumb, 2342 Track, 2343 }; 2344 2345 static Maybe<nscolor> CalcScrollbarColor(nsIFrame* aFrame, 2346 ScrollbarColorKind aKind) { 2347 ComputedStyle* scrollbarStyle = nsLayoutUtils::StyleForScrollbar(aFrame); 2348 const auto& colors = scrollbarStyle->StyleUI()->mScrollbarColor; 2349 if (colors.IsAuto()) { 2350 return Nothing(); 2351 } 2352 const auto& color = aKind == ScrollbarColorKind::Thumb 2353 ? colors.AsColors().thumb 2354 : colors.AsColors().track; 2355 return Some(color.CalcColor(*scrollbarStyle)); 2356 } 2357 2358 static nscolor GetBackgroundColor(nsIFrame* aFrame, 2359 const ComputedStyle* aStyle) { 2360 switch (aStyle->StyleDisplay()->EffectiveAppearance()) { 2361 case StyleAppearance::ScrollbarthumbVertical: 2362 case StyleAppearance::ScrollbarthumbHorizontal: { 2363 if (Maybe<nscolor> overrideColor = 2364 CalcScrollbarColor(aFrame, ScrollbarColorKind::Thumb)) { 2365 return *overrideColor; 2366 } 2367 break; 2368 } 2369 case StyleAppearance::ScrollbarVertical: 2370 case StyleAppearance::ScrollbarHorizontal: 2371 case StyleAppearance::Scrollcorner: { 2372 if (Maybe<nscolor> overrideColor = 2373 CalcScrollbarColor(aFrame, ScrollbarColorKind::Track)) { 2374 return *overrideColor; 2375 } 2376 break; 2377 } 2378 default: 2379 break; 2380 } 2381 return aStyle->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor); 2382 } 2383 2384 nscolor nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext, 2385 const ComputedStyle* aStyle, 2386 nsIFrame* aFrame, 2387 bool& aDrawBackgroundImage, 2388 bool& aDrawBackgroundColor) { 2389 auto shouldPaint = aFrame->ComputeShouldPaintBackground(); 2390 aDrawBackgroundImage = shouldPaint.mImage; 2391 aDrawBackgroundColor = shouldPaint.mColor; 2392 2393 const nsStyleBackground* bg = aStyle->StyleBackground(); 2394 nscolor bgColor; 2395 if (aDrawBackgroundColor) { 2396 bgColor = GetBackgroundColor(aFrame, aStyle); 2397 if (NS_GET_A(bgColor) == 0) { 2398 aDrawBackgroundColor = false; 2399 } 2400 } else { 2401 // If GetBackgroundColorDraw() is false, we are still expected to 2402 // draw color in the background of any frame that's not completely 2403 // transparent, but we are expected to use white instead of whatever 2404 // color was specified. 2405 bgColor = NS_RGB(255, 255, 255); 2406 if (aDrawBackgroundImage || !bg->IsTransparent(aStyle)) { 2407 aDrawBackgroundColor = true; 2408 } else { 2409 bgColor = NS_RGBA(0, 0, 0, 0); 2410 } 2411 } 2412 2413 // We can skip painting the background color if a background image is opaque. 2414 nsStyleImageLayers::Repeat repeat = bg->BottomLayer().mRepeat; 2415 bool xFullRepeat = repeat.mXRepeat == StyleImageLayerRepeat::Repeat || 2416 repeat.mXRepeat == StyleImageLayerRepeat::Round; 2417 bool yFullRepeat = repeat.mYRepeat == StyleImageLayerRepeat::Repeat || 2418 repeat.mYRepeat == StyleImageLayerRepeat::Round; 2419 if (aDrawBackgroundColor && xFullRepeat && yFullRepeat && 2420 bg->BottomLayer().mImage.IsOpaque() && 2421 bg->BottomLayer().mBlendMode == StyleBlend::Normal) { 2422 aDrawBackgroundColor = false; 2423 } 2424 2425 return bgColor; 2426 } 2427 2428 static CompositionOp DetermineCompositionOp( 2429 const nsCSSRendering::PaintBGParams& aParams, 2430 const nsStyleImageLayers& aLayers, uint32_t aLayerIndex) { 2431 if (aParams.layer >= 0) { 2432 // When drawing a single layer, use the specified composition op. 2433 return aParams.compositionOp; 2434 } 2435 2436 const nsStyleImageLayers::Layer& layer = aLayers.mLayers[aLayerIndex]; 2437 // When drawing all layers, get the compositon op from each image layer. 2438 if (aParams.paintFlags & nsCSSRendering::PAINTBG_MASK_IMAGE) { 2439 // Always using OP_OVER mode while drawing the bottom mask layer. 2440 if (aLayerIndex == (aLayers.mImageCount - 1)) { 2441 return CompositionOp::OP_OVER; 2442 } 2443 2444 return nsCSSRendering::GetGFXCompositeMode(layer.mComposite); 2445 } 2446 2447 return nsCSSRendering::GetGFXBlendMode(layer.mBlendMode); 2448 } 2449 2450 ImgDrawResult nsCSSRendering::PaintStyleImageLayerWithSC( 2451 const PaintBGParams& aParams, gfxContext& aRenderingCtx, 2452 const ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) { 2453 MOZ_ASSERT(aParams.frame, 2454 "Frame is expected to be provided to PaintStyleImageLayerWithSC"); 2455 2456 // If we're drawing all layers, aCompositonOp is ignored, so make sure that 2457 // it was left at its default value. 2458 MOZ_ASSERT(aParams.layer != -1 || 2459 aParams.compositionOp == CompositionOp::OP_OVER); 2460 2461 // Check to see if we have an appearance defined. If so, we let the theme 2462 // renderer draw the background and bail out. 2463 // XXXzw this ignores aParams.bgClipRect. 2464 StyleAppearance appearance = 2465 aParams.frame->StyleDisplay()->EffectiveAppearance(); 2466 if (appearance != StyleAppearance::None) { 2467 nsITheme* theme = aParams.presCtx.Theme(); 2468 if (theme->ThemeSupportsWidget(&aParams.presCtx, aParams.frame, 2469 appearance)) { 2470 nsRect drawing(aParams.borderArea); 2471 theme->GetWidgetOverflow(aParams.presCtx.DeviceContext(), aParams.frame, 2472 appearance, &drawing); 2473 drawing.IntersectRect(drawing, aParams.dirtyRect); 2474 theme->DrawWidgetBackground(&aRenderingCtx, aParams.frame, appearance, 2475 aParams.borderArea, drawing); 2476 return ImgDrawResult::SUCCESS; 2477 } 2478 } 2479 2480 // For canvas frames (in the CSS sense) we draw the background color using 2481 // a solid color item that gets added in nsLayoutUtils::PaintFrame, or 2482 // nsSubDocumentFrame::BuildDisplayList (bug 488242). Either way we don't 2483 // need to paint the background color here. 2484 bool isCanvasFrame = aParams.frame->IsCanvasFrame(); 2485 const bool paintMask = aParams.paintFlags & PAINTBG_MASK_IMAGE; 2486 2487 // Determine whether we are drawing background images and/or 2488 // background colors. 2489 bool drawBackgroundImage = true; 2490 bool drawBackgroundColor = !paintMask; 2491 nscolor bgColor = NS_RGBA(0, 0, 0, 0); 2492 if (!paintMask) { 2493 bgColor = 2494 DetermineBackgroundColor(&aParams.presCtx, aBackgroundSC, aParams.frame, 2495 drawBackgroundImage, drawBackgroundColor); 2496 } 2497 2498 // Masks shouldn't be suppressed for print. 2499 MOZ_ASSERT_IF(paintMask, drawBackgroundImage); 2500 2501 const nsStyleImageLayers& layers = 2502 paintMask ? aBackgroundSC->StyleSVGReset()->mMask 2503 : aBackgroundSC->StyleBackground()->mImage; 2504 // If we're drawing a specific layer, we don't want to draw the 2505 // background color. 2506 if (drawBackgroundColor && aParams.layer >= 0) { 2507 drawBackgroundColor = false; 2508 } 2509 2510 // At this point, drawBackgroundImage and drawBackgroundColor are 2511 // true if and only if we are actually supposed to paint an image or 2512 // color into aDirtyRect, respectively. 2513 if (!drawBackgroundImage && !drawBackgroundColor) { 2514 return ImgDrawResult::SUCCESS; 2515 } 2516 2517 // The 'bgClipArea' (used only by the image tiling logic, far below) 2518 // is the caller-provided aParams.bgClipRect if any, or else the area 2519 // determined by the value of 'background-clip' in 2520 // SetupCurrentBackgroundClip. (Arguably it should be the 2521 // intersection, but that breaks the table painter -- in particular, 2522 // taking the intersection breaks reftests/bugs/403249-1[ab].) 2523 nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel(); 2524 ImageLayerClipState clipState; 2525 if (aParams.bgClipRect) { 2526 clipState.mBGClipArea = *aParams.bgClipRect; 2527 clipState.mCustomClip = true; 2528 clipState.mHasRoundedCorners = false; 2529 SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel, 2530 &clipState.mDirtyRectInAppUnits, 2531 &clipState.mDirtyRectInDevPx); 2532 } else { 2533 GetImageLayerClip(layers.BottomLayer(), aParams.frame, aBorder, 2534 aParams.borderArea, aParams.dirtyRect, 2535 (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER), 2536 appUnitsPerPixel, &clipState); 2537 } 2538 2539 // If we might be using a background color, go ahead and set it now. 2540 if (drawBackgroundColor && !isCanvasFrame) { 2541 aRenderingCtx.SetColor(sRGBColor::FromABGR(bgColor)); 2542 } 2543 2544 // If there is no background image, draw a color. (If there is 2545 // neither a background image nor a color, we wouldn't have gotten 2546 // this far.) 2547 if (!drawBackgroundImage) { 2548 if (!isCanvasFrame) { 2549 DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel); 2550 } 2551 return ImgDrawResult::SUCCESS; 2552 } 2553 2554 if (layers.mImageCount < 1) { 2555 // Return if there are no background layers, all work from this point 2556 // onwards happens iteratively on these. 2557 return ImgDrawResult::SUCCESS; 2558 } 2559 2560 MOZ_ASSERT((aParams.layer < 0) || 2561 (layers.mImageCount > uint32_t(aParams.layer))); 2562 2563 // The background color is rendered over the entire dirty area, 2564 // even if the image isn't. 2565 if (drawBackgroundColor && !isCanvasFrame) { 2566 DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel); 2567 } 2568 2569 // Compute the outermost boundary of the area that might be painted. 2570 // Same coordinate space as aParams.borderArea & aParams.bgClipRect. 2571 Sides skipSides = aParams.frame->GetSkipSides(); 2572 nsRect paintBorderArea = BoxDecorationRectForBackground( 2573 aParams.frame, aParams.borderArea, skipSides, &aBorder); 2574 nsRect clipBorderArea = BoxDecorationRectForBorder( 2575 aParams.frame, aParams.borderArea, skipSides, &aBorder); 2576 2577 ImgDrawResult result = ImgDrawResult::SUCCESS; 2578 StyleGeometryBox currentBackgroundClip = StyleGeometryBox::BorderBox; 2579 const bool drawAllLayers = (aParams.layer < 0); 2580 uint32_t count = drawAllLayers 2581 ? layers.mImageCount // iterate all image layers. 2582 : layers.mImageCount - 2583 aParams.layer; // iterate from the bottom layer to 2584 // the 'aParams.layer-th' layer. 2585 NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE( 2586 i, layers, layers.mImageCount - 1, count) { 2587 // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved(ctx) 2588 // in the cases we need it. 2589 gfxContextAutoSaveRestore autoSR; 2590 const nsStyleImageLayers::Layer& layer = layers.mLayers[i]; 2591 2592 ImageLayerClipState currentLayerClipState = clipState; 2593 if (!aParams.bgClipRect) { 2594 bool isBottomLayer = (i == layers.mImageCount - 1); 2595 if (currentBackgroundClip != layer.mClip || isBottomLayer) { 2596 currentBackgroundClip = layer.mClip; 2597 if (!isBottomLayer) { 2598 currentLayerClipState = {}; 2599 // For the bottom layer, we already called GetImageLayerClip above 2600 // and it stored its results in clipState. 2601 GetImageLayerClip(layer, aParams.frame, aBorder, aParams.borderArea, 2602 aParams.dirtyRect, 2603 (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER), 2604 appUnitsPerPixel, ¤tLayerClipState); 2605 } 2606 SetupImageLayerClip(currentLayerClipState, &aRenderingCtx, 2607 appUnitsPerPixel, &autoSR); 2608 if (!clipBorderArea.IsEqualEdges(aParams.borderArea)) { 2609 // We're drawing the background for the joined continuation boxes 2610 // so we need to clip that to the slice that we want for this 2611 // frame. 2612 gfxRect clip = nsLayoutUtils::RectToGfxRect(aParams.borderArea, 2613 appUnitsPerPixel); 2614 autoSR.EnsureSaved(&aRenderingCtx); 2615 aRenderingCtx.SnappedClip(clip); 2616 } 2617 } 2618 } 2619 2620 // Skip the following layer preparing and painting code if the current 2621 // layer is not selected for drawing. 2622 if (aParams.layer >= 0 && i != (uint32_t)aParams.layer) { 2623 continue; 2624 } 2625 nsBackgroundLayerState state = PrepareImageLayer( 2626 &aParams.presCtx, aParams.frame, aParams.paintFlags, paintBorderArea, 2627 currentLayerClipState.mBGClipArea, layer, nullptr); 2628 result &= state.mImageRenderer.PrepareResult(); 2629 2630 // Skip the layer painting code if we found the dirty region is empty. 2631 if (currentLayerClipState.mDirtyRectInDevPx.IsEmpty()) { 2632 continue; 2633 } 2634 2635 if (!state.mFillArea.IsEmpty()) { 2636 CompositionOp co = DetermineCompositionOp(aParams, layers, i); 2637 if (co != CompositionOp::OP_OVER) { 2638 NS_ASSERTION(aRenderingCtx.CurrentOp() == CompositionOp::OP_OVER, 2639 "It is assumed the initial op is OP_OVER, when it is " 2640 "restored later"); 2641 aRenderingCtx.SetOp(co); 2642 } 2643 2644 result &= state.mImageRenderer.DrawLayer( 2645 &aParams.presCtx, aRenderingCtx, state.mDestArea, state.mFillArea, 2646 state.mAnchor + paintBorderArea.TopLeft(), 2647 currentLayerClipState.mDirtyRectInAppUnits, state.mRepeatSize, 2648 aParams.opacity); 2649 2650 if (co != CompositionOp::OP_OVER) { 2651 aRenderingCtx.SetOp(CompositionOp::OP_OVER); 2652 } 2653 } 2654 } 2655 2656 return result; 2657 } 2658 2659 ImgDrawResult 2660 nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayerWithSC( 2661 const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder, 2662 mozilla::wr::IpcResourceUpdateQueue& aResources, 2663 const mozilla::layers::StackingContextHelper& aSc, 2664 mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem, 2665 ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) { 2666 MOZ_ASSERT(!(aParams.paintFlags & PAINTBG_MASK_IMAGE)); 2667 2668 nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel(); 2669 ImageLayerClipState clipState; 2670 2671 clipState.mBGClipArea = *aParams.bgClipRect; 2672 clipState.mCustomClip = true; 2673 clipState.mHasRoundedCorners = false; 2674 SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel, 2675 &clipState.mDirtyRectInAppUnits, 2676 &clipState.mDirtyRectInDevPx); 2677 2678 // Compute the outermost boundary of the area that might be painted. 2679 // Same coordinate space as aParams.borderArea & aParams.bgClipRect. 2680 Sides skipSides = aParams.frame->GetSkipSides(); 2681 nsRect paintBorderArea = BoxDecorationRectForBackground( 2682 aParams.frame, aParams.borderArea, skipSides, &aBorder); 2683 2684 const nsStyleImageLayers& layers = aBackgroundSC->StyleBackground()->mImage; 2685 const nsStyleImageLayers::Layer& layer = layers.mLayers[aParams.layer]; 2686 2687 // Skip the following layer painting code if we found the dirty region is 2688 // empty or the current layer is not selected for drawing. 2689 if (clipState.mDirtyRectInDevPx.IsEmpty()) { 2690 return ImgDrawResult::SUCCESS; 2691 } 2692 2693 ImgDrawResult result = ImgDrawResult::SUCCESS; 2694 nsBackgroundLayerState state = 2695 PrepareImageLayer(&aParams.presCtx, aParams.frame, aParams.paintFlags, 2696 paintBorderArea, clipState.mBGClipArea, layer, nullptr); 2697 result &= state.mImageRenderer.PrepareResult(); 2698 2699 if (!state.mFillArea.IsEmpty()) { 2700 result &= state.mImageRenderer.BuildWebRenderDisplayItemsForLayer( 2701 &aParams.presCtx, aBuilder, aResources, aSc, aManager, aItem, 2702 state.mDestArea, state.mFillArea, 2703 state.mAnchor + paintBorderArea.TopLeft(), 2704 clipState.mDirtyRectInAppUnits, state.mRepeatSize, aParams.opacity); 2705 } 2706 2707 return result; 2708 } 2709 2710 nsRect nsCSSRendering::ComputeImageLayerPositioningArea( 2711 nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea, 2712 const nsStyleImageLayers::Layer& aLayer, nsIFrame** aAttachedToFrame, 2713 bool* aOutIsTransformedFixed) { 2714 // Compute {background|mask} origin area relative to aBorderArea now as we 2715 // may need it to compute the effective image size for a CSS gradient. 2716 nsRect positionArea; 2717 2718 StyleGeometryBox layerOrigin = 2719 ComputeBoxValueForOrigin(aForFrame, aLayer.mOrigin); 2720 2721 if (IsSVGStyleGeometryBox(layerOrigin)) { 2722 MOZ_ASSERT(aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)); 2723 *aAttachedToFrame = aForFrame; 2724 2725 positionArea = 2726 nsLayoutUtils::ComputeSVGReferenceRect(aForFrame, layerOrigin); 2727 2728 nsPoint toStrokeBoxOffset = nsPoint(0, 0); 2729 if (layerOrigin != StyleGeometryBox::StrokeBox) { 2730 nsRect strokeBox = nsLayoutUtils::ComputeSVGReferenceRect( 2731 aForFrame, StyleGeometryBox::StrokeBox); 2732 toStrokeBoxOffset = positionArea.TopLeft() - strokeBox.TopLeft(); 2733 } 2734 2735 // For SVG frames, the return value is relative to the stroke box 2736 return nsRect(toStrokeBoxOffset, positionArea.Size()); 2737 } 2738 2739 MOZ_ASSERT(!aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)); 2740 2741 LayoutFrameType frameType = aForFrame->Type(); 2742 nsIFrame* geometryFrame = aForFrame; 2743 if (MOZ_UNLIKELY(frameType == LayoutFrameType::ScrollContainer && 2744 StyleImageLayerAttachment::Local == aLayer.mAttachment)) { 2745 ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(aForFrame); 2746 positionArea = 2747 nsRect(scrollContainerFrame->GetScrolledFrame()->GetPosition() 2748 // For the dir=rtl case: 2749 + scrollContainerFrame->GetScrollRange().TopLeft(), 2750 scrollContainerFrame->GetScrolledRect().Size()); 2751 // The ScrolledRect’s size does not include the borders or scrollbars, 2752 // reverse the handling of background-origin 2753 // compared to the common case below. 2754 if (layerOrigin == StyleGeometryBox::BorderBox) { 2755 nsMargin border = geometryFrame->GetUsedBorder(); 2756 border.ApplySkipSides(geometryFrame->GetSkipSides()); 2757 positionArea.Inflate(border); 2758 positionArea.Inflate(scrollContainerFrame->GetActualScrollbarSizes()); 2759 } else if (layerOrigin != StyleGeometryBox::PaddingBox) { 2760 nsMargin padding = geometryFrame->GetUsedPadding(); 2761 padding.ApplySkipSides(geometryFrame->GetSkipSides()); 2762 positionArea.Deflate(padding); 2763 NS_ASSERTION(layerOrigin == StyleGeometryBox::ContentBox, 2764 "unknown background-origin value"); 2765 } 2766 *aAttachedToFrame = aForFrame; 2767 return positionArea; 2768 } 2769 2770 if (MOZ_UNLIKELY(frameType == LayoutFrameType::Canvas)) { 2771 geometryFrame = aForFrame->PrincipalChildList().FirstChild(); 2772 // geometryFrame might be null if this canvas is a page created 2773 // as an overflow container (e.g. the in-flow content has already 2774 // finished and this page only displays the continuations of 2775 // absolutely positioned content). 2776 if (geometryFrame) { 2777 positionArea = 2778 nsPlaceholderFrame::GetRealFrameFor(geometryFrame)->GetRect(); 2779 } 2780 } else { 2781 positionArea = nsRect(nsPoint(0, 0), aBorderArea.Size()); 2782 } 2783 2784 // See the comment of StyleGeometryBox::MarginBox. 2785 // Hitting this assertion means we decide to turn on margin-box support for 2786 // positioned mask from CSS parser and style system. In this case, you 2787 // should *inflate* positionArea by the margin returning from 2788 // geometryFrame->GetUsedMargin() in the code chunk bellow. 2789 MOZ_ASSERT(aLayer.mOrigin != StyleGeometryBox::MarginBox, 2790 "StyleGeometryBox::MarginBox rendering is not supported yet.\n"); 2791 2792 // {background|mask} images are tiled over the '{background|mask}-clip' area 2793 // but the origin of the tiling is based on the '{background|mask}-origin' 2794 // area. 2795 if (layerOrigin != StyleGeometryBox::BorderBox && geometryFrame) { 2796 nsMargin border = geometryFrame->GetUsedBorder(); 2797 if (layerOrigin != StyleGeometryBox::PaddingBox) { 2798 border += geometryFrame->GetUsedPadding(); 2799 NS_ASSERTION(layerOrigin == StyleGeometryBox::ContentBox, 2800 "unknown background-origin value"); 2801 } 2802 positionArea.Deflate(border); 2803 } 2804 2805 nsIFrame* attachedToFrame = aForFrame; 2806 if (StyleImageLayerAttachment::Fixed == aLayer.mAttachment) { 2807 // If it's a fixed background attachment, then the image is placed 2808 // relative to the viewport, which is the area of the root frame 2809 // in a screen context or the page content frame in a print context. 2810 attachedToFrame = aPresContext->PresShell()->GetRootFrame(); 2811 NS_ASSERTION(attachedToFrame, "no root frame"); 2812 nsIFrame* pageContentFrame = nullptr; 2813 if (aPresContext->IsPaginated()) { 2814 pageContentFrame = nsLayoutUtils::GetClosestFrameOfType( 2815 aForFrame, LayoutFrameType::PageContent); 2816 if (pageContentFrame) { 2817 attachedToFrame = pageContentFrame; 2818 } 2819 // else this is an embedded shell and its root frame is what we want 2820 } 2821 2822 // If the background is affected by a transform, treat is as if it 2823 // wasn't fixed. 2824 if (nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame)) { 2825 attachedToFrame = aForFrame; 2826 *aOutIsTransformedFixed = true; 2827 } else { 2828 // Set the background positioning area to the viewport's area 2829 // (relative to aForFrame) 2830 positionArea = nsRect(-aForFrame->GetOffsetTo(attachedToFrame), 2831 attachedToFrame->GetSize()); 2832 2833 if (!pageContentFrame) { 2834 // Subtract the size of scrollbars. 2835 if (ScrollContainerFrame* sf = 2836 aPresContext->PresShell()->GetRootScrollContainerFrame()) { 2837 nsMargin scrollbars = sf->GetActualScrollbarSizes(); 2838 positionArea.Deflate(scrollbars); 2839 } 2840 } 2841 2842 // If we have the dynamic toolbar, we need to expand the image area to 2843 // include the region under the dynamic toolbar, otherwise we will see a 2844 // blank space under the toolbar. 2845 if (aPresContext->IsRootContentDocumentCrossProcess() && 2846 aPresContext->HasDynamicToolbar()) { 2847 positionArea.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar( 2848 aPresContext, positionArea.Size())); 2849 } 2850 } 2851 } 2852 *aAttachedToFrame = attachedToFrame; 2853 2854 return positionArea; 2855 } 2856 2857 /* static */ 2858 nscoord nsCSSRendering::ComputeRoundedSize(nscoord aCurrentSize, 2859 nscoord aPositioningSize) { 2860 float repeatCount = NS_roundf(float(aPositioningSize) / float(aCurrentSize)); 2861 if (repeatCount < 1.0f) { 2862 return aPositioningSize; 2863 } 2864 return nscoord(NS_lround(float(aPositioningSize) / repeatCount)); 2865 } 2866 2867 // Apply the CSS image sizing algorithm as it applies to background images. 2868 // See http://www.w3.org/TR/css3-background/#the-background-size . 2869 // aIntrinsicSize is the size that the background image 'would like to be'. 2870 // It can be found by calling nsImageRenderer::ComputeIntrinsicSize. 2871 static nsSize ComputeDrawnSizeForBackground( 2872 const CSSSizeOrRatio& aIntrinsicSize, const nsSize& aBgPositioningArea, 2873 const StyleBackgroundSize& aLayerSize, StyleImageLayerRepeat aXRepeat, 2874 StyleImageLayerRepeat aYRepeat) { 2875 nsSize imageSize; 2876 2877 // Size is dictated by cover or contain rules. 2878 if (aLayerSize.IsContain() || aLayerSize.IsCover()) { 2879 nsImageRenderer::FitType fitType = aLayerSize.IsCover() 2880 ? nsImageRenderer::COVER 2881 : nsImageRenderer::CONTAIN; 2882 imageSize = nsImageRenderer::ComputeConstrainedSize( 2883 aBgPositioningArea, aIntrinsicSize.mRatio, fitType); 2884 } else { 2885 MOZ_ASSERT(aLayerSize.IsExplicitSize()); 2886 const auto& width = aLayerSize.explicit_size.width; 2887 const auto& height = aLayerSize.explicit_size.height; 2888 // No cover/contain constraint, use default algorithm. 2889 CSSSizeOrRatio specifiedSize; 2890 if (width.IsLengthPercentage()) { 2891 specifiedSize.SetWidth( 2892 width.AsLengthPercentage().Resolve(aBgPositioningArea.width)); 2893 } 2894 if (height.IsLengthPercentage()) { 2895 specifiedSize.SetHeight( 2896 height.AsLengthPercentage().Resolve(aBgPositioningArea.height)); 2897 } 2898 2899 imageSize = nsImageRenderer::ComputeConcreteSize( 2900 specifiedSize, aIntrinsicSize, aBgPositioningArea); 2901 } 2902 2903 // See https://www.w3.org/TR/css3-background/#background-size . 2904 // "If 'background-repeat' is 'round' for one (or both) dimensions, there is a 2905 // second 2906 // step. The UA must scale the image in that dimension (or both dimensions) 2907 // so that it fits a whole number of times in the background positioning 2908 // area." 2909 // "If 'background-repeat' is 'round' for one dimension only and if 2910 // 'background-size' 2911 // is 'auto' for the other dimension, then there is a third step: that other 2912 // dimension is scaled so that the original aspect ratio is restored." 2913 bool isRepeatRoundInBothDimensions = 2914 aXRepeat == StyleImageLayerRepeat::Round && 2915 aYRepeat == StyleImageLayerRepeat::Round; 2916 2917 // Calculate the rounded size only if the background-size computation 2918 // returned a correct size for the image. 2919 if (imageSize.width && aXRepeat == StyleImageLayerRepeat::Round) { 2920 imageSize.width = nsCSSRendering::ComputeRoundedSize( 2921 imageSize.width, aBgPositioningArea.width); 2922 if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() && 2923 aLayerSize.explicit_size.height.IsAuto()) { 2924 // Restore intrinsic ratio 2925 if (aIntrinsicSize.mRatio) { 2926 imageSize.height = 2927 aIntrinsicSize.mRatio.Inverted().ApplyTo(imageSize.width); 2928 } 2929 } 2930 } 2931 2932 // Calculate the rounded size only if the background-size computation 2933 // returned a correct size for the image. 2934 if (imageSize.height && aYRepeat == StyleImageLayerRepeat::Round) { 2935 imageSize.height = nsCSSRendering::ComputeRoundedSize( 2936 imageSize.height, aBgPositioningArea.height); 2937 if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() && 2938 aLayerSize.explicit_size.width.IsAuto()) { 2939 // Restore intrinsic ratio 2940 if (aIntrinsicSize.mRatio) { 2941 imageSize.width = aIntrinsicSize.mRatio.ApplyTo(imageSize.height); 2942 } 2943 } 2944 } 2945 2946 return imageSize; 2947 } 2948 2949 /* ComputeSpacedRepeatSize 2950 * aImageDimension: the image width/height 2951 * aAvailableSpace: the background positioning area width/height 2952 * aRepeat: determine whether the image is repeated 2953 * Returns the image size plus gap size of app units for use as spacing 2954 */ 2955 static nscoord ComputeSpacedRepeatSize(nscoord aImageDimension, 2956 nscoord aAvailableSpace, bool& aRepeat) { 2957 float ratio = static_cast<float>(aAvailableSpace) / aImageDimension; 2958 2959 if (ratio < 2.0f) { // If you can't repeat at least twice, then don't repeat. 2960 aRepeat = false; 2961 return aImageDimension; 2962 } 2963 2964 aRepeat = true; 2965 return (aAvailableSpace - aImageDimension) / (NSToIntFloor(ratio) - 1); 2966 } 2967 2968 /* static */ 2969 nscoord nsCSSRendering::ComputeBorderSpacedRepeatSize(nscoord aImageDimension, 2970 nscoord aAvailableSpace, 2971 nscoord& aSpace) { 2972 int32_t count = aImageDimension ? (aAvailableSpace / aImageDimension) : 0; 2973 aSpace = (aAvailableSpace - aImageDimension * count) / (count + 1); 2974 return aSpace + aImageDimension; 2975 } 2976 2977 nsBackgroundLayerState nsCSSRendering::PrepareImageLayer( 2978 nsPresContext* aPresContext, nsIFrame* aForFrame, uint32_t aFlags, 2979 const nsRect& aBorderArea, const nsRect& aBGClipRect, 2980 const nsStyleImageLayers::Layer& aLayer, bool* aOutIsTransformedFixed) { 2981 /* 2982 * The properties we need to keep in mind when drawing style image 2983 * layers are: 2984 * 2985 * background-image/ mask-image 2986 * background-repeat/ mask-repeat 2987 * background-attachment 2988 * background-position/ mask-position 2989 * background-clip/ mask-clip 2990 * background-origin/ mask-origin 2991 * background-size/ mask-size 2992 * background-blend-mode 2993 * box-decoration-break 2994 * mask-mode 2995 * mask-composite 2996 * 2997 * (background-color applies to the entire element and not to individual 2998 * layers, so it is irrelevant to this method.) 2999 * 3000 * These properties have the following dependencies upon each other when 3001 * determining rendering: 3002 * 3003 * background-image/ mask-image 3004 * no dependencies 3005 * background-repeat/ mask-repeat 3006 * no dependencies 3007 * background-attachment 3008 * no dependencies 3009 * background-position/ mask-position 3010 * depends upon background-size/mask-size (for the image's scaled size) 3011 * and background-break (for the background positioning area) 3012 * background-clip/ mask-clip 3013 * no dependencies 3014 * background-origin/ mask-origin 3015 * depends upon background-attachment (only in the case where that value 3016 * is 'fixed') 3017 * background-size/ mask-size 3018 * depends upon box-decoration-break (for the background positioning area 3019 * for resolving percentages), background-image (for the image's intrinsic 3020 * size), background-repeat (if that value is 'round'), and 3021 * background-origin (for the background painting area, when 3022 * background-repeat is 'round') 3023 * background-blend-mode 3024 * no dependencies 3025 * mask-mode 3026 * no dependencies 3027 * mask-composite 3028 * no dependencies 3029 * box-decoration-break 3030 * no dependencies 3031 * 3032 * As a result of only-if dependencies we don't strictly do a topological 3033 * sort of the above properties when processing, but it's pretty close to one: 3034 * 3035 * background-clip/mask-clip (by caller) 3036 * background-image/ mask-image 3037 * box-decoration-break, background-origin/ mask origin 3038 * background-attachment (postfix for background-origin if 'fixed') 3039 * background-size/ mask-size 3040 * background-position/ mask-position 3041 * background-repeat/ mask-repeat 3042 */ 3043 3044 uint32_t irFlags = 0; 3045 if (aFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) { 3046 irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES; 3047 } 3048 if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) { 3049 irFlags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW; 3050 } 3051 if (aFlags & nsCSSRendering::PAINTBG_HIGH_QUALITY_SCALING) { 3052 irFlags |= nsImageRenderer::FLAG_HIGH_QUALITY_SCALING; 3053 } 3054 // Only do partial bg image drawing in content documents: non-content 3055 // documents are viewed as UI of the browser and a partial draw of a bg image 3056 // might look weird in that context. 3057 if (XRE_IsContentProcess() && !aPresContext->IsChrome()) { 3058 irFlags |= nsImageRenderer::FLAG_DRAW_PARTIAL_FRAMES; 3059 } 3060 3061 nsBackgroundLayerState state(aForFrame, &aLayer.mImage, irFlags); 3062 if (!state.mImageRenderer.PrepareImage()) { 3063 // There's no image or it's not ready to be painted. 3064 if (aOutIsTransformedFixed && 3065 StyleImageLayerAttachment::Fixed == aLayer.mAttachment) { 3066 nsIFrame* attachedToFrame = aPresContext->PresShell()->GetRootFrame(); 3067 NS_ASSERTION(attachedToFrame, "no root frame"); 3068 nsIFrame* pageContentFrame = nullptr; 3069 if (aPresContext->IsPaginated()) { 3070 pageContentFrame = nsLayoutUtils::GetClosestFrameOfType( 3071 aForFrame, LayoutFrameType::PageContent); 3072 if (pageContentFrame) { 3073 attachedToFrame = pageContentFrame; 3074 } 3075 // else this is an embedded shell and its root frame is what we want 3076 } 3077 3078 *aOutIsTransformedFixed = 3079 nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame); 3080 } 3081 return state; 3082 } 3083 3084 // The frame to which the background is attached 3085 nsIFrame* attachedToFrame = aForFrame; 3086 // Is the background marked 'fixed', but affected by a transform? 3087 bool transformedFixed = false; 3088 // Compute background origin area relative to aBorderArea now as we may need 3089 // it to compute the effective image size for a CSS gradient. 3090 nsRect positionArea = ComputeImageLayerPositioningArea( 3091 aPresContext, aForFrame, aBorderArea, aLayer, &attachedToFrame, 3092 &transformedFixed); 3093 if (aOutIsTransformedFixed) { 3094 *aOutIsTransformedFixed = transformedFixed; 3095 } 3096 3097 // For background-attachment:fixed backgrounds, we'll override the area 3098 // where the background can be drawn to the viewport. 3099 nsRect bgClipRect = aBGClipRect; 3100 3101 if (StyleImageLayerAttachment::Fixed == aLayer.mAttachment && 3102 !transformedFixed && (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW)) { 3103 bgClipRect = positionArea + aBorderArea.TopLeft(); 3104 } 3105 3106 StyleImageLayerRepeat repeatX = aLayer.mRepeat.mXRepeat; 3107 StyleImageLayerRepeat repeatY = aLayer.mRepeat.mYRepeat; 3108 3109 // Scale the image as specified for background-size and background-repeat. 3110 // Also as required for proper background positioning when background-position 3111 // is defined with percentages. 3112 CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize(); 3113 nsSize bgPositionSize = positionArea.Size(); 3114 nsSize imageSize = ComputeDrawnSizeForBackground( 3115 intrinsicSize, bgPositionSize, aLayer.mSize, repeatX, repeatY); 3116 3117 if (imageSize.width <= 0 || imageSize.height <= 0) { 3118 return state; 3119 } 3120 3121 state.mImageRenderer.SetPreferredSize(intrinsicSize, imageSize); 3122 3123 // Compute the anchor point. 3124 // 3125 // relative to aBorderArea.TopLeft() (which is where the top-left 3126 // of aForFrame's border-box will be rendered) 3127 nsPoint imageTopLeft; 3128 3129 // Compute the position of the background now that the background's size is 3130 // determined. 3131 nsImageRenderer::ComputeObjectAnchorPoint(aLayer.mPosition, bgPositionSize, 3132 imageSize, &imageTopLeft, 3133 &state.mAnchor); 3134 state.mRepeatSize = imageSize; 3135 if (repeatX == StyleImageLayerRepeat::Space) { 3136 bool isRepeat; 3137 state.mRepeatSize.width = ComputeSpacedRepeatSize( 3138 imageSize.width, bgPositionSize.width, isRepeat); 3139 if (isRepeat) { 3140 imageTopLeft.x = 0; 3141 state.mAnchor.x = 0; 3142 } else { 3143 repeatX = StyleImageLayerRepeat::NoRepeat; 3144 } 3145 } 3146 3147 if (repeatY == StyleImageLayerRepeat::Space) { 3148 bool isRepeat; 3149 state.mRepeatSize.height = ComputeSpacedRepeatSize( 3150 imageSize.height, bgPositionSize.height, isRepeat); 3151 if (isRepeat) { 3152 imageTopLeft.y = 0; 3153 state.mAnchor.y = 0; 3154 } else { 3155 repeatY = StyleImageLayerRepeat::NoRepeat; 3156 } 3157 } 3158 3159 imageTopLeft += positionArea.TopLeft(); 3160 state.mAnchor += positionArea.TopLeft(); 3161 state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize); 3162 state.mFillArea = state.mDestArea; 3163 3164 ExtendMode repeatMode = ExtendMode::CLAMP; 3165 if (repeatX == StyleImageLayerRepeat::Repeat || 3166 repeatX == StyleImageLayerRepeat::Round || 3167 repeatX == StyleImageLayerRepeat::Space) { 3168 state.mFillArea.x = bgClipRect.x; 3169 state.mFillArea.width = bgClipRect.width; 3170 repeatMode = ExtendMode::REPEAT_X; 3171 } 3172 if (repeatY == StyleImageLayerRepeat::Repeat || 3173 repeatY == StyleImageLayerRepeat::Round || 3174 repeatY == StyleImageLayerRepeat::Space) { 3175 state.mFillArea.y = bgClipRect.y; 3176 state.mFillArea.height = bgClipRect.height; 3177 3178 /*** 3179 * We're repeating on the X axis already, 3180 * so if we have to repeat in the Y axis, 3181 * we really need to repeat in both directions. 3182 */ 3183 if (repeatMode == ExtendMode::REPEAT_X) { 3184 repeatMode = ExtendMode::REPEAT; 3185 } else { 3186 repeatMode = ExtendMode::REPEAT_Y; 3187 } 3188 } 3189 state.mImageRenderer.SetExtendMode(repeatMode); 3190 state.mImageRenderer.SetMaskOp(aLayer.mMaskMode); 3191 3192 state.mFillArea.IntersectRect(state.mFillArea, bgClipRect); 3193 3194 return state; 3195 } 3196 3197 nsRect nsCSSRendering::GetBackgroundLayerRect( 3198 nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea, 3199 const nsRect& aClipRect, const nsStyleImageLayers::Layer& aLayer, 3200 uint32_t aFlags) { 3201 Sides skipSides = aForFrame->GetSkipSides(); 3202 nsRect borderArea = 3203 BoxDecorationRectForBackground(aForFrame, aBorderArea, skipSides); 3204 nsBackgroundLayerState state = PrepareImageLayer( 3205 aPresContext, aForFrame, aFlags, borderArea, aClipRect, aLayer); 3206 return state.mFillArea; 3207 } 3208 3209 // Begin table border-collapsing section 3210 // These functions were written to not disrupt the normal ones and yet satisfy 3211 // some additional requirements At some point, all functions should be unified 3212 // to include the additional functionality that these provide 3213 3214 static nscoord RoundIntToPixel(nscoord aValue, nscoord aOneDevPixel, 3215 bool aRoundDown = false) { 3216 if (aOneDevPixel <= 0) { 3217 // We must be rendering to a device that has a resolution greater than 3218 // one device pixel! 3219 // In that case, aValue is as accurate as it's going to get. 3220 return aValue; 3221 } 3222 3223 nscoord halfPixel = NSToCoordRound(aOneDevPixel / 2.0f); 3224 nscoord extra = aValue % aOneDevPixel; 3225 nscoord finalValue = (!aRoundDown && (extra >= halfPixel)) 3226 ? aValue + (aOneDevPixel - extra) 3227 : aValue - extra; 3228 return finalValue; 3229 } 3230 3231 static nscoord RoundFloatToPixel(float aValue, nscoord aOneDevPixel, 3232 bool aRoundDown = false) { 3233 return RoundIntToPixel(NSToCoordRound(aValue), aOneDevPixel, aRoundDown); 3234 } 3235 3236 static void SetPoly(const Rect& aRect, Point* poly) { 3237 poly[0].x = aRect.x; 3238 poly[0].y = aRect.y; 3239 poly[1].x = aRect.x + aRect.width; 3240 poly[1].y = aRect.y; 3241 poly[2].x = aRect.x + aRect.width; 3242 poly[2].y = aRect.y + aRect.height; 3243 poly[3].x = aRect.x; 3244 poly[3].y = aRect.y + aRect.height; 3245 } 3246 3247 static void DrawDashedSegment(DrawTarget& aDrawTarget, nsRect aRect, 3248 nscoord aDashLength, nscolor aColor, 3249 int32_t aAppUnitsPerDevPixel, bool aHorizontal) { 3250 ColorPattern color(ToDeviceColor(aColor)); 3251 DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE); 3252 StrokeOptions strokeOptions; 3253 3254 Float dash[2]; 3255 dash[0] = Float(aDashLength) / aAppUnitsPerDevPixel; 3256 dash[1] = dash[0]; 3257 3258 strokeOptions.mDashPattern = dash; 3259 strokeOptions.mDashLength = std::size(dash); 3260 3261 if (aHorizontal) { 3262 nsPoint left = (aRect.TopLeft() + aRect.BottomLeft()) / 2; 3263 nsPoint right = (aRect.TopRight() + aRect.BottomRight()) / 2; 3264 strokeOptions.mLineWidth = Float(aRect.height) / aAppUnitsPerDevPixel; 3265 StrokeLineWithSnapping(left, right, aAppUnitsPerDevPixel, aDrawTarget, 3266 color, strokeOptions, drawOptions); 3267 } else { 3268 nsPoint top = (aRect.TopLeft() + aRect.TopRight()) / 2; 3269 nsPoint bottom = (aRect.BottomLeft() + aRect.BottomRight()) / 2; 3270 strokeOptions.mLineWidth = Float(aRect.width) / aAppUnitsPerDevPixel; 3271 StrokeLineWithSnapping(top, bottom, aAppUnitsPerDevPixel, aDrawTarget, 3272 color, strokeOptions, drawOptions); 3273 } 3274 } 3275 3276 static void DrawSolidBorderSegment( 3277 DrawTarget& aDrawTarget, nsRect aRect, nscolor aColor, 3278 int32_t aAppUnitsPerDevPixel, 3279 mozilla::Side aStartBevelSide = mozilla::eSideTop, 3280 nscoord aStartBevelOffset = 0, 3281 mozilla::Side aEndBevelSide = mozilla::eSideTop, 3282 nscoord aEndBevelOffset = 0) { 3283 ColorPattern color(ToDeviceColor(aColor)); 3284 DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE); 3285 3286 nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel); 3287 // We don't need to bevel single pixel borders 3288 if ((aRect.width == oneDevPixel) || (aRect.height == oneDevPixel) || 3289 ((0 == aStartBevelOffset) && (0 == aEndBevelOffset))) { 3290 // simple rectangle 3291 aDrawTarget.FillRect( 3292 NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget), color, 3293 drawOptions); 3294 } else { 3295 // polygon with beveling 3296 Point poly[4]; 3297 SetPoly(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget), 3298 poly); 3299 3300 Float startBevelOffset = 3301 NSAppUnitsToFloatPixels(aStartBevelOffset, aAppUnitsPerDevPixel); 3302 switch (aStartBevelSide) { 3303 case eSideTop: 3304 poly[0].x += startBevelOffset; 3305 break; 3306 case eSideBottom: 3307 poly[3].x += startBevelOffset; 3308 break; 3309 case eSideRight: 3310 poly[1].y += startBevelOffset; 3311 break; 3312 case eSideLeft: 3313 poly[0].y += startBevelOffset; 3314 } 3315 3316 Float endBevelOffset = 3317 NSAppUnitsToFloatPixels(aEndBevelOffset, aAppUnitsPerDevPixel); 3318 switch (aEndBevelSide) { 3319 case eSideTop: 3320 poly[1].x -= endBevelOffset; 3321 break; 3322 case eSideBottom: 3323 poly[2].x -= endBevelOffset; 3324 break; 3325 case eSideRight: 3326 poly[2].y -= endBevelOffset; 3327 break; 3328 case eSideLeft: 3329 poly[3].y -= endBevelOffset; 3330 } 3331 3332 RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(); 3333 builder->MoveTo(poly[0]); 3334 builder->LineTo(poly[1]); 3335 builder->LineTo(poly[2]); 3336 builder->LineTo(poly[3]); 3337 builder->Close(); 3338 RefPtr<Path> path = builder->Finish(); 3339 aDrawTarget.Fill(path, color, drawOptions); 3340 } 3341 } 3342 3343 static void GetDashInfo(nscoord aBorderLength, nscoord aDashLength, 3344 nscoord aOneDevPixel, int32_t& aNumDashSpaces, 3345 nscoord& aStartDashLength, nscoord& aEndDashLength) { 3346 aNumDashSpaces = 0; 3347 if (aStartDashLength + aDashLength + aEndDashLength >= aBorderLength) { 3348 aStartDashLength = aBorderLength; 3349 aEndDashLength = 0; 3350 } else { 3351 aNumDashSpaces = 3352 (aBorderLength - aDashLength) / (2 * aDashLength); // round down 3353 nscoord extra = aBorderLength - aStartDashLength - aEndDashLength - 3354 (((2 * aNumDashSpaces) - 1) * aDashLength); 3355 if (extra > 0) { 3356 nscoord half = RoundIntToPixel(extra / 2, aOneDevPixel); 3357 aStartDashLength += half; 3358 aEndDashLength += (extra - half); 3359 } 3360 } 3361 } 3362 3363 void nsCSSRendering::DrawTableBorderSegment( 3364 DrawTarget& aDrawTarget, StyleBorderStyle aBorderStyle, 3365 nscolor aBorderColor, const nsRect& aBorder, int32_t aAppUnitsPerDevPixel, 3366 mozilla::Side aStartBevelSide, nscoord aStartBevelOffset, 3367 mozilla::Side aEndBevelSide, nscoord aEndBevelOffset) { 3368 bool horizontal = 3369 ((eSideTop == aStartBevelSide) || (eSideBottom == aStartBevelSide)); 3370 nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel); 3371 3372 if ((oneDevPixel >= aBorder.width) || (oneDevPixel >= aBorder.height) || 3373 (StyleBorderStyle::Dashed == aBorderStyle) || 3374 (StyleBorderStyle::Dotted == aBorderStyle)) { 3375 // no beveling for 1 pixel border, dash or dot 3376 aStartBevelOffset = 0; 3377 aEndBevelOffset = 0; 3378 } 3379 3380 switch (aBorderStyle) { 3381 case StyleBorderStyle::None: 3382 case StyleBorderStyle::Hidden: 3383 // NS_ASSERTION(false, "style of none or hidden"); 3384 break; 3385 case StyleBorderStyle::Dotted: 3386 case StyleBorderStyle::Dashed: { 3387 nscoord dashLength = 3388 (StyleBorderStyle::Dashed == aBorderStyle) ? DASH_LENGTH : DOT_LENGTH; 3389 // make the dash length proportional to the border thickness 3390 dashLength *= (horizontal) ? aBorder.height : aBorder.width; 3391 // make the min dash length for the ends 1/2 the dash length 3392 nscoord minDashLength = 3393 (StyleBorderStyle::Dashed == aBorderStyle) 3394 ? RoundFloatToPixel(((float)dashLength) / 2.0f, 3395 aAppUnitsPerDevPixel) 3396 : dashLength; 3397 minDashLength = std::max(minDashLength, oneDevPixel); 3398 nscoord numDashSpaces = 0; 3399 nscoord startDashLength = minDashLength; 3400 nscoord endDashLength = minDashLength; 3401 if (horizontal) { 3402 GetDashInfo(aBorder.width, dashLength, aAppUnitsPerDevPixel, 3403 numDashSpaces, startDashLength, endDashLength); 3404 nsRect rect(aBorder.x, aBorder.y, startDashLength, aBorder.height); 3405 DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor, 3406 aAppUnitsPerDevPixel); 3407 3408 rect.x += startDashLength + dashLength; 3409 rect.width = 3410 aBorder.width - (startDashLength + endDashLength + dashLength); 3411 DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor, 3412 aAppUnitsPerDevPixel, horizontal); 3413 3414 rect.x += rect.width; 3415 rect.width = endDashLength; 3416 DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor, 3417 aAppUnitsPerDevPixel); 3418 } else { 3419 GetDashInfo(aBorder.height, dashLength, aAppUnitsPerDevPixel, 3420 numDashSpaces, startDashLength, endDashLength); 3421 nsRect rect(aBorder.x, aBorder.y, aBorder.width, startDashLength); 3422 DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor, 3423 aAppUnitsPerDevPixel); 3424 3425 rect.y += rect.height + dashLength; 3426 rect.height = 3427 aBorder.height - (startDashLength + endDashLength + dashLength); 3428 DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor, 3429 aAppUnitsPerDevPixel, horizontal); 3430 3431 rect.y += rect.height; 3432 rect.height = endDashLength; 3433 DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor, 3434 aAppUnitsPerDevPixel); 3435 } 3436 } break; 3437 default: 3438 AutoTArray<SolidBeveledBorderSegment, 3> segments; 3439 GetTableBorderSolidSegments( 3440 segments, aBorderStyle, aBorderColor, aBorder, aAppUnitsPerDevPixel, 3441 aStartBevelSide, aStartBevelOffset, aEndBevelSide, aEndBevelOffset); 3442 for (const auto& segment : segments) { 3443 DrawSolidBorderSegment( 3444 aDrawTarget, segment.mRect, segment.mColor, aAppUnitsPerDevPixel, 3445 segment.mStartBevel.mSide, segment.mStartBevel.mOffset, 3446 segment.mEndBevel.mSide, segment.mEndBevel.mOffset); 3447 } 3448 break; 3449 } 3450 } 3451 3452 void nsCSSRendering::GetTableBorderSolidSegments( 3453 nsTArray<SolidBeveledBorderSegment>& aSegments, 3454 StyleBorderStyle aBorderStyle, nscolor aBorderColor, const nsRect& aBorder, 3455 int32_t aAppUnitsPerDevPixel, mozilla::Side aStartBevelSide, 3456 nscoord aStartBevelOffset, mozilla::Side aEndBevelSide, 3457 nscoord aEndBevelOffset) { 3458 const bool horizontal = 3459 eSideTop == aStartBevelSide || eSideBottom == aStartBevelSide; 3460 const nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel); 3461 3462 switch (aBorderStyle) { 3463 case StyleBorderStyle::None: 3464 case StyleBorderStyle::Hidden: 3465 return; 3466 case StyleBorderStyle::Dotted: 3467 case StyleBorderStyle::Dashed: 3468 MOZ_ASSERT_UNREACHABLE("Caller should have checked"); 3469 return; 3470 case StyleBorderStyle::Groove: 3471 case StyleBorderStyle::Ridge: 3472 if ((horizontal && (oneDevPixel >= aBorder.height)) || 3473 (!horizontal && (oneDevPixel >= aBorder.width))) { 3474 aSegments.AppendElement( 3475 SolidBeveledBorderSegment{aBorder, 3476 aBorderColor, 3477 {aStartBevelSide, aStartBevelOffset}, 3478 {aEndBevelSide, aEndBevelOffset}}); 3479 } else { 3480 nscoord startBevel = 3481 (aStartBevelOffset > 0) 3482 ? RoundFloatToPixel(0.5f * (float)aStartBevelOffset, 3483 aAppUnitsPerDevPixel, true) 3484 : 0; 3485 nscoord endBevel = 3486 (aEndBevelOffset > 0) 3487 ? RoundFloatToPixel(0.5f * (float)aEndBevelOffset, 3488 aAppUnitsPerDevPixel, true) 3489 : 0; 3490 mozilla::Side ridgeGrooveSide = (horizontal) ? eSideTop : eSideLeft; 3491 // FIXME: In theory, this should use the visited-dependent 3492 // background color, but I don't care. 3493 nscolor bevelColor = 3494 MakeBevelColor(ridgeGrooveSide, aBorderStyle, aBorderColor); 3495 nsRect rect(aBorder); 3496 nscoord half; 3497 if (horizontal) { // top, bottom 3498 half = RoundFloatToPixel(0.5f * (float)aBorder.height, 3499 aAppUnitsPerDevPixel); 3500 rect.height = half; 3501 if (eSideTop == aStartBevelSide) { 3502 rect.x += startBevel; 3503 rect.width -= startBevel; 3504 } 3505 if (eSideTop == aEndBevelSide) { 3506 rect.width -= endBevel; 3507 } 3508 aSegments.AppendElement( 3509 SolidBeveledBorderSegment{rect, 3510 bevelColor, 3511 {aStartBevelSide, startBevel}, 3512 {aEndBevelSide, endBevel}}); 3513 } else { // left, right 3514 half = RoundFloatToPixel(0.5f * (float)aBorder.width, 3515 aAppUnitsPerDevPixel); 3516 rect.width = half; 3517 if (eSideLeft == aStartBevelSide) { 3518 rect.y += startBevel; 3519 rect.height -= startBevel; 3520 } 3521 if (eSideLeft == aEndBevelSide) { 3522 rect.height -= endBevel; 3523 } 3524 aSegments.AppendElement( 3525 SolidBeveledBorderSegment{rect, 3526 bevelColor, 3527 {aStartBevelSide, startBevel}, 3528 {aEndBevelSide, endBevel}}); 3529 } 3530 3531 rect = aBorder; 3532 ridgeGrooveSide = 3533 (eSideTop == ridgeGrooveSide) ? eSideBottom : eSideRight; 3534 // FIXME: In theory, this should use the visited-dependent 3535 // background color, but I don't care. 3536 bevelColor = 3537 MakeBevelColor(ridgeGrooveSide, aBorderStyle, aBorderColor); 3538 if (horizontal) { 3539 rect.y = rect.y + half; 3540 rect.height = aBorder.height - half; 3541 if (eSideBottom == aStartBevelSide) { 3542 rect.x += startBevel; 3543 rect.width -= startBevel; 3544 } 3545 if (eSideBottom == aEndBevelSide) { 3546 rect.width -= endBevel; 3547 } 3548 aSegments.AppendElement( 3549 SolidBeveledBorderSegment{rect, 3550 bevelColor, 3551 {aStartBevelSide, startBevel}, 3552 {aEndBevelSide, endBevel}}); 3553 } else { 3554 rect.x = rect.x + half; 3555 rect.width = aBorder.width - half; 3556 if (eSideRight == aStartBevelSide) { 3557 rect.y += aStartBevelOffset - startBevel; 3558 rect.height -= startBevel; 3559 } 3560 if (eSideRight == aEndBevelSide) { 3561 rect.height -= endBevel; 3562 } 3563 aSegments.AppendElement( 3564 SolidBeveledBorderSegment{rect, 3565 bevelColor, 3566 {aStartBevelSide, startBevel}, 3567 {aEndBevelSide, endBevel}}); 3568 } 3569 } 3570 break; 3571 case StyleBorderStyle::Double: 3572 // We can only do "double" borders if the thickness of the border 3573 // is more than 2px. Otherwise, we fall through to painting a 3574 // solid border. 3575 if ((aBorder.width > 2 * oneDevPixel || horizontal) && 3576 (aBorder.height > 2 * oneDevPixel || !horizontal)) { 3577 nscoord startBevel = 3578 (aStartBevelOffset > 0) 3579 ? RoundFloatToPixel(0.333333f * (float)aStartBevelOffset, 3580 aAppUnitsPerDevPixel) 3581 : 0; 3582 nscoord endBevel = 3583 (aEndBevelOffset > 0) 3584 ? RoundFloatToPixel(0.333333f * (float)aEndBevelOffset, 3585 aAppUnitsPerDevPixel) 3586 : 0; 3587 if (horizontal) { // top, bottom 3588 nscoord thirdHeight = RoundFloatToPixel( 3589 0.333333f * (float)aBorder.height, aAppUnitsPerDevPixel); 3590 3591 // draw the top line or rect 3592 nsRect topRect(aBorder.x, aBorder.y, aBorder.width, thirdHeight); 3593 if (eSideTop == aStartBevelSide) { 3594 topRect.x += aStartBevelOffset - startBevel; 3595 topRect.width -= aStartBevelOffset - startBevel; 3596 } 3597 if (eSideTop == aEndBevelSide) { 3598 topRect.width -= aEndBevelOffset - endBevel; 3599 } 3600 3601 aSegments.AppendElement( 3602 SolidBeveledBorderSegment{topRect, 3603 aBorderColor, 3604 {aStartBevelSide, startBevel}, 3605 {aEndBevelSide, endBevel}}); 3606 3607 // draw the botom line or rect 3608 nscoord heightOffset = aBorder.height - thirdHeight; 3609 nsRect bottomRect(aBorder.x, aBorder.y + heightOffset, aBorder.width, 3610 aBorder.height - heightOffset); 3611 if (eSideBottom == aStartBevelSide) { 3612 bottomRect.x += aStartBevelOffset - startBevel; 3613 bottomRect.width -= aStartBevelOffset - startBevel; 3614 } 3615 if (eSideBottom == aEndBevelSide) { 3616 bottomRect.width -= aEndBevelOffset - endBevel; 3617 } 3618 aSegments.AppendElement( 3619 SolidBeveledBorderSegment{bottomRect, 3620 aBorderColor, 3621 {aStartBevelSide, startBevel}, 3622 {aEndBevelSide, endBevel}}); 3623 } else { // left, right 3624 nscoord thirdWidth = RoundFloatToPixel( 3625 0.333333f * (float)aBorder.width, aAppUnitsPerDevPixel); 3626 3627 nsRect leftRect(aBorder.x, aBorder.y, thirdWidth, aBorder.height); 3628 if (eSideLeft == aStartBevelSide) { 3629 leftRect.y += aStartBevelOffset - startBevel; 3630 leftRect.height -= aStartBevelOffset - startBevel; 3631 } 3632 if (eSideLeft == aEndBevelSide) { 3633 leftRect.height -= aEndBevelOffset - endBevel; 3634 } 3635 3636 aSegments.AppendElement( 3637 SolidBeveledBorderSegment{leftRect, 3638 aBorderColor, 3639 {aStartBevelSide, startBevel}, 3640 {aEndBevelSide, endBevel}}); 3641 3642 nscoord widthOffset = aBorder.width - thirdWidth; 3643 nsRect rightRect(aBorder.x + widthOffset, aBorder.y, 3644 aBorder.width - widthOffset, aBorder.height); 3645 if (eSideRight == aStartBevelSide) { 3646 rightRect.y += aStartBevelOffset - startBevel; 3647 rightRect.height -= aStartBevelOffset - startBevel; 3648 } 3649 if (eSideRight == aEndBevelSide) { 3650 rightRect.height -= aEndBevelOffset - endBevel; 3651 } 3652 aSegments.AppendElement( 3653 SolidBeveledBorderSegment{rightRect, 3654 aBorderColor, 3655 {aStartBevelSide, startBevel}, 3656 {aEndBevelSide, endBevel}}); 3657 } 3658 break; 3659 } 3660 // else fall through to solid 3661 [[fallthrough]]; 3662 case StyleBorderStyle::Solid: 3663 aSegments.AppendElement( 3664 SolidBeveledBorderSegment{aBorder, 3665 aBorderColor, 3666 {aStartBevelSide, aStartBevelOffset}, 3667 {aEndBevelSide, aEndBevelOffset}}); 3668 break; 3669 case StyleBorderStyle::Outset: 3670 case StyleBorderStyle::Inset: 3671 MOZ_ASSERT_UNREACHABLE( 3672 "inset, outset should have been converted to groove, ridge"); 3673 break; 3674 } 3675 } 3676 3677 // End table border-collapsing section 3678 3679 Rect nsCSSRendering::ExpandPaintingRectForDecorationLine( 3680 nsIFrame* aFrame, const StyleTextDecorationStyle aStyle, 3681 const Rect& aClippedRect, const Float aICoordInFrame, 3682 const Float aCycleLength, bool aVertical) { 3683 switch (aStyle) { 3684 case StyleTextDecorationStyle::Dotted: 3685 case StyleTextDecorationStyle::Dashed: 3686 case StyleTextDecorationStyle::Wavy: 3687 break; 3688 default: 3689 NS_ERROR("Invalid style was specified"); 3690 return aClippedRect; 3691 } 3692 3693 nsBlockFrame* block = nullptr; 3694 // Note that when we paint the decoration lines in relative positioned 3695 // box, we should paint them like all of the boxes are positioned as static. 3696 nscoord framePosInBlockAppUnits = 0; 3697 for (nsIFrame* f = aFrame; f; f = f->GetParent()) { 3698 block = do_QueryFrame(f); 3699 if (block) { 3700 break; 3701 } 3702 framePosInBlockAppUnits += 3703 aVertical ? f->GetNormalPosition().y : f->GetNormalPosition().x; 3704 } 3705 3706 NS_ENSURE_TRUE(block, aClippedRect); 3707 3708 nsPresContext* pc = aFrame->PresContext(); 3709 Float framePosInBlock = 3710 Float(pc->AppUnitsToGfxUnits(framePosInBlockAppUnits)); 3711 int32_t rectPosInBlock = int32_t(NS_round(framePosInBlock + aICoordInFrame)); 3712 int32_t extraStartEdge = 3713 rectPosInBlock - (rectPosInBlock / int32_t(aCycleLength) * aCycleLength); 3714 Rect rect(aClippedRect); 3715 if (aVertical) { 3716 rect.y -= extraStartEdge; 3717 rect.height += extraStartEdge; 3718 } else { 3719 rect.x -= extraStartEdge; 3720 rect.width += extraStartEdge; 3721 } 3722 return rect; 3723 } 3724 3725 // Converts a GfxFont to an SkFont 3726 // Either returns true if it was successful, or false if something went wrong 3727 static bool GetSkFontFromGfxFont(DrawTarget& aDrawTarget, gfxFont* aFont, 3728 SkFont& aSkFont) { 3729 RefPtr<ScaledFont> scaledFont = aFont->GetScaledFont(&aDrawTarget); 3730 if (!scaledFont) { 3731 return false; 3732 } 3733 3734 ScaledFontBase* fontBase = static_cast<ScaledFontBase*>(scaledFont.get()); 3735 3736 SkTypeface* typeface = fontBase->GetSkTypeface(); 3737 if (!typeface) { 3738 return false; 3739 } 3740 3741 aSkFont = SkFont(sk_ref_sp(typeface), SkFloatToScalar(fontBase->GetSize())); 3742 return true; 3743 } 3744 3745 // Computes data used to position the decoration line within a 3746 // SkTextBlob, data is returned through aBounds 3747 static void GetPositioning( 3748 const nsCSSRendering::PaintDecorationLineParams& aParams, const Rect& aRect, 3749 Float aOneCSSPixel, Float aCenterBaselineOffset, SkScalar aBounds[]) { 3750 /** 3751 * How Positioning in Skia Works 3752 * Take the letter "n" for example 3753 * We set textPos as 0, 0 3754 * This is represented in Skia like so (not to scale) 3755 * ^ 3756 * -10px | _ __ 3757 * | | '_ \ 3758 * -5px | | | | | 3759 * y-axis | |_| |_| 3760 * (0,0) -----------------------> 3761 * | 5px 10px 3762 * 5px | 3763 * | 3764 * 10px | 3765 * v 3766 * 0 on the x axis is a line that touches the bottom of the n 3767 * (0,0) is the bottom left-hand corner of the n character 3768 * Moving "up" from the n is going in a negative y direction 3769 * Moving "down" from the n is going in a positive y direction 3770 * 3771 * The intercepts that are returned in this arrangement will be 3772 * offset by the original point it starts at. (This happens in 3773 * the SkipInk function below). 3774 * 3775 * In Skia, text MUST be laid out such that the next character 3776 * in the RunBuffer is further along the x-axis than the previous 3777 * character, otherwise there is undefined/strange behavior. 3778 */ 3779 3780 Float rectThickness = aParams.vertical ? aRect.Width() : aRect.Height(); 3781 3782 // the upper and lower lines/edges of the under or over line 3783 SkScalar upperLine, lowerLine; 3784 if (aParams.decoration == mozilla::StyleTextDecorationLine::OVERLINE) { 3785 lowerLine = 3786 -aParams.offset + aParams.defaultLineThickness - aCenterBaselineOffset; 3787 upperLine = lowerLine - rectThickness; 3788 } else { 3789 // underlines in vertical text are offset from the center of 3790 // the text, and not the baseline 3791 // Skia sets the text at it's baseline so we have to offset it 3792 // for text in vertical-* writing modes 3793 upperLine = -aParams.offset - aCenterBaselineOffset; 3794 lowerLine = upperLine + rectThickness; 3795 } 3796 3797 // set up the bounds, add in a little padding to the thickness of the line 3798 // (unless the line is <= 1 CSS pixel thick) 3799 Float lineThicknessPadding = aParams.lineSize.height > aOneCSSPixel 3800 ? 0.25f * aParams.lineSize.height 3801 : 0; 3802 // don't allow padding greater than 0.75 CSS pixel 3803 lineThicknessPadding = std::min(lineThicknessPadding, 0.75f * aOneCSSPixel); 3804 aBounds[0] = upperLine - lineThicknessPadding; 3805 aBounds[1] = lowerLine + lineThicknessPadding; 3806 } 3807 3808 // positions an individual glyph according to the given offset 3809 static SkPoint GlyphPosition(const gfxTextRun::DetailedGlyph& aGlyph, 3810 const SkPoint& aTextPos, 3811 int32_t aAppUnitsPerDevPixel) { 3812 SkPoint point = {aGlyph.mOffset.x, aGlyph.mOffset.y}; 3813 3814 // convert to device pixels 3815 point.fX /= (float)aAppUnitsPerDevPixel; 3816 point.fY /= (float)aAppUnitsPerDevPixel; 3817 3818 // add offsets 3819 point.fX += aTextPos.fX; 3820 point.fY += aTextPos.fY; 3821 return point; 3822 } 3823 3824 // returns a count of all the glyphs that will be rendered 3825 // excludes ligature continuations, includes the number of individual 3826 // glyph records. This includes the number of DetailedGlyphs that a single 3827 // CompressedGlyph record points to. This function is necessary because Skia 3828 // needs the total length of glyphs to add to it's run buffer before it creates 3829 // the RunBuffer object, and this cannot be resized later. 3830 static uint32_t CountAllGlyphs( 3831 const gfxTextRun* aTextRun, 3832 const gfxTextRun::CompressedGlyph* aCompressedGlyph, uint32_t aStringStart, 3833 uint32_t aStringEnd) { 3834 uint32_t totalGlyphCount = 0; 3835 3836 for (const gfxTextRun::CompressedGlyph* cg = aCompressedGlyph + aStringStart; 3837 cg < aCompressedGlyph + aStringEnd; ++cg) { 3838 totalGlyphCount += cg->IsSimpleGlyph() ? 1 : cg->GetGlyphCount(); 3839 } 3840 3841 return totalGlyphCount; 3842 } 3843 3844 static void AddDetailedGlyph(const SkTextBlobBuilder::RunBuffer& aRunBuffer, 3845 const gfxTextRun::DetailedGlyph& aGlyph, 3846 int aIndex, float aAppUnitsPerDevPixel, 3847 SkPoint& aTextPos) { 3848 // add glyph ID to the run buffer at i 3849 aRunBuffer.glyphs[aIndex] = aGlyph.mGlyphID; 3850 3851 // position the glyph correctly using the detailed offsets 3852 SkPoint position = GlyphPosition(aGlyph, aTextPos, aAppUnitsPerDevPixel); 3853 aRunBuffer.pos[2 * aIndex] = position.fX; 3854 aRunBuffer.pos[(2 * aIndex) + 1] = position.fY; 3855 3856 // increase aTextPos.fx by the advance 3857 aTextPos.fX += ((float)aGlyph.mAdvance / aAppUnitsPerDevPixel); 3858 } 3859 3860 static void AddSimpleGlyph(const SkTextBlobBuilder::RunBuffer& aRunBuffer, 3861 const gfxTextRun::CompressedGlyph& aGlyph, 3862 int aIndex, float aAppUnitsPerDevPixel, 3863 SkPoint& aTextPos) { 3864 aRunBuffer.glyphs[aIndex] = aGlyph.GetSimpleGlyph(); 3865 3866 // simple glyphs are offset from 0, so we'll just use textPos 3867 aRunBuffer.pos[2 * aIndex] = aTextPos.fX; 3868 aRunBuffer.pos[(2 * aIndex) + 1] = aTextPos.fY; 3869 3870 // increase aTextPos.fX by the advance 3871 aTextPos.fX += ((float)aGlyph.GetSimpleAdvance() / aAppUnitsPerDevPixel); 3872 } 3873 3874 // Sets up a Skia TextBlob of the specified font, text position, and made up of 3875 // the glyphs between aStringStart and aStringEnd. Handles RTL and LTR text 3876 // and positions each glyph within the text blob 3877 static sk_sp<const SkTextBlob> CreateTextBlob( 3878 const gfxTextRun* aTextRun, 3879 const gfxTextRun::CompressedGlyph* aCompressedGlyph, const SkFont& aFont, 3880 const gfxTextRun::PropertyProvider::Spacing* aSpacing, 3881 uint32_t aStringStart, uint32_t aStringEnd, float aAppUnitsPerDevPixel, 3882 SkPoint& aTextPos, int32_t& aSpacingOffset) { 3883 // allocate space for the run buffer, then fill it with the glyphs 3884 uint32_t len = 3885 CountAllGlyphs(aTextRun, aCompressedGlyph, aStringStart, aStringEnd); 3886 if (len <= 0) { 3887 return nullptr; 3888 } 3889 3890 SkTextBlobBuilder builder; 3891 const SkTextBlobBuilder::RunBuffer& run = builder.allocRunPos(aFont, len); 3892 3893 // RTL text should be read in by glyph starting at aStringEnd - 1 down until 3894 // aStringStart. 3895 bool isRTL = aTextRun->IsRightToLeft(); 3896 uint32_t currIndex = isRTL ? aStringEnd - 1 : aStringStart; // textRun index 3897 // currIndex will be advanced by |step| until it reaches |limit|, which is the 3898 // final index to be handled (NOT one beyond the final index) 3899 int step = isRTL ? -1 : 1; 3900 uint32_t limit = isRTL ? aStringStart : aStringEnd - 1; 3901 3902 uint32_t i = 0; // index into the SkTextBlob we're building 3903 while (true) { 3904 // Loop exit test is below, just before we update currIndex. 3905 aTextPos.fX += 3906 isRTL ? aSpacing[aSpacingOffset].mAfter / aAppUnitsPerDevPixel 3907 : aSpacing[aSpacingOffset].mBefore / aAppUnitsPerDevPixel; 3908 3909 if (aCompressedGlyph[currIndex].IsSimpleGlyph()) { 3910 MOZ_ASSERT(i < len, "glyph count error!"); 3911 AddSimpleGlyph(run, aCompressedGlyph[currIndex], i, aAppUnitsPerDevPixel, 3912 aTextPos); 3913 i++; 3914 } else { 3915 // if it's detailed, potentially add multiple into run.glyphs 3916 uint32_t count = aCompressedGlyph[currIndex].GetGlyphCount(); 3917 if (count > 0) { 3918 gfxTextRun::DetailedGlyph* detailGlyph = 3919 aTextRun->GetDetailedGlyphs(currIndex); 3920 for (uint32_t d = isRTL ? count - 1 : 0; count; count--, d += step) { 3921 MOZ_ASSERT(i < len, "glyph count error!"); 3922 AddDetailedGlyph(run, detailGlyph[d], i, aAppUnitsPerDevPixel, 3923 aTextPos); 3924 i++; 3925 } 3926 } 3927 } 3928 aTextPos.fX += isRTL 3929 ? aSpacing[aSpacingOffset].mBefore / aAppUnitsPerDevPixel 3930 : aSpacing[aSpacingOffset].mAfter / aAppUnitsPerDevPixel; 3931 aSpacingOffset += step; 3932 3933 if (currIndex == limit) { 3934 break; 3935 } 3936 currIndex += step; 3937 } 3938 3939 MOZ_ASSERT(i == len, "glyph count error!"); 3940 3941 return builder.make(); 3942 } 3943 3944 // Given a TextBlob, the bounding lines, and the set of current intercepts this 3945 // function adds the intercepts for the current TextBlob into the given set of 3946 // previoulsy calculated intercepts. This set is either of length 0, or a 3947 // multiple of 2 (since every intersection with a piece of text results in two 3948 // intercepts: entering/exiting) 3949 static void GetTextIntercepts(const sk_sp<const SkTextBlob>& aBlob, 3950 const SkScalar aBounds[], 3951 nsTArray<SkScalar>& aIntercepts) { 3952 // It's possible that we'll encounter a Windows exception deep inside 3953 // Skia's DirectWrite code while trying to get the intercepts. To avoid 3954 // crashing in this case, catch any such exception here and discard the 3955 // newly-added (and incompletely filled in) elements. 3956 int count = 0; 3957 MOZ_SEH_TRY { 3958 // https://skia.org/user/api/SkTextBlob_Reference#Text_Blob_Text_Intercepts 3959 count = aBlob->getIntercepts(aBounds, nullptr); 3960 if (count < 2) { 3961 return; 3962 } 3963 aBlob->getIntercepts(aBounds, aIntercepts.AppendElements(count)); 3964 } 3965 MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { 3966 gfxCriticalNote << "Exception occurred getting text intercepts"; 3967 aIntercepts.TruncateLength(aIntercepts.Length() - count); 3968 } 3969 } 3970 3971 // This function, given a set of intercepts that represent each intersection 3972 // between an under/overline and text, makes a series of calls to 3973 // PaintDecorationLineInternal that paints a series of clip rects which 3974 // implement the text-decoration-skip-ink property 3975 // Logic for where to place each clipped rect, and the length of each rect is 3976 // included here 3977 static void SkipInk(nsIFrame* aFrame, DrawTarget& aDrawTarget, 3978 const nsCSSRendering::PaintDecorationLineParams& aParams, 3979 const nsTArray<SkScalar>& aIntercepts, Float aPadding, 3980 Rect& aRect) { 3981 nsCSSRendering::PaintDecorationLineParams clipParams = aParams; 3982 const unsigned length = aIntercepts.Length(); 3983 3984 // For selections, this points to the selection start, which may not be at the 3985 // line start. 3986 const Float relativeTextStart = 3987 aParams.vertical ? aParams.pt.y : aParams.pt.x; 3988 const Float relativeTextEnd = relativeTextStart + aParams.lineSize.width; 3989 // The actual line start position needs to be adjusted by the offset of the 3990 // start position in the frame, because the intercept positions are based off 3991 // the whole text run. 3992 const Float absoluteLineStart = relativeTextStart - aParams.icoordInFrame; 3993 3994 // Compute the min/max positions based on inset. 3995 const Float insetLineDrawAreaStart = 3996 relativeTextStart + (aParams.insetLeft - aPadding); 3997 const Float insetLineDrawAreaEnd = 3998 relativeTextEnd - (aParams.insetRight - aPadding); 3999 4000 for (unsigned i = 0; i <= length; i += 2) { 4001 // Handle start/end edge cases and set up general case. 4002 // While we use the inset start/end values, it is possible the inset cuts 4003 // of the first intercept and into the next, so we will need to clamp 4004 // the dimensions in the other case too. 4005 SkScalar startIntercept = insetLineDrawAreaStart; 4006 if (i > 0) { 4007 startIntercept = 4008 std::max(aIntercepts[i - 1] + absoluteLineStart, startIntercept); 4009 } 4010 SkScalar endIntercept = insetLineDrawAreaEnd; 4011 if (i < length) { 4012 endIntercept = std::min(aIntercepts[i] + absoluteLineStart, endIntercept); 4013 } 4014 4015 // remove padding at both ends for width 4016 // the start of the line is calculated so the padding removes just 4017 // enough so that the line starts at its normal position 4018 clipParams.lineSize.width = 4019 endIntercept - startIntercept - (2.0 * aPadding); 4020 4021 // Don't draw decoration lines that have a smaller width than 1, or half 4022 // the line-end padding dimension. 4023 // This will catch the case of an intercept being fully removed by the 4024 // inset values, in which case the width will be negative. 4025 if (clipParams.lineSize.width < std::max(aPadding * 0.5, 1.0)) { 4026 continue; 4027 } 4028 4029 // Start the line right after the intercept's location plus room for 4030 // padding; snap the rect edges to device pixels for consistent rendering 4031 // of dots across separate fragments of a dotted line. 4032 if (aParams.vertical) { 4033 clipParams.pt.y = 4034 aParams.sidewaysLeft 4035 ? relativeTextEnd - (endIntercept - relativeTextStart) + aPadding 4036 : startIntercept + aPadding; 4037 aRect.y = std::floor(clipParams.pt.y + 0.5); 4038 aRect.SetBottomEdge( 4039 std::floor(clipParams.pt.y + clipParams.lineSize.width + 0.5)); 4040 } else { 4041 clipParams.pt.x = startIntercept + aPadding; 4042 aRect.x = std::floor(clipParams.pt.x + 0.5); 4043 aRect.SetRightEdge( 4044 std::floor(clipParams.pt.x + clipParams.lineSize.width + 0.5)); 4045 } 4046 4047 nsCSSRendering::PaintDecorationLineInternal(aFrame, aDrawTarget, clipParams, 4048 aRect); 4049 } 4050 } 4051 4052 void nsCSSRendering::PaintDecorationLine( 4053 nsIFrame* aFrame, DrawTarget& aDrawTarget, 4054 const PaintDecorationLineParams& aParams) { 4055 NS_ASSERTION(aParams.style != StyleTextDecorationStyle::None, 4056 "aStyle is none"); 4057 4058 Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams)); 4059 if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) { 4060 return; 4061 } 4062 4063 if (aParams.decoration != StyleTextDecorationLine::UNDERLINE && 4064 aParams.decoration != StyleTextDecorationLine::OVERLINE && 4065 aParams.decoration != StyleTextDecorationLine::LINE_THROUGH) { 4066 MOZ_ASSERT_UNREACHABLE("Invalid text decoration value"); 4067 return; 4068 } 4069 4070 // Check if decoration line will skip past ascenders/descenders 4071 // text-decoration-skip-ink only applies to overlines/underlines 4072 bool skipInkEnabled = 4073 aParams.skipInk != mozilla::StyleTextDecorationSkipInk::None && 4074 aParams.decoration != StyleTextDecorationLine::LINE_THROUGH && 4075 aParams.allowInkSkipping && aFrame->IsTextFrame(); 4076 4077 if (!skipInkEnabled || aParams.glyphRange.Length() == 0) { 4078 PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect); 4079 return; 4080 } 4081 4082 // Must be a text frame, otherwise skipInkEnabled (above) would be false. 4083 nsTextFrame* textFrame = static_cast<nsTextFrame*>(aFrame); 4084 4085 // get text run and current text offset (for line wrapping) 4086 gfxTextRun* textRun = 4087 textFrame->GetTextRun(nsTextFrame::TextRunType::eInflated); 4088 4089 // used for conversions from app units to device pixels 4090 int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); 4091 4092 // pointer to the array of glyphs for this TextRun 4093 gfxTextRun::CompressedGlyph* characterGlyphs = textRun->GetCharacterGlyphs(); 4094 4095 // get positioning info 4096 SkPoint textPos = {0, aParams.baselineOffset}; 4097 SkScalar bounds[] = {0, 0}; 4098 Float oneCSSPixel = aFrame->PresContext()->CSSPixelsToDevPixels(1.0f); 4099 if (!textRun->UseCenterBaseline()) { 4100 GetPositioning(aParams, rect, oneCSSPixel, 0, bounds); 4101 } 4102 4103 // array for the text intercepts 4104 AutoTArray<SkScalar, 256> intercepts; 4105 4106 // array for spacing data 4107 AutoTArray<gfxTextRun::PropertyProvider::Spacing, 64> spacing; 4108 spacing.SetLength(aParams.glyphRange.Length()); 4109 if (aParams.provider != nullptr) { 4110 aParams.provider->GetSpacing(aParams.glyphRange, spacing.Elements()); 4111 } 4112 4113 // loop through each glyph run 4114 // in most cases there will only be one 4115 bool isRTL = textRun->IsRightToLeft(); 4116 int32_t spacingOffset = isRTL ? aParams.glyphRange.Length() - 1 : 0; 4117 gfxTextRun::GlyphRunIterator iter(textRun, aParams.glyphRange, isRTL); 4118 4119 // For any glyph run where we don't actually do skipping, we'll need to 4120 // advance the current position by its width. 4121 // (For runs we do process, CreateTextBlob will update the position.) 4122 auto currentGlyphRunAdvance = [&]() { 4123 return textRun->GetAdvanceWidth( 4124 gfxTextRun::Range(iter.StringStart(), iter.StringEnd()), 4125 aParams.provider) / 4126 appUnitsPerDevPixel; 4127 }; 4128 4129 for (; !iter.AtEnd(); iter.NextRun()) { 4130 if (iter.GlyphRun()->mOrientation == 4131 mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT || 4132 (iter.GlyphRun()->mIsCJK && 4133 aParams.skipInk == mozilla::StyleTextDecorationSkipInk::Auto)) { 4134 // We don't support upright text in vertical modes currently 4135 // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1572294), 4136 // but we do need to update textPos so that following runs will be 4137 // correctly positioned. 4138 // We also don't apply skip-ink to CJK text runs because many fonts 4139 // have an underline that looks really bad if this is done 4140 // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1573249), 4141 // when skip-ink is set to 'auto'. 4142 textPos.fX += currentGlyphRunAdvance(); 4143 continue; 4144 } 4145 4146 gfxFont* font = iter.GlyphRun()->mFont; 4147 // Don't try to apply skip-ink to 'sbix' fonts like Apple Color Emoji, 4148 // because old macOS (10.9) may crash trying to retrieve glyph paths 4149 // that don't exist. 4150 if (font->GetFontEntry()->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) { 4151 textPos.fX += currentGlyphRunAdvance(); 4152 continue; 4153 } 4154 4155 // get a Skia version of the glyph run's font 4156 SkFont skiafont; 4157 if (!GetSkFontFromGfxFont(aDrawTarget, font, skiafont)) { 4158 PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect); 4159 return; 4160 } 4161 4162 // Create a text blob with correctly positioned glyphs. This also updates 4163 // textPos.fX with the advance of the glyphs. 4164 sk_sp<const SkTextBlob> textBlob = 4165 CreateTextBlob(textRun, characterGlyphs, skiafont, spacing.Elements(), 4166 iter.StringStart(), iter.StringEnd(), 4167 (float)appUnitsPerDevPixel, textPos, spacingOffset); 4168 4169 if (!textBlob) { 4170 textPos.fX += currentGlyphRunAdvance(); 4171 continue; 4172 } 4173 4174 if (textRun->UseCenterBaseline()) { 4175 // writing modes that use a center baseline need to be adjusted on a 4176 // font-by-font basis since Skia lines up the text on a alphabetic 4177 // baseline, but for some vertical-* writing modes the offset is from the 4178 // center. 4179 gfxFont::Metrics metrics = font->GetMetrics(nsFontMetrics::eHorizontal); 4180 Float centerToBaseline = (metrics.emAscent - metrics.emDescent) / 2.0f; 4181 GetPositioning(aParams, rect, oneCSSPixel, centerToBaseline, bounds); 4182 } 4183 4184 // compute the text intercepts that need to be skipped 4185 GetTextIntercepts(textBlob, bounds, intercepts); 4186 } 4187 bool needsSkipInk = intercepts.Length() > 0; 4188 4189 if (needsSkipInk) { 4190 // Padding between glyph intercepts and the decoration line: we use the 4191 // decoration line thickness, clamped to a minimum of 1px and a maximum 4192 // of 0.2em. 4193 Float padding = 4194 std::min(std::max(aParams.lineSize.height, oneCSSPixel), 4195 Float(textRun->GetFontGroup()->GetStyle()->size / 5.0)); 4196 SkipInk(aFrame, aDrawTarget, aParams, intercepts, padding, rect); 4197 } else { 4198 PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect); 4199 } 4200 } 4201 4202 void nsCSSRendering::PaintDecorationLineInternal( 4203 nsIFrame* aFrame, DrawTarget& aDrawTarget, 4204 const PaintDecorationLineParams& aParams, Rect aRect) { 4205 const Float lineThickness = aParams.lineSize.height; 4206 DeviceColor color = ToDeviceColor(aParams.color); 4207 ColorPattern colorPat(color); 4208 StrokeOptions strokeOptions(aParams.lineSize.height); 4209 DrawOptions drawOptions; 4210 4211 Float dash[2]; 4212 4213 AutoPopClips autoPopClips(&aDrawTarget); 4214 4215 mozilla::layout::TextDrawTarget* textDrawer = nullptr; 4216 if (aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT) { 4217 textDrawer = static_cast<mozilla::layout::TextDrawTarget*>(&aDrawTarget); 4218 } 4219 4220 switch (aParams.style) { 4221 case StyleTextDecorationStyle::Solid: 4222 case StyleTextDecorationStyle::Double: 4223 break; 4224 case StyleTextDecorationStyle::Dashed: { 4225 autoPopClips.PushClipRect(aRect); 4226 Float dashWidth = lineThickness * DOT_LENGTH * DASH_LENGTH; 4227 dash[0] = dashWidth; 4228 dash[1] = dashWidth; 4229 strokeOptions.mDashPattern = dash; 4230 strokeOptions.mDashLength = std::size(dash); 4231 strokeOptions.mLineCap = CapStyle::BUTT; 4232 aRect = ExpandPaintingRectForDecorationLine( 4233 aFrame, aParams.style, aRect, aParams.icoordInFrame, dashWidth * 2, 4234 aParams.vertical); 4235 // We should continue to draw the last dash even if it is not in the rect. 4236 aRect.width += dashWidth; 4237 break; 4238 } 4239 case StyleTextDecorationStyle::Dotted: { 4240 autoPopClips.PushClipRect(aRect); 4241 Float dashWidth = lineThickness * DOT_LENGTH; 4242 if (lineThickness > 2.0) { 4243 dash[0] = 0.f; 4244 dash[1] = dashWidth * 2.f; 4245 strokeOptions.mLineCap = CapStyle::ROUND; 4246 } else { 4247 dash[0] = dashWidth; 4248 dash[1] = dashWidth; 4249 } 4250 strokeOptions.mDashPattern = dash; 4251 strokeOptions.mDashLength = std::size(dash); 4252 aRect = ExpandPaintingRectForDecorationLine( 4253 aFrame, aParams.style, aRect, aParams.icoordInFrame, dashWidth * 2, 4254 aParams.vertical); 4255 // We should continue to draw the last dot even if it is not in the rect. 4256 aRect.width += dashWidth; 4257 break; 4258 } 4259 case StyleTextDecorationStyle::Wavy: 4260 autoPopClips.PushClipRect(aRect); 4261 if (lineThickness > 2.0) { 4262 drawOptions.mAntialiasMode = AntialiasMode::SUBPIXEL; 4263 } else { 4264 // Don't use anti-aliasing here. Because looks like lighter color wavy 4265 // line at this case. And probably, users don't think the 4266 // non-anti-aliased wavy line is not pretty. 4267 drawOptions.mAntialiasMode = AntialiasMode::NONE; 4268 } 4269 break; 4270 default: 4271 NS_ERROR("Invalid style value!"); 4272 return; 4273 } 4274 4275 // The block-direction position should be set to the middle of the line. 4276 if (aParams.vertical) { 4277 aRect.x += lineThickness / 2; 4278 } else { 4279 aRect.y += lineThickness / 2; 4280 } 4281 4282 switch (aParams.style) { 4283 case StyleTextDecorationStyle::Solid: 4284 case StyleTextDecorationStyle::Dotted: 4285 case StyleTextDecorationStyle::Dashed: { 4286 Point p1 = aRect.TopLeft(); 4287 Point p2 = aParams.vertical ? aRect.BottomLeft() : aRect.TopRight(); 4288 if (textDrawer) { 4289 textDrawer->AppendDecoration(p1, p2, lineThickness, aParams.vertical, 4290 color, aParams.style); 4291 } else { 4292 aDrawTarget.StrokeLine(p1, p2, colorPat, strokeOptions, drawOptions); 4293 } 4294 return; 4295 } 4296 case StyleTextDecorationStyle::Double: { 4297 /** 4298 * We are drawing double line as: 4299 * 4300 * +-------------------------------------------+ 4301 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^ 4302 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness 4303 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v 4304 * | | 4305 * | | 4306 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^ 4307 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness 4308 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v 4309 * +-------------------------------------------+ 4310 */ 4311 Point p1a = aRect.TopLeft(); 4312 Point p2a = aParams.vertical ? aRect.BottomLeft() : aRect.TopRight(); 4313 4314 if (aParams.vertical) { 4315 aRect.width -= lineThickness; 4316 } else { 4317 aRect.height -= lineThickness; 4318 } 4319 4320 Point p1b = aParams.vertical ? aRect.TopRight() : aRect.BottomLeft(); 4321 Point p2b = aRect.BottomRight(); 4322 4323 if (textDrawer) { 4324 textDrawer->AppendDecoration(p1a, p2a, lineThickness, aParams.vertical, 4325 color, StyleTextDecorationStyle::Solid); 4326 textDrawer->AppendDecoration(p1b, p2b, lineThickness, aParams.vertical, 4327 color, StyleTextDecorationStyle::Solid); 4328 } else { 4329 aDrawTarget.StrokeLine(p1a, p2a, colorPat, strokeOptions, drawOptions); 4330 aDrawTarget.StrokeLine(p1b, p2b, colorPat, strokeOptions, drawOptions); 4331 } 4332 return; 4333 } 4334 case StyleTextDecorationStyle::Wavy: { 4335 /** 4336 * We are drawing wavy line as: 4337 * 4338 * P: Path, X: Painted pixel 4339 * 4340 * +---------------------------------------+ 4341 * XX|X XXXXXX XXXXXX | 4342 * PP|PX XPPPPPPX XPPPPPPX | ^ 4343 * XX|XPX XPXXXXXXPX XPXXXXXXPX| | 4344 * | XPX XPX XPX XPX XP|X |adv 4345 * | XPXXXXXXPX XPXXXXXXPX X|PX | 4346 * | XPPPPPPX XPPPPPPX |XPX v 4347 * | XXXXXX XXXXXX | XX 4348 * +---------------------------------------+ 4349 * <---><---> ^ 4350 * adv flatLengthAtVertex rightMost 4351 * 4352 * 1. Always starts from top-left of the drawing area, however, we need 4353 * to draw the line from outside of the rect. Because the start 4354 * point of the line is not good style if we draw from inside it. 4355 * 2. First, draw horizontal line from outside the rect to top-left of 4356 * the rect; 4357 * 3. Goes down to bottom of the area at 45 degrees. 4358 * 4. Slides to right horizontaly, see |flatLengthAtVertex|. 4359 * 5. Goes up to top of the area at 45 degrees. 4360 * 6. Slides to right horizontaly. 4361 * 7. Repeat from 2 until reached to right-most edge of the area. 4362 * 4363 * In the vertical case, swap horizontal and vertical coordinates and 4364 * directions in the above description. 4365 */ 4366 4367 Float& rectICoord = aParams.vertical ? aRect.y : aRect.x; 4368 Float& rectISize = aParams.vertical ? aRect.height : aRect.width; 4369 const Float rectBSize = aParams.vertical ? aRect.width : aRect.height; 4370 4371 const Float adv = rectBSize - lineThickness; 4372 const Float flatLengthAtVertex = 4373 std::max((lineThickness - 1.0) * 2.0, 1.0); 4374 4375 // Align the start of wavy lines to the nearest ancestor block. 4376 const Float cycleLength = 2 * (adv + flatLengthAtVertex); 4377 aRect = ExpandPaintingRectForDecorationLine( 4378 aFrame, aParams.style, aRect, aParams.icoordInFrame, cycleLength, 4379 aParams.vertical); 4380 4381 if (textDrawer) { 4382 // Undo attempted centering 4383 Float& rectBCoord = aParams.vertical ? aRect.x : aRect.y; 4384 rectBCoord -= lineThickness / 2; 4385 4386 textDrawer->AppendWavyDecoration(aRect, lineThickness, aParams.vertical, 4387 color); 4388 return; 4389 } 4390 4391 // figure out if we can trim whole cycles from the left and right edges 4392 // of the line, to try and avoid creating an unnecessarily long and 4393 // complex path (but don't do this for webrender, ) 4394 const Float dirtyRectICoord = 4395 aParams.vertical ? aParams.dirtyRect.y : aParams.dirtyRect.x; 4396 int32_t skipCycles = floor((dirtyRectICoord - rectICoord) / cycleLength); 4397 if (skipCycles > 0) { 4398 rectICoord += skipCycles * cycleLength; 4399 rectISize -= skipCycles * cycleLength; 4400 } 4401 4402 rectICoord += lineThickness / 2.0; 4403 4404 Point pt(aRect.TopLeft()); 4405 Float& ptICoord = aParams.vertical ? pt.y.value : pt.x.value; 4406 Float& ptBCoord = aParams.vertical ? pt.x.value : pt.y.value; 4407 if (aParams.vertical) { 4408 ptBCoord += adv; 4409 } 4410 Float iCoordLimit = ptICoord + rectISize + lineThickness; 4411 4412 const Float dirtyRectIMost = aParams.vertical ? aParams.dirtyRect.YMost() 4413 : aParams.dirtyRect.XMost(); 4414 skipCycles = floor((iCoordLimit - dirtyRectIMost) / cycleLength); 4415 if (skipCycles > 0) { 4416 iCoordLimit -= skipCycles * cycleLength; 4417 } 4418 4419 RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(); 4420 RefPtr<Path> path; 4421 4422 ptICoord -= lineThickness; 4423 builder->MoveTo(pt); // 1 4424 4425 ptICoord = rectICoord; 4426 builder->LineTo(pt); // 2 4427 4428 // In vertical mode, to go "down" relative to the text we need to 4429 // decrease the block coordinate, whereas in horizontal we increase 4430 // it. So the sense of this flag is effectively inverted. 4431 bool goDown = !aParams.vertical; 4432 uint32_t iter = 0; 4433 while (ptICoord < iCoordLimit) { 4434 if (++iter > 1000) { 4435 // stroke the current path and start again, to avoid pathological 4436 // behavior in cairo with huge numbers of path segments 4437 path = builder->Finish(); 4438 aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions); 4439 builder = aDrawTarget.CreatePathBuilder(); 4440 builder->MoveTo(pt); 4441 iter = 0; 4442 } 4443 ptICoord += adv; 4444 ptBCoord += goDown ? adv : -adv; 4445 4446 builder->LineTo(pt); // 3 and 5 4447 4448 ptICoord += flatLengthAtVertex; 4449 builder->LineTo(pt); // 4 and 6 4450 4451 goDown = !goDown; 4452 } 4453 path = builder->Finish(); 4454 aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions); 4455 return; 4456 } 4457 default: 4458 NS_ERROR("Invalid style value!"); 4459 } 4460 } 4461 4462 Rect nsCSSRendering::DecorationLineToPath( 4463 const PaintDecorationLineParams& aParams) { 4464 NS_ASSERTION(aParams.style != StyleTextDecorationStyle::None, 4465 "aStyle is none"); 4466 4467 Rect path; // To benefit from RVO, we return this from all return points 4468 4469 Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams)); 4470 if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) { 4471 return path; 4472 } 4473 4474 if (aParams.decoration != StyleTextDecorationLine::UNDERLINE && 4475 aParams.decoration != StyleTextDecorationLine::OVERLINE && 4476 aParams.decoration != StyleTextDecorationLine::LINE_THROUGH) { 4477 MOZ_ASSERT_UNREACHABLE("Invalid text decoration value"); 4478 return path; 4479 } 4480 4481 if (aParams.style != StyleTextDecorationStyle::Solid) { 4482 // For the moment, we support only solid text decorations. 4483 return path; 4484 } 4485 4486 const Float lineThickness = aParams.lineSize.height; 4487 // The block-direction position should be set to the middle of the line. 4488 if (aParams.vertical) { 4489 rect.x += lineThickness / 2; 4490 path = Rect(rect.TopLeft() - Point(lineThickness / 2, 0.0), 4491 Size(lineThickness, rect.Height())); 4492 } else { 4493 rect.y += lineThickness / 2; 4494 path = Rect(rect.TopLeft() - Point(0.0, lineThickness / 2), 4495 Size(rect.Width(), lineThickness)); 4496 } 4497 4498 return path; 4499 } 4500 4501 nsRect nsCSSRendering::GetTextDecorationRect( 4502 nsPresContext* aPresContext, const DecorationRectParams& aParams) { 4503 NS_ASSERTION(aPresContext, "aPresContext is null"); 4504 NS_ASSERTION(aParams.style != StyleTextDecorationStyle::None, 4505 "aStyle is none"); 4506 4507 gfxRect rect = GetTextDecorationRectInternal(Point(0, 0), aParams); 4508 // The rect values are already rounded to nearest device pixels. 4509 nsRect r; 4510 r.x = aPresContext->GfxUnitsToAppUnits(rect.X()); 4511 r.y = aPresContext->GfxUnitsToAppUnits(rect.Y()); 4512 r.width = aPresContext->GfxUnitsToAppUnits(rect.Width()); 4513 r.height = aPresContext->GfxUnitsToAppUnits(rect.Height()); 4514 return r; 4515 } 4516 4517 gfxRect nsCSSRendering::GetTextDecorationRectInternal( 4518 const Point& aPt, const DecorationRectParams& aParams) { 4519 NS_ASSERTION(aParams.style <= StyleTextDecorationStyle::Wavy, 4520 "Invalid aStyle value"); 4521 4522 if (aParams.style == StyleTextDecorationStyle::None) { 4523 return gfxRect(0, 0, 0, 0); 4524 } 4525 4526 bool canLiftUnderline = aParams.descentLimit >= 0.0; 4527 4528 gfxFloat iCoord = aParams.vertical ? aPt.y : aPt.x; 4529 gfxFloat bCoord = aParams.vertical ? aPt.x : aPt.y; 4530 4531 // 'left' and 'right' are relative to the line, so for vertical writing modes 4532 // they will actually become top and bottom of the rendered line. 4533 // Similarly, aLineSize.width and .height are actually length and thickness 4534 // of the line, which runs horizontally or vertically according to aVertical. 4535 const gfxFloat left = floor(iCoord + 0.5), 4536 right = floor(iCoord + aParams.lineSize.width + 0.5); 4537 4538 // We compute |r| as if for a horizontal text run, and then swap vertical 4539 // and horizontal coordinates at the end if vertical was requested. 4540 gfxRect r(left, 0, right - left, 0); 4541 4542 const gfxFloat lineThickness = aParams.lineSize.height; 4543 gfxFloat defaultLineThickness = NS_round(aParams.defaultLineThickness); 4544 defaultLineThickness = std::max(defaultLineThickness, 1.0); 4545 4546 gfxFloat ascent = NS_round(aParams.ascent); 4547 gfxFloat descentLimit = floor(aParams.descentLimit); 4548 4549 gfxFloat suggestedMaxRectHeight = 4550 std::max(std::min(ascent, descentLimit), 1.0); 4551 r.height = lineThickness; 4552 if (aParams.style == StyleTextDecorationStyle::Double) { 4553 /** 4554 * We will draw double line as: 4555 * 4556 * +-------------------------------------------+ 4557 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^ 4558 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness 4559 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v 4560 * | | ^ 4561 * | | | gap 4562 * | | v 4563 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^ 4564 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness 4565 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v 4566 * +-------------------------------------------+ 4567 */ 4568 gfxFloat gap = NS_round(lineThickness / 2.0); 4569 gap = std::max(gap, 1.0); 4570 r.height = lineThickness * 2.0 + gap; 4571 if (canLiftUnderline) { 4572 if (r.Height() > suggestedMaxRectHeight) { 4573 // Don't shrink the line height, because the thickness has some meaning. 4574 // We can just shrink the gap at this time. 4575 r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0 + 1.0); 4576 } 4577 } 4578 } else if (aParams.style == StyleTextDecorationStyle::Wavy) { 4579 /** 4580 * We will draw wavy line as: 4581 * 4582 * +-------------------------------------------+ 4583 * |XXXXX XXXXXX XXXXXX | ^ 4584 * |XXXXXX XXXXXXXX XXXXXXXX | | lineThickness 4585 * |XXXXXXX XXXXXXXXXX XXXXXXXXXX| v 4586 * | XXX XXX XXX XXX XX| 4587 * | XXXXXXXXXX XXXXXXXXXX X| 4588 * | XXXXXXXX XXXXXXXX | 4589 * | XXXXXX XXXXXX | 4590 * +-------------------------------------------+ 4591 */ 4592 r.height = lineThickness > 2.0 ? lineThickness * 4.0 : lineThickness * 3.0; 4593 if (canLiftUnderline) { 4594 if (r.Height() > suggestedMaxRectHeight) { 4595 // Don't shrink the line height even if there is not enough space, 4596 // because the thickness has some meaning. E.g., the 1px wavy line and 4597 // 2px wavy line can be used for different meaning in IME selections 4598 // at same time. 4599 r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0); 4600 } 4601 } 4602 } 4603 4604 gfxFloat baseline = floor(bCoord + aParams.ascent + 0.5); 4605 4606 // Calculate adjusted offset based on writing-mode/orientation and thickness 4607 // of decoration line. The input value aParams.offset is the nominal position 4608 // (offset from baseline) where we would draw a single, infinitely-thin line; 4609 // but for a wavy or double line, we'll need to move the bounding rect of the 4610 // decoration outwards from the baseline so that an underline remains below 4611 // the glyphs, and an overline above them, despite the increased block-dir 4612 // extent of the decoration. 4613 // 4614 // So adjustments by r.Height() are used to make the wider line styles (wavy 4615 // and double) "grow" in the appropriate direction compared to the basic 4616 // single line. 4617 // 4618 // Note that at this point, the decoration rect is being calculated in line- 4619 // relative coordinates, where 'x' is line-rightwards, and 'y' is line- 4620 // upwards. We'll swap them to be physical coords at the end. 4621 gfxFloat offset = 0.0; 4622 4623 if (aParams.decoration == StyleTextDecorationLine::UNDERLINE) { 4624 offset = aParams.offset; 4625 if (canLiftUnderline) { 4626 if (descentLimit < -offset + r.Height()) { 4627 // If we can ignore the offset and the decoration line is overflowing, 4628 // we should align the bottom edge of the decoration line rect if it's 4629 // possible. Otherwise, we should lift up the top edge of the rect as 4630 // far as possible. 4631 gfxFloat offsetBottomAligned = -descentLimit + r.Height(); 4632 gfxFloat offsetTopAligned = 0.0; 4633 offset = std::min(offsetBottomAligned, offsetTopAligned); 4634 } 4635 } 4636 } else if (aParams.decoration == StyleTextDecorationLine::OVERLINE) { 4637 // For overline, we adjust the offset by defaultlineThickness (the default 4638 // thickness of a single decoration line) because empirically it looks 4639 // better to draw the overline just inside rather than outside the font's 4640 // ascent, which is what nsTextFrame passes as aParams.offset (as fonts 4641 // don't provide an explicit overline-offset). 4642 offset = aParams.offset - defaultLineThickness + r.Height(); 4643 } else if (aParams.decoration == StyleTextDecorationLine::LINE_THROUGH) { 4644 // To maintain a consistent mid-point for line-through decorations, 4645 // we adjust the offset by half of the decoration rect's height. 4646 gfxFloat extra = floor(r.Height() / 2.0 + 0.5); 4647 extra = std::max(extra, lineThickness); 4648 // computes offset for when user specifies a decoration width since 4649 // aParams.offset is derived from the font metric's line height 4650 gfxFloat decorationThicknessOffset = 4651 (lineThickness - defaultLineThickness) / 2.0; 4652 offset = aParams.offset - lineThickness + extra + decorationThicknessOffset; 4653 } else { 4654 MOZ_ASSERT_UNREACHABLE("Invalid text decoration value"); 4655 } 4656 4657 // Take text decoration inset into account. 4658 r.x += aParams.insetLeft; 4659 r.width -= aParams.insetLeft + aParams.insetRight; 4660 r.width = std::max(r.width, 0.0); 4661 4662 // Convert line-relative coordinate system (x = line-right, y = line-up) 4663 // to physical coords, and move the decoration rect to the calculated 4664 // offset from baseline. 4665 if (aParams.vertical) { 4666 std::swap(r.x, r.y); 4667 std::swap(r.width, r.height); 4668 // line-upwards in vertical mode = physical-right, so we /add/ offset 4669 // to baseline. Except in sideways-lr mode, where line-upwards will be 4670 // physical leftwards. 4671 if (aParams.sidewaysLeft) { 4672 r.x = baseline - floor(offset + 0.5); 4673 } else { 4674 r.x = baseline + floor(offset - r.Width() + 0.5); 4675 } 4676 } else { 4677 // line-upwards in horizontal mode = physical-up, but our physical coord 4678 // system works downwards, so we /subtract/ offset from baseline. 4679 r.y = baseline - floor(offset + 0.5); 4680 } 4681 4682 return r; 4683 } 4684 4685 #define MAX_BLUR_RADIUS 300 4686 #define MAX_SPREAD_RADIUS 50 4687 4688 static inline gfxPoint ComputeBlurStdDev(nscoord aBlurRadius, 4689 int32_t aAppUnitsPerDevPixel, 4690 gfxFloat aScaleX, gfxFloat aScaleY) { 4691 // http://dev.w3.org/csswg/css3-background/#box-shadow says that the 4692 // standard deviation of the blur should be half the given blur value. 4693 gfxFloat blurStdDev = gfxFloat(aBlurRadius) / gfxFloat(aAppUnitsPerDevPixel); 4694 4695 return gfxPoint( 4696 std::min((blurStdDev * aScaleX), gfxFloat(MAX_BLUR_RADIUS)) / 2.0, 4697 std::min((blurStdDev * aScaleY), gfxFloat(MAX_BLUR_RADIUS)) / 2.0); 4698 } 4699 4700 static inline IntSize ComputeBlurRadius(nscoord aBlurRadius, 4701 int32_t aAppUnitsPerDevPixel, 4702 gfxFloat aScaleX = 1.0, 4703 gfxFloat aScaleY = 1.0) { 4704 gfxPoint scaledBlurStdDev = 4705 ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, aScaleX, aScaleY); 4706 return gfxGaussianBlur::CalculateBlurRadius(scaledBlurStdDev); 4707 } 4708 4709 // ----- 4710 // nsContextBoxBlur 4711 // ----- 4712 gfxContext* nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius, 4713 nscoord aBlurRadius, 4714 int32_t aAppUnitsPerDevPixel, 4715 gfxContext* aDestinationCtx, 4716 const nsRect& aDirtyRect, 4717 const gfxRect* aSkipRect, uint32_t aFlags) { 4718 if (aRect.IsEmpty()) { 4719 mContext = nullptr; 4720 return nullptr; 4721 } 4722 4723 IntSize blurRadius; 4724 IntSize spreadRadius; 4725 GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel, 4726 aBlurRadius, aSpreadRadius, blurRadius, spreadRadius); 4727 4728 mDestinationCtx = aDestinationCtx; 4729 4730 // If not blurring, draw directly onto the destination device 4731 if (blurRadius.width <= 0 && blurRadius.height <= 0 && 4732 spreadRadius.width <= 0 && spreadRadius.height <= 0 && 4733 !(aFlags & FORCE_MASK)) { 4734 mContext = aDestinationCtx; 4735 return mContext; 4736 } 4737 4738 // Convert from app units to device pixels 4739 gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel); 4740 4741 gfxRect dirtyRect = 4742 nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel); 4743 dirtyRect.RoundOut(); 4744 4745 gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble(); 4746 rect = transform.TransformBounds(rect); 4747 4748 mPreTransformed = !transform.IsIdentity(); 4749 4750 // Create the temporary surface for blurring 4751 dirtyRect = transform.TransformBounds(dirtyRect); 4752 if (aSkipRect) { 4753 gfxRect skipRect = transform.TransformBounds(*aSkipRect); 4754 mOwnedContext = mGaussianBlur.Init(aDestinationCtx, rect, spreadRadius, 4755 blurRadius, &dirtyRect, &skipRect); 4756 } else { 4757 mOwnedContext = mGaussianBlur.Init(aDestinationCtx, rect, spreadRadius, 4758 blurRadius, &dirtyRect, nullptr); 4759 } 4760 mContext = mOwnedContext.get(); 4761 4762 if (mContext) { 4763 // we don't need to blur if skipRect is equal to rect 4764 // and mContext will be nullptr 4765 mContext->Multiply(transform); 4766 } 4767 return mContext; 4768 } 4769 4770 void nsContextBoxBlur::DoPaint() { 4771 if (mContext == mDestinationCtx) { 4772 return; 4773 } 4774 4775 gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx); 4776 4777 if (mPreTransformed) { 4778 mDestinationCtx->SetMatrix(Matrix()); 4779 } 4780 4781 mGaussianBlur.Paint(mDestinationCtx); 4782 } 4783 4784 gfxContext* nsContextBoxBlur::GetContext() { return mContext; } 4785 4786 /* static */ 4787 nsMargin nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius, 4788 int32_t aAppUnitsPerDevPixel) { 4789 IntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel); 4790 4791 nsMargin result; 4792 result.top = result.bottom = blurRadius.height * aAppUnitsPerDevPixel; 4793 result.left = result.right = blurRadius.width * aAppUnitsPerDevPixel; 4794 return result; 4795 } 4796 4797 /* static */ 4798 void nsContextBoxBlur::BlurRectangle( 4799 gfxContext* aDestinationCtx, const nsRect& aRect, 4800 int32_t aAppUnitsPerDevPixel, RectCornerRadii* aCornerRadii, 4801 nscoord aBlurRadius, const sRGBColor& aShadowColor, 4802 const nsRect& aDirtyRect, const gfxRect& aSkipRect) { 4803 DrawTarget& aDestDrawTarget = *aDestinationCtx->GetDrawTarget(); 4804 4805 if (aRect.IsEmpty()) { 4806 return; 4807 } 4808 4809 Rect shadowGfxRect = NSRectToRect(aRect, aAppUnitsPerDevPixel); 4810 4811 if (aBlurRadius <= 0) { 4812 ColorPattern color(ToDeviceColor(aShadowColor)); 4813 if (aCornerRadii) { 4814 RefPtr<Path> roundedRect = 4815 MakePathForRoundedRect(aDestDrawTarget, shadowGfxRect, *aCornerRadii); 4816 aDestDrawTarget.Fill(roundedRect, color); 4817 } else { 4818 aDestDrawTarget.FillRect(shadowGfxRect, color); 4819 } 4820 return; 4821 } 4822 4823 gfxFloat scaleX = 1; 4824 gfxFloat scaleY = 1; 4825 4826 // Do blurs in device space when possible. 4827 // Chrome/Skia always does the blurs in device space 4828 // and will sometimes get incorrect results (e.g. rotated blurs) 4829 gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble(); 4830 // XXX: we could probably handle negative scales but for now it's easier just 4831 // to fallback 4832 if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && 4833 transform._22 > 0.0) { 4834 scaleX = transform._11; 4835 scaleY = transform._22; 4836 aDestinationCtx->SetMatrix(Matrix()); 4837 } else { 4838 transform = gfxMatrix(); 4839 } 4840 4841 gfxPoint blurStdDev = 4842 ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY); 4843 4844 gfxRect dirtyRect = 4845 nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel); 4846 dirtyRect.RoundOut(); 4847 4848 gfxRect shadowThebesRect = 4849 transform.TransformBounds(ThebesRect(shadowGfxRect)); 4850 dirtyRect = transform.TransformBounds(dirtyRect); 4851 gfxRect skipRect = transform.TransformBounds(aSkipRect); 4852 4853 if (aCornerRadii) { 4854 aCornerRadii->Scale(scaleX, scaleY); 4855 } 4856 4857 gfxGaussianBlur::BlurRectangle(aDestinationCtx, shadowThebesRect, 4858 aCornerRadii, blurStdDev, aShadowColor, 4859 dirtyRect, skipRect); 4860 } 4861 4862 /* static */ 4863 void nsContextBoxBlur::GetBlurAndSpreadRadius( 4864 DrawTarget* aDestDrawTarget, int32_t aAppUnitsPerDevPixel, 4865 nscoord aBlurRadius, nscoord aSpreadRadius, IntSize& aOutBlurRadius, 4866 IntSize& aOutSpreadRadius, bool aConstrainSpreadRadius) { 4867 // Do blurs in device space when possible. 4868 // Chrome/Skia always does the blurs in device space 4869 // and will sometimes get incorrect results (e.g. rotated blurs) 4870 Matrix transform = aDestDrawTarget->GetTransform(); 4871 // XXX: we could probably handle negative scales but for now it's easier just 4872 // to fallback 4873 gfxFloat scaleX, scaleY; 4874 if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 || 4875 transform._22 <= 0.0) { 4876 scaleX = 1; 4877 scaleY = 1; 4878 } else { 4879 scaleX = transform._11; 4880 scaleY = transform._22; 4881 } 4882 4883 // compute a large or smaller blur radius 4884 aOutBlurRadius = 4885 ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY); 4886 aOutSpreadRadius = 4887 IntSize(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel), 4888 int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel)); 4889 4890 if (aConstrainSpreadRadius) { 4891 aOutSpreadRadius.width = 4892 std::min(aOutSpreadRadius.width, int32_t(MAX_SPREAD_RADIUS)); 4893 aOutSpreadRadius.height = 4894 std::min(aOutSpreadRadius.height, int32_t(MAX_SPREAD_RADIUS)); 4895 } 4896 } 4897 4898 /* static */ 4899 bool nsContextBoxBlur::InsetBoxBlur( 4900 gfxContext* aDestinationCtx, Rect aDestinationRect, Rect aShadowClipRect, 4901 sRGBColor& aShadowColor, nscoord aBlurRadiusAppUnits, 4902 nscoord aSpreadDistanceAppUnits, int32_t aAppUnitsPerDevPixel, 4903 bool aHasBorderRadius, RectCornerRadii& aInnerClipRectRadii, Rect aSkipRect, 4904 Point aShadowOffset) { 4905 if (aDestinationRect.IsEmpty()) { 4906 mContext = nullptr; 4907 return false; 4908 } 4909 4910 gfxContextAutoSaveRestore autoRestore(aDestinationCtx); 4911 4912 IntSize blurRadius; 4913 IntSize spreadRadius; 4914 // Convert the blur and spread radius to device pixels 4915 bool constrainSpreadRadius = false; 4916 GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel, 4917 aBlurRadiusAppUnits, aSpreadDistanceAppUnits, 4918 blurRadius, spreadRadius, constrainSpreadRadius); 4919 4920 // The blur and spread radius are scaled already, so scale all 4921 // input data to the blur. This way, we don't have to scale the min 4922 // inset blur to the invert of the dest context, then rescale it back 4923 // when we draw to the destination surface. 4924 auto scale = aDestinationCtx->CurrentMatrix().ScaleFactors(); 4925 Matrix transform = aDestinationCtx->CurrentMatrix(); 4926 4927 // XXX: we could probably handle negative scales but for now it's easier just 4928 // to fallback 4929 if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && 4930 transform._22 > 0.0) { 4931 // If we don't have a rotation, we're pre-transforming all the rects. 4932 aDestinationCtx->SetMatrix(Matrix()); 4933 } else { 4934 // Don't touch anything, we have a rotation. 4935 transform = Matrix(); 4936 } 4937 4938 Rect transformedDestRect = transform.TransformBounds(aDestinationRect); 4939 Rect transformedShadowClipRect = transform.TransformBounds(aShadowClipRect); 4940 Rect transformedSkipRect = transform.TransformBounds(aSkipRect); 4941 4942 transformedDestRect.Round(); 4943 transformedShadowClipRect.Round(); 4944 transformedSkipRect.RoundIn(); 4945 4946 for (auto corner : AllPhysicalCorners()) { 4947 aInnerClipRectRadii[corner].width = 4948 std::floor(scale.xScale * aInnerClipRectRadii[corner].width); 4949 aInnerClipRectRadii[corner].height = 4950 std::floor(scale.yScale * aInnerClipRectRadii[corner].height); 4951 } 4952 4953 mGaussianBlur.BlurInsetBox(aDestinationCtx, transformedDestRect, 4954 transformedShadowClipRect, blurRadius, 4955 aShadowColor, 4956 aHasBorderRadius ? &aInnerClipRectRadii : nullptr, 4957 transformedSkipRect, aShadowOffset); 4958 return true; 4959 }