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