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"
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"
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"
37 #include "nsIFrameInlines.h"
38 #include "nsPageSequenceFrame.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"
49 #include "nsCSSColorUtils.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"
65 #include "gfxGradientCache.h"
66 #include "nsInlineFrame.h"
67 #include "nsRubyTextContainerFrame.h"
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()
87 mLineContainer(nullptr),
88 mContinuationPoint(0),
90 mLineContinuationPoint(0),
95 ~InlineBackgroundData() = default;
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
)));
114 nscoord pos
; // an x coordinate if writing-mode is horizontal;
115 // y coordinate if vertical
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();
155 // aFrame itself is also to the right of its left edge, so add its
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
;
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.
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
) {
193 mPIStartBorderData
.SetCoord(joinedBorderArea
.y
);
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;
202 mPIStartBorderData
.SetCoord(
203 temp
.GetContinuousRect(mPIStartBorderData
.mFrame
).y
);
205 mPIStartBorderData
.SetCoord(
206 temp
.GetContinuousRect(mPIStartBorderData
.mFrame
).x
);
210 // ... and restore it when possible.
211 mPIStartBorderData
.SetCoord(saved
.mCoord
);
214 if (joinedBorderArea
.y
> mPIStartBorderData
.mCoord
) {
216 -(mUnbrokenMeasure
+ joinedBorderArea
.y
- aBorderArea
.height
);
218 joinedBorderArea
.y
-= mPIStartBorderData
.mCoord
;
221 if (joinedBorderArea
.x
> mPIStartBorderData
.mCoord
) {
223 -(mUnbrokenMeasure
+ joinedBorderArea
.x
- aBorderArea
.width
);
225 joinedBorderArea
.x
-= mPIStartBorderData
.mCoord
;
228 return joinedBorderArea
;
231 nsRect
GetBoundingRect(nsIFrame
* 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
);
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
259 void SetCoord(nscoord aCoord
) {
266 nsIFrame
* mLineContainer
;
268 nscoord mContinuationPoint
;
269 nscoord mUnbrokenMeasure
;
270 nscoord mLineContinuationPoint
;
271 PhysicalInlineStartBorderData mPIStartBorderData
;
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
) {
284 nsIFrame
* prevContinuation
= GetPrevContinuation(aFrame
);
286 if (!prevContinuation
|| mFrame
!= prevContinuation
) {
287 // Ok, we've got the wrong frame. We have to start from scratch.
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.
300 (aFrame
->GetPrevInFlow() || !AreOnSameLine(mFrame
, aFrame
))) {
301 mLineContinuationPoint
= mContinuationPoint
;
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());
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?");
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());
329 nextCont
= block
->GetProperty(nsIFrame::IBSplitSibling());
330 NS_ASSERTION(nextCont
, "How did that happen?");
336 void Init(nsIFrame
* aFrame
) {
337 mPIStartBorderData
.Reset();
338 mBidiEnabled
= aFrame
->PresContext()->BidiEnabled();
340 // Find the line container frame
341 mLineContainer
= aFrame
;
342 while (mLineContainer
&& mLineContainer
->IsLineParticipant()) {
343 mLineContainer
= mLineContainer
->GetParent();
346 MOZ_ASSERT(mLineContainer
, "Cannot find line containing frame.");
347 MOZ_ASSERT(mLineContainer
!= aFrame
,
348 "line container frame "
349 "should be an ancestor of the target frame.");
352 mVertical
= aFrame
->GetWritingMode().IsVertical();
354 // Start with the previous flow frame as our continuation point
355 // is the total of the widths of the previous frames.
356 nsIFrame
* inlineFrame
= GetPrevContinuation(aFrame
);
357 bool changedLines
= false;
358 while (inlineFrame
) {
359 if (!mPIStartBorderData
.mFrame
&&
360 !(mVertical
? inlineFrame
->GetSkipSides().Top()
361 : inlineFrame
->GetSkipSides().Left())) {
362 mPIStartBorderData
.mFrame
= inlineFrame
;
364 nsRect rect
= inlineFrame
->GetRect();
365 mContinuationPoint
+= mVertical
? rect
.height
: rect
.width
;
367 (changedLines
|| !AreOnSameLine(aFrame
, inlineFrame
))) {
368 mLineContinuationPoint
+= mVertical
? rect
.height
: rect
.width
;
371 mUnbrokenMeasure
+= mVertical
? rect
.height
: rect
.width
;
372 mBoundingBox
.UnionRect(mBoundingBox
, rect
);
373 inlineFrame
= GetPrevContinuation(inlineFrame
);
376 // Next add this frame and subsequent frames to the bounding box and
378 inlineFrame
= aFrame
;
379 while (inlineFrame
) {
380 if (!mPIStartBorderData
.mFrame
&&
381 !(mVertical
? inlineFrame
->GetSkipSides().Top()
382 : inlineFrame
->GetSkipSides().Left())) {
383 mPIStartBorderData
.mFrame
= inlineFrame
;
385 nsRect rect
= inlineFrame
->GetRect();
386 mUnbrokenMeasure
+= mVertical
? rect
.height
: rect
.width
;
387 mBoundingBox
.UnionRect(mBoundingBox
, rect
);
388 inlineFrame
= GetNextContinuation(inlineFrame
);
394 bool AreOnSameLine(nsIFrame
* aFrame1
, nsIFrame
* aFrame2
) {
395 if (nsBlockFrame
* blockFrame
= do_QueryFrame(mLineContainer
)) {
396 bool isValid1
, isValid2
;
397 nsBlockInFlowLineIterator
it1(blockFrame
, aFrame1
, &isValid1
);
398 nsBlockInFlowLineIterator
it2(blockFrame
, aFrame2
, &isValid2
);
399 return isValid1
&& isValid2
&&
400 // Make sure aFrame1 and aFrame2 are in the same continuation of
402 it1
.GetContainer() == it2
.GetContainer() &&
403 // And on the same line in it
404 it1
.GetLine().get() == it2
.GetLine().get();
406 if (nsRubyTextContainerFrame
* rtcFrame
= do_QueryFrame(mLineContainer
)) {
407 nsBlockFrame
* block
= nsLayoutUtils::FindNearestBlockAncestor(rtcFrame
);
408 // Ruby text container can only hold one line of text, so if they
409 // are in the same continuation, they are in the same line. Since
410 // ruby text containers are bidi isolate, they are never split for
411 // bidi reordering, which means being in different continuation
412 // indicates being in different lines.
413 for (nsIFrame
* frame
= rtcFrame
->FirstContinuation(); frame
;
414 frame
= frame
->GetNextContinuation()) {
416 nsLayoutUtils::IsProperAncestorFrame(frame
, aFrame1
, block
);
418 nsLayoutUtils::IsProperAncestorFrame(frame
, aFrame2
, block
);
419 if (isDescendant1
&& isDescendant2
) {
422 if (isDescendant1
|| isDescendant2
) {
426 MOZ_ASSERT_UNREACHABLE("None of the frames is a descendant of this rtc?");
428 MOZ_ASSERT_UNREACHABLE("Do we have any other type of line container?");
433 static StaticAutoPtr
<InlineBackgroundData
> gInlineBGData
;
435 // Initialize any static variables used by nsCSSRendering.
436 void nsCSSRendering::Init() {
437 NS_ASSERTION(!gInlineBGData
, "Init called twice");
438 gInlineBGData
= new InlineBackgroundData();
441 // Clean up any global variables used by nsCSSRendering.
442 void nsCSSRendering::Shutdown() { gInlineBGData
= nullptr; }
447 static nscolor
MakeBevelColor(mozilla::Side whichSide
, StyleBorderStyle style
,
448 nscolor aBorderColor
) {
452 // Given a background color and a border color
453 // calculate the color used for the shading
454 NS_GetSpecial3DColors(colors
, aBorderColor
);
456 if ((style
== StyleBorderStyle::Outset
) ||
457 (style
== StyleBorderStyle::Ridge
)) {
458 // Flip colors for these two border styles
461 whichSide
= eSideTop
;
464 whichSide
= eSideLeft
;
467 whichSide
= eSideBottom
;
470 whichSide
= eSideRight
;
477 theColor
= colors
[1];
480 theColor
= colors
[1];
483 theColor
= colors
[0];
487 theColor
= colors
[0];
493 static bool GetRadii(nsIFrame
* aForFrame
, const nsStyleBorder
& aBorder
,
494 const nsRect
& aOrigBorderArea
, const nsRect
& aBorderArea
,
496 bool haveRoundedCorners
;
497 nsSize sz
= aBorderArea
.Size();
498 nsSize frameSize
= aForFrame
->GetSize();
499 if (&aBorder
== aForFrame
->StyleBorder() &&
500 frameSize
== aOrigBorderArea
.Size()) {
501 haveRoundedCorners
= aForFrame
->GetBorderRadii(sz
, sz
, Sides(), aRadii
);
503 haveRoundedCorners
= nsIFrame::ComputeBorderRadii(
504 aBorder
.mBorderRadius
, frameSize
, sz
, Sides(), aRadii
);
507 return haveRoundedCorners
;
510 static bool GetRadii(nsIFrame
* aForFrame
, const nsStyleBorder
& aBorder
,
511 const nsRect
& aOrigBorderArea
, const nsRect
& aBorderArea
,
512 RectCornerRadii
* aBgRadii
) {
514 bool haveRoundedCorners
=
515 GetRadii(aForFrame
, aBorder
, aOrigBorderArea
, aBorderArea
, radii
);
517 if (haveRoundedCorners
) {
518 auto d2a
= aForFrame
->PresContext()->AppUnitsPerDevPixel();
519 nsCSSRendering::ComputePixelRadii(radii
, d2a
, aBgRadii
);
521 return haveRoundedCorners
;
524 static nsRect
JoinBoxesForBlockAxisSlice(nsIFrame
* aFrame
,
525 const nsRect
& aBorderArea
) {
526 // Inflate the block-axis size as if our continuations were laid out
527 // adjacent in that axis. Note that we don't touch the inline size.
528 const auto wm
= aFrame
->GetWritingMode();
529 const nsSize dummyContainerSize
;
530 LogicalRect
borderArea(wm
, aBorderArea
, dummyContainerSize
);
532 nsIFrame
* f
= aFrame
->GetNextContinuation();
533 for (; f
; f
= f
->GetNextContinuation()) {
534 bSize
+= f
->BSize(wm
);
536 borderArea
.BSize(wm
) += bSize
;
538 f
= aFrame
->GetPrevContinuation();
539 for (; f
; f
= f
->GetPrevContinuation()) {
540 bSize
+= f
->BSize(wm
);
542 borderArea
.BStart(wm
) -= bSize
;
543 borderArea
.BSize(wm
) += bSize
;
544 return borderArea
.GetPhysicalRect(wm
, dummyContainerSize
);
548 * Inflate aBorderArea which is relative to aFrame's origin to calculate
549 * a hypothetical non-split frame area for all the continuations.
550 * See "Joining Boxes for 'slice'" in
551 * http://dev.w3.org/csswg/css-break/#break-decoration
553 enum InlineBoxOrder
{ eForBorder
, eForBackground
};
554 static nsRect
JoinBoxesForSlice(nsIFrame
* aFrame
, const nsRect
& aBorderArea
,
555 InlineBoxOrder aOrder
) {
556 if (static_cast<nsInlineFrame
*>(do_QueryFrame(aFrame
))) {
557 return (aOrder
== eForBorder
558 ? gInlineBGData
->GetBorderContinuousRect(aFrame
, aBorderArea
)
559 : gInlineBGData
->GetContinuousRect(aFrame
)) +
560 aBorderArea
.TopLeft();
562 return JoinBoxesForBlockAxisSlice(aFrame
, aBorderArea
);
566 bool nsCSSRendering::IsBoxDecorationSlice(const nsStyleBorder
& aStyleBorder
) {
567 return aStyleBorder
.mBoxDecorationBreak
== StyleBoxDecorationBreak::Slice
;
571 nsRect
nsCSSRendering::BoxDecorationRectForBorder(
572 nsIFrame
* aFrame
, const nsRect
& aBorderArea
, Sides aSkipSides
,
573 const nsStyleBorder
* aStyleBorder
) {
575 aStyleBorder
= aFrame
->StyleBorder();
577 // If aSkipSides.IsEmpty() then there are no continuations, or it's
578 // a ::first-letter that wants all border sides on the first continuation.
579 return IsBoxDecorationSlice(*aStyleBorder
) && !aSkipSides
.IsEmpty()
580 ? ::JoinBoxesForSlice(aFrame
, aBorderArea
, eForBorder
)
585 nsRect
nsCSSRendering::BoxDecorationRectForBackground(
586 nsIFrame
* aFrame
, const nsRect
& aBorderArea
, Sides aSkipSides
,
587 const nsStyleBorder
* aStyleBorder
) {
589 aStyleBorder
= aFrame
->StyleBorder();
591 // If aSkipSides.IsEmpty() then there are no continuations, or it's
592 // a ::first-letter that wants all border sides on the first continuation.
593 return IsBoxDecorationSlice(*aStyleBorder
) && !aSkipSides
.IsEmpty()
594 ? ::JoinBoxesForSlice(aFrame
, aBorderArea
, eForBackground
)
598 //----------------------------------------------------------------------
599 // Thebes Border Rendering Code Start
602 * Compute the float-pixel radii that should be used for drawing
603 * this border/outline, given the various input bits.
606 void nsCSSRendering::ComputePixelRadii(const nscoord
* aAppUnitsRadii
,
607 nscoord aAppUnitsPerPixel
,
608 RectCornerRadii
* oBorderRadii
) {
610 for (const auto corner
: mozilla::AllPhysicalHalfCorners()) {
611 radii
[corner
] = Float(aAppUnitsRadii
[corner
]) / aAppUnitsPerPixel
;
614 (*oBorderRadii
)[C_TL
] = Size(radii
[eCornerTopLeftX
], radii
[eCornerTopLeftY
]);
615 (*oBorderRadii
)[C_TR
] =
616 Size(radii
[eCornerTopRightX
], radii
[eCornerTopRightY
]);
617 (*oBorderRadii
)[C_BR
] =
618 Size(radii
[eCornerBottomRightX
], radii
[eCornerBottomRightY
]);
619 (*oBorderRadii
)[C_BL
] =
620 Size(radii
[eCornerBottomLeftX
], radii
[eCornerBottomLeftY
]);
623 static Maybe
<nsStyleBorder
> GetBorderIfVisited(const ComputedStyle
& aStyle
) {
624 Maybe
<nsStyleBorder
> result
;
625 // Don't check RelevantLinkVisited here, since we want to take the
626 // same amount of time whether or not it's true.
627 const ComputedStyle
* styleIfVisited
= aStyle
.GetStyleIfVisited();
628 if (MOZ_LIKELY(!styleIfVisited
)) {
632 result
.emplace(*aStyle
.StyleBorder());
633 auto& newBorder
= result
.ref();
634 for (const auto side
: mozilla::AllPhysicalSides()) {
635 nscolor color
= aStyle
.GetVisitedDependentColor(
636 nsStyleBorder::BorderColorFieldFor(side
));
637 newBorder
.BorderColorFor(side
) = StyleColor::FromColor(color
);
643 ImgDrawResult
nsCSSRendering::PaintBorder(
644 nsPresContext
* aPresContext
, gfxContext
& aRenderingContext
,
645 nsIFrame
* aForFrame
, const nsRect
& aDirtyRect
, const nsRect
& aBorderArea
,
646 ComputedStyle
* aStyle
, PaintBorderFlags aFlags
, Sides aSkipSides
) {
647 AUTO_PROFILER_LABEL("nsCSSRendering::PaintBorder", GRAPHICS
);
649 Maybe
<nsStyleBorder
> visitedBorder
= GetBorderIfVisited(*aStyle
);
650 return PaintBorderWithStyleBorder(
651 aPresContext
, aRenderingContext
, aForFrame
, aDirtyRect
, aBorderArea
,
652 visitedBorder
.refOr(*aStyle
->StyleBorder()), aStyle
, aFlags
, aSkipSides
);
655 Maybe
<nsCSSBorderRenderer
> nsCSSRendering::CreateBorderRenderer(
656 nsPresContext
* aPresContext
, DrawTarget
* aDrawTarget
, nsIFrame
* aForFrame
,
657 const nsRect
& aDirtyRect
, const nsRect
& aBorderArea
, ComputedStyle
* aStyle
,
658 bool* aOutBorderIsEmpty
, Sides aSkipSides
) {
659 Maybe
<nsStyleBorder
> visitedBorder
= GetBorderIfVisited(*aStyle
);
660 return CreateBorderRendererWithStyleBorder(
661 aPresContext
, aDrawTarget
, aForFrame
, aDirtyRect
, aBorderArea
,
662 visitedBorder
.refOr(*aStyle
->StyleBorder()), aStyle
, aOutBorderIsEmpty
,
666 ImgDrawResult
nsCSSRendering::CreateWebRenderCommandsForBorder(
667 nsDisplayItem
* aItem
, nsIFrame
* aForFrame
, const nsRect
& aBorderArea
,
668 mozilla::wr::DisplayListBuilder
& aBuilder
,
669 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
670 const mozilla::layers::StackingContextHelper
& aSc
,
671 mozilla::layers::RenderRootStateManager
* aManager
,
672 nsDisplayListBuilder
* aDisplayListBuilder
) {
673 const auto* style
= aForFrame
->Style();
674 Maybe
<nsStyleBorder
> visitedBorder
= GetBorderIfVisited(*style
);
675 return nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
676 aItem
, aForFrame
, aBorderArea
, aBuilder
, aResources
, aSc
, aManager
,
677 aDisplayListBuilder
, visitedBorder
.refOr(*style
->StyleBorder()));
680 void nsCSSRendering::CreateWebRenderCommandsForNullBorder(
681 nsDisplayItem
* aItem
, nsIFrame
* aForFrame
, const nsRect
& aBorderArea
,
682 mozilla::wr::DisplayListBuilder
& aBuilder
,
683 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
684 const mozilla::layers::StackingContextHelper
& aSc
,
685 const nsStyleBorder
& aStyleBorder
) {
686 bool borderIsEmpty
= false;
687 Maybe
<nsCSSBorderRenderer
> br
=
688 nsCSSRendering::CreateNullBorderRendererWithStyleBorder(
689 aForFrame
->PresContext(), nullptr, aForFrame
, nsRect(), aBorderArea
,
690 aStyleBorder
, aForFrame
->Style(), &borderIsEmpty
,
691 aForFrame
->GetSkipSides());
692 if (!borderIsEmpty
&& br
) {
693 br
->CreateWebRenderCommands(aItem
, aBuilder
, aResources
, aSc
);
697 ImgDrawResult
nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
698 nsDisplayItem
* aItem
, nsIFrame
* aForFrame
, const nsRect
& aBorderArea
,
699 mozilla::wr::DisplayListBuilder
& aBuilder
,
700 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
701 const mozilla::layers::StackingContextHelper
& aSc
,
702 mozilla::layers::RenderRootStateManager
* aManager
,
703 nsDisplayListBuilder
* aDisplayListBuilder
,
704 const nsStyleBorder
& aStyleBorder
) {
705 auto& borderImage
= aStyleBorder
.mBorderImageSource
;
706 // First try to create commands for simple borders.
707 if (borderImage
.IsNone()) {
708 CreateWebRenderCommandsForNullBorder(
709 aItem
, aForFrame
, aBorderArea
, aBuilder
, aResources
, aSc
, aStyleBorder
);
710 return ImgDrawResult::SUCCESS
;
713 // Next we try image and gradient borders. Gradients are not supported at
715 if (!borderImage
.IsImageRequestType()) {
716 return ImgDrawResult::NOT_SUPPORTED
;
719 if (aStyleBorder
.mBorderImageRepeatH
== StyleBorderImageRepeat::Space
||
720 aStyleBorder
.mBorderImageRepeatV
== StyleBorderImageRepeat::Space
) {
721 return ImgDrawResult::NOT_SUPPORTED
;
725 if (aDisplayListBuilder
->IsPaintingToWindow()) {
726 flags
|= nsImageRenderer::FLAG_PAINTING_TO_WINDOW
;
728 if (aDisplayListBuilder
->ShouldSyncDecodeImages()) {
729 flags
|= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES
;
733 image::ImgDrawResult result
;
734 Maybe
<nsCSSBorderImageRenderer
> bir
=
735 nsCSSBorderImageRenderer::CreateBorderImageRenderer(
736 aForFrame
->PresContext(), aForFrame
, aBorderArea
, aStyleBorder
,
737 aItem
->GetBounds(aDisplayListBuilder
, &dummy
),
738 aForFrame
->GetSkipSides(), flags
, &result
);
741 // We aren't ready. Try to fallback to the null border image if present but
742 // return the draw result for the border image renderer.
743 CreateWebRenderCommandsForNullBorder(
744 aItem
, aForFrame
, aBorderArea
, aBuilder
, aResources
, aSc
, aStyleBorder
);
748 return bir
->CreateWebRenderCommands(aItem
, aForFrame
, aBuilder
, aResources
,
749 aSc
, aManager
, aDisplayListBuilder
);
752 static nsCSSBorderRenderer
ConstructBorderRenderer(
753 nsPresContext
* aPresContext
, ComputedStyle
* aStyle
, DrawTarget
* aDrawTarget
,
754 nsIFrame
* aForFrame
, const nsRect
& aDirtyRect
, const nsRect
& aBorderArea
,
755 const nsStyleBorder
& aStyleBorder
, Sides aSkipSides
, bool* aNeedsClip
) {
756 nsMargin border
= aStyleBorder
.GetComputedBorder();
758 // Compute the outermost boundary of the area that might be painted.
759 // Same coordinate space as aBorderArea & aBGClipRect.
760 nsRect joinedBorderArea
= nsCSSRendering::BoxDecorationRectForBorder(
761 aForFrame
, aBorderArea
, aSkipSides
, &aStyleBorder
);
762 RectCornerRadii bgRadii
;
763 ::GetRadii(aForFrame
, aStyleBorder
, aBorderArea
, joinedBorderArea
, &bgRadii
);
765 PrintAsFormatString(" joinedBorderArea: %d %d %d %d\n", joinedBorderArea
.x
,
766 joinedBorderArea
.y
, joinedBorderArea
.width
,
767 joinedBorderArea
.height
);
770 if (nsCSSRendering::IsBoxDecorationSlice(aStyleBorder
)) {
771 if (joinedBorderArea
.IsEqualEdges(aBorderArea
)) {
772 // No need for a clip, just skip the sides we don't want.
773 border
.ApplySkipSides(aSkipSides
);
775 // We're drawing borders around the joined continuation boxes so we need
776 // to clip that to the slice that we want for this frame.
780 MOZ_ASSERT(joinedBorderArea
.IsEqualEdges(aBorderArea
),
781 "Should use aBorderArea for box-decoration-break:clone");
783 aForFrame
->GetSkipSides().IsEmpty() ||
784 aForFrame
->IsTrueOverflowContainer() ||
785 aForFrame
->IsColumnSetFrame(), // a little broader than column-rule
786 "Should not skip sides for box-decoration-break:clone except "
787 "::first-letter/line continuations or other frame types that "
788 "don't have borders but those shouldn't reach this point. "
789 "Overflow containers do reach this point though, as does "
790 "column-rule drawing (which always involves a columnset).");
791 border
.ApplySkipSides(aSkipSides
);
794 // Convert to dev pixels.
795 nscoord oneDevPixel
= aPresContext
->DevPixelsToAppUnits(1);
796 Rect joinedBorderAreaPx
= NSRectToRect(joinedBorderArea
, oneDevPixel
);
797 Float borderWidths
[4] = {
798 Float(border
.top
) / oneDevPixel
, Float(border
.right
) / oneDevPixel
,
799 Float(border
.bottom
) / oneDevPixel
, Float(border
.left
) / oneDevPixel
};
800 Rect dirtyRect
= NSRectToRect(aDirtyRect
, oneDevPixel
);
802 StyleBorderStyle borderStyles
[4];
803 nscolor borderColors
[4];
805 // pull out styles, colors
806 for (const auto i
: mozilla::AllPhysicalSides()) {
807 borderStyles
[i
] = aStyleBorder
.GetBorderStyle(i
);
808 borderColors
[i
] = aStyleBorder
.BorderColorFor(i
).CalcColor(*aStyle
);
812 " borderStyles: %d %d %d %d\n", static_cast<int>(borderStyles
[0]),
813 static_cast<int>(borderStyles
[1]), static_cast<int>(borderStyles
[2]),
814 static_cast<int>(borderStyles
[3]));
816 return nsCSSBorderRenderer(
817 aPresContext
, aDrawTarget
, dirtyRect
, joinedBorderAreaPx
, borderStyles
,
818 borderWidths
, bgRadii
, borderColors
, !aForFrame
->BackfaceIsHidden(),
819 *aNeedsClip
? Some(NSRectToRect(aBorderArea
, oneDevPixel
)) : Nothing());
822 ImgDrawResult
nsCSSRendering::PaintBorderWithStyleBorder(
823 nsPresContext
* aPresContext
, gfxContext
& aRenderingContext
,
824 nsIFrame
* aForFrame
, const nsRect
& aDirtyRect
, const nsRect
& aBorderArea
,
825 const nsStyleBorder
& aStyleBorder
, ComputedStyle
* aStyle
,
826 PaintBorderFlags aFlags
, Sides aSkipSides
) {
827 DrawTarget
& aDrawTarget
= *aRenderingContext
.GetDrawTarget();
829 PrintAsStringNewline("++ PaintBorder");
831 // Check to see if we have an appearance defined. If so, we let the theme
832 // renderer draw the border. DO not get the data from aForFrame, since the
833 // passed in ComputedStyle may be different! Always use |aStyle|!
834 StyleAppearance appearance
= aStyle
->StyleDisplay()->EffectiveAppearance();
835 if (appearance
!= StyleAppearance::None
) {
836 nsITheme
* theme
= aPresContext
->Theme();
837 if (theme
->ThemeSupportsWidget(aPresContext
, aForFrame
, appearance
)) {
838 return ImgDrawResult::SUCCESS
; // Let the theme handle it.
842 if (!aStyleBorder
.mBorderImageSource
.IsNone()) {
843 ImgDrawResult result
= ImgDrawResult::SUCCESS
;
845 uint32_t irFlags
= 0;
846 if (aFlags
& PaintBorderFlags::SyncDecodeImages
) {
847 irFlags
|= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES
;
850 // Creating the border image renderer will request a decode, and we rely on
852 Maybe
<nsCSSBorderImageRenderer
> renderer
=
853 nsCSSBorderImageRenderer::CreateBorderImageRenderer(
854 aPresContext
, aForFrame
, aBorderArea
, aStyleBorder
, aDirtyRect
,
855 aSkipSides
, irFlags
, &result
);
856 // renderer was created successfully, which means border image is ready to
859 MOZ_ASSERT(result
== ImgDrawResult::SUCCESS
);
860 return renderer
->DrawBorderImage(aPresContext
, aRenderingContext
,
861 aForFrame
, aDirtyRect
);
865 ImgDrawResult result
= ImgDrawResult::SUCCESS
;
867 // If we had a border-image, but it wasn't loaded, then we should return
868 // ImgDrawResult::NOT_READY; we'll want to try again if we do a paint with
869 // sync decoding enabled.
870 if (!aStyleBorder
.mBorderImageSource
.IsNone()) {
871 result
= ImgDrawResult::NOT_READY
;
874 nsMargin border
= aStyleBorder
.GetComputedBorder();
875 if (0 == border
.left
&& 0 == border
.right
&& 0 == border
.top
&&
876 0 == border
.bottom
) {
881 bool needsClip
= false;
882 nsCSSBorderRenderer br
= ConstructBorderRenderer(
883 aPresContext
, aStyle
, &aDrawTarget
, aForFrame
, aDirtyRect
, aBorderArea
,
884 aStyleBorder
, aSkipSides
, &needsClip
);
886 aDrawTarget
.PushClipRect(NSRectToSnappedRect(
887 aBorderArea
, aForFrame
->PresContext()->AppUnitsPerDevPixel(),
894 aDrawTarget
.PopClip();
897 PrintAsStringNewline();
902 Maybe
<nsCSSBorderRenderer
> nsCSSRendering::CreateBorderRendererWithStyleBorder(
903 nsPresContext
* aPresContext
, DrawTarget
* aDrawTarget
, nsIFrame
* aForFrame
,
904 const nsRect
& aDirtyRect
, const nsRect
& aBorderArea
,
905 const nsStyleBorder
& aStyleBorder
, ComputedStyle
* aStyle
,
906 bool* aOutBorderIsEmpty
, Sides aSkipSides
) {
907 if (!aStyleBorder
.mBorderImageSource
.IsNone()) {
910 return CreateNullBorderRendererWithStyleBorder(
911 aPresContext
, aDrawTarget
, aForFrame
, aDirtyRect
, aBorderArea
,
912 aStyleBorder
, aStyle
, aOutBorderIsEmpty
, aSkipSides
);
915 Maybe
<nsCSSBorderRenderer
>
916 nsCSSRendering::CreateNullBorderRendererWithStyleBorder(
917 nsPresContext
* aPresContext
, DrawTarget
* aDrawTarget
, nsIFrame
* aForFrame
,
918 const nsRect
& aDirtyRect
, const nsRect
& aBorderArea
,
919 const nsStyleBorder
& aStyleBorder
, ComputedStyle
* aStyle
,
920 bool* aOutBorderIsEmpty
, Sides aSkipSides
) {
921 StyleAppearance appearance
= aStyle
->StyleDisplay()->EffectiveAppearance();
922 if (appearance
!= StyleAppearance::None
) {
923 nsITheme
* theme
= aPresContext
->Theme();
924 if (theme
->ThemeSupportsWidget(aPresContext
, aForFrame
, appearance
)) {
925 // The border will be draw as part of the themed background item created
926 // for this same frame. If no themed background item was created then not
927 // drawing also matches that we do without webrender and what
928 // nsDisplayBorder does for themed borders.
929 if (aOutBorderIsEmpty
) {
930 *aOutBorderIsEmpty
= true;
936 nsMargin border
= aStyleBorder
.GetComputedBorder();
937 if (0 == border
.left
&& 0 == border
.right
&& 0 == border
.top
&&
938 0 == border
.bottom
) {
940 if (aOutBorderIsEmpty
) {
941 *aOutBorderIsEmpty
= true;
946 bool needsClip
= false;
947 nsCSSBorderRenderer br
= ConstructBorderRenderer(
948 aPresContext
, aStyle
, aDrawTarget
, aForFrame
, aDirtyRect
, aBorderArea
,
949 aStyleBorder
, aSkipSides
, &needsClip
);
953 Maybe
<nsCSSBorderRenderer
>
954 nsCSSRendering::CreateBorderRendererForNonThemedOutline(
955 nsPresContext
* aPresContext
, DrawTarget
* aDrawTarget
, nsIFrame
* aForFrame
,
956 const nsRect
& aDirtyRect
, const nsRect
& aInnerRect
, ComputedStyle
* aStyle
) {
957 // Get our ComputedStyle's color struct.
958 const nsStyleOutline
* ourOutline
= aStyle
->StyleOutline();
959 if (!ourOutline
->ShouldPaintOutline()) {
964 nsRect innerRect
= aInnerRect
;
966 const nsSize effectiveOffset
= ourOutline
->EffectiveOffsetFor(innerRect
);
967 innerRect
.Inflate(effectiveOffset
);
969 // If the dirty rect is completely inside the border area (e.g., only the
970 // content is being painted), then we can skip out now
971 // XXX this isn't exactly true for rounded borders, where the inside curves
972 // may encroach into the content area. A safer calculation would be to
973 // shorten insideRect by the radius one each side before performing this test.
974 if (innerRect
.Contains(aDirtyRect
)) {
978 const nscoord width
= ourOutline
->GetOutlineWidth();
980 StyleBorderStyle outlineStyle
;
981 // Themed outlines are handled by our callers, if supported.
982 if (ourOutline
->mOutlineStyle
.IsAuto()) {
984 return Nothing(); // empty outline
986 // http://dev.w3.org/csswg/css-ui/#outline
987 // "User agents may treat 'auto' as 'solid'."
988 outlineStyle
= StyleBorderStyle::Solid
;
990 outlineStyle
= ourOutline
->mOutlineStyle
.AsBorderStyle();
993 RectCornerRadii outlineRadii
;
994 nsRect outerRect
= innerRect
;
995 outerRect
.Inflate(width
);
997 const nscoord oneDevPixel
= aPresContext
->AppUnitsPerDevPixel();
998 Rect
oRect(NSRectToRect(outerRect
, oneDevPixel
));
1000 const Float outlineWidths
[4] = {
1001 Float(width
) / oneDevPixel
, Float(width
) / oneDevPixel
,
1002 Float(width
) / oneDevPixel
, Float(width
) / oneDevPixel
};
1004 // convert the radii
1005 nscoord twipsRadii
[8];
1007 // get the radius for our outline
1008 if (aForFrame
->GetBorderRadii(twipsRadii
)) {
1009 RectCornerRadii innerRadii
;
1010 ComputePixelRadii(twipsRadii
, oneDevPixel
, &innerRadii
);
1012 const auto devPxOffset
= LayoutDeviceSize::FromAppUnits(
1013 effectiveOffset
, aPresContext
->AppUnitsPerDevPixel());
1015 const Float widths
[4] = {outlineWidths
[0] + devPxOffset
.Height(),
1016 outlineWidths
[1] + devPxOffset
.Width(),
1017 outlineWidths
[2] + devPxOffset
.Height(),
1018 outlineWidths
[3] + devPxOffset
.Width()};
1019 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii
, widths
, &outlineRadii
);
1022 StyleBorderStyle outlineStyles
[4] = {outlineStyle
, outlineStyle
, outlineStyle
,
1025 // This handles treating the initial color as 'currentColor'; if we
1026 // ever want 'invert' back we'll need to do a bit of work here too.
1027 nscolor outlineColor
=
1028 aStyle
->GetVisitedDependentColor(&nsStyleOutline::mOutlineColor
);
1029 nscolor outlineColors
[4] = {outlineColor
, outlineColor
, outlineColor
,
1032 Rect dirtyRect
= NSRectToRect(aDirtyRect
, oneDevPixel
);
1034 return Some(nsCSSBorderRenderer(
1035 aPresContext
, aDrawTarget
, dirtyRect
, oRect
, outlineStyles
, outlineWidths
,
1036 outlineRadii
, outlineColors
, !aForFrame
->BackfaceIsHidden(), Nothing()));
1039 void nsCSSRendering::PaintNonThemedOutline(nsPresContext
* aPresContext
,
1040 gfxContext
& aRenderingContext
,
1041 nsIFrame
* aForFrame
,
1042 const nsRect
& aDirtyRect
,
1043 const nsRect
& aInnerRect
,
1044 ComputedStyle
* aStyle
) {
1045 Maybe
<nsCSSBorderRenderer
> br
= CreateBorderRendererForNonThemedOutline(
1046 aPresContext
, aRenderingContext
.GetDrawTarget(), aForFrame
, aDirtyRect
,
1047 aInnerRect
, aStyle
);
1055 PrintAsStringNewline();
1058 void nsCSSRendering::PaintFocus(nsPresContext
* aPresContext
,
1059 DrawTarget
* aDrawTarget
,
1060 const nsRect
& aFocusRect
, nscolor aColor
) {
1061 nscoord oneCSSPixel
= nsPresContext::CSSPixelsToAppUnits(1);
1062 nscoord oneDevPixel
= aPresContext
->DevPixelsToAppUnits(1);
1064 Rect
focusRect(NSRectToRect(aFocusRect
, oneDevPixel
));
1066 RectCornerRadii focusRadii
;
1068 nscoord twipsRadii
[8] = {0, 0, 0, 0, 0, 0, 0, 0};
1069 ComputePixelRadii(twipsRadii
, oneDevPixel
, &focusRadii
);
1071 Float focusWidths
[4] = {
1072 Float(oneCSSPixel
) / oneDevPixel
, Float(oneCSSPixel
) / oneDevPixel
,
1073 Float(oneCSSPixel
) / oneDevPixel
, Float(oneCSSPixel
) / oneDevPixel
};
1075 StyleBorderStyle focusStyles
[4] = {
1076 StyleBorderStyle::Dotted
, StyleBorderStyle::Dotted
,
1077 StyleBorderStyle::Dotted
, StyleBorderStyle::Dotted
};
1078 nscolor focusColors
[4] = {aColor
, aColor
, aColor
, aColor
};
1080 // Because this renders a dotted border, the background color
1081 // should not be used. Therefore, we provide a value that will
1082 // be blatantly wrong if it ever does get used. (If this becomes
1083 // something that CSS can style, this function will then have access
1084 // to a ComputedStyle and can use the same logic that PaintBorder
1085 // and PaintOutline do.)
1087 // WebRender layers-free mode don't use PaintFocus function. Just assign
1088 // the backface-visibility to true for this case.
1089 nsCSSBorderRenderer
br(aPresContext
, aDrawTarget
, focusRect
, focusRect
,
1090 focusStyles
, focusWidths
, focusRadii
, focusColors
,
1094 PrintAsStringNewline();
1097 // Thebes Border Rendering Code End
1098 //----------------------------------------------------------------------
1100 //----------------------------------------------------------------------
1103 * Helper for ComputeObjectAnchorPoint; parameters are the same as for
1104 * that function, except they're for a single coordinate / a single size
1105 * dimension. (so, x/width vs. y/height)
1107 static void ComputeObjectAnchorCoord(const LengthPercentage
& aCoord
,
1108 const nscoord aOriginBounds
,
1109 const nscoord aImageSize
,
1110 nscoord
* aTopLeftCoord
,
1111 nscoord
* aAnchorPointCoord
) {
1112 nscoord extraSpace
= aOriginBounds
- aImageSize
;
1114 // The anchor-point doesn't care about our image's size; just the size
1115 // of the region we're rendering into.
1116 *aAnchorPointCoord
= aCoord
.Resolve(
1117 aOriginBounds
, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp
));
1118 // Adjust aTopLeftCoord by the specified % of the extra space.
1119 *aTopLeftCoord
= aCoord
.Resolve(
1120 extraSpace
, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp
));
1123 void nsImageRenderer::ComputeObjectAnchorPoint(const Position
& aPos
,
1124 const nsSize
& aOriginBounds
,
1125 const nsSize
& aImageSize
,
1127 nsPoint
* aAnchorPoint
) {
1128 ComputeObjectAnchorCoord(aPos
.horizontal
, aOriginBounds
.width
,
1129 aImageSize
.width
, &aTopLeft
->x
, &aAnchorPoint
->x
);
1131 ComputeObjectAnchorCoord(aPos
.vertical
, aOriginBounds
.height
,
1132 aImageSize
.height
, &aTopLeft
->y
, &aAnchorPoint
->y
);
1135 // In print / print preview we have multiple canvas frames (one for each page,
1136 // and one for the document as a whole). For the topmost one, we really want the
1137 // page sequence page background, not the root or body's background.
1138 static nsIFrame
* GetPageSequenceForCanvas(const nsIFrame
* aCanvasFrame
) {
1139 MOZ_ASSERT(aCanvasFrame
->IsCanvasFrame(), "not a canvas frame");
1140 nsPresContext
* pc
= aCanvasFrame
->PresContext();
1141 if (!pc
->IsRootPaginatedDocument()) {
1144 auto* ps
= pc
->PresShell()->GetPageSequenceFrame();
1145 if (NS_WARN_IF(!ps
)) {
1148 if (ps
->GetParent() != aCanvasFrame
) {
1154 auto nsCSSRendering::FindEffectiveBackgroundColor(nsIFrame
* aFrame
,
1156 bool aPreferBodyToCanvas
)
1157 -> EffectiveBackgroundColor
{
1159 nsPresContext
* pc
= aFrame
->PresContext();
1160 auto BgColorIfNotTransparent
= [&](nsIFrame
* aFrame
) -> Maybe
<nscolor
> {
1162 aFrame
->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor
);
1163 if (NS_GET_A(c
) == 255) {
1167 // TODO(emilio): We should maybe just blend with ancestor bg colors and
1168 // such, but this is probably good enough for now, matches pre-existing
1170 const nscolor defaultBg
= pc
->DefaultBackgroundColor();
1171 MOZ_ASSERT(NS_GET_A(defaultBg
) == 255, "PreferenceSheet guarantees this");
1172 return Some(NS_ComposeColors(defaultBg
, c
));
1177 for (nsIFrame
* frame
= aFrame
; frame
;
1178 frame
= nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame
)) {
1179 if (auto bg
= BgColorIfNotTransparent(frame
)) {
1183 if (aStopAtThemed
&& frame
->IsThemed()) {
1184 return {NS_TRANSPARENT
, true};
1187 if (frame
->IsCanvasFrame()) {
1188 if (aPreferBodyToCanvas
&& !GetPageSequenceForCanvas(frame
)) {
1189 if (auto* body
= pc
->Document()->GetBodyElement()) {
1190 if (nsIFrame
* f
= body
->GetPrimaryFrame()) {
1191 if (auto bg
= BgColorIfNotTransparent(f
)) {
1197 if (nsIFrame
* bgFrame
= FindBackgroundFrame(frame
)) {
1198 if (auto bg
= BgColorIfNotTransparent(bgFrame
)) {
1205 return {pc
->DefaultBackgroundColor()};
1208 nsIFrame
* nsCSSRendering::FindBackgroundStyleFrame(nsIFrame
* aForFrame
) {
1209 const nsStyleBackground
* result
= aForFrame
->StyleBackground();
1211 // Check if we need to do propagation from BODY rather than HTML.
1212 if (!result
->IsTransparent(aForFrame
)) {
1216 nsIContent
* content
= aForFrame
->GetContent();
1217 // The root element content can't be null. We wouldn't know what
1218 // frame to create for aFrame.
1219 // Use |OwnerDoc| so it works during destruction.
1224 Document
* document
= content
->OwnerDoc();
1226 dom::Element
* bodyContent
= document
->GetBodyElement();
1227 // We need to null check the body node (bug 118829) since
1228 // there are cases, thanks to the fix for bug 5569, where we
1229 // will reflow a document with no body. In particular, if a
1230 // SCRIPT element in the head blocks the parser and then has a
1231 // SCRIPT that does "document.location.href = 'foo'", then
1232 // nsParser::Terminate will call |DidBuildModel| methods
1233 // through to the content sink, which will call |StartLayout|
1234 // and thus |Initialize| on the pres shell. See bug 119351
1235 // for the ugly details.
1236 if (!bodyContent
|| aForFrame
->StyleDisplay()->IsContainAny()) {
1240 nsIFrame
* bodyFrame
= bodyContent
->GetPrimaryFrame();
1241 if (!bodyFrame
|| bodyFrame
->StyleDisplay()->IsContainAny()) {
1245 return nsLayoutUtils::GetStyleFrame(bodyFrame
);
1249 * |FindBackground| finds the correct style data to use to paint the
1250 * background. It is responsible for handling the following two
1251 * statements in section 14.2 of CSS2:
1253 * The background of the box generated by the root element covers the
1256 * For HTML documents, however, we recommend that authors specify the
1257 * background for the BODY element rather than the HTML element. User
1258 * agents should observe the following precedence rules to fill in the
1259 * background: if the value of the 'background' property for the HTML
1260 * element is different from 'transparent' then use it, else use the
1261 * value of the 'background' property for the BODY element. If the
1262 * resulting value is 'transparent', the rendering is undefined.
1264 * Thus, in our implementation, it is responsible for ensuring that:
1265 * + we paint the correct background on the |nsCanvasFrame| or |nsPageFrame|,
1266 * + we don't paint the background on the root element, and
1267 * + we don't paint the background on the BODY element in *some* cases,
1268 * and for SGML-based HTML documents only.
1270 * |FindBackground| checks whether a background should be painted. If yes, it
1271 * returns the resulting ComputedStyle to use for the background information;
1272 * Otherwise, it returns nullptr.
1274 ComputedStyle
* nsCSSRendering::FindRootFrameBackground(nsIFrame
* aForFrame
) {
1275 return FindBackgroundStyleFrame(aForFrame
)->Style();
1278 static nsIFrame
* FindCanvasBackgroundFrame(const nsIFrame
* aForFrame
,
1279 nsIFrame
* aRootElementFrame
) {
1280 MOZ_ASSERT(aForFrame
->IsCanvasFrame(), "not a canvas frame");
1281 if (auto* ps
= GetPageSequenceForCanvas(aForFrame
)) {
1284 if (aRootElementFrame
) {
1285 return nsCSSRendering::FindBackgroundStyleFrame(aRootElementFrame
);
1287 // This should always give transparent, so we'll fill it in with the default
1288 // color if needed. This seems to happen a bit while a page is being loaded.
1289 return const_cast<nsIFrame
*>(aForFrame
);
1292 // Helper for FindBackgroundFrame. Returns true if aForFrame has a meaningful
1293 // background that it should draw (i.e. that it hasn't propagated to another
1294 // frame). See documentation for FindBackground.
1295 inline bool FrameHasMeaningfulBackground(const nsIFrame
* aForFrame
,
1296 nsIFrame
* aRootElementFrame
) {
1297 MOZ_ASSERT(!aForFrame
->IsCanvasFrame(),
1298 "FindBackgroundFrame handles canvas frames before calling us, "
1299 "so we don't need to consider them here");
1301 if (aForFrame
== aRootElementFrame
) {
1302 // We must have propagated our background to the viewport or canvas. Abort.
1306 // Return true unless the frame is for a BODY element whose background
1307 // was propagated to the viewport.
1309 nsIContent
* content
= aForFrame
->GetContent();
1310 if (!content
|| content
->NodeInfo()->NameAtom() != nsGkAtoms::body
) {
1311 return true; // not frame for a "body" element
1313 // It could be a non-HTML "body" element but that's OK, we'd fail the
1314 // bodyContent check below
1316 if (aForFrame
->Style()->GetPseudoType() != PseudoStyleType::NotPseudo
||
1317 aForFrame
->StyleDisplay()->IsContainAny()) {
1318 return true; // A pseudo-element frame, or contained.
1321 // We should only look at the <html> background if we're in an HTML document
1322 Document
* document
= content
->OwnerDoc();
1324 dom::Element
* bodyContent
= document
->GetBodyElement();
1325 if (bodyContent
!= content
) {
1326 return true; // this wasn't the background that was propagated
1329 // This can be called even when there's no root element yet, during frame
1330 // construction, via nsLayoutUtils::FrameHasTransparency and
1331 // nsContainerFrame::SyncFrameViewProperties.
1332 if (!aRootElementFrame
|| aRootElementFrame
->StyleDisplay()->IsContainAny()) {
1336 const nsStyleBackground
* htmlBG
= aRootElementFrame
->StyleBackground();
1337 return !htmlBG
->IsTransparent(aRootElementFrame
);
1340 nsIFrame
* nsCSSRendering::FindBackgroundFrame(const nsIFrame
* aForFrame
) {
1341 nsIFrame
* rootElementFrame
=
1342 aForFrame
->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
1343 if (aForFrame
->IsCanvasFrame()) {
1344 return FindCanvasBackgroundFrame(aForFrame
, rootElementFrame
);
1347 if (FrameHasMeaningfulBackground(aForFrame
, rootElementFrame
)) {
1348 return const_cast<nsIFrame
*>(aForFrame
);
1354 ComputedStyle
* nsCSSRendering::FindBackground(const nsIFrame
* aForFrame
) {
1355 if (auto* backgroundFrame
= FindBackgroundFrame(aForFrame
)) {
1356 return backgroundFrame
->Style();
1361 void nsCSSRendering::BeginFrameTreesLocked() { ++gFrameTreeLockCount
; }
1363 void nsCSSRendering::EndFrameTreesLocked() {
1364 NS_ASSERTION(gFrameTreeLockCount
> 0, "Unbalanced EndFrameTreeLocked");
1365 --gFrameTreeLockCount
;
1366 if (gFrameTreeLockCount
== 0) {
1367 gInlineBGData
->Reset();
1371 bool nsCSSRendering::HasBoxShadowNativeTheme(nsIFrame
* aFrame
,
1372 bool& aMaybeHasBorderRadius
) {
1373 const nsStyleDisplay
* styleDisplay
= aFrame
->StyleDisplay();
1374 nsITheme::Transparency transparency
;
1375 if (aFrame
->IsThemed(styleDisplay
, &transparency
)) {
1376 aMaybeHasBorderRadius
= false;
1377 // For opaque (rectangular) theme widgets we can take the generic
1378 // border-box path with border-radius disabled.
1379 return transparency
!= nsITheme::eOpaque
;
1382 aMaybeHasBorderRadius
= true;
1386 gfx::sRGBColor
nsCSSRendering::GetShadowColor(const StyleSimpleShadow
& aShadow
,
1389 // Get the shadow color; if not specified, use the foreground color
1390 nscolor shadowColor
= aShadow
.color
.CalcColor(aFrame
);
1391 sRGBColor color
= sRGBColor::FromABGR(shadowColor
);
1392 color
.a
*= aOpacity
;
1396 nsRect
nsCSSRendering::GetShadowRect(const nsRect
& aFrameArea
,
1397 bool aNativeTheme
, nsIFrame
* aForFrame
) {
1398 nsRect frameRect
= aNativeTheme
? aForFrame
->InkOverflowRectRelativeToSelf() +
1399 aFrameArea
.TopLeft()
1401 Sides skipSides
= aForFrame
->GetSkipSides();
1402 frameRect
= BoxDecorationRectForBorder(aForFrame
, frameRect
, skipSides
);
1404 // Explicitly do not need to account for the spread radius here
1405 // Webrender does it for us or PaintBoxShadow will for non-WR
1409 bool nsCSSRendering::GetBorderRadii(const nsRect
& aFrameRect
,
1410 const nsRect
& aBorderRect
, nsIFrame
* aFrame
,
1411 RectCornerRadii
& aOutRadii
) {
1412 const nscoord oneDevPixel
= aFrame
->PresContext()->DevPixelsToAppUnits(1);
1413 nscoord twipsRadii
[8];
1415 aBorderRect
.Size() == aFrame
->VisualBorderRectRelativeToSelf().Size(),
1417 nsSize sz
= aFrameRect
.Size();
1418 bool hasBorderRadius
= aFrame
->GetBorderRadii(sz
, sz
, Sides(), twipsRadii
);
1419 if (hasBorderRadius
) {
1420 ComputePixelRadii(twipsRadii
, oneDevPixel
, &aOutRadii
);
1423 return hasBorderRadius
;
1426 void nsCSSRendering::PaintBoxShadowOuter(nsPresContext
* aPresContext
,
1427 gfxContext
& aRenderingContext
,
1428 nsIFrame
* aForFrame
,
1429 const nsRect
& aFrameArea
,
1430 const nsRect
& aDirtyRect
,
1432 DrawTarget
& aDrawTarget
= *aRenderingContext
.GetDrawTarget();
1433 auto shadows
= aForFrame
->StyleEffects()->mBoxShadow
.AsSpan();
1434 if (shadows
.IsEmpty()) {
1438 bool hasBorderRadius
;
1439 // mutually exclusive with hasBorderRadius
1440 bool nativeTheme
= HasBoxShadowNativeTheme(aForFrame
, hasBorderRadius
);
1441 const nsStyleDisplay
* styleDisplay
= aForFrame
->StyleDisplay();
1443 nsRect frameRect
= GetShadowRect(aFrameArea
, nativeTheme
, aForFrame
);
1445 // Get any border radius, since box-shadow must also have rounded corners if
1447 RectCornerRadii borderRadii
;
1448 const nscoord oneDevPixel
= aPresContext
->DevPixelsToAppUnits(1);
1449 if (hasBorderRadius
) {
1450 nscoord twipsRadii
[8];
1452 aFrameArea
.Size() == aForFrame
->VisualBorderRectRelativeToSelf().Size(),
1454 nsSize sz
= frameRect
.Size();
1455 hasBorderRadius
= aForFrame
->GetBorderRadii(sz
, sz
, Sides(), twipsRadii
);
1456 if (hasBorderRadius
) {
1457 ComputePixelRadii(twipsRadii
, oneDevPixel
, &borderRadii
);
1461 // We don't show anything that intersects with the frame we're blurring on. So
1462 // tell the blurrer not to do unnecessary work there.
1463 gfxRect skipGfxRect
= ThebesRect(NSRectToRect(frameRect
, oneDevPixel
));
1464 skipGfxRect
.Round();
1465 bool useSkipGfxRect
= true;
1467 // Optimize non-leaf native-themed frames by skipping computing pixels
1468 // in the padding-box. We assume the padding-box is going to be painted
1469 // opaquely for non-leaf frames.
1470 // XXX this may not be a safe assumption; we should make this go away
1471 // by optimizing box-shadow drawing more for the cases where we don't have a
1473 useSkipGfxRect
= !aForFrame
->IsLeaf();
1474 nsRect paddingRect
=
1475 aForFrame
->GetPaddingRectRelativeToSelf() + aFrameArea
.TopLeft();
1476 skipGfxRect
= nsLayoutUtils::RectToGfxRect(paddingRect
, oneDevPixel
);
1477 } else if (hasBorderRadius
) {
1478 skipGfxRect
.Deflate(gfxMargin(
1479 std::max(borderRadii
[C_TL
].height
, borderRadii
[C_TR
].height
), 0,
1480 std::max(borderRadii
[C_BL
].height
, borderRadii
[C_BR
].height
), 0));
1483 for (const StyleBoxShadow
& shadow
: Reversed(shadows
)) {
1488 nsRect shadowRect
= frameRect
;
1489 nsPoint
shadowOffset(shadow
.base
.horizontal
.ToAppUnits(),
1490 shadow
.base
.vertical
.ToAppUnits());
1491 shadowRect
.MoveBy(shadowOffset
);
1492 nscoord shadowSpread
= shadow
.spread
.ToAppUnits();
1494 shadowRect
.Inflate(shadowSpread
);
1497 // shadowRect won't include the blur, so make an extra rect here that
1498 // includes the blur for use in the even-odd rule below.
1499 nsRect shadowRectPlusBlur
= shadowRect
;
1500 nscoord blurRadius
= shadow
.base
.blur
.ToAppUnits();
1501 shadowRectPlusBlur
.Inflate(
1502 nsContextBoxBlur::GetBlurRadiusMargin(blurRadius
, oneDevPixel
));
1504 Rect shadowGfxRectPlusBlur
= NSRectToRect(shadowRectPlusBlur
, oneDevPixel
);
1505 shadowGfxRectPlusBlur
.RoundOut();
1506 MaybeSnapToDevicePixels(shadowGfxRectPlusBlur
, aDrawTarget
, true);
1508 sRGBColor gfxShadowColor
= GetShadowColor(shadow
.base
, aForFrame
, aOpacity
);
1511 nsContextBoxBlur blurringArea
;
1513 // When getting the widget shape from the native theme, we're going
1514 // to draw the widget into the shadow surface to create a mask.
1515 // We need to ensure that there actually *is* a shadow surface
1516 // and that we're not going to draw directly into aRenderingContext.
1517 gfxContext
* shadowContext
= blurringArea
.Init(
1518 shadowRect
, shadowSpread
, blurRadius
, oneDevPixel
, &aRenderingContext
,
1519 aDirtyRect
, useSkipGfxRect
? &skipGfxRect
: nullptr,
1520 nsContextBoxBlur::FORCE_MASK
);
1521 if (!shadowContext
) continue;
1523 MOZ_ASSERT(shadowContext
== blurringArea
.GetContext());
1525 aRenderingContext
.Save();
1526 aRenderingContext
.SetColor(gfxShadowColor
);
1528 // Draw the shape of the frame so it can be blurred. Recall how
1529 // nsContextBoxBlur doesn't make any temporary surfaces if blur is 0 and
1530 // it just returns the original surface? If we have no blur, we're
1531 // painting this fill on the actual content surface (aRenderingContext ==
1532 // shadowContext) which is why we set up the color and clip before doing
1535 // We don't clip the border-box from the shadow, nor any other box.
1536 // We assume that the native theme is going to paint over the shadow.
1538 // Draw the widget shape
1539 gfxContextMatrixAutoSaveRestore
save(shadowContext
);
1540 gfxPoint devPixelOffset
= nsLayoutUtils::PointToGfxPoint(
1541 shadowOffset
, aPresContext
->AppUnitsPerDevPixel());
1542 shadowContext
->SetMatrixDouble(
1543 shadowContext
->CurrentMatrixDouble().PreTranslate(devPixelOffset
));
1545 nsRect nativeRect
= aDirtyRect
;
1546 nativeRect
.MoveBy(-shadowOffset
);
1547 nativeRect
.IntersectRect(frameRect
, nativeRect
);
1548 aPresContext
->Theme()->DrawWidgetBackground(
1549 shadowContext
, aForFrame
, styleDisplay
->EffectiveAppearance(),
1550 aFrameArea
, nativeRect
, nsITheme::DrawOverflow::No
);
1552 blurringArea
.DoPaint();
1553 aRenderingContext
.Restore();
1555 aRenderingContext
.Save();
1558 Rect innerClipRect
= NSRectToRect(frameRect
, oneDevPixel
);
1559 if (!MaybeSnapToDevicePixels(innerClipRect
, aDrawTarget
, true)) {
1560 innerClipRect
.Round();
1563 // Clip out the interior of the frame's border edge so that the shadow
1564 // is only painted outside that area.
1565 RefPtr
<PathBuilder
> builder
=
1566 aDrawTarget
.CreatePathBuilder(FillRule::FILL_EVEN_ODD
);
1567 AppendRectToPath(builder
, shadowGfxRectPlusBlur
);
1568 if (hasBorderRadius
) {
1569 AppendRoundedRectToPath(builder
, innerClipRect
, borderRadii
);
1571 AppendRectToPath(builder
, innerClipRect
);
1573 RefPtr
<Path
> path
= builder
->Finish();
1574 aRenderingContext
.Clip(path
);
1577 // Clip the shadow so that we only get the part that applies to aForFrame.
1578 nsRect fragmentClip
= shadowRectPlusBlur
;
1579 Sides skipSides
= aForFrame
->GetSkipSides();
1580 if (!skipSides
.IsEmpty()) {
1581 if (skipSides
.Left()) {
1582 nscoord xmost
= fragmentClip
.XMost();
1583 fragmentClip
.x
= aFrameArea
.x
;
1584 fragmentClip
.width
= xmost
- fragmentClip
.x
;
1586 if (skipSides
.Right()) {
1587 nscoord xmost
= fragmentClip
.XMost();
1588 nscoord overflow
= xmost
- aFrameArea
.XMost();
1590 fragmentClip
.width
-= overflow
;
1593 if (skipSides
.Top()) {
1594 nscoord ymost
= fragmentClip
.YMost();
1595 fragmentClip
.y
= aFrameArea
.y
;
1596 fragmentClip
.height
= ymost
- fragmentClip
.y
;
1598 if (skipSides
.Bottom()) {
1599 nscoord ymost
= fragmentClip
.YMost();
1600 nscoord overflow
= ymost
- aFrameArea
.YMost();
1602 fragmentClip
.height
-= overflow
;
1606 fragmentClip
= fragmentClip
.Intersect(aDirtyRect
);
1607 aRenderingContext
.Clip(NSRectToSnappedRect(
1608 fragmentClip
, aForFrame
->PresContext()->AppUnitsPerDevPixel(),
1611 RectCornerRadii clipRectRadii
;
1612 if (hasBorderRadius
) {
1613 Float spreadDistance
= Float(shadowSpread
/ oneDevPixel
);
1615 Float borderSizes
[4];
1617 borderSizes
[eSideLeft
] = spreadDistance
;
1618 borderSizes
[eSideTop
] = spreadDistance
;
1619 borderSizes
[eSideRight
] = spreadDistance
;
1620 borderSizes
[eSideBottom
] = spreadDistance
;
1622 nsCSSBorderRenderer::ComputeOuterRadii(borderRadii
, borderSizes
,
1625 nsContextBoxBlur::BlurRectangle(
1626 &aRenderingContext
, shadowRect
, oneDevPixel
,
1627 hasBorderRadius
? &clipRectRadii
: nullptr, blurRadius
,
1628 gfxShadowColor
, aDirtyRect
, skipGfxRect
);
1629 aRenderingContext
.Restore();
1634 nsRect
nsCSSRendering::GetBoxShadowInnerPaddingRect(nsIFrame
* aFrame
,
1635 const nsRect
& aFrameArea
) {
1636 Sides skipSides
= aFrame
->GetSkipSides();
1637 nsRect frameRect
= BoxDecorationRectForBorder(aFrame
, aFrameArea
, skipSides
);
1639 nsRect paddingRect
= frameRect
;
1640 nsMargin border
= aFrame
->GetUsedBorder();
1641 paddingRect
.Deflate(border
);
1645 bool nsCSSRendering::ShouldPaintBoxShadowInner(nsIFrame
* aFrame
) {
1646 const Span
<const StyleBoxShadow
> shadows
=
1647 aFrame
->StyleEffects()->mBoxShadow
.AsSpan();
1648 if (shadows
.IsEmpty()) {
1652 if (aFrame
->IsThemed() && aFrame
->GetContent() &&
1653 !nsContentUtils::IsChromeDoc(aFrame
->GetContent()->GetComposedDoc())) {
1654 // There's no way of getting hold of a shape corresponding to a
1655 // "padding-box" for native-themed widgets, so just don't draw
1656 // inner box-shadows for them. But we allow chrome to paint inner
1657 // box shadows since chrome can be aware of the platform theme.
1664 bool nsCSSRendering::GetShadowInnerRadii(nsIFrame
* aFrame
,
1665 const nsRect
& aFrameArea
,
1666 RectCornerRadii
& aOutInnerRadii
) {
1667 // Get any border radius, since box-shadow must also have rounded corners
1668 // if the frame does.
1669 nscoord twipsRadii
[8];
1671 BoxDecorationRectForBorder(aFrame
, aFrameArea
, aFrame
->GetSkipSides());
1672 nsSize sz
= frameRect
.Size();
1673 nsMargin border
= aFrame
->GetUsedBorder();
1674 aFrame
->GetBorderRadii(sz
, sz
, Sides(), twipsRadii
);
1675 const nscoord oneDevPixel
= aFrame
->PresContext()->DevPixelsToAppUnits(1);
1677 RectCornerRadii borderRadii
;
1679 const bool hasBorderRadius
=
1680 GetBorderRadii(frameRect
, aFrameArea
, aFrame
, borderRadii
);
1682 if (hasBorderRadius
) {
1683 ComputePixelRadii(twipsRadii
, oneDevPixel
, &borderRadii
);
1685 Float borderSizes
[4] = {
1686 Float(border
.top
) / oneDevPixel
, Float(border
.right
) / oneDevPixel
,
1687 Float(border
.bottom
) / oneDevPixel
, Float(border
.left
) / oneDevPixel
};
1688 nsCSSBorderRenderer::ComputeInnerRadii(borderRadii
, borderSizes
,
1692 return hasBorderRadius
;
1695 void nsCSSRendering::PaintBoxShadowInner(nsPresContext
* aPresContext
,
1696 gfxContext
& aRenderingContext
,
1697 nsIFrame
* aForFrame
,
1698 const nsRect
& aFrameArea
) {
1699 if (!ShouldPaintBoxShadowInner(aForFrame
)) {
1703 const Span
<const StyleBoxShadow
> shadows
=
1704 aForFrame
->StyleEffects()->mBoxShadow
.AsSpan();
1706 aForFrame
->IsFieldSetFrame() || aFrameArea
.Size() == aForFrame
->GetSize(),
1709 nsRect paddingRect
= GetBoxShadowInnerPaddingRect(aForFrame
, aFrameArea
);
1711 RectCornerRadii innerRadii
;
1712 bool hasBorderRadius
= GetShadowInnerRadii(aForFrame
, aFrameArea
, innerRadii
);
1714 const nscoord oneDevPixel
= aPresContext
->DevPixelsToAppUnits(1);
1716 for (const StyleBoxShadow
& shadow
: Reversed(shadows
)) {
1717 if (!shadow
.inset
) {
1721 // shadowPaintRect: the area to paint on the temp surface
1722 // shadowClipRect: the area on the temporary surface within shadowPaintRect
1723 // that we will NOT paint in
1724 nscoord blurRadius
= shadow
.base
.blur
.ToAppUnits();
1725 nsMargin blurMargin
=
1726 nsContextBoxBlur::GetBlurRadiusMargin(blurRadius
, oneDevPixel
);
1727 nsRect shadowPaintRect
= paddingRect
;
1728 shadowPaintRect
.Inflate(blurMargin
);
1730 // Round the spread radius to device pixels (by truncation).
1731 // This mostly matches what we do for borders, except that we don't round
1732 // up values between zero and one device pixels to one device pixel.
1733 // This way of rounding is symmetric around zero, which makes sense for
1734 // the spread radius.
1735 int32_t spreadDistance
= shadow
.spread
.ToAppUnits() / oneDevPixel
;
1736 nscoord spreadDistanceAppUnits
=
1737 aPresContext
->DevPixelsToAppUnits(spreadDistance
);
1739 nsRect shadowClipRect
= paddingRect
;
1740 shadowClipRect
.MoveBy(shadow
.base
.horizontal
.ToAppUnits(),
1741 shadow
.base
.vertical
.ToAppUnits());
1742 shadowClipRect
.Deflate(spreadDistanceAppUnits
, spreadDistanceAppUnits
);
1744 Rect shadowClipGfxRect
= NSRectToRect(shadowClipRect
, oneDevPixel
);
1745 shadowClipGfxRect
.Round();
1747 RectCornerRadii clipRectRadii
;
1748 if (hasBorderRadius
) {
1749 // Calculate the radii the inner clipping rect will have
1750 Float borderSizes
[4] = {0, 0, 0, 0};
1752 // See PaintBoxShadowOuter and bug 514670
1753 if (innerRadii
[C_TL
].width
> 0 || innerRadii
[C_BL
].width
> 0) {
1754 borderSizes
[eSideLeft
] = spreadDistance
;
1757 if (innerRadii
[C_TL
].height
> 0 || innerRadii
[C_TR
].height
> 0) {
1758 borderSizes
[eSideTop
] = spreadDistance
;
1761 if (innerRadii
[C_TR
].width
> 0 || innerRadii
[C_BR
].width
> 0) {
1762 borderSizes
[eSideRight
] = spreadDistance
;
1765 if (innerRadii
[C_BL
].height
> 0 || innerRadii
[C_BR
].height
> 0) {
1766 borderSizes
[eSideBottom
] = spreadDistance
;
1769 nsCSSBorderRenderer::ComputeInnerRadii(innerRadii
, borderSizes
,
1773 // Set the "skip rect" to the area within the frame that we don't paint in,
1774 // including after blurring.
1775 nsRect skipRect
= shadowClipRect
;
1776 skipRect
.Deflate(blurMargin
);
1777 gfxRect skipGfxRect
= nsLayoutUtils::RectToGfxRect(skipRect
, oneDevPixel
);
1778 if (hasBorderRadius
) {
1779 skipGfxRect
.Deflate(gfxMargin(
1780 std::max(clipRectRadii
[C_TL
].height
, clipRectRadii
[C_TR
].height
), 0,
1781 std::max(clipRectRadii
[C_BL
].height
, clipRectRadii
[C_BR
].height
), 0));
1784 // When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area
1785 // unchanged. And by construction the gfxSkipRect is not touched by the
1786 // rendered shadow (even after blurring), so those pixels must be completely
1787 // transparent in the shadow, so drawing them changes nothing.
1788 DrawTarget
* drawTarget
= aRenderingContext
.GetDrawTarget();
1790 // Clip the context to the area of the frame's padding rect, so no part of
1791 // the shadow is painted outside. Also cut out anything beyond where the
1792 // inset shadow will be.
1793 Rect shadowGfxRect
= NSRectToRect(paddingRect
, oneDevPixel
);
1794 shadowGfxRect
.Round();
1796 sRGBColor shadowColor
= GetShadowColor(shadow
.base
, aForFrame
, 1.0);
1797 aRenderingContext
.Save();
1799 // This clips the outside border radius.
1800 // clipRectRadii is the border radius inside the inset shadow.
1801 if (hasBorderRadius
) {
1802 RefPtr
<Path
> roundedRect
=
1803 MakePathForRoundedRect(*drawTarget
, shadowGfxRect
, innerRadii
);
1804 aRenderingContext
.Clip(roundedRect
);
1806 aRenderingContext
.Clip(shadowGfxRect
);
1809 nsContextBoxBlur insetBoxBlur
;
1811 nsLayoutUtils::RectToGfxRect(shadowPaintRect
, oneDevPixel
);
1812 Point
shadowOffset(shadow
.base
.horizontal
.ToAppUnits() / oneDevPixel
,
1813 shadow
.base
.vertical
.ToAppUnits() / oneDevPixel
);
1815 insetBoxBlur
.InsetBoxBlur(
1816 &aRenderingContext
, ToRect(destRect
), shadowClipGfxRect
, shadowColor
,
1817 blurRadius
, spreadDistanceAppUnits
, oneDevPixel
, hasBorderRadius
,
1818 clipRectRadii
, ToRect(skipGfxRect
), shadowOffset
);
1819 aRenderingContext
.Restore();
1824 nsCSSRendering::PaintBGParams
nsCSSRendering::PaintBGParams::ForAllLayers(
1825 nsPresContext
& aPresCtx
, const nsRect
& aDirtyRect
,
1826 const nsRect
& aBorderArea
, nsIFrame
* aFrame
, uint32_t aPaintFlags
,
1830 PaintBGParams
result(aPresCtx
, aDirtyRect
, aBorderArea
, aFrame
, aPaintFlags
,
1831 -1, CompositionOp::OP_OVER
, aOpacity
);
1837 nsCSSRendering::PaintBGParams
nsCSSRendering::PaintBGParams::ForSingleLayer(
1838 nsPresContext
& aPresCtx
, const nsRect
& aDirtyRect
,
1839 const nsRect
& aBorderArea
, nsIFrame
* aFrame
, uint32_t aPaintFlags
,
1840 int32_t aLayer
, CompositionOp aCompositionOp
, float aOpacity
) {
1841 MOZ_ASSERT(aFrame
&& (aLayer
!= -1));
1843 PaintBGParams
result(aPresCtx
, aDirtyRect
, aBorderArea
, aFrame
, aPaintFlags
,
1844 aLayer
, aCompositionOp
, aOpacity
);
1849 ImgDrawResult
nsCSSRendering::PaintStyleImageLayer(const PaintBGParams
& aParams
,
1850 gfxContext
& aRenderingCtx
) {
1851 AUTO_PROFILER_LABEL("nsCSSRendering::PaintStyleImageLayer", GRAPHICS
);
1853 MOZ_ASSERT(aParams
.frame
,
1854 "Frame is expected to be provided to PaintStyleImageLayer");
1856 const ComputedStyle
* sc
= FindBackground(aParams
.frame
);
1858 // We don't want to bail out if moz-appearance is set on a root
1859 // node. If it has a parent content node, bail because it's not
1860 // a root, otherwise keep going in order to let the theme stuff
1861 // draw the background. The canvas really should be drawing the
1862 // bg, but there's no way to hook that up via css.
1863 if (!aParams
.frame
->StyleDisplay()->HasAppearance()) {
1864 return ImgDrawResult::SUCCESS
;
1867 nsIContent
* content
= aParams
.frame
->GetContent();
1868 if (!content
|| content
->GetParent()) {
1869 return ImgDrawResult::SUCCESS
;
1872 sc
= aParams
.frame
->Style();
1875 return PaintStyleImageLayerWithSC(aParams
, aRenderingCtx
, sc
,
1876 *aParams
.frame
->StyleBorder());
1879 bool nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer(
1880 WebRenderLayerManager
* aManager
, nsPresContext
& aPresCtx
, nsIFrame
* aFrame
,
1881 const nsStyleBackground
* aBackgroundStyle
, int32_t aLayer
,
1882 uint32_t aPaintFlags
) {
1883 if (!aBackgroundStyle
) {
1887 MOZ_ASSERT(aFrame
&& aLayer
>= 0 &&
1888 (uint32_t)aLayer
< aBackgroundStyle
->mImage
.mLayers
.Length());
1890 // We cannot draw native themed backgrounds
1891 StyleAppearance appearance
= aFrame
->StyleDisplay()->EffectiveAppearance();
1892 if (appearance
!= StyleAppearance::None
) {
1893 nsITheme
* theme
= aPresCtx
.Theme();
1894 if (theme
->ThemeSupportsWidget(&aPresCtx
, aFrame
, appearance
)) {
1899 // We only support painting gradients and image for a single style image
1900 // layer, and we don't support crop-rects.
1901 const auto& styleImage
=
1902 aBackgroundStyle
->mImage
.mLayers
[aLayer
].mImage
.FinalImage();
1903 if (styleImage
.IsImageRequestType()) {
1904 imgRequestProxy
* requestProxy
= styleImage
.GetImageRequest();
1905 if (!requestProxy
) {
1909 uint32_t imageFlags
= imgIContainer::FLAG_NONE
;
1910 if (aPaintFlags
& nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES
) {
1911 imageFlags
|= imgIContainer::FLAG_SYNC_DECODE
;
1914 nsCOMPtr
<imgIContainer
> srcImage
;
1915 requestProxy
->GetImage(getter_AddRefs(srcImage
));
1917 !srcImage
->IsImageContainerAvailable(aManager
, imageFlags
)) {
1924 if (styleImage
.IsGradient()) {
1931 ImgDrawResult
nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer(
1932 const PaintBGParams
& aParams
, mozilla::wr::DisplayListBuilder
& aBuilder
,
1933 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
1934 const mozilla::layers::StackingContextHelper
& aSc
,
1935 mozilla::layers::RenderRootStateManager
* aManager
, nsDisplayItem
* aItem
) {
1936 MOZ_ASSERT(aParams
.frame
,
1937 "Frame is expected to be provided to "
1938 "BuildWebRenderDisplayItemsForStyleImageLayer");
1940 ComputedStyle
* sc
= FindBackground(aParams
.frame
);
1942 // We don't want to bail out if moz-appearance is set on a root
1943 // node. If it has a parent content node, bail because it's not
1944 // a root, otherwise keep going in order to let the theme stuff
1945 // draw the background. The canvas really should be drawing the
1946 // bg, but there's no way to hook that up via css.
1947 if (!aParams
.frame
->StyleDisplay()->HasAppearance()) {
1948 return ImgDrawResult::SUCCESS
;
1951 nsIContent
* content
= aParams
.frame
->GetContent();
1952 if (!content
|| content
->GetParent()) {
1953 return ImgDrawResult::SUCCESS
;
1956 sc
= aParams
.frame
->Style();
1958 return BuildWebRenderDisplayItemsForStyleImageLayerWithSC(
1959 aParams
, aBuilder
, aResources
, aSc
, aManager
, aItem
, sc
,
1960 *aParams
.frame
->StyleBorder());
1963 static bool IsOpaqueBorderEdge(const nsStyleBorder
& aBorder
,
1964 mozilla::Side aSide
) {
1965 if (aBorder
.GetComputedBorder().Side(aSide
) == 0) return true;
1966 switch (aBorder
.GetBorderStyle(aSide
)) {
1967 case StyleBorderStyle::Solid
:
1968 case StyleBorderStyle::Groove
:
1969 case StyleBorderStyle::Ridge
:
1970 case StyleBorderStyle::Inset
:
1971 case StyleBorderStyle::Outset
:
1977 // If we're using a border image, assume it's not fully opaque,
1978 // because we may not even have the image loaded at this point, and
1979 // even if we did, checking whether the relevant tile is fully
1980 // opaque would be too much work.
1981 if (!aBorder
.mBorderImageSource
.IsNone()) {
1985 StyleColor color
= aBorder
.BorderColorFor(aSide
);
1986 // We don't know the foreground color here, so if it's being used
1987 // we must assume it might be transparent.
1988 return !color
.MaybeTransparent();
1992 * Returns true if all border edges are either missing or opaque.
1994 static bool IsOpaqueBorder(const nsStyleBorder
& aBorder
) {
1995 for (const auto i
: mozilla::AllPhysicalSides()) {
1996 if (!IsOpaqueBorderEdge(aBorder
, i
)) {
2003 static inline void SetupDirtyRects(const nsRect
& aBGClipArea
,
2004 const nsRect
& aCallerDirtyRect
,
2005 nscoord aAppUnitsPerPixel
,
2007 nsRect
* aDirtyRect
, gfxRect
* aDirtyRectGfx
) {
2008 aDirtyRect
->IntersectRect(aBGClipArea
, aCallerDirtyRect
);
2010 // Compute the Thebes equivalent of the dirtyRect.
2011 *aDirtyRectGfx
= nsLayoutUtils::RectToGfxRect(*aDirtyRect
, aAppUnitsPerPixel
);
2012 NS_WARNING_ASSERTION(aDirtyRect
->IsEmpty() || !aDirtyRectGfx
->IsEmpty(),
2013 "converted dirty rect should not be empty");
2014 MOZ_ASSERT(!aDirtyRect
->IsEmpty() || aDirtyRectGfx
->IsEmpty(),
2015 "second should be empty if first is");
2018 static bool IsSVGStyleGeometryBox(StyleGeometryBox aBox
) {
2019 return (aBox
== StyleGeometryBox::FillBox
||
2020 aBox
== StyleGeometryBox::StrokeBox
||
2021 aBox
== StyleGeometryBox::ViewBox
);
2024 static bool IsHTMLStyleGeometryBox(StyleGeometryBox aBox
) {
2025 return (aBox
== StyleGeometryBox::ContentBox
||
2026 aBox
== StyleGeometryBox::PaddingBox
||
2027 aBox
== StyleGeometryBox::BorderBox
||
2028 aBox
== StyleGeometryBox::MarginBox
);
2031 static StyleGeometryBox
ComputeBoxValueForOrigin(nsIFrame
* aForFrame
,
2032 StyleGeometryBox aBox
) {
2033 // The mapping for mask-origin is from
2034 // https://drafts.fxtf.org/css-masking/#the-mask-origin
2035 if (!aForFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
)) {
2036 // For elements with associated CSS layout box, the values fill-box,
2037 // stroke-box and view-box compute to the initial value of mask-origin.
2038 if (IsSVGStyleGeometryBox(aBox
)) {
2039 return StyleGeometryBox::BorderBox
;
2042 // For SVG elements without associated CSS layout box, the values
2043 // content-box, padding-box, border-box compute to fill-box.
2044 if (IsHTMLStyleGeometryBox(aBox
)) {
2045 return StyleGeometryBox::FillBox
;
2052 static StyleGeometryBox
ComputeBoxValueForClip(const nsIFrame
* aForFrame
,
2053 StyleGeometryBox aBox
) {
2054 // The mapping for mask-clip is from
2055 // https://drafts.fxtf.org/css-masking/#the-mask-clip
2056 if (aForFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
)) {
2057 // For SVG elements without associated CSS layout box, the used values for
2058 // content-box and padding-box compute to fill-box and for border-box and
2059 // margin-box compute to stroke-box.
2061 case StyleGeometryBox::ContentBox
:
2062 case StyleGeometryBox::PaddingBox
:
2063 return StyleGeometryBox::FillBox
;
2064 case StyleGeometryBox::BorderBox
:
2065 case StyleGeometryBox::MarginBox
:
2066 return StyleGeometryBox::StrokeBox
;
2072 // For elements with associated CSS layout box, the used values for fill-box
2073 // compute to content-box and for stroke-box and view-box compute to
2076 case StyleGeometryBox::FillBox
:
2077 return StyleGeometryBox::ContentBox
;
2078 case StyleGeometryBox::StrokeBox
:
2079 case StyleGeometryBox::ViewBox
:
2080 return StyleGeometryBox::BorderBox
;
2086 bool nsCSSRendering::ImageLayerClipState::IsValid() const {
2087 // mDirtyRectInDevPx comes from mDirtyRectInAppUnits. mDirtyRectInAppUnits
2088 // can not be empty if mDirtyRectInDevPx is not.
2089 if (!mDirtyRectInDevPx
.IsEmpty() && mDirtyRectInAppUnits
.IsEmpty()) {
2093 if (mHasRoundedCorners
== mClippedRadii
.IsEmpty()) {
2101 void nsCSSRendering::GetImageLayerClip(
2102 const nsStyleImageLayers::Layer
& aLayer
, nsIFrame
* aForFrame
,
2103 const nsStyleBorder
& aBorder
, const nsRect
& aBorderArea
,
2104 const nsRect
& aCallerDirtyRect
, bool aWillPaintBorder
,
2105 nscoord aAppUnitsPerPixel
,
2106 /* out */ ImageLayerClipState
* aClipState
) {
2107 StyleGeometryBox layerClip
= ComputeBoxValueForClip(aForFrame
, aLayer
.mClip
);
2108 if (IsSVGStyleGeometryBox(layerClip
)) {
2109 MOZ_ASSERT(aForFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
));
2111 // The coordinate space of clipArea is svg user space.
2113 nsLayoutUtils::ComputeSVGReferenceRect(aForFrame
, layerClip
);
2115 nsRect strokeBox
= (layerClip
== StyleGeometryBox::StrokeBox
)
2117 : nsLayoutUtils::ComputeSVGReferenceRect(
2118 aForFrame
, StyleGeometryBox::StrokeBox
);
2119 nsRect clipAreaRelativeToStrokeBox
= clipArea
- strokeBox
.TopLeft();
2121 // aBorderArea is the stroke-box area in a coordinate space defined by
2122 // the caller. This coordinate space can be svg user space of aForFrame,
2123 // the space of aForFrame's reference-frame, or anything else.
2125 // Which coordinate space chosen for aBorderArea is not matter. What
2126 // matter is to ensure returning aClipState->mBGClipArea in the consistent
2127 // coordiante space with aBorderArea. So we evaluate the position of clip
2128 // area base on the position of aBorderArea here.
2129 aClipState
->mBGClipArea
=
2130 clipAreaRelativeToStrokeBox
+ aBorderArea
.TopLeft();
2132 SetupDirtyRects(aClipState
->mBGClipArea
, aCallerDirtyRect
,
2133 aAppUnitsPerPixel
, &aClipState
->mDirtyRectInAppUnits
,
2134 &aClipState
->mDirtyRectInDevPx
);
2135 MOZ_ASSERT(aClipState
->IsValid());
2139 if (layerClip
== StyleGeometryBox::NoClip
) {
2140 aClipState
->mBGClipArea
= aCallerDirtyRect
;
2142 SetupDirtyRects(aClipState
->mBGClipArea
, aCallerDirtyRect
,
2143 aAppUnitsPerPixel
, &aClipState
->mDirtyRectInAppUnits
,
2144 &aClipState
->mDirtyRectInDevPx
);
2145 MOZ_ASSERT(aClipState
->IsValid());
2149 MOZ_ASSERT(!aForFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
));
2151 // Compute the outermost boundary of the area that might be painted.
2152 // Same coordinate space as aBorderArea.
2153 Sides skipSides
= aForFrame
->GetSkipSides();
2154 nsRect clipBorderArea
=
2155 BoxDecorationRectForBorder(aForFrame
, aBorderArea
, skipSides
, &aBorder
);
2157 bool haveRoundedCorners
= false;
2158 LayoutFrameType fType
= aForFrame
->Type();
2159 if (fType
!= LayoutFrameType::TableColGroup
&&
2160 fType
!= LayoutFrameType::TableCol
&&
2161 fType
!= LayoutFrameType::TableRow
&&
2162 fType
!= LayoutFrameType::TableRowGroup
) {
2163 haveRoundedCorners
= GetRadii(aForFrame
, aBorder
, aBorderArea
,
2164 clipBorderArea
, aClipState
->mRadii
);
2166 bool isSolidBorder
= aWillPaintBorder
&& IsOpaqueBorder(aBorder
);
2167 if (isSolidBorder
&& layerClip
== StyleGeometryBox::BorderBox
) {
2168 // If we have rounded corners, we need to inflate the background
2169 // drawing area a bit to avoid seams between the border and
2171 layerClip
= haveRoundedCorners
? StyleGeometryBox::MozAlmostPadding
2172 : StyleGeometryBox::PaddingBox
;
2175 aClipState
->mBGClipArea
= clipBorderArea
;
2177 if (aForFrame
->IsScrollFrame() &&
2178 StyleImageLayerAttachment::Local
== aLayer
.mAttachment
) {
2179 // As of this writing, this is still in discussion in the CSS Working Group
2180 // http://lists.w3.org/Archives/Public/www-style/2013Jul/0250.html
2182 // The rectangle for 'background-clip' scrolls with the content,
2183 // but the background is also clipped at a non-scrolling 'padding-box'
2184 // like the content. (See below.)
2185 // Therefore, only 'content-box' makes a difference here.
2186 if (layerClip
== StyleGeometryBox::ContentBox
) {
2187 nsIScrollableFrame
* scrollableFrame
= do_QueryFrame(aForFrame
);
2188 // Clip at a rectangle attached to the scrolled content.
2189 aClipState
->mHasAdditionalBGClipArea
= true;
2190 aClipState
->mAdditionalBGClipArea
=
2191 nsRect(aClipState
->mBGClipArea
.TopLeft() +
2192 scrollableFrame
->GetScrolledFrame()->GetPosition()
2193 // For the dir=rtl case:
2194 + scrollableFrame
->GetScrollRange().TopLeft(),
2195 scrollableFrame
->GetScrolledRect().Size());
2196 nsMargin padding
= aForFrame
->GetUsedPadding();
2197 // padding-bottom is ignored on scrollable frames:
2198 // https://bugzilla.mozilla.org/show_bug.cgi?id=748518
2200 padding
.ApplySkipSides(skipSides
);
2201 aClipState
->mAdditionalBGClipArea
.Deflate(padding
);
2204 // Also clip at a non-scrolling, rounded-corner 'padding-box',
2205 // same as the scrolled content because of the 'overflow' property.
2206 layerClip
= StyleGeometryBox::PaddingBox
;
2209 // See the comment of StyleGeometryBox::Margin.
2210 // Hitting this assertion means we decide to turn on margin-box support for
2211 // positioned mask from CSS parser and style system. In this case, you
2212 // should *inflate* mBGClipArea by the margin returning from
2213 // aForFrame->GetUsedMargin() in the code chunk bellow.
2214 MOZ_ASSERT(layerClip
!= StyleGeometryBox::MarginBox
,
2215 "StyleGeometryBox::MarginBox rendering is not supported yet.\n");
2217 if (layerClip
!= StyleGeometryBox::BorderBox
&&
2218 layerClip
!= StyleGeometryBox::Text
) {
2219 nsMargin border
= aForFrame
->GetUsedBorder();
2220 if (layerClip
== StyleGeometryBox::MozAlmostPadding
) {
2221 // Reduce |border| by 1px (device pixels) on all sides, if
2222 // possible, so that we don't get antialiasing seams between the
2223 // {background|mask} and border.
2224 border
.top
= std::max(0, border
.top
- aAppUnitsPerPixel
);
2225 border
.right
= std::max(0, border
.right
- aAppUnitsPerPixel
);
2226 border
.bottom
= std::max(0, border
.bottom
- aAppUnitsPerPixel
);
2227 border
.left
= std::max(0, border
.left
- aAppUnitsPerPixel
);
2228 } else if (layerClip
!= StyleGeometryBox::PaddingBox
) {
2229 NS_ASSERTION(layerClip
== StyleGeometryBox::ContentBox
,
2230 "unexpected background-clip");
2231 border
+= aForFrame
->GetUsedPadding();
2233 border
.ApplySkipSides(skipSides
);
2234 aClipState
->mBGClipArea
.Deflate(border
);
2236 if (haveRoundedCorners
) {
2237 nsIFrame::AdjustBorderRadii(aClipState
->mRadii
, -border
);
2241 if (haveRoundedCorners
) {
2242 auto d2a
= aForFrame
->PresContext()->AppUnitsPerDevPixel();
2243 nsCSSRendering::ComputePixelRadii(aClipState
->mRadii
, d2a
,
2244 &aClipState
->mClippedRadii
);
2245 aClipState
->mHasRoundedCorners
= !aClipState
->mClippedRadii
.IsEmpty();
2248 if (!haveRoundedCorners
&& aClipState
->mHasAdditionalBGClipArea
) {
2249 // Do the intersection here to account for the fast path(?) below.
2250 aClipState
->mBGClipArea
=
2251 aClipState
->mBGClipArea
.Intersect(aClipState
->mAdditionalBGClipArea
);
2252 aClipState
->mHasAdditionalBGClipArea
= false;
2255 SetupDirtyRects(aClipState
->mBGClipArea
, aCallerDirtyRect
, aAppUnitsPerPixel
,
2256 &aClipState
->mDirtyRectInAppUnits
,
2257 &aClipState
->mDirtyRectInDevPx
);
2259 MOZ_ASSERT(aClipState
->IsValid());
2262 static void SetupImageLayerClip(nsCSSRendering::ImageLayerClipState
& aClipState
,
2263 gfxContext
* aCtx
, nscoord aAppUnitsPerPixel
,
2264 gfxContextAutoSaveRestore
* aAutoSR
) {
2265 if (aClipState
.mDirtyRectInDevPx
.IsEmpty()) {
2266 // Our caller won't draw anything under this condition, so no need
2271 if (aClipState
.mCustomClip
) {
2272 // We don't support custom clips and rounded corners, arguably a bug, but
2273 // table painting seems to depend on it.
2277 // If we have rounded corners, clip all subsequent drawing to the
2278 // rounded rectangle defined by bgArea and bgRadii (we don't know
2279 // whether the rounded corners intrude on the dirtyRect or not).
2280 // Do not do this if we have a caller-provided clip rect --
2281 // as above with bgArea, arguably a bug, but table painting seems
2284 if (aClipState
.mHasAdditionalBGClipArea
) {
2285 gfxRect bgAreaGfx
= nsLayoutUtils::RectToGfxRect(
2286 aClipState
.mAdditionalBGClipArea
, aAppUnitsPerPixel
);
2288 gfxUtils::ConditionRect(bgAreaGfx
);
2290 aAutoSR
->EnsureSaved(aCtx
);
2291 aCtx
->SnappedClip(bgAreaGfx
);
2294 if (aClipState
.mHasRoundedCorners
) {
2295 Rect bgAreaGfx
= NSRectToRect(aClipState
.mBGClipArea
, aAppUnitsPerPixel
);
2298 if (bgAreaGfx
.IsEmpty()) {
2299 // I think it's become possible to hit this since
2300 // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
2301 NS_WARNING("converted background area should not be empty");
2302 // Make our caller not do anything.
2303 aClipState
.mDirtyRectInDevPx
.SizeTo(gfxSize(0.0, 0.0));
2307 aAutoSR
->EnsureSaved(aCtx
);
2309 RefPtr
<Path
> roundedRect
= MakePathForRoundedRect(
2310 *aCtx
->GetDrawTarget(), bgAreaGfx
, aClipState
.mClippedRadii
);
2311 aCtx
->Clip(roundedRect
);
2315 static void DrawBackgroundColor(nsCSSRendering::ImageLayerClipState
& aClipState
,
2316 gfxContext
* aCtx
, nscoord aAppUnitsPerPixel
) {
2317 if (aClipState
.mDirtyRectInDevPx
.IsEmpty()) {
2318 // Our caller won't draw anything under this condition, so no need
2323 DrawTarget
* drawTarget
= aCtx
->GetDrawTarget();
2325 // We don't support custom clips and rounded corners, arguably a bug, but
2326 // table painting seems to depend on it.
2327 if (!aClipState
.mHasRoundedCorners
|| aClipState
.mCustomClip
) {
2329 aCtx
->SnappedRectangle(aClipState
.mDirtyRectInDevPx
);
2334 Rect bgAreaGfx
= NSRectToRect(aClipState
.mBGClipArea
, aAppUnitsPerPixel
);
2337 if (bgAreaGfx
.IsEmpty()) {
2338 // I think it's become possible to hit this since
2339 // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
2340 NS_WARNING("converted background area should not be empty");
2341 // Make our caller not do anything.
2342 aClipState
.mDirtyRectInDevPx
.SizeTo(gfxSize(0.0, 0.0));
2347 gfxRect dirty
= ThebesRect(bgAreaGfx
).Intersect(aClipState
.mDirtyRectInDevPx
);
2349 aCtx
->SnappedClip(dirty
);
2351 if (aClipState
.mHasAdditionalBGClipArea
) {
2352 gfxRect bgAdditionalAreaGfx
= nsLayoutUtils::RectToGfxRect(
2353 aClipState
.mAdditionalBGClipArea
, aAppUnitsPerPixel
);
2354 bgAdditionalAreaGfx
.Round();
2355 gfxUtils::ConditionRect(bgAdditionalAreaGfx
);
2356 aCtx
->SnappedClip(bgAdditionalAreaGfx
);
2359 RefPtr
<Path
> roundedRect
=
2360 MakePathForRoundedRect(*drawTarget
, bgAreaGfx
, aClipState
.mClippedRadii
);
2361 aCtx
->SetPath(roundedRect
);
2366 enum class ScrollbarColorKind
{
2371 static Maybe
<nscolor
> CalcScrollbarColor(nsIFrame
* aFrame
,
2372 ScrollbarColorKind aKind
) {
2373 ComputedStyle
* scrollbarStyle
= nsLayoutUtils::StyleForScrollbar(aFrame
);
2374 const auto& colors
= scrollbarStyle
->StyleUI()->mScrollbarColor
;
2375 if (colors
.IsAuto()) {
2378 const auto& color
= aKind
== ScrollbarColorKind::Thumb
2379 ? colors
.AsColors().thumb
2380 : colors
.AsColors().track
;
2381 return Some(color
.CalcColor(*scrollbarStyle
));
2384 static nscolor
GetBackgroundColor(nsIFrame
* aFrame
,
2385 const ComputedStyle
* aStyle
) {
2386 switch (aStyle
->StyleDisplay()->EffectiveAppearance()) {
2387 case StyleAppearance::ScrollbarthumbVertical
:
2388 case StyleAppearance::ScrollbarthumbHorizontal
: {
2389 if (Maybe
<nscolor
> overrideColor
=
2390 CalcScrollbarColor(aFrame
, ScrollbarColorKind::Thumb
)) {
2391 return *overrideColor
;
2395 case StyleAppearance::ScrollbarVertical
:
2396 case StyleAppearance::ScrollbarHorizontal
:
2397 case StyleAppearance::Scrollcorner
: {
2398 if (Maybe
<nscolor
> overrideColor
=
2399 CalcScrollbarColor(aFrame
, ScrollbarColorKind::Track
)) {
2400 return *overrideColor
;
2407 return aStyle
->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor
);
2410 nscolor
nsCSSRendering::DetermineBackgroundColor(nsPresContext
* aPresContext
,
2411 const ComputedStyle
* aStyle
,
2413 bool& aDrawBackgroundImage
,
2414 bool& aDrawBackgroundColor
) {
2415 auto shouldPaint
= aFrame
->ComputeShouldPaintBackground();
2416 aDrawBackgroundImage
= shouldPaint
.mImage
;
2417 aDrawBackgroundColor
= shouldPaint
.mColor
;
2419 const nsStyleBackground
* bg
= aStyle
->StyleBackground();
2421 if (aDrawBackgroundColor
) {
2422 bgColor
= GetBackgroundColor(aFrame
, aStyle
);
2423 if (NS_GET_A(bgColor
) == 0) {
2424 aDrawBackgroundColor
= false;
2427 // If GetBackgroundColorDraw() is false, we are still expected to
2428 // draw color in the background of any frame that's not completely
2429 // transparent, but we are expected to use white instead of whatever
2430 // color was specified.
2431 bgColor
= NS_RGB(255, 255, 255);
2432 if (aDrawBackgroundImage
|| !bg
->IsTransparent(aStyle
)) {
2433 aDrawBackgroundColor
= true;
2435 bgColor
= NS_RGBA(0, 0, 0, 0);
2439 // We can skip painting the background color if a background image is opaque.
2440 nsStyleImageLayers::Repeat repeat
= bg
->BottomLayer().mRepeat
;
2441 bool xFullRepeat
= repeat
.mXRepeat
== StyleImageLayerRepeat::Repeat
||
2442 repeat
.mXRepeat
== StyleImageLayerRepeat::Round
;
2443 bool yFullRepeat
= repeat
.mYRepeat
== StyleImageLayerRepeat::Repeat
||
2444 repeat
.mYRepeat
== StyleImageLayerRepeat::Round
;
2445 if (aDrawBackgroundColor
&& xFullRepeat
&& yFullRepeat
&&
2446 bg
->BottomLayer().mImage
.IsOpaque() &&
2447 bg
->BottomLayer().mBlendMode
== StyleBlend::Normal
) {
2448 aDrawBackgroundColor
= false;
2454 static CompositionOp
DetermineCompositionOp(
2455 const nsCSSRendering::PaintBGParams
& aParams
,
2456 const nsStyleImageLayers
& aLayers
, uint32_t aLayerIndex
) {
2457 if (aParams
.layer
>= 0) {
2458 // When drawing a single layer, use the specified composition op.
2459 return aParams
.compositionOp
;
2462 const nsStyleImageLayers::Layer
& layer
= aLayers
.mLayers
[aLayerIndex
];
2463 // When drawing all layers, get the compositon op from each image layer.
2464 if (aParams
.paintFlags
& nsCSSRendering::PAINTBG_MASK_IMAGE
) {
2465 // Always using OP_OVER mode while drawing the bottom mask layer.
2466 if (aLayerIndex
== (aLayers
.mImageCount
- 1)) {
2467 return CompositionOp::OP_OVER
;
2470 return nsCSSRendering::GetGFXCompositeMode(layer
.mComposite
);
2473 return nsCSSRendering::GetGFXBlendMode(layer
.mBlendMode
);
2476 ImgDrawResult
nsCSSRendering::PaintStyleImageLayerWithSC(
2477 const PaintBGParams
& aParams
, gfxContext
& aRenderingCtx
,
2478 const ComputedStyle
* aBackgroundSC
, const nsStyleBorder
& aBorder
) {
2479 MOZ_ASSERT(aParams
.frame
,
2480 "Frame is expected to be provided to PaintStyleImageLayerWithSC");
2482 // If we're drawing all layers, aCompositonOp is ignored, so make sure that
2483 // it was left at its default value.
2484 MOZ_ASSERT(aParams
.layer
!= -1 ||
2485 aParams
.compositionOp
== CompositionOp::OP_OVER
);
2487 // Check to see if we have an appearance defined. If so, we let the theme
2488 // renderer draw the background and bail out.
2489 // XXXzw this ignores aParams.bgClipRect.
2490 StyleAppearance appearance
=
2491 aParams
.frame
->StyleDisplay()->EffectiveAppearance();
2492 if (appearance
!= StyleAppearance::None
) {
2493 nsITheme
* theme
= aParams
.presCtx
.Theme();
2494 if (theme
->ThemeSupportsWidget(&aParams
.presCtx
, aParams
.frame
,
2496 nsRect
drawing(aParams
.borderArea
);
2497 theme
->GetWidgetOverflow(aParams
.presCtx
.DeviceContext(), aParams
.frame
,
2498 appearance
, &drawing
);
2499 drawing
.IntersectRect(drawing
, aParams
.dirtyRect
);
2500 theme
->DrawWidgetBackground(&aRenderingCtx
, aParams
.frame
, appearance
,
2501 aParams
.borderArea
, drawing
);
2502 return ImgDrawResult::SUCCESS
;
2506 // For canvas frames (in the CSS sense) we draw the background color using
2507 // a solid color item that gets added in nsLayoutUtils::PaintFrame,
2508 // or nsSubDocumentFrame::BuildDisplayList (bug 488242). (The solid
2509 // color may be moved into nsDisplayCanvasBackground by
2510 // PresShell::AddCanvasBackgroundColorItem(), and painted by
2511 // nsDisplayCanvasBackground directly.) Either way we don't need to
2512 // paint the background color here.
2513 bool isCanvasFrame
= aParams
.frame
->IsCanvasFrame();
2514 const bool paintMask
= aParams
.paintFlags
& PAINTBG_MASK_IMAGE
;
2516 // Determine whether we are drawing background images and/or
2517 // background colors.
2518 bool drawBackgroundImage
= true;
2519 bool drawBackgroundColor
= !paintMask
;
2520 nscolor bgColor
= NS_RGBA(0, 0, 0, 0);
2523 DetermineBackgroundColor(&aParams
.presCtx
, aBackgroundSC
, aParams
.frame
,
2524 drawBackgroundImage
, drawBackgroundColor
);
2527 // Masks shouldn't be suppressed for print.
2528 MOZ_ASSERT_IF(paintMask
, drawBackgroundImage
);
2530 const nsStyleImageLayers
& layers
=
2531 paintMask
? aBackgroundSC
->StyleSVGReset()->mMask
2532 : aBackgroundSC
->StyleBackground()->mImage
;
2533 // If we're drawing a specific layer, we don't want to draw the
2534 // background color.
2535 if (drawBackgroundColor
&& aParams
.layer
>= 0) {
2536 drawBackgroundColor
= false;
2539 // At this point, drawBackgroundImage and drawBackgroundColor are
2540 // true if and only if we are actually supposed to paint an image or
2541 // color into aDirtyRect, respectively.
2542 if (!drawBackgroundImage
&& !drawBackgroundColor
) {
2543 return ImgDrawResult::SUCCESS
;
2546 // The 'bgClipArea' (used only by the image tiling logic, far below)
2547 // is the caller-provided aParams.bgClipRect if any, or else the area
2548 // determined by the value of 'background-clip' in
2549 // SetupCurrentBackgroundClip. (Arguably it should be the
2550 // intersection, but that breaks the table painter -- in particular,
2551 // taking the intersection breaks reftests/bugs/403249-1[ab].)
2552 nscoord appUnitsPerPixel
= aParams
.presCtx
.AppUnitsPerDevPixel();
2553 ImageLayerClipState clipState
;
2554 if (aParams
.bgClipRect
) {
2555 clipState
.mBGClipArea
= *aParams
.bgClipRect
;
2556 clipState
.mCustomClip
= true;
2557 clipState
.mHasRoundedCorners
= false;
2558 SetupDirtyRects(clipState
.mBGClipArea
, aParams
.dirtyRect
, appUnitsPerPixel
,
2559 &clipState
.mDirtyRectInAppUnits
,
2560 &clipState
.mDirtyRectInDevPx
);
2562 GetImageLayerClip(layers
.BottomLayer(), aParams
.frame
, aBorder
,
2563 aParams
.borderArea
, aParams
.dirtyRect
,
2564 (aParams
.paintFlags
& PAINTBG_WILL_PAINT_BORDER
),
2565 appUnitsPerPixel
, &clipState
);
2568 // If we might be using a background color, go ahead and set it now.
2569 if (drawBackgroundColor
&& !isCanvasFrame
) {
2570 aRenderingCtx
.SetColor(sRGBColor::FromABGR(bgColor
));
2573 // If there is no background image, draw a color. (If there is
2574 // neither a background image nor a color, we wouldn't have gotten
2576 if (!drawBackgroundImage
) {
2577 if (!isCanvasFrame
) {
2578 DrawBackgroundColor(clipState
, &aRenderingCtx
, appUnitsPerPixel
);
2580 return ImgDrawResult::SUCCESS
;
2583 if (layers
.mImageCount
< 1) {
2584 // Return if there are no background layers, all work from this point
2585 // onwards happens iteratively on these.
2586 return ImgDrawResult::SUCCESS
;
2589 MOZ_ASSERT((aParams
.layer
< 0) ||
2590 (layers
.mImageCount
> uint32_t(aParams
.layer
)));
2592 // The background color is rendered over the entire dirty area,
2593 // even if the image isn't.
2594 if (drawBackgroundColor
&& !isCanvasFrame
) {
2595 DrawBackgroundColor(clipState
, &aRenderingCtx
, appUnitsPerPixel
);
2598 // Compute the outermost boundary of the area that might be painted.
2599 // Same coordinate space as aParams.borderArea & aParams.bgClipRect.
2600 Sides skipSides
= aParams
.frame
->GetSkipSides();
2601 nsRect paintBorderArea
= BoxDecorationRectForBackground(
2602 aParams
.frame
, aParams
.borderArea
, skipSides
, &aBorder
);
2603 nsRect clipBorderArea
= BoxDecorationRectForBorder(
2604 aParams
.frame
, aParams
.borderArea
, skipSides
, &aBorder
);
2606 ImgDrawResult result
= ImgDrawResult::SUCCESS
;
2607 StyleGeometryBox currentBackgroundClip
= StyleGeometryBox::BorderBox
;
2608 const bool drawAllLayers
= (aParams
.layer
< 0);
2609 uint32_t count
= drawAllLayers
2610 ? layers
.mImageCount
// iterate all image layers.
2611 : layers
.mImageCount
-
2612 aParams
.layer
; // iterate from the bottom layer to
2613 // the 'aParams.layer-th' layer.
2614 NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE(
2615 i
, layers
, layers
.mImageCount
- 1, count
) {
2616 // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved(ctx)
2617 // in the cases we need it.
2618 gfxContextAutoSaveRestore autoSR
;
2619 const nsStyleImageLayers::Layer
& layer
= layers
.mLayers
[i
];
2621 ImageLayerClipState currentLayerClipState
= clipState
;
2622 if (!aParams
.bgClipRect
) {
2623 bool isBottomLayer
= (i
== layers
.mImageCount
- 1);
2624 if (currentBackgroundClip
!= layer
.mClip
|| isBottomLayer
) {
2625 currentBackgroundClip
= layer
.mClip
;
2626 if (!isBottomLayer
) {
2627 currentLayerClipState
= {};
2628 // For the bottom layer, we already called GetImageLayerClip above
2629 // and it stored its results in clipState.
2630 GetImageLayerClip(layer
, aParams
.frame
, aBorder
, aParams
.borderArea
,
2632 (aParams
.paintFlags
& PAINTBG_WILL_PAINT_BORDER
),
2633 appUnitsPerPixel
, ¤tLayerClipState
);
2635 SetupImageLayerClip(currentLayerClipState
, &aRenderingCtx
,
2636 appUnitsPerPixel
, &autoSR
);
2637 if (!clipBorderArea
.IsEqualEdges(aParams
.borderArea
)) {
2638 // We're drawing the background for the joined continuation boxes
2639 // so we need to clip that to the slice that we want for this
2641 gfxRect clip
= nsLayoutUtils::RectToGfxRect(aParams
.borderArea
,
2643 autoSR
.EnsureSaved(&aRenderingCtx
);
2644 aRenderingCtx
.SnappedClip(clip
);
2649 // Skip the following layer preparing and painting code if the current
2650 // layer is not selected for drawing.
2651 if (aParams
.layer
>= 0 && i
!= (uint32_t)aParams
.layer
) {
2654 nsBackgroundLayerState state
= PrepareImageLayer(
2655 &aParams
.presCtx
, aParams
.frame
, aParams
.paintFlags
, paintBorderArea
,
2656 currentLayerClipState
.mBGClipArea
, layer
, nullptr);
2657 result
&= state
.mImageRenderer
.PrepareResult();
2659 // Skip the layer painting code if we found the dirty region is empty.
2660 if (currentLayerClipState
.mDirtyRectInDevPx
.IsEmpty()) {
2664 if (!state
.mFillArea
.IsEmpty()) {
2665 CompositionOp co
= DetermineCompositionOp(aParams
, layers
, i
);
2666 if (co
!= CompositionOp::OP_OVER
) {
2667 NS_ASSERTION(aRenderingCtx
.CurrentOp() == CompositionOp::OP_OVER
,
2668 "It is assumed the initial op is OP_OVER, when it is "
2670 aRenderingCtx
.SetOp(co
);
2673 result
&= state
.mImageRenderer
.DrawLayer(
2674 &aParams
.presCtx
, aRenderingCtx
, state
.mDestArea
, state
.mFillArea
,
2675 state
.mAnchor
+ paintBorderArea
.TopLeft(),
2676 currentLayerClipState
.mDirtyRectInAppUnits
, state
.mRepeatSize
,
2679 if (co
!= CompositionOp::OP_OVER
) {
2680 aRenderingCtx
.SetOp(CompositionOp::OP_OVER
);
2689 nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayerWithSC(
2690 const PaintBGParams
& aParams
, mozilla::wr::DisplayListBuilder
& aBuilder
,
2691 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
2692 const mozilla::layers::StackingContextHelper
& aSc
,
2693 mozilla::layers::RenderRootStateManager
* aManager
, nsDisplayItem
* aItem
,
2694 ComputedStyle
* aBackgroundSC
, const nsStyleBorder
& aBorder
) {
2695 MOZ_ASSERT(!(aParams
.paintFlags
& PAINTBG_MASK_IMAGE
));
2697 nscoord appUnitsPerPixel
= aParams
.presCtx
.AppUnitsPerDevPixel();
2698 ImageLayerClipState clipState
;
2700 clipState
.mBGClipArea
= *aParams
.bgClipRect
;
2701 clipState
.mCustomClip
= true;
2702 clipState
.mHasRoundedCorners
= false;
2703 SetupDirtyRects(clipState
.mBGClipArea
, aParams
.dirtyRect
, appUnitsPerPixel
,
2704 &clipState
.mDirtyRectInAppUnits
,
2705 &clipState
.mDirtyRectInDevPx
);
2707 // Compute the outermost boundary of the area that might be painted.
2708 // Same coordinate space as aParams.borderArea & aParams.bgClipRect.
2709 Sides skipSides
= aParams
.frame
->GetSkipSides();
2710 nsRect paintBorderArea
= BoxDecorationRectForBackground(
2711 aParams
.frame
, aParams
.borderArea
, skipSides
, &aBorder
);
2713 const nsStyleImageLayers
& layers
= aBackgroundSC
->StyleBackground()->mImage
;
2714 const nsStyleImageLayers::Layer
& layer
= layers
.mLayers
[aParams
.layer
];
2716 // Skip the following layer painting code if we found the dirty region is
2717 // empty or the current layer is not selected for drawing.
2718 if (clipState
.mDirtyRectInDevPx
.IsEmpty()) {
2719 return ImgDrawResult::SUCCESS
;
2722 ImgDrawResult result
= ImgDrawResult::SUCCESS
;
2723 nsBackgroundLayerState state
=
2724 PrepareImageLayer(&aParams
.presCtx
, aParams
.frame
, aParams
.paintFlags
,
2725 paintBorderArea
, clipState
.mBGClipArea
, layer
, nullptr);
2726 result
&= state
.mImageRenderer
.PrepareResult();
2728 if (!state
.mFillArea
.IsEmpty()) {
2729 result
&= state
.mImageRenderer
.BuildWebRenderDisplayItemsForLayer(
2730 &aParams
.presCtx
, aBuilder
, aResources
, aSc
, aManager
, aItem
,
2731 state
.mDestArea
, state
.mFillArea
,
2732 state
.mAnchor
+ paintBorderArea
.TopLeft(),
2733 clipState
.mDirtyRectInAppUnits
, state
.mRepeatSize
, aParams
.opacity
);
2739 nsRect
nsCSSRendering::ComputeImageLayerPositioningArea(
2740 nsPresContext
* aPresContext
, nsIFrame
* aForFrame
, const nsRect
& aBorderArea
,
2741 const nsStyleImageLayers::Layer
& aLayer
, nsIFrame
** aAttachedToFrame
,
2742 bool* aOutIsTransformedFixed
) {
2743 // Compute {background|mask} origin area relative to aBorderArea now as we
2744 // may need it to compute the effective image size for a CSS gradient.
2745 nsRect positionArea
;
2747 StyleGeometryBox layerOrigin
=
2748 ComputeBoxValueForOrigin(aForFrame
, aLayer
.mOrigin
);
2750 if (IsSVGStyleGeometryBox(layerOrigin
)) {
2751 MOZ_ASSERT(aForFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
));
2752 *aAttachedToFrame
= aForFrame
;
2755 nsLayoutUtils::ComputeSVGReferenceRect(aForFrame
, layerOrigin
);
2757 nsPoint toStrokeBoxOffset
= nsPoint(0, 0);
2758 if (layerOrigin
!= StyleGeometryBox::StrokeBox
) {
2759 nsRect strokeBox
= nsLayoutUtils::ComputeSVGReferenceRect(
2760 aForFrame
, StyleGeometryBox::StrokeBox
);
2761 toStrokeBoxOffset
= positionArea
.TopLeft() - strokeBox
.TopLeft();
2764 // For SVG frames, the return value is relative to the stroke box
2765 return nsRect(toStrokeBoxOffset
, positionArea
.Size());
2768 MOZ_ASSERT(!aForFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
));
2770 LayoutFrameType frameType
= aForFrame
->Type();
2771 nsIFrame
* geometryFrame
= aForFrame
;
2772 if (MOZ_UNLIKELY(frameType
== LayoutFrameType::Scroll
&&
2773 StyleImageLayerAttachment::Local
== aLayer
.mAttachment
)) {
2774 nsIScrollableFrame
* scrollableFrame
= do_QueryFrame(aForFrame
);
2775 positionArea
= nsRect(scrollableFrame
->GetScrolledFrame()->GetPosition()
2776 // For the dir=rtl case:
2777 + scrollableFrame
->GetScrollRange().TopLeft(),
2778 scrollableFrame
->GetScrolledRect().Size());
2779 // The ScrolledRect’s size does not include the borders or scrollbars,
2780 // reverse the handling of background-origin
2781 // compared to the common case below.
2782 if (layerOrigin
== StyleGeometryBox::BorderBox
) {
2783 nsMargin border
= geometryFrame
->GetUsedBorder();
2784 border
.ApplySkipSides(geometryFrame
->GetSkipSides());
2785 positionArea
.Inflate(border
);
2786 positionArea
.Inflate(scrollableFrame
->GetActualScrollbarSizes());
2787 } else if (layerOrigin
!= StyleGeometryBox::PaddingBox
) {
2788 nsMargin padding
= geometryFrame
->GetUsedPadding();
2789 padding
.ApplySkipSides(geometryFrame
->GetSkipSides());
2790 positionArea
.Deflate(padding
);
2791 NS_ASSERTION(layerOrigin
== StyleGeometryBox::ContentBox
,
2792 "unknown background-origin value");
2794 *aAttachedToFrame
= aForFrame
;
2795 return positionArea
;
2798 if (MOZ_UNLIKELY(frameType
== LayoutFrameType::Canvas
)) {
2799 geometryFrame
= aForFrame
->PrincipalChildList().FirstChild();
2800 // geometryFrame might be null if this canvas is a page created
2801 // as an overflow container (e.g. the in-flow content has already
2802 // finished and this page only displays the continuations of
2803 // absolutely positioned content).
2804 if (geometryFrame
) {
2806 nsPlaceholderFrame::GetRealFrameFor(geometryFrame
)->GetRect();
2809 positionArea
= nsRect(nsPoint(0, 0), aBorderArea
.Size());
2812 // See the comment of StyleGeometryBox::MarginBox.
2813 // Hitting this assertion means we decide to turn on margin-box support for
2814 // positioned mask from CSS parser and style system. In this case, you
2815 // should *inflate* positionArea by the margin returning from
2816 // geometryFrame->GetUsedMargin() in the code chunk bellow.
2817 MOZ_ASSERT(aLayer
.mOrigin
!= StyleGeometryBox::MarginBox
,
2818 "StyleGeometryBox::MarginBox rendering is not supported yet.\n");
2820 // {background|mask} images are tiled over the '{background|mask}-clip' area
2821 // but the origin of the tiling is based on the '{background|mask}-origin'
2823 if (layerOrigin
!= StyleGeometryBox::BorderBox
&& geometryFrame
) {
2824 nsMargin border
= geometryFrame
->GetUsedBorder();
2825 if (layerOrigin
!= StyleGeometryBox::PaddingBox
) {
2826 border
+= geometryFrame
->GetUsedPadding();
2827 NS_ASSERTION(layerOrigin
== StyleGeometryBox::ContentBox
,
2828 "unknown background-origin value");
2830 positionArea
.Deflate(border
);
2833 nsIFrame
* attachedToFrame
= aForFrame
;
2834 if (StyleImageLayerAttachment::Fixed
== aLayer
.mAttachment
) {
2835 // If it's a fixed background attachment, then the image is placed
2836 // relative to the viewport, which is the area of the root frame
2837 // in a screen context or the page content frame in a print context.
2838 attachedToFrame
= aPresContext
->PresShell()->GetRootFrame();
2839 NS_ASSERTION(attachedToFrame
, "no root frame");
2840 nsIFrame
* pageContentFrame
= nullptr;
2841 if (aPresContext
->IsPaginated()) {
2842 pageContentFrame
= nsLayoutUtils::GetClosestFrameOfType(
2843 aForFrame
, LayoutFrameType::PageContent
);
2844 if (pageContentFrame
) {
2845 attachedToFrame
= pageContentFrame
;
2847 // else this is an embedded shell and its root frame is what we want
2850 // If the background is affected by a transform, treat is as if it
2852 if (nsLayoutUtils::IsTransformed(aForFrame
, attachedToFrame
)) {
2853 attachedToFrame
= aForFrame
;
2854 *aOutIsTransformedFixed
= true;
2856 // Set the background positioning area to the viewport's area
2857 // (relative to aForFrame)
2858 positionArea
= nsRect(-aForFrame
->GetOffsetTo(attachedToFrame
),
2859 attachedToFrame
->GetSize());
2861 if (!pageContentFrame
) {
2862 // Subtract the size of scrollbars.
2863 nsIScrollableFrame
* scrollableFrame
=
2864 aPresContext
->PresShell()->GetRootScrollFrameAsScrollable();
2865 if (scrollableFrame
) {
2866 nsMargin scrollbars
= scrollableFrame
->GetActualScrollbarSizes();
2867 positionArea
.Deflate(scrollbars
);
2871 // If we have the dynamic toolbar, we need to expand the image area to
2872 // include the region under the dynamic toolbar, otherwise we will see a
2873 // blank space under the toolbar.
2874 if (aPresContext
->IsRootContentDocumentCrossProcess() &&
2875 aPresContext
->HasDynamicToolbar()) {
2876 positionArea
.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
2877 aPresContext
, positionArea
.Size()));
2881 *aAttachedToFrame
= attachedToFrame
;
2883 return positionArea
;
2887 nscoord
nsCSSRendering::ComputeRoundedSize(nscoord aCurrentSize
,
2888 nscoord aPositioningSize
) {
2889 float repeatCount
= NS_roundf(float(aPositioningSize
) / float(aCurrentSize
));
2890 if (repeatCount
< 1.0f
) {
2891 return aPositioningSize
;
2893 return nscoord(NS_lround(float(aPositioningSize
) / repeatCount
));
2896 // Apply the CSS image sizing algorithm as it applies to background images.
2897 // See http://www.w3.org/TR/css3-background/#the-background-size .
2898 // aIntrinsicSize is the size that the background image 'would like to be'.
2899 // It can be found by calling nsImageRenderer::ComputeIntrinsicSize.
2900 static nsSize
ComputeDrawnSizeForBackground(
2901 const CSSSizeOrRatio
& aIntrinsicSize
, const nsSize
& aBgPositioningArea
,
2902 const StyleBackgroundSize
& aLayerSize
, StyleImageLayerRepeat aXRepeat
,
2903 StyleImageLayerRepeat aYRepeat
) {
2906 // Size is dictated by cover or contain rules.
2907 if (aLayerSize
.IsContain() || aLayerSize
.IsCover()) {
2908 nsImageRenderer::FitType fitType
= aLayerSize
.IsCover()
2909 ? nsImageRenderer::COVER
2910 : nsImageRenderer::CONTAIN
;
2911 imageSize
= nsImageRenderer::ComputeConstrainedSize(
2912 aBgPositioningArea
, aIntrinsicSize
.mRatio
, fitType
);
2914 MOZ_ASSERT(aLayerSize
.IsExplicitSize());
2915 const auto& width
= aLayerSize
.explicit_size
.width
;
2916 const auto& height
= aLayerSize
.explicit_size
.height
;
2917 // No cover/contain constraint, use default algorithm.
2918 CSSSizeOrRatio specifiedSize
;
2919 if (width
.IsLengthPercentage()) {
2920 specifiedSize
.SetWidth(
2921 width
.AsLengthPercentage().Resolve(aBgPositioningArea
.width
));
2923 if (height
.IsLengthPercentage()) {
2924 specifiedSize
.SetHeight(
2925 height
.AsLengthPercentage().Resolve(aBgPositioningArea
.height
));
2928 imageSize
= nsImageRenderer::ComputeConcreteSize(
2929 specifiedSize
, aIntrinsicSize
, aBgPositioningArea
);
2932 // See https://www.w3.org/TR/css3-background/#background-size .
2933 // "If 'background-repeat' is 'round' for one (or both) dimensions, there is a
2935 // step. The UA must scale the image in that dimension (or both dimensions)
2936 // so that it fits a whole number of times in the background positioning
2938 // "If 'background-repeat' is 'round' for one dimension only and if
2939 // 'background-size'
2940 // is 'auto' for the other dimension, then there is a third step: that other
2941 // dimension is scaled so that the original aspect ratio is restored."
2942 bool isRepeatRoundInBothDimensions
=
2943 aXRepeat
== StyleImageLayerRepeat::Round
&&
2944 aYRepeat
== StyleImageLayerRepeat::Round
;
2946 // Calculate the rounded size only if the background-size computation
2947 // returned a correct size for the image.
2948 if (imageSize
.width
&& aXRepeat
== StyleImageLayerRepeat::Round
) {
2949 imageSize
.width
= nsCSSRendering::ComputeRoundedSize(
2950 imageSize
.width
, aBgPositioningArea
.width
);
2951 if (!isRepeatRoundInBothDimensions
&& aLayerSize
.IsExplicitSize() &&
2952 aLayerSize
.explicit_size
.height
.IsAuto()) {
2953 // Restore intrinsic ratio
2954 if (aIntrinsicSize
.mRatio
) {
2956 aIntrinsicSize
.mRatio
.Inverted().ApplyTo(imageSize
.width
);
2961 // Calculate the rounded size only if the background-size computation
2962 // returned a correct size for the image.
2963 if (imageSize
.height
&& aYRepeat
== StyleImageLayerRepeat::Round
) {
2964 imageSize
.height
= nsCSSRendering::ComputeRoundedSize(
2965 imageSize
.height
, aBgPositioningArea
.height
);
2966 if (!isRepeatRoundInBothDimensions
&& aLayerSize
.IsExplicitSize() &&
2967 aLayerSize
.explicit_size
.width
.IsAuto()) {
2968 // Restore intrinsic ratio
2969 if (aIntrinsicSize
.mRatio
) {
2970 imageSize
.width
= aIntrinsicSize
.mRatio
.ApplyTo(imageSize
.height
);
2978 /* ComputeSpacedRepeatSize
2979 * aImageDimension: the image width/height
2980 * aAvailableSpace: the background positioning area width/height
2981 * aRepeat: determine whether the image is repeated
2982 * Returns the image size plus gap size of app units for use as spacing
2984 static nscoord
ComputeSpacedRepeatSize(nscoord aImageDimension
,
2985 nscoord aAvailableSpace
, bool& aRepeat
) {
2986 float ratio
= static_cast<float>(aAvailableSpace
) / aImageDimension
;
2988 if (ratio
< 2.0f
) { // If you can't repeat at least twice, then don't repeat.
2990 return aImageDimension
;
2994 return (aAvailableSpace
- aImageDimension
) / (NSToIntFloor(ratio
) - 1);
2998 nscoord
nsCSSRendering::ComputeBorderSpacedRepeatSize(nscoord aImageDimension
,
2999 nscoord aAvailableSpace
,
3001 int32_t count
= aImageDimension
? (aAvailableSpace
/ aImageDimension
) : 0;
3002 aSpace
= (aAvailableSpace
- aImageDimension
* count
) / (count
+ 1);
3003 return aSpace
+ aImageDimension
;
3006 nsBackgroundLayerState
nsCSSRendering::PrepareImageLayer(
3007 nsPresContext
* aPresContext
, nsIFrame
* aForFrame
, uint32_t aFlags
,
3008 const nsRect
& aBorderArea
, const nsRect
& aBGClipRect
,
3009 const nsStyleImageLayers::Layer
& aLayer
, bool* aOutIsTransformedFixed
) {
3011 * The properties we need to keep in mind when drawing style image
3014 * background-image/ mask-image
3015 * background-repeat/ mask-repeat
3016 * background-attachment
3017 * background-position/ mask-position
3018 * background-clip/ mask-clip
3019 * background-origin/ mask-origin
3020 * background-size/ mask-size
3021 * background-blend-mode
3022 * box-decoration-break
3026 * (background-color applies to the entire element and not to individual
3027 * layers, so it is irrelevant to this method.)
3029 * These properties have the following dependencies upon each other when
3030 * determining rendering:
3032 * background-image/ mask-image
3034 * background-repeat/ mask-repeat
3036 * background-attachment
3038 * background-position/ mask-position
3039 * depends upon background-size/mask-size (for the image's scaled size)
3040 * and background-break (for the background positioning area)
3041 * background-clip/ mask-clip
3043 * background-origin/ mask-origin
3044 * depends upon background-attachment (only in the case where that value
3046 * background-size/ mask-size
3047 * depends upon box-decoration-break (for the background positioning area
3048 * for resolving percentages), background-image (for the image's intrinsic
3049 * size), background-repeat (if that value is 'round'), and
3050 * background-origin (for the background painting area, when
3051 * background-repeat is 'round')
3052 * background-blend-mode
3058 * box-decoration-break
3061 * As a result of only-if dependencies we don't strictly do a topological
3062 * sort of the above properties when processing, but it's pretty close to one:
3064 * background-clip/mask-clip (by caller)
3065 * background-image/ mask-image
3066 * box-decoration-break, background-origin/ mask origin
3067 * background-attachment (postfix for background-origin if 'fixed')
3068 * background-size/ mask-size
3069 * background-position/ mask-position
3070 * background-repeat/ mask-repeat
3073 uint32_t irFlags
= 0;
3074 if (aFlags
& nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES
) {
3075 irFlags
|= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES
;
3077 if (aFlags
& nsCSSRendering::PAINTBG_TO_WINDOW
) {
3078 irFlags
|= nsImageRenderer::FLAG_PAINTING_TO_WINDOW
;
3080 if (aFlags
& nsCSSRendering::PAINTBG_HIGH_QUALITY_SCALING
) {
3081 irFlags
|= nsImageRenderer::FLAG_HIGH_QUALITY_SCALING
;
3083 // Only do partial bg image drawing in content documents: non-content
3084 // documents are viewed as UI of the browser and a partial draw of a bg image
3085 // might look weird in that context.
3086 if (StaticPrefs::layout_display_partial_background_images() &&
3087 XRE_IsContentProcess() && !aPresContext
->IsChrome()) {
3088 irFlags
|= nsImageRenderer::FLAG_DRAW_PARTIAL_FRAMES
;
3091 nsBackgroundLayerState
state(aForFrame
, &aLayer
.mImage
, irFlags
);
3092 if (!state
.mImageRenderer
.PrepareImage()) {
3093 // There's no image or it's not ready to be painted.
3094 if (aOutIsTransformedFixed
&&
3095 StyleImageLayerAttachment::Fixed
== aLayer
.mAttachment
) {
3096 nsIFrame
* attachedToFrame
= aPresContext
->PresShell()->GetRootFrame();
3097 NS_ASSERTION(attachedToFrame
, "no root frame");
3098 nsIFrame
* pageContentFrame
= nullptr;
3099 if (aPresContext
->IsPaginated()) {
3100 pageContentFrame
= nsLayoutUtils::GetClosestFrameOfType(
3101 aForFrame
, LayoutFrameType::PageContent
);
3102 if (pageContentFrame
) {
3103 attachedToFrame
= pageContentFrame
;
3105 // else this is an embedded shell and its root frame is what we want
3108 *aOutIsTransformedFixed
=
3109 nsLayoutUtils::IsTransformed(aForFrame
, attachedToFrame
);
3114 // The frame to which the background is attached
3115 nsIFrame
* attachedToFrame
= aForFrame
;
3116 // Is the background marked 'fixed', but affected by a transform?
3117 bool transformedFixed
= false;
3118 // Compute background origin area relative to aBorderArea now as we may need
3119 // it to compute the effective image size for a CSS gradient.
3120 nsRect positionArea
= ComputeImageLayerPositioningArea(
3121 aPresContext
, aForFrame
, aBorderArea
, aLayer
, &attachedToFrame
,
3123 if (aOutIsTransformedFixed
) {
3124 *aOutIsTransformedFixed
= transformedFixed
;
3127 // For background-attachment:fixed backgrounds, we'll override the area
3128 // where the background can be drawn to the viewport.
3129 nsRect bgClipRect
= aBGClipRect
;
3131 if (StyleImageLayerAttachment::Fixed
== aLayer
.mAttachment
&&
3132 !transformedFixed
&& (aFlags
& nsCSSRendering::PAINTBG_TO_WINDOW
)) {
3133 bgClipRect
= positionArea
+ aBorderArea
.TopLeft();
3136 StyleImageLayerRepeat repeatX
= aLayer
.mRepeat
.mXRepeat
;
3137 StyleImageLayerRepeat repeatY
= aLayer
.mRepeat
.mYRepeat
;
3139 // Scale the image as specified for background-size and background-repeat.
3140 // Also as required for proper background positioning when background-position
3141 // is defined with percentages.
3142 CSSSizeOrRatio intrinsicSize
= state
.mImageRenderer
.ComputeIntrinsicSize();
3143 nsSize bgPositionSize
= positionArea
.Size();
3144 nsSize imageSize
= ComputeDrawnSizeForBackground(
3145 intrinsicSize
, bgPositionSize
, aLayer
.mSize
, repeatX
, repeatY
);
3147 if (imageSize
.width
<= 0 || imageSize
.height
<= 0) return state
;
3149 state
.mImageRenderer
.SetPreferredSize(intrinsicSize
, imageSize
);
3151 // Compute the anchor point.
3153 // relative to aBorderArea.TopLeft() (which is where the top-left
3154 // of aForFrame's border-box will be rendered)
3155 nsPoint imageTopLeft
;
3157 // Compute the position of the background now that the background's size is
3159 nsImageRenderer::ComputeObjectAnchorPoint(aLayer
.mPosition
, bgPositionSize
,
3160 imageSize
, &imageTopLeft
,
3162 state
.mRepeatSize
= imageSize
;
3163 if (repeatX
== StyleImageLayerRepeat::Space
) {
3165 state
.mRepeatSize
.width
= ComputeSpacedRepeatSize(
3166 imageSize
.width
, bgPositionSize
.width
, isRepeat
);
3169 state
.mAnchor
.x
= 0;
3171 repeatX
= StyleImageLayerRepeat::NoRepeat
;
3175 if (repeatY
== StyleImageLayerRepeat::Space
) {
3177 state
.mRepeatSize
.height
= ComputeSpacedRepeatSize(
3178 imageSize
.height
, bgPositionSize
.height
, isRepeat
);
3181 state
.mAnchor
.y
= 0;
3183 repeatY
= StyleImageLayerRepeat::NoRepeat
;
3187 imageTopLeft
+= positionArea
.TopLeft();
3188 state
.mAnchor
+= positionArea
.TopLeft();
3189 state
.mDestArea
= nsRect(imageTopLeft
+ aBorderArea
.TopLeft(), imageSize
);
3190 state
.mFillArea
= state
.mDestArea
;
3192 ExtendMode repeatMode
= ExtendMode::CLAMP
;
3193 if (repeatX
== StyleImageLayerRepeat::Repeat
||
3194 repeatX
== StyleImageLayerRepeat::Round
||
3195 repeatX
== StyleImageLayerRepeat::Space
) {
3196 state
.mFillArea
.x
= bgClipRect
.x
;
3197 state
.mFillArea
.width
= bgClipRect
.width
;
3198 repeatMode
= ExtendMode::REPEAT_X
;
3200 if (repeatY
== StyleImageLayerRepeat::Repeat
||
3201 repeatY
== StyleImageLayerRepeat::Round
||
3202 repeatY
== StyleImageLayerRepeat::Space
) {
3203 state
.mFillArea
.y
= bgClipRect
.y
;
3204 state
.mFillArea
.height
= bgClipRect
.height
;
3207 * We're repeating on the X axis already,
3208 * so if we have to repeat in the Y axis,
3209 * we really need to repeat in both directions.
3211 if (repeatMode
== ExtendMode::REPEAT_X
) {
3212 repeatMode
= ExtendMode::REPEAT
;
3214 repeatMode
= ExtendMode::REPEAT_Y
;
3217 state
.mImageRenderer
.SetExtendMode(repeatMode
);
3218 state
.mImageRenderer
.SetMaskOp(aLayer
.mMaskMode
);
3220 state
.mFillArea
.IntersectRect(state
.mFillArea
, bgClipRect
);
3225 nsRect
nsCSSRendering::GetBackgroundLayerRect(
3226 nsPresContext
* aPresContext
, nsIFrame
* aForFrame
, const nsRect
& aBorderArea
,
3227 const nsRect
& aClipRect
, const nsStyleImageLayers::Layer
& aLayer
,
3229 Sides skipSides
= aForFrame
->GetSkipSides();
3231 BoxDecorationRectForBackground(aForFrame
, aBorderArea
, skipSides
);
3232 nsBackgroundLayerState state
= PrepareImageLayer(
3233 aPresContext
, aForFrame
, aFlags
, borderArea
, aClipRect
, aLayer
);
3234 return state
.mFillArea
;
3237 // Begin table border-collapsing section
3238 // These functions were written to not disrupt the normal ones and yet satisfy
3239 // some additional requirements At some point, all functions should be unified
3240 // to include the additional functionality that these provide
3242 static nscoord
RoundIntToPixel(nscoord aValue
, nscoord aOneDevPixel
,
3243 bool aRoundDown
= false) {
3244 if (aOneDevPixel
<= 0) {
3245 // We must be rendering to a device that has a resolution greater than
3246 // one device pixel!
3247 // In that case, aValue is as accurate as it's going to get.
3251 nscoord halfPixel
= NSToCoordRound(aOneDevPixel
/ 2.0f
);
3252 nscoord extra
= aValue
% aOneDevPixel
;
3253 nscoord finalValue
= (!aRoundDown
&& (extra
>= halfPixel
))
3254 ? aValue
+ (aOneDevPixel
- extra
)
3259 static nscoord
RoundFloatToPixel(float aValue
, nscoord aOneDevPixel
,
3260 bool aRoundDown
= false) {
3261 return RoundIntToPixel(NSToCoordRound(aValue
), aOneDevPixel
, aRoundDown
);
3264 static void SetPoly(const Rect
& aRect
, Point
* poly
) {
3265 poly
[0].x
= aRect
.x
;
3266 poly
[0].y
= aRect
.y
;
3267 poly
[1].x
= aRect
.x
+ aRect
.width
;
3268 poly
[1].y
= aRect
.y
;
3269 poly
[2].x
= aRect
.x
+ aRect
.width
;
3270 poly
[2].y
= aRect
.y
+ aRect
.height
;
3271 poly
[3].x
= aRect
.x
;
3272 poly
[3].y
= aRect
.y
+ aRect
.height
;
3275 static void DrawDashedSegment(DrawTarget
& aDrawTarget
, nsRect aRect
,
3276 nscoord aDashLength
, nscolor aColor
,
3277 int32_t aAppUnitsPerDevPixel
, bool aHorizontal
) {
3278 ColorPattern
color(ToDeviceColor(aColor
));
3279 DrawOptions
drawOptions(1.f
, CompositionOp::OP_OVER
, AntialiasMode::NONE
);
3280 StrokeOptions strokeOptions
;
3283 dash
[0] = Float(aDashLength
) / aAppUnitsPerDevPixel
;
3286 strokeOptions
.mDashPattern
= dash
;
3287 strokeOptions
.mDashLength
= MOZ_ARRAY_LENGTH(dash
);
3290 nsPoint left
= (aRect
.TopLeft() + aRect
.BottomLeft()) / 2;
3291 nsPoint right
= (aRect
.TopRight() + aRect
.BottomRight()) / 2;
3292 strokeOptions
.mLineWidth
= Float(aRect
.height
) / aAppUnitsPerDevPixel
;
3293 StrokeLineWithSnapping(left
, right
, aAppUnitsPerDevPixel
, aDrawTarget
,
3294 color
, strokeOptions
, drawOptions
);
3296 nsPoint top
= (aRect
.TopLeft() + aRect
.TopRight()) / 2;
3297 nsPoint bottom
= (aRect
.BottomLeft() + aRect
.BottomRight()) / 2;
3298 strokeOptions
.mLineWidth
= Float(aRect
.width
) / aAppUnitsPerDevPixel
;
3299 StrokeLineWithSnapping(top
, bottom
, aAppUnitsPerDevPixel
, aDrawTarget
,
3300 color
, strokeOptions
, drawOptions
);
3304 static void DrawSolidBorderSegment(
3305 DrawTarget
& aDrawTarget
, nsRect aRect
, nscolor aColor
,
3306 int32_t aAppUnitsPerDevPixel
,
3307 mozilla::Side aStartBevelSide
= mozilla::eSideTop
,
3308 nscoord aStartBevelOffset
= 0,
3309 mozilla::Side aEndBevelSide
= mozilla::eSideTop
,
3310 nscoord aEndBevelOffset
= 0) {
3311 ColorPattern
color(ToDeviceColor(aColor
));
3312 DrawOptions
drawOptions(1.f
, CompositionOp::OP_OVER
, AntialiasMode::NONE
);
3314 nscoord oneDevPixel
= NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel
);
3315 // We don't need to bevel single pixel borders
3316 if ((aRect
.width
== oneDevPixel
) || (aRect
.height
== oneDevPixel
) ||
3317 ((0 == aStartBevelOffset
) && (0 == aEndBevelOffset
))) {
3319 aDrawTarget
.FillRect(
3320 NSRectToSnappedRect(aRect
, aAppUnitsPerDevPixel
, aDrawTarget
), color
,
3323 // polygon with beveling
3325 SetPoly(NSRectToSnappedRect(aRect
, aAppUnitsPerDevPixel
, aDrawTarget
),
3328 Float startBevelOffset
=
3329 NSAppUnitsToFloatPixels(aStartBevelOffset
, aAppUnitsPerDevPixel
);
3330 switch (aStartBevelSide
) {
3332 poly
[0].x
+= startBevelOffset
;
3335 poly
[3].x
+= startBevelOffset
;
3338 poly
[1].y
+= startBevelOffset
;
3341 poly
[0].y
+= startBevelOffset
;
3344 Float endBevelOffset
=
3345 NSAppUnitsToFloatPixels(aEndBevelOffset
, aAppUnitsPerDevPixel
);
3346 switch (aEndBevelSide
) {
3348 poly
[1].x
-= endBevelOffset
;
3351 poly
[2].x
-= endBevelOffset
;
3354 poly
[2].y
-= endBevelOffset
;
3357 poly
[3].y
-= endBevelOffset
;
3360 RefPtr
<PathBuilder
> builder
= aDrawTarget
.CreatePathBuilder();
3361 builder
->MoveTo(poly
[0]);
3362 builder
->LineTo(poly
[1]);
3363 builder
->LineTo(poly
[2]);
3364 builder
->LineTo(poly
[3]);
3366 RefPtr
<Path
> path
= builder
->Finish();
3367 aDrawTarget
.Fill(path
, color
, drawOptions
);
3371 static void GetDashInfo(nscoord aBorderLength
, nscoord aDashLength
,
3372 nscoord aOneDevPixel
, int32_t& aNumDashSpaces
,
3373 nscoord
& aStartDashLength
, nscoord
& aEndDashLength
) {
3375 if (aStartDashLength
+ aDashLength
+ aEndDashLength
>= aBorderLength
) {
3376 aStartDashLength
= aBorderLength
;
3380 (aBorderLength
- aDashLength
) / (2 * aDashLength
); // round down
3381 nscoord extra
= aBorderLength
- aStartDashLength
- aEndDashLength
-
3382 (((2 * aNumDashSpaces
) - 1) * aDashLength
);
3384 nscoord half
= RoundIntToPixel(extra
/ 2, aOneDevPixel
);
3385 aStartDashLength
+= half
;
3386 aEndDashLength
+= (extra
- half
);
3391 void nsCSSRendering::DrawTableBorderSegment(
3392 DrawTarget
& aDrawTarget
, StyleBorderStyle aBorderStyle
,
3393 nscolor aBorderColor
, const nsRect
& aBorder
, int32_t aAppUnitsPerDevPixel
,
3394 mozilla::Side aStartBevelSide
, nscoord aStartBevelOffset
,
3395 mozilla::Side aEndBevelSide
, nscoord aEndBevelOffset
) {
3397 ((eSideTop
== aStartBevelSide
) || (eSideBottom
== aStartBevelSide
));
3398 nscoord oneDevPixel
= NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel
);
3400 if ((oneDevPixel
>= aBorder
.width
) || (oneDevPixel
>= aBorder
.height
) ||
3401 (StyleBorderStyle::Dashed
== aBorderStyle
) ||
3402 (StyleBorderStyle::Dotted
== aBorderStyle
)) {
3403 // no beveling for 1 pixel border, dash or dot
3404 aStartBevelOffset
= 0;
3405 aEndBevelOffset
= 0;
3408 switch (aBorderStyle
) {
3409 case StyleBorderStyle::None
:
3410 case StyleBorderStyle::Hidden
:
3411 // NS_ASSERTION(false, "style of none or hidden");
3413 case StyleBorderStyle::Dotted
:
3414 case StyleBorderStyle::Dashed
: {
3415 nscoord dashLength
=
3416 (StyleBorderStyle::Dashed
== aBorderStyle
) ? DASH_LENGTH
: DOT_LENGTH
;
3417 // make the dash length proportional to the border thickness
3418 dashLength
*= (horizontal
) ? aBorder
.height
: aBorder
.width
;
3419 // make the min dash length for the ends 1/2 the dash length
3420 nscoord minDashLength
=
3421 (StyleBorderStyle::Dashed
== aBorderStyle
)
3422 ? RoundFloatToPixel(((float)dashLength
) / 2.0f
,
3423 aAppUnitsPerDevPixel
)
3425 minDashLength
= std::max(minDashLength
, oneDevPixel
);
3426 nscoord numDashSpaces
= 0;
3427 nscoord startDashLength
= minDashLength
;
3428 nscoord endDashLength
= minDashLength
;
3430 GetDashInfo(aBorder
.width
, dashLength
, aAppUnitsPerDevPixel
,
3431 numDashSpaces
, startDashLength
, endDashLength
);
3432 nsRect
rect(aBorder
.x
, aBorder
.y
, startDashLength
, aBorder
.height
);
3433 DrawSolidBorderSegment(aDrawTarget
, rect
, aBorderColor
,
3434 aAppUnitsPerDevPixel
);
3436 rect
.x
+= startDashLength
+ dashLength
;
3438 aBorder
.width
- (startDashLength
+ endDashLength
+ dashLength
);
3439 DrawDashedSegment(aDrawTarget
, rect
, dashLength
, aBorderColor
,
3440 aAppUnitsPerDevPixel
, horizontal
);
3442 rect
.x
+= rect
.width
;
3443 rect
.width
= endDashLength
;
3444 DrawSolidBorderSegment(aDrawTarget
, rect
, aBorderColor
,
3445 aAppUnitsPerDevPixel
);
3447 GetDashInfo(aBorder
.height
, dashLength
, aAppUnitsPerDevPixel
,
3448 numDashSpaces
, startDashLength
, endDashLength
);
3449 nsRect
rect(aBorder
.x
, aBorder
.y
, aBorder
.width
, startDashLength
);
3450 DrawSolidBorderSegment(aDrawTarget
, rect
, aBorderColor
,
3451 aAppUnitsPerDevPixel
);
3453 rect
.y
+= rect
.height
+ dashLength
;
3455 aBorder
.height
- (startDashLength
+ endDashLength
+ dashLength
);
3456 DrawDashedSegment(aDrawTarget
, rect
, dashLength
, aBorderColor
,
3457 aAppUnitsPerDevPixel
, horizontal
);
3459 rect
.y
+= rect
.height
;
3460 rect
.height
= endDashLength
;
3461 DrawSolidBorderSegment(aDrawTarget
, rect
, aBorderColor
,
3462 aAppUnitsPerDevPixel
);
3466 AutoTArray
<SolidBeveledBorderSegment
, 3> segments
;
3467 GetTableBorderSolidSegments(
3468 segments
, aBorderStyle
, aBorderColor
, aBorder
, aAppUnitsPerDevPixel
,
3469 aStartBevelSide
, aStartBevelOffset
, aEndBevelSide
, aEndBevelOffset
);
3470 for (const auto& segment
: segments
) {
3471 DrawSolidBorderSegment(
3472 aDrawTarget
, segment
.mRect
, segment
.mColor
, aAppUnitsPerDevPixel
,
3473 segment
.mStartBevel
.mSide
, segment
.mStartBevel
.mOffset
,
3474 segment
.mEndBevel
.mSide
, segment
.mEndBevel
.mOffset
);
3480 void nsCSSRendering::GetTableBorderSolidSegments(
3481 nsTArray
<SolidBeveledBorderSegment
>& aSegments
,
3482 StyleBorderStyle aBorderStyle
, nscolor aBorderColor
, const nsRect
& aBorder
,
3483 int32_t aAppUnitsPerDevPixel
, mozilla::Side aStartBevelSide
,
3484 nscoord aStartBevelOffset
, mozilla::Side aEndBevelSide
,
3485 nscoord aEndBevelOffset
) {
3486 const bool horizontal
=
3487 eSideTop
== aStartBevelSide
|| eSideBottom
== aStartBevelSide
;
3488 const nscoord oneDevPixel
= NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel
);
3490 switch (aBorderStyle
) {
3491 case StyleBorderStyle::None
:
3492 case StyleBorderStyle::Hidden
:
3494 case StyleBorderStyle::Dotted
:
3495 case StyleBorderStyle::Dashed
:
3496 MOZ_ASSERT_UNREACHABLE("Caller should have checked");
3498 case StyleBorderStyle::Groove
:
3499 case StyleBorderStyle::Ridge
:
3500 if ((horizontal
&& (oneDevPixel
>= aBorder
.height
)) ||
3501 (!horizontal
&& (oneDevPixel
>= aBorder
.width
))) {
3502 aSegments
.AppendElement(
3503 SolidBeveledBorderSegment
{aBorder
,
3505 {aStartBevelSide
, aStartBevelOffset
},
3506 {aEndBevelSide
, aEndBevelOffset
}});
3508 nscoord startBevel
=
3509 (aStartBevelOffset
> 0)
3510 ? RoundFloatToPixel(0.5f
* (float)aStartBevelOffset
,
3511 aAppUnitsPerDevPixel
, true)
3514 (aEndBevelOffset
> 0)
3515 ? RoundFloatToPixel(0.5f
* (float)aEndBevelOffset
,
3516 aAppUnitsPerDevPixel
, true)
3518 mozilla::Side ridgeGrooveSide
= (horizontal
) ? eSideTop
: eSideLeft
;
3519 // FIXME: In theory, this should use the visited-dependent
3520 // background color, but I don't care.
3521 nscolor bevelColor
=
3522 MakeBevelColor(ridgeGrooveSide
, aBorderStyle
, aBorderColor
);
3523 nsRect
rect(aBorder
);
3525 if (horizontal
) { // top, bottom
3526 half
= RoundFloatToPixel(0.5f
* (float)aBorder
.height
,
3527 aAppUnitsPerDevPixel
);
3529 if (eSideTop
== aStartBevelSide
) {
3530 rect
.x
+= startBevel
;
3531 rect
.width
-= startBevel
;
3533 if (eSideTop
== aEndBevelSide
) {
3534 rect
.width
-= endBevel
;
3536 aSegments
.AppendElement(
3537 SolidBeveledBorderSegment
{rect
,
3539 {aStartBevelSide
, startBevel
},
3540 {aEndBevelSide
, endBevel
}});
3541 } else { // left, right
3542 half
= RoundFloatToPixel(0.5f
* (float)aBorder
.width
,
3543 aAppUnitsPerDevPixel
);
3545 if (eSideLeft
== aStartBevelSide
) {
3546 rect
.y
+= startBevel
;
3547 rect
.height
-= startBevel
;
3549 if (eSideLeft
== aEndBevelSide
) {
3550 rect
.height
-= endBevel
;
3552 aSegments
.AppendElement(
3553 SolidBeveledBorderSegment
{rect
,
3555 {aStartBevelSide
, startBevel
},
3556 {aEndBevelSide
, endBevel
}});
3561 (eSideTop
== ridgeGrooveSide
) ? eSideBottom
: eSideRight
;
3562 // FIXME: In theory, this should use the visited-dependent
3563 // background color, but I don't care.
3565 MakeBevelColor(ridgeGrooveSide
, aBorderStyle
, aBorderColor
);
3567 rect
.y
= rect
.y
+ half
;
3568 rect
.height
= aBorder
.height
- half
;
3569 if (eSideBottom
== aStartBevelSide
) {
3570 rect
.x
+= startBevel
;
3571 rect
.width
-= startBevel
;
3573 if (eSideBottom
== aEndBevelSide
) {
3574 rect
.width
-= endBevel
;
3576 aSegments
.AppendElement(
3577 SolidBeveledBorderSegment
{rect
,
3579 {aStartBevelSide
, startBevel
},
3580 {aEndBevelSide
, endBevel
}});
3582 rect
.x
= rect
.x
+ half
;
3583 rect
.width
= aBorder
.width
- half
;
3584 if (eSideRight
== aStartBevelSide
) {
3585 rect
.y
+= aStartBevelOffset
- startBevel
;
3586 rect
.height
-= startBevel
;
3588 if (eSideRight
== aEndBevelSide
) {
3589 rect
.height
-= endBevel
;
3591 aSegments
.AppendElement(
3592 SolidBeveledBorderSegment
{rect
,
3594 {aStartBevelSide
, startBevel
},
3595 {aEndBevelSide
, endBevel
}});
3599 case StyleBorderStyle::Double
:
3600 // We can only do "double" borders if the thickness of the border
3601 // is more than 2px. Otherwise, we fall through to painting a
3603 if ((aBorder
.width
> 2 * oneDevPixel
|| horizontal
) &&
3604 (aBorder
.height
> 2 * oneDevPixel
|| !horizontal
)) {
3605 nscoord startBevel
=
3606 (aStartBevelOffset
> 0)
3607 ? RoundFloatToPixel(0.333333f
* (float)aStartBevelOffset
,
3608 aAppUnitsPerDevPixel
)
3611 (aEndBevelOffset
> 0)
3612 ? RoundFloatToPixel(0.333333f
* (float)aEndBevelOffset
,
3613 aAppUnitsPerDevPixel
)
3615 if (horizontal
) { // top, bottom
3616 nscoord thirdHeight
= RoundFloatToPixel(
3617 0.333333f
* (float)aBorder
.height
, aAppUnitsPerDevPixel
);
3619 // draw the top line or rect
3620 nsRect
topRect(aBorder
.x
, aBorder
.y
, aBorder
.width
, thirdHeight
);
3621 if (eSideTop
== aStartBevelSide
) {
3622 topRect
.x
+= aStartBevelOffset
- startBevel
;
3623 topRect
.width
-= aStartBevelOffset
- startBevel
;
3625 if (eSideTop
== aEndBevelSide
) {
3626 topRect
.width
-= aEndBevelOffset
- endBevel
;
3629 aSegments
.AppendElement(
3630 SolidBeveledBorderSegment
{topRect
,
3632 {aStartBevelSide
, startBevel
},
3633 {aEndBevelSide
, endBevel
}});
3635 // draw the botom line or rect
3636 nscoord heightOffset
= aBorder
.height
- thirdHeight
;
3637 nsRect
bottomRect(aBorder
.x
, aBorder
.y
+ heightOffset
, aBorder
.width
,
3638 aBorder
.height
- heightOffset
);
3639 if (eSideBottom
== aStartBevelSide
) {
3640 bottomRect
.x
+= aStartBevelOffset
- startBevel
;
3641 bottomRect
.width
-= aStartBevelOffset
- startBevel
;
3643 if (eSideBottom
== aEndBevelSide
) {
3644 bottomRect
.width
-= aEndBevelOffset
- endBevel
;
3646 aSegments
.AppendElement(
3647 SolidBeveledBorderSegment
{bottomRect
,
3649 {aStartBevelSide
, startBevel
},
3650 {aEndBevelSide
, endBevel
}});
3651 } else { // left, right
3652 nscoord thirdWidth
= RoundFloatToPixel(
3653 0.333333f
* (float)aBorder
.width
, aAppUnitsPerDevPixel
);
3655 nsRect
leftRect(aBorder
.x
, aBorder
.y
, thirdWidth
, aBorder
.height
);
3656 if (eSideLeft
== aStartBevelSide
) {
3657 leftRect
.y
+= aStartBevelOffset
- startBevel
;
3658 leftRect
.height
-= aStartBevelOffset
- startBevel
;
3660 if (eSideLeft
== aEndBevelSide
) {
3661 leftRect
.height
-= aEndBevelOffset
- endBevel
;
3664 aSegments
.AppendElement(
3665 SolidBeveledBorderSegment
{leftRect
,
3667 {aStartBevelSide
, startBevel
},
3668 {aEndBevelSide
, endBevel
}});
3670 nscoord widthOffset
= aBorder
.width
- thirdWidth
;
3671 nsRect
rightRect(aBorder
.x
+ widthOffset
, aBorder
.y
,
3672 aBorder
.width
- widthOffset
, aBorder
.height
);
3673 if (eSideRight
== aStartBevelSide
) {
3674 rightRect
.y
+= aStartBevelOffset
- startBevel
;
3675 rightRect
.height
-= aStartBevelOffset
- startBevel
;
3677 if (eSideRight
== aEndBevelSide
) {
3678 rightRect
.height
-= aEndBevelOffset
- endBevel
;
3680 aSegments
.AppendElement(
3681 SolidBeveledBorderSegment
{rightRect
,
3683 {aStartBevelSide
, startBevel
},
3684 {aEndBevelSide
, endBevel
}});
3688 // else fall through to solid
3690 case StyleBorderStyle::Solid
:
3691 aSegments
.AppendElement(
3692 SolidBeveledBorderSegment
{aBorder
,
3694 {aStartBevelSide
, aStartBevelOffset
},
3695 {aEndBevelSide
, aEndBevelOffset
}});
3697 case StyleBorderStyle::Outset
:
3698 case StyleBorderStyle::Inset
:
3699 MOZ_ASSERT_UNREACHABLE(
3700 "inset, outset should have been converted to groove, ridge");
3705 // End table border-collapsing section
3707 Rect
nsCSSRendering::ExpandPaintingRectForDecorationLine(
3708 nsIFrame
* aFrame
, const StyleTextDecorationStyle aStyle
,
3709 const Rect
& aClippedRect
, const Float aICoordInFrame
,
3710 const Float aCycleLength
, bool aVertical
) {
3712 case StyleTextDecorationStyle::Dotted
:
3713 case StyleTextDecorationStyle::Dashed
:
3714 case StyleTextDecorationStyle::Wavy
:
3717 NS_ERROR("Invalid style was specified");
3718 return aClippedRect
;
3721 nsBlockFrame
* block
= nullptr;
3722 // Note that when we paint the decoration lines in relative positioned
3723 // box, we should paint them like all of the boxes are positioned as static.
3724 nscoord framePosInBlockAppUnits
= 0;
3725 for (nsIFrame
* f
= aFrame
; f
; f
= f
->GetParent()) {
3726 block
= do_QueryFrame(f
);
3730 framePosInBlockAppUnits
+=
3731 aVertical
? f
->GetNormalPosition().y
: f
->GetNormalPosition().x
;
3734 NS_ENSURE_TRUE(block
, aClippedRect
);
3736 nsPresContext
* pc
= aFrame
->PresContext();
3737 Float framePosInBlock
=
3738 Float(pc
->AppUnitsToGfxUnits(framePosInBlockAppUnits
));
3739 int32_t rectPosInBlock
= int32_t(NS_round(framePosInBlock
+ aICoordInFrame
));
3740 int32_t extraStartEdge
=
3741 rectPosInBlock
- (rectPosInBlock
/ int32_t(aCycleLength
) * aCycleLength
);
3742 Rect
rect(aClippedRect
);
3744 rect
.y
-= extraStartEdge
;
3745 rect
.height
+= extraStartEdge
;
3747 rect
.x
-= extraStartEdge
;
3748 rect
.width
+= extraStartEdge
;
3753 // Converts a GfxFont to an SkFont
3754 // Either returns true if it was successful, or false if something went wrong
3755 static bool GetSkFontFromGfxFont(DrawTarget
& aDrawTarget
, gfxFont
* aFont
,
3757 RefPtr
<ScaledFont
> scaledFont
= aFont
->GetScaledFont(&aDrawTarget
);
3762 ScaledFontBase
* fontBase
= static_cast<ScaledFontBase
*>(scaledFont
.get());
3764 SkTypeface
* typeface
= fontBase
->GetSkTypeface();
3769 aSkFont
= SkFont(sk_ref_sp(typeface
), SkFloatToScalar(fontBase
->GetSize()));
3773 // Computes data used to position the decoration line within a
3774 // SkTextBlob, data is returned through aBounds
3775 static void GetPositioning(
3776 const nsCSSRendering::PaintDecorationLineParams
& aParams
, const Rect
& aRect
,
3777 Float aOneCSSPixel
, Float aCenterBaselineOffset
, SkScalar aBounds
[]) {
3779 * How Positioning in Skia Works
3780 * Take the letter "n" for example
3781 * We set textPos as 0, 0
3782 * This is represented in Skia like so (not to scale)
3788 * (0,0) ----------------------->
3794 * 0 on the x axis is a line that touches the bottom of the n
3795 * (0,0) is the bottom left-hand corner of the n character
3796 * Moving "up" from the n is going in a negative y direction
3797 * Moving "down" from the n is going in a positive y direction
3799 * The intercepts that are returned in this arrangement will be
3800 * offset by the original point it starts at. (This happens in
3801 * the SkipInk function below).
3803 * In Skia, text MUST be laid out such that the next character
3804 * in the RunBuffer is further along the x-axis than the previous
3805 * character, otherwise there is undefined/strange behavior.
3808 Float rectThickness
= aParams
.vertical
? aRect
.Width() : aRect
.Height();
3810 // the upper and lower lines/edges of the under or over line
3811 SkScalar upperLine
, lowerLine
;
3812 if (aParams
.decoration
== mozilla::StyleTextDecorationLine::OVERLINE
) {
3814 -aParams
.offset
+ aParams
.defaultLineThickness
- aCenterBaselineOffset
;
3815 upperLine
= lowerLine
- rectThickness
;
3817 // underlines in vertical text are offset from the center of
3818 // the text, and not the baseline
3819 // Skia sets the text at it's baseline so we have to offset it
3820 // for text in vertical-* writing modes
3821 upperLine
= -aParams
.offset
- aCenterBaselineOffset
;
3822 lowerLine
= upperLine
+ rectThickness
;
3825 // set up the bounds, add in a little padding to the thickness of the line
3826 // (unless the line is <= 1 CSS pixel thick)
3827 Float lineThicknessPadding
= aParams
.lineSize
.height
> aOneCSSPixel
3828 ? 0.25f
* aParams
.lineSize
.height
3830 // don't allow padding greater than 0.75 CSS pixel
3831 lineThicknessPadding
= std::min(lineThicknessPadding
, 0.75f
* aOneCSSPixel
);
3832 aBounds
[0] = upperLine
- lineThicknessPadding
;
3833 aBounds
[1] = lowerLine
+ lineThicknessPadding
;
3836 // positions an individual glyph according to the given offset
3837 static SkPoint
GlyphPosition(const gfxTextRun::DetailedGlyph
& aGlyph
,
3838 const SkPoint
& aTextPos
,
3839 int32_t aAppUnitsPerDevPixel
) {
3840 SkPoint point
= {aGlyph
.mOffset
.x
, aGlyph
.mOffset
.y
};
3842 // convert to device pixels
3843 point
.fX
/= (float)aAppUnitsPerDevPixel
;
3844 point
.fY
/= (float)aAppUnitsPerDevPixel
;
3847 point
.fX
+= aTextPos
.fX
;
3848 point
.fY
+= aTextPos
.fY
;
3852 // returns a count of all the glyphs that will be rendered
3853 // excludes ligature continuations, includes the number of individual
3854 // glyph records. This includes the number of DetailedGlyphs that a single
3855 // CompressedGlyph record points to. This function is necessary because Skia
3856 // needs the total length of glyphs to add to it's run buffer before it creates
3857 // the RunBuffer object, and this cannot be resized later.
3858 static uint32_t CountAllGlyphs(
3859 const gfxTextRun
* aTextRun
,
3860 const gfxTextRun::CompressedGlyph
* aCompressedGlyph
, uint32_t aStringStart
,
3861 uint32_t aStringEnd
) {
3862 uint32_t totalGlyphCount
= 0;
3864 for (const gfxTextRun::CompressedGlyph
* cg
= aCompressedGlyph
+ aStringStart
;
3865 cg
< aCompressedGlyph
+ aStringEnd
; ++cg
) {
3866 totalGlyphCount
+= cg
->IsSimpleGlyph() ? 1 : cg
->GetGlyphCount();
3869 return totalGlyphCount
;
3872 static void AddDetailedGlyph(const SkTextBlobBuilder::RunBuffer
& aRunBuffer
,
3873 const gfxTextRun::DetailedGlyph
& aGlyph
,
3874 int aIndex
, float aAppUnitsPerDevPixel
,
3875 SkPoint
& aTextPos
) {
3876 // add glyph ID to the run buffer at i
3877 aRunBuffer
.glyphs
[aIndex
] = aGlyph
.mGlyphID
;
3879 // position the glyph correctly using the detailed offsets
3880 SkPoint position
= GlyphPosition(aGlyph
, aTextPos
, aAppUnitsPerDevPixel
);
3881 aRunBuffer
.pos
[2 * aIndex
] = position
.fX
;
3882 aRunBuffer
.pos
[(2 * aIndex
) + 1] = position
.fY
;
3884 // increase aTextPos.fx by the advance
3885 aTextPos
.fX
+= ((float)aGlyph
.mAdvance
/ aAppUnitsPerDevPixel
);
3888 static void AddSimpleGlyph(const SkTextBlobBuilder::RunBuffer
& aRunBuffer
,
3889 const gfxTextRun::CompressedGlyph
& aGlyph
,
3890 int aIndex
, float aAppUnitsPerDevPixel
,
3891 SkPoint
& aTextPos
) {
3892 aRunBuffer
.glyphs
[aIndex
] = aGlyph
.GetSimpleGlyph();
3894 // simple glyphs are offset from 0, so we'll just use textPos
3895 aRunBuffer
.pos
[2 * aIndex
] = aTextPos
.fX
;
3896 aRunBuffer
.pos
[(2 * aIndex
) + 1] = aTextPos
.fY
;
3898 // increase aTextPos.fX by the advance
3899 aTextPos
.fX
+= ((float)aGlyph
.GetSimpleAdvance() / aAppUnitsPerDevPixel
);
3902 // Sets up a Skia TextBlob of the specified font, text position, and made up of
3903 // the glyphs between aStringStart and aStringEnd. Handles RTL and LTR text
3904 // and positions each glyph within the text blob
3905 static sk_sp
<const SkTextBlob
> CreateTextBlob(
3906 const gfxTextRun
* aTextRun
,
3907 const gfxTextRun::CompressedGlyph
* aCompressedGlyph
, const SkFont
& aFont
,
3908 const gfxTextRun::PropertyProvider::Spacing
* aSpacing
,
3909 uint32_t aStringStart
, uint32_t aStringEnd
, float aAppUnitsPerDevPixel
,
3910 SkPoint
& aTextPos
, int32_t& aSpacingOffset
) {
3911 // allocate space for the run buffer, then fill it with the glyphs
3913 CountAllGlyphs(aTextRun
, aCompressedGlyph
, aStringStart
, aStringEnd
);
3918 SkTextBlobBuilder builder
;
3919 const SkTextBlobBuilder::RunBuffer
& run
= builder
.allocRunPos(aFont
, len
);
3921 // RTL text should be read in by glyph starting at aStringEnd - 1 down until
3923 bool isRTL
= aTextRun
->IsRightToLeft();
3924 uint32_t currIndex
= isRTL
? aStringEnd
- 1 : aStringStart
; // textRun index
3925 // currIndex will be advanced by |step| until it reaches |limit|, which is the
3926 // final index to be handled (NOT one beyond the final index)
3927 int step
= isRTL
? -1 : 1;
3928 uint32_t limit
= isRTL
? aStringStart
: aStringEnd
- 1;
3930 uint32_t i
= 0; // index into the SkTextBlob we're building
3932 // Loop exit test is below, just before we update currIndex.
3934 isRTL
? aSpacing
[aSpacingOffset
].mAfter
/ aAppUnitsPerDevPixel
3935 : aSpacing
[aSpacingOffset
].mBefore
/ aAppUnitsPerDevPixel
;
3937 if (aCompressedGlyph
[currIndex
].IsSimpleGlyph()) {
3938 MOZ_ASSERT(i
< len
, "glyph count error!");
3939 AddSimpleGlyph(run
, aCompressedGlyph
[currIndex
], i
, aAppUnitsPerDevPixel
,
3943 // if it's detailed, potentially add multiple into run.glyphs
3944 uint32_t count
= aCompressedGlyph
[currIndex
].GetGlyphCount();
3946 gfxTextRun::DetailedGlyph
* detailGlyph
=
3947 aTextRun
->GetDetailedGlyphs(currIndex
);
3948 for (uint32_t d
= isRTL
? count
- 1 : 0; count
; count
--, d
+= step
) {
3949 MOZ_ASSERT(i
< len
, "glyph count error!");
3950 AddDetailedGlyph(run
, detailGlyph
[d
], i
, aAppUnitsPerDevPixel
,
3956 aTextPos
.fX
+= isRTL
3957 ? aSpacing
[aSpacingOffset
].mBefore
/ aAppUnitsPerDevPixel
3958 : aSpacing
[aSpacingOffset
].mAfter
/ aAppUnitsPerDevPixel
;
3959 aSpacingOffset
+= step
;
3961 if (currIndex
== limit
) {
3967 MOZ_ASSERT(i
== len
, "glyph count error!");
3969 return builder
.make();
3972 // Given a TextBlob, the bounding lines, and the set of current intercepts this
3973 // function adds the intercepts for the current TextBlob into the given set of
3974 // previoulsy calculated intercepts. This set is either of length 0, or a
3975 // multiple of 2 (since every intersection with a piece of text results in two
3976 // intercepts: entering/exiting)
3977 static void GetTextIntercepts(const sk_sp
<const SkTextBlob
>& aBlob
,
3978 const SkScalar aBounds
[],
3979 nsTArray
<SkScalar
>& aIntercepts
) {
3980 // It's possible that we'll encounter a Windows exception deep inside
3981 // Skia's DirectWrite code while trying to get the intercepts. To avoid
3982 // crashing in this case, catch any such exception here and discard the
3983 // newly-added (and incompletely filled in) elements.
3986 // https://skia.org/user/api/SkTextBlob_Reference#Text_Blob_Text_Intercepts
3987 count
= aBlob
->getIntercepts(aBounds
, nullptr);
3991 aBlob
->getIntercepts(aBounds
, aIntercepts
.AppendElements(count
));
3993 MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER
) {
3994 gfxCriticalNote
<< "Exception occurred getting text intercepts";
3995 aIntercepts
.TruncateLength(aIntercepts
.Length() - count
);
3999 // This function, given a set of intercepts that represent each intersection
4000 // between an under/overline and text, makes a series of calls to
4001 // PaintDecorationLineInternal that paints a series of clip rects which
4002 // implement the text-decoration-skip-ink property
4003 // Logic for where to place each clipped rect, and the length of each rect is
4005 static void SkipInk(nsIFrame
* aFrame
, DrawTarget
& aDrawTarget
,
4006 const nsCSSRendering::PaintDecorationLineParams
& aParams
,
4007 const nsTArray
<SkScalar
>& aIntercepts
, Float aPadding
,
4009 nsCSSRendering::PaintDecorationLineParams clipParams
= aParams
;
4010 int length
= aIntercepts
.Length();
4012 SkScalar startIntercept
= 0;
4013 SkScalar endIntercept
= 0;
4015 // keep track of the direction we are drawing the clipped rects in
4016 // for sideways text, our intercepts from the first glyph are actually
4017 // decreasing (towards the top edge of the page), so we use a negative
4020 Float lineStart
= aParams
.vertical
? aParams
.pt
.y
: aParams
.pt
.x
;
4021 Float lineEnd
= lineStart
+ aParams
.lineSize
.width
;
4022 if (aParams
.sidewaysLeft
) {
4024 std::swap(lineStart
, lineEnd
);
4027 for (int i
= 0; i
<= length
; i
+= 2) {
4028 // handle start/end edge cases and set up general case
4029 startIntercept
= (i
> 0) ? (dir
* aIntercepts
[i
- 1]) + lineStart
4030 : lineStart
- (dir
* aPadding
);
4031 endIntercept
= (i
< length
) ? (dir
* aIntercepts
[i
]) + lineStart
4032 : lineEnd
+ (dir
* aPadding
);
4034 // remove padding at both ends for width
4035 // the start of the line is calculated so the padding removes just
4036 // enough so that the line starts at its normal position
4037 clipParams
.lineSize
.width
=
4038 (dir
* (endIntercept
- startIntercept
)) - (2.0 * aPadding
);
4040 // Don't draw decoration lines that have a smaller width than 1, or half
4041 // the line-end padding dimension.
4042 if (clipParams
.lineSize
.width
< std::max(aPadding
* 0.5, 1.0)) {
4046 // Start the line right after the intercept's location plus room for
4047 // padding; snap the rect edges to device pixels for consistent rendering
4048 // of dots across separate fragments of a dotted line.
4049 if (aParams
.vertical
) {
4050 clipParams
.pt
.y
= aParams
.sidewaysLeft
? endIntercept
+ aPadding
4051 : startIntercept
+ aPadding
;
4052 aRect
.y
= std::floor(clipParams
.pt
.y
+ 0.5);
4053 aRect
.SetBottomEdge(
4054 std::floor(clipParams
.pt
.y
+ clipParams
.lineSize
.width
+ 0.5));
4056 clipParams
.pt
.x
= startIntercept
+ aPadding
;
4057 aRect
.x
= std::floor(clipParams
.pt
.x
+ 0.5);
4059 std::floor(clipParams
.pt
.x
+ clipParams
.lineSize
.width
+ 0.5));
4062 nsCSSRendering::PaintDecorationLineInternal(aFrame
, aDrawTarget
, clipParams
,
4067 void nsCSSRendering::PaintDecorationLine(
4068 nsIFrame
* aFrame
, DrawTarget
& aDrawTarget
,
4069 const PaintDecorationLineParams
& aParams
) {
4070 NS_ASSERTION(aParams
.style
!= StyleTextDecorationStyle::None
,
4073 Rect rect
= ToRect(GetTextDecorationRectInternal(aParams
.pt
, aParams
));
4074 if (rect
.IsEmpty() || !rect
.Intersects(aParams
.dirtyRect
)) {
4078 if (aParams
.decoration
!= StyleTextDecorationLine::UNDERLINE
&&
4079 aParams
.decoration
!= StyleTextDecorationLine::OVERLINE
&&
4080 aParams
.decoration
!= StyleTextDecorationLine::LINE_THROUGH
) {
4081 MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
4085 // Check if decoration line will skip past ascenders/descenders
4086 // text-decoration-skip-ink only applies to overlines/underlines
4087 mozilla::StyleTextDecorationSkipInk skipInk
=
4088 aFrame
->StyleText()->mTextDecorationSkipInk
;
4089 bool skipInkEnabled
=
4090 skipInk
!= mozilla::StyleTextDecorationSkipInk::None
&&
4091 aParams
.decoration
!= StyleTextDecorationLine::LINE_THROUGH
;
4093 if (!skipInkEnabled
|| aParams
.glyphRange
.Length() == 0) {
4094 PaintDecorationLineInternal(aFrame
, aDrawTarget
, aParams
, rect
);
4098 // check if the frame is a text frame or not
4099 nsTextFrame
* textFrame
= nullptr;
4100 if (aFrame
->IsTextFrame()) {
4101 textFrame
= static_cast<nsTextFrame
*>(aFrame
);
4103 PaintDecorationLineInternal(aFrame
, aDrawTarget
, aParams
, rect
);
4107 // get text run and current text offset (for line wrapping)
4108 gfxTextRun
* textRun
=
4109 textFrame
->GetTextRun(nsTextFrame::TextRunType::eInflated
);
4111 // used for conversions from app units to device pixels
4112 int32_t appUnitsPerDevPixel
= aFrame
->PresContext()->AppUnitsPerDevPixel();
4114 // pointer to the array of glyphs for this TextRun
4115 gfxTextRun::CompressedGlyph
* characterGlyphs
= textRun
->GetCharacterGlyphs();
4117 // get positioning info
4118 SkPoint textPos
= {0, aParams
.baselineOffset
};
4119 SkScalar bounds
[] = {0, 0};
4120 Float oneCSSPixel
= aFrame
->PresContext()->CSSPixelsToDevPixels(1.0f
);
4121 if (!textRun
->UseCenterBaseline()) {
4122 GetPositioning(aParams
, rect
, oneCSSPixel
, 0, bounds
);
4125 // array for the text intercepts
4126 AutoTArray
<SkScalar
, 256> intercepts
;
4128 // array for spacing data
4129 AutoTArray
<gfxTextRun::PropertyProvider::Spacing
, 64> spacing
;
4130 spacing
.SetLength(aParams
.glyphRange
.Length());
4131 if (aParams
.provider
!= nullptr) {
4132 aParams
.provider
->GetSpacing(aParams
.glyphRange
, spacing
.Elements());
4135 // loop through each glyph run
4136 // in most cases there will only be one
4137 bool isRTL
= textRun
->IsRightToLeft();
4138 int32_t spacingOffset
= isRTL
? aParams
.glyphRange
.Length() - 1 : 0;
4139 gfxTextRun::GlyphRunIterator
iter(textRun
, aParams
.glyphRange
, isRTL
);
4141 // For any glyph run where we don't actually do skipping, we'll need to
4142 // advance the current position by its width.
4143 // (For runs we do process, CreateTextBlob will update the position.)
4144 auto currentGlyphRunAdvance
= [&]() {
4145 return textRun
->GetAdvanceWidth(
4146 gfxTextRun::Range(iter
.StringStart(), iter
.StringEnd()),
4148 appUnitsPerDevPixel
;
4151 for (; !iter
.AtEnd(); iter
.NextRun()) {
4152 if (iter
.GlyphRun()->mOrientation
==
4153 mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
||
4154 (iter
.GlyphRun()->mIsCJK
&&
4155 skipInk
== mozilla::StyleTextDecorationSkipInk::Auto
)) {
4156 // We don't support upright text in vertical modes currently
4157 // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1572294),
4158 // but we do need to update textPos so that following runs will be
4159 // correctly positioned.
4160 // We also don't apply skip-ink to CJK text runs because many fonts
4161 // have an underline that looks really bad if this is done
4162 // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1573249),
4163 // when skip-ink is set to 'auto'.
4164 textPos
.fX
+= currentGlyphRunAdvance();
4168 gfxFont
* font
= iter
.GlyphRun()->mFont
;
4169 // Don't try to apply skip-ink to 'sbix' fonts like Apple Color Emoji,
4170 // because old macOS (10.9) may crash trying to retrieve glyph paths
4171 // that don't exist.
4172 if (font
->GetFontEntry()->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) {
4173 textPos
.fX
+= currentGlyphRunAdvance();
4177 // get a Skia version of the glyph run's font
4179 if (!GetSkFontFromGfxFont(aDrawTarget
, font
, skiafont
)) {
4180 PaintDecorationLineInternal(aFrame
, aDrawTarget
, aParams
, rect
);
4184 // Create a text blob with correctly positioned glyphs. This also updates
4185 // textPos.fX with the advance of the glyphs.
4186 sk_sp
<const SkTextBlob
> textBlob
=
4187 CreateTextBlob(textRun
, characterGlyphs
, skiafont
, spacing
.Elements(),
4188 iter
.StringStart(), iter
.StringEnd(),
4189 (float)appUnitsPerDevPixel
, textPos
, spacingOffset
);
4192 textPos
.fX
+= currentGlyphRunAdvance();
4196 if (textRun
->UseCenterBaseline()) {
4197 // writing modes that use a center baseline need to be adjusted on a
4198 // font-by-font basis since Skia lines up the text on a alphabetic
4199 // baseline, but for some vertical-* writing modes the offset is from the
4201 gfxFont::Metrics metrics
= font
->GetMetrics(nsFontMetrics::eHorizontal
);
4202 Float centerToBaseline
= (metrics
.emAscent
- metrics
.emDescent
) / 2.0f
;
4203 GetPositioning(aParams
, rect
, oneCSSPixel
, centerToBaseline
, bounds
);
4206 // compute the text intercepts that need to be skipped
4207 GetTextIntercepts(textBlob
, bounds
, intercepts
);
4209 bool needsSkipInk
= intercepts
.Length() > 0;
4212 // Padding between glyph intercepts and the decoration line: we use the
4213 // decoration line thickness, clamped to a minimum of 1px and a maximum
4216 std::min(std::max(aParams
.lineSize
.height
, oneCSSPixel
),
4217 Float(textRun
->GetFontGroup()->GetStyle()->size
/ 5.0));
4218 SkipInk(aFrame
, aDrawTarget
, aParams
, intercepts
, padding
, rect
);
4220 PaintDecorationLineInternal(aFrame
, aDrawTarget
, aParams
, rect
);
4224 void nsCSSRendering::PaintDecorationLineInternal(
4225 nsIFrame
* aFrame
, DrawTarget
& aDrawTarget
,
4226 const PaintDecorationLineParams
& aParams
, Rect aRect
) {
4227 Float lineThickness
= std::max(NS_round(aParams
.lineSize
.height
), 1.0);
4229 DeviceColor color
= ToDeviceColor(aParams
.color
);
4230 ColorPattern
colorPat(color
);
4231 StrokeOptions
strokeOptions(lineThickness
);
4232 DrawOptions drawOptions
;
4236 AutoPopClips
autoPopClips(&aDrawTarget
);
4238 mozilla::layout::TextDrawTarget
* textDrawer
= nullptr;
4239 if (aDrawTarget
.GetBackendType() == BackendType::WEBRENDER_TEXT
) {
4240 textDrawer
= static_cast<mozilla::layout::TextDrawTarget
*>(&aDrawTarget
);
4243 switch (aParams
.style
) {
4244 case StyleTextDecorationStyle::Solid
:
4245 case StyleTextDecorationStyle::Double
:
4247 case StyleTextDecorationStyle::Dashed
: {
4248 autoPopClips
.PushClipRect(aRect
);
4249 Float dashWidth
= lineThickness
* DOT_LENGTH
* DASH_LENGTH
;
4250 dash
[0] = dashWidth
;
4251 dash
[1] = dashWidth
;
4252 strokeOptions
.mDashPattern
= dash
;
4253 strokeOptions
.mDashLength
= MOZ_ARRAY_LENGTH(dash
);
4254 strokeOptions
.mLineCap
= CapStyle::BUTT
;
4255 aRect
= ExpandPaintingRectForDecorationLine(
4256 aFrame
, aParams
.style
, aRect
, aParams
.icoordInFrame
, dashWidth
* 2,
4258 // We should continue to draw the last dash even if it is not in the rect.
4259 aRect
.width
+= dashWidth
;
4262 case StyleTextDecorationStyle::Dotted
: {
4263 autoPopClips
.PushClipRect(aRect
);
4264 Float dashWidth
= lineThickness
* DOT_LENGTH
;
4265 if (lineThickness
> 2.0) {
4267 dash
[1] = dashWidth
* 2.f
;
4268 strokeOptions
.mLineCap
= CapStyle::ROUND
;
4270 dash
[0] = dashWidth
;
4271 dash
[1] = dashWidth
;
4273 strokeOptions
.mDashPattern
= dash
;
4274 strokeOptions
.mDashLength
= MOZ_ARRAY_LENGTH(dash
);
4275 aRect
= ExpandPaintingRectForDecorationLine(
4276 aFrame
, aParams
.style
, aRect
, aParams
.icoordInFrame
, dashWidth
* 2,
4278 // We should continue to draw the last dot even if it is not in the rect.
4279 aRect
.width
+= dashWidth
;
4282 case StyleTextDecorationStyle::Wavy
:
4283 autoPopClips
.PushClipRect(aRect
);
4284 if (lineThickness
> 2.0) {
4285 drawOptions
.mAntialiasMode
= AntialiasMode::SUBPIXEL
;
4287 // Don't use anti-aliasing here. Because looks like lighter color wavy
4288 // line at this case. And probably, users don't think the
4289 // non-anti-aliased wavy line is not pretty.
4290 drawOptions
.mAntialiasMode
= AntialiasMode::NONE
;
4294 NS_ERROR("Invalid style value!");
4298 // The block-direction position should be set to the middle of the line.
4299 if (aParams
.vertical
) {
4300 aRect
.x
+= lineThickness
/ 2;
4302 aRect
.y
+= lineThickness
/ 2;
4305 switch (aParams
.style
) {
4306 case StyleTextDecorationStyle::Solid
:
4307 case StyleTextDecorationStyle::Dotted
:
4308 case StyleTextDecorationStyle::Dashed
: {
4309 Point p1
= aRect
.TopLeft();
4310 Point p2
= aParams
.vertical
? aRect
.BottomLeft() : aRect
.TopRight();
4312 textDrawer
->AppendDecoration(p1
, p2
, lineThickness
, aParams
.vertical
,
4313 color
, aParams
.style
);
4315 aDrawTarget
.StrokeLine(p1
, p2
, colorPat
, strokeOptions
, drawOptions
);
4319 case StyleTextDecorationStyle::Double
: {
4321 * We are drawing double line as:
4323 * +-------------------------------------------+
4324 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4325 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4326 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4329 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4330 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4331 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4332 * +-------------------------------------------+
4334 Point p1a
= aRect
.TopLeft();
4335 Point p2a
= aParams
.vertical
? aRect
.BottomLeft() : aRect
.TopRight();
4337 if (aParams
.vertical
) {
4338 aRect
.width
-= lineThickness
;
4340 aRect
.height
-= lineThickness
;
4343 Point p1b
= aParams
.vertical
? aRect
.TopRight() : aRect
.BottomLeft();
4344 Point p2b
= aRect
.BottomRight();
4347 textDrawer
->AppendDecoration(p1a
, p2a
, lineThickness
, aParams
.vertical
,
4348 color
, StyleTextDecorationStyle::Solid
);
4349 textDrawer
->AppendDecoration(p1b
, p2b
, lineThickness
, aParams
.vertical
,
4350 color
, StyleTextDecorationStyle::Solid
);
4352 aDrawTarget
.StrokeLine(p1a
, p2a
, colorPat
, strokeOptions
, drawOptions
);
4353 aDrawTarget
.StrokeLine(p1b
, p2b
, colorPat
, strokeOptions
, drawOptions
);
4357 case StyleTextDecorationStyle::Wavy
: {
4359 * We are drawing wavy line as:
4361 * P: Path, X: Painted pixel
4363 * +---------------------------------------+
4364 * XX|X XXXXXX XXXXXX |
4365 * PP|PX XPPPPPPX XPPPPPPX | ^
4366 * XX|XPX XPXXXXXXPX XPXXXXXXPX| |
4367 * | XPX XPX XPX XPX XP|X |adv
4368 * | XPXXXXXXPX XPXXXXXXPX X|PX |
4369 * | XPPPPPPX XPPPPPPX |XPX v
4370 * | XXXXXX XXXXXX | XX
4371 * +---------------------------------------+
4373 * adv flatLengthAtVertex rightMost
4375 * 1. Always starts from top-left of the drawing area, however, we need
4376 * to draw the line from outside of the rect. Because the start
4377 * point of the line is not good style if we draw from inside it.
4378 * 2. First, draw horizontal line from outside the rect to top-left of
4380 * 3. Goes down to bottom of the area at 45 degrees.
4381 * 4. Slides to right horizontaly, see |flatLengthAtVertex|.
4382 * 5. Goes up to top of the area at 45 degrees.
4383 * 6. Slides to right horizontaly.
4384 * 7. Repeat from 2 until reached to right-most edge of the area.
4386 * In the vertical case, swap horizontal and vertical coordinates and
4387 * directions in the above description.
4390 Float
& rectICoord
= aParams
.vertical
? aRect
.y
: aRect
.x
;
4391 Float
& rectISize
= aParams
.vertical
? aRect
.height
: aRect
.width
;
4392 const Float rectBSize
= aParams
.vertical
? aRect
.width
: aRect
.height
;
4394 const Float adv
= rectBSize
- lineThickness
;
4395 const Float flatLengthAtVertex
=
4396 std::max((lineThickness
- 1.0) * 2.0, 1.0);
4398 // Align the start of wavy lines to the nearest ancestor block.
4399 const Float cycleLength
= 2 * (adv
+ flatLengthAtVertex
);
4400 aRect
= ExpandPaintingRectForDecorationLine(
4401 aFrame
, aParams
.style
, aRect
, aParams
.icoordInFrame
, cycleLength
,
4405 // Undo attempted centering
4406 Float
& rectBCoord
= aParams
.vertical
? aRect
.x
: aRect
.y
;
4407 rectBCoord
-= lineThickness
/ 2;
4409 textDrawer
->AppendWavyDecoration(aRect
, lineThickness
, aParams
.vertical
,
4414 // figure out if we can trim whole cycles from the left and right edges
4415 // of the line, to try and avoid creating an unnecessarily long and
4416 // complex path (but don't do this for webrender, )
4417 const Float dirtyRectICoord
=
4418 aParams
.vertical
? aParams
.dirtyRect
.y
: aParams
.dirtyRect
.x
;
4419 int32_t skipCycles
= floor((dirtyRectICoord
- rectICoord
) / cycleLength
);
4420 if (skipCycles
> 0) {
4421 rectICoord
+= skipCycles
* cycleLength
;
4422 rectISize
-= skipCycles
* cycleLength
;
4425 rectICoord
+= lineThickness
/ 2.0;
4427 Point
pt(aRect
.TopLeft());
4428 Float
& ptICoord
= aParams
.vertical
? pt
.y
.value
: pt
.x
.value
;
4429 Float
& ptBCoord
= aParams
.vertical
? pt
.x
.value
: pt
.y
.value
;
4430 if (aParams
.vertical
) {
4433 Float iCoordLimit
= ptICoord
+ rectISize
+ lineThickness
;
4435 const Float dirtyRectIMost
= aParams
.vertical
? aParams
.dirtyRect
.YMost()
4436 : aParams
.dirtyRect
.XMost();
4437 skipCycles
= floor((iCoordLimit
- dirtyRectIMost
) / cycleLength
);
4438 if (skipCycles
> 0) {
4439 iCoordLimit
-= skipCycles
* cycleLength
;
4442 RefPtr
<PathBuilder
> builder
= aDrawTarget
.CreatePathBuilder();
4445 ptICoord
-= lineThickness
;
4446 builder
->MoveTo(pt
); // 1
4448 ptICoord
= rectICoord
;
4449 builder
->LineTo(pt
); // 2
4451 // In vertical mode, to go "down" relative to the text we need to
4452 // decrease the block coordinate, whereas in horizontal we increase
4453 // it. So the sense of this flag is effectively inverted.
4454 bool goDown
= !aParams
.vertical
;
4456 while (ptICoord
< iCoordLimit
) {
4457 if (++iter
> 1000) {
4458 // stroke the current path and start again, to avoid pathological
4459 // behavior in cairo with huge numbers of path segments
4460 path
= builder
->Finish();
4461 aDrawTarget
.Stroke(path
, colorPat
, strokeOptions
, drawOptions
);
4462 builder
= aDrawTarget
.CreatePathBuilder();
4463 builder
->MoveTo(pt
);
4467 ptBCoord
+= goDown
? adv
: -adv
;
4469 builder
->LineTo(pt
); // 3 and 5
4471 ptICoord
+= flatLengthAtVertex
;
4472 builder
->LineTo(pt
); // 4 and 6
4476 path
= builder
->Finish();
4477 aDrawTarget
.Stroke(path
, colorPat
, strokeOptions
, drawOptions
);
4481 NS_ERROR("Invalid style value!");
4485 Rect
nsCSSRendering::DecorationLineToPath(
4486 const PaintDecorationLineParams
& aParams
) {
4487 NS_ASSERTION(aParams
.style
!= StyleTextDecorationStyle::None
,
4490 Rect path
; // To benefit from RVO, we return this from all return points
4492 Rect rect
= ToRect(GetTextDecorationRectInternal(aParams
.pt
, aParams
));
4493 if (rect
.IsEmpty() || !rect
.Intersects(aParams
.dirtyRect
)) {
4497 if (aParams
.decoration
!= StyleTextDecorationLine::UNDERLINE
&&
4498 aParams
.decoration
!= StyleTextDecorationLine::OVERLINE
&&
4499 aParams
.decoration
!= StyleTextDecorationLine::LINE_THROUGH
) {
4500 MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
4504 if (aParams
.style
!= StyleTextDecorationStyle::Solid
) {
4505 // For the moment, we support only solid text decorations.
4509 Float lineThickness
= std::max(NS_round(aParams
.lineSize
.height
), 1.0);
4511 // The block-direction position should be set to the middle of the line.
4512 if (aParams
.vertical
) {
4513 rect
.x
+= lineThickness
/ 2;
4514 path
= Rect(rect
.TopLeft() - Point(lineThickness
/ 2, 0.0),
4515 Size(lineThickness
, rect
.Height()));
4517 rect
.y
+= lineThickness
/ 2;
4518 path
= Rect(rect
.TopLeft() - Point(0.0, lineThickness
/ 2),
4519 Size(rect
.Width(), lineThickness
));
4525 nsRect
nsCSSRendering::GetTextDecorationRect(
4526 nsPresContext
* aPresContext
, const DecorationRectParams
& aParams
) {
4527 NS_ASSERTION(aPresContext
, "aPresContext is null");
4528 NS_ASSERTION(aParams
.style
!= StyleTextDecorationStyle::None
,
4531 gfxRect rect
= GetTextDecorationRectInternal(Point(0, 0), aParams
);
4532 // The rect values are already rounded to nearest device pixels.
4534 r
.x
= aPresContext
->GfxUnitsToAppUnits(rect
.X());
4535 r
.y
= aPresContext
->GfxUnitsToAppUnits(rect
.Y());
4536 r
.width
= aPresContext
->GfxUnitsToAppUnits(rect
.Width());
4537 r
.height
= aPresContext
->GfxUnitsToAppUnits(rect
.Height());
4541 gfxRect
nsCSSRendering::GetTextDecorationRectInternal(
4542 const Point
& aPt
, const DecorationRectParams
& aParams
) {
4543 NS_ASSERTION(aParams
.style
<= StyleTextDecorationStyle::Wavy
,
4544 "Invalid aStyle value");
4546 if (aParams
.style
== StyleTextDecorationStyle::None
) {
4547 return gfxRect(0, 0, 0, 0);
4550 bool canLiftUnderline
= aParams
.descentLimit
>= 0.0;
4552 gfxFloat iCoord
= aParams
.vertical
? aPt
.y
: aPt
.x
;
4553 gfxFloat bCoord
= aParams
.vertical
? aPt
.x
: aPt
.y
;
4555 // 'left' and 'right' are relative to the line, so for vertical writing modes
4556 // they will actually become top and bottom of the rendered line.
4557 // Similarly, aLineSize.width and .height are actually length and thickness
4558 // of the line, which runs horizontally or vertically according to aVertical.
4559 const gfxFloat left
= floor(iCoord
+ 0.5),
4560 right
= floor(iCoord
+ aParams
.lineSize
.width
+ 0.5);
4562 // We compute |r| as if for a horizontal text run, and then swap vertical
4563 // and horizontal coordinates at the end if vertical was requested.
4564 gfxRect
r(left
, 0, right
- left
, 0);
4566 gfxFloat lineThickness
= NS_round(aParams
.lineSize
.height
);
4567 lineThickness
= std::max(lineThickness
, 1.0);
4568 gfxFloat defaultLineThickness
= NS_round(aParams
.defaultLineThickness
);
4569 defaultLineThickness
= std::max(defaultLineThickness
, 1.0);
4571 gfxFloat ascent
= NS_round(aParams
.ascent
);
4572 gfxFloat descentLimit
= floor(aParams
.descentLimit
);
4574 gfxFloat suggestedMaxRectHeight
=
4575 std::max(std::min(ascent
, descentLimit
), 1.0);
4576 r
.height
= lineThickness
;
4577 if (aParams
.style
== StyleTextDecorationStyle::Double
) {
4579 * We will draw double line as:
4581 * +-------------------------------------------+
4582 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4583 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4584 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4588 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4589 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4590 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4591 * +-------------------------------------------+
4593 gfxFloat gap
= NS_round(lineThickness
/ 2.0);
4594 gap
= std::max(gap
, 1.0);
4595 r
.height
= lineThickness
* 2.0 + gap
;
4596 if (canLiftUnderline
) {
4597 if (r
.Height() > suggestedMaxRectHeight
) {
4598 // Don't shrink the line height, because the thickness has some meaning.
4599 // We can just shrink the gap at this time.
4600 r
.height
= std::max(suggestedMaxRectHeight
, lineThickness
* 2.0 + 1.0);
4603 } else if (aParams
.style
== StyleTextDecorationStyle::Wavy
) {
4605 * We will draw wavy line as:
4607 * +-------------------------------------------+
4608 * |XXXXX XXXXXX XXXXXX | ^
4609 * |XXXXXX XXXXXXXX XXXXXXXX | | lineThickness
4610 * |XXXXXXX XXXXXXXXXX XXXXXXXXXX| v
4611 * | XXX XXX XXX XXX XX|
4612 * | XXXXXXXXXX XXXXXXXXXX X|
4613 * | XXXXXXXX XXXXXXXX |
4615 * +-------------------------------------------+
4617 r
.height
= lineThickness
> 2.0 ? lineThickness
* 4.0 : lineThickness
* 3.0;
4618 if (canLiftUnderline
) {
4619 if (r
.Height() > suggestedMaxRectHeight
) {
4620 // Don't shrink the line height even if there is not enough space,
4621 // because the thickness has some meaning. E.g., the 1px wavy line and
4622 // 2px wavy line can be used for different meaning in IME selections
4624 r
.height
= std::max(suggestedMaxRectHeight
, lineThickness
* 2.0);
4629 gfxFloat baseline
= floor(bCoord
+ aParams
.ascent
+ 0.5);
4631 // Calculate adjusted offset based on writing-mode/orientation and thickness
4632 // of decoration line. The input value aParams.offset is the nominal position
4633 // (offset from baseline) where we would draw a single, infinitely-thin line;
4634 // but for a wavy or double line, we'll need to move the bounding rect of the
4635 // decoration outwards from the baseline so that an underline remains below
4636 // the glyphs, and an overline above them, despite the increased block-dir
4637 // extent of the decoration.
4639 // So adjustments by r.Height() are used to make the wider line styles (wavy
4640 // and double) "grow" in the appropriate direction compared to the basic
4643 // Note that at this point, the decoration rect is being calculated in line-
4644 // relative coordinates, where 'x' is line-rightwards, and 'y' is line-
4645 // upwards. We'll swap them to be physical coords at the end.
4646 gfxFloat offset
= 0.0;
4648 if (aParams
.decoration
== StyleTextDecorationLine::UNDERLINE
) {
4649 offset
= aParams
.offset
;
4650 if (canLiftUnderline
) {
4651 if (descentLimit
< -offset
+ r
.Height()) {
4652 // If we can ignore the offset and the decoration line is overflowing,
4653 // we should align the bottom edge of the decoration line rect if it's
4654 // possible. Otherwise, we should lift up the top edge of the rect as
4656 gfxFloat offsetBottomAligned
= -descentLimit
+ r
.Height();
4657 gfxFloat offsetTopAligned
= 0.0;
4658 offset
= std::min(offsetBottomAligned
, offsetTopAligned
);
4661 } else if (aParams
.decoration
== StyleTextDecorationLine::OVERLINE
) {
4662 // For overline, we adjust the offset by defaultlineThickness (the default
4663 // thickness of a single decoration line) because empirically it looks
4664 // better to draw the overline just inside rather than outside the font's
4665 // ascent, which is what nsTextFrame passes as aParams.offset (as fonts
4666 // don't provide an explicit overline-offset).
4667 offset
= aParams
.offset
- defaultLineThickness
+ r
.Height();
4668 } else if (aParams
.decoration
== StyleTextDecorationLine::LINE_THROUGH
) {
4669 // To maintain a consistent mid-point for line-through decorations,
4670 // we adjust the offset by half of the decoration rect's height.
4671 gfxFloat extra
= floor(r
.Height() / 2.0 + 0.5);
4672 extra
= std::max(extra
, lineThickness
);
4673 // computes offset for when user specifies a decoration width since
4674 // aParams.offset is derived from the font metric's line height
4675 gfxFloat decorationThicknessOffset
=
4676 (lineThickness
- defaultLineThickness
) / 2.0;
4677 offset
= aParams
.offset
- lineThickness
+ extra
+ decorationThicknessOffset
;
4679 MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
4682 // Convert line-relative coordinate system (x = line-right, y = line-up)
4683 // to physical coords, and move the decoration rect to the calculated
4684 // offset from baseline.
4685 if (aParams
.vertical
) {
4686 std::swap(r
.x
, r
.y
);
4687 std::swap(r
.width
, r
.height
);
4688 // line-upwards in vertical mode = physical-right, so we /add/ offset
4689 // to baseline. Except in sideways-lr mode, where line-upwards will be
4690 // physical leftwards.
4691 if (aParams
.sidewaysLeft
) {
4692 r
.x
= baseline
- floor(offset
+ 0.5);
4694 r
.x
= baseline
+ floor(offset
- r
.Width() + 0.5);
4697 // line-upwards in horizontal mode = physical-up, but our physical coord
4698 // system works downwards, so we /subtract/ offset from baseline.
4699 r
.y
= baseline
- floor(offset
+ 0.5);
4705 #define MAX_BLUR_RADIUS 300
4706 #define MAX_SPREAD_RADIUS 50
4708 static inline gfxPoint
ComputeBlurStdDev(nscoord aBlurRadius
,
4709 int32_t aAppUnitsPerDevPixel
,
4710 gfxFloat aScaleX
, gfxFloat aScaleY
) {
4711 // http://dev.w3.org/csswg/css3-background/#box-shadow says that the
4712 // standard deviation of the blur should be half the given blur value.
4713 gfxFloat blurStdDev
= gfxFloat(aBlurRadius
) / gfxFloat(aAppUnitsPerDevPixel
);
4716 std::min((blurStdDev
* aScaleX
), gfxFloat(MAX_BLUR_RADIUS
)) / 2.0,
4717 std::min((blurStdDev
* aScaleY
), gfxFloat(MAX_BLUR_RADIUS
)) / 2.0);
4720 static inline IntSize
ComputeBlurRadius(nscoord aBlurRadius
,
4721 int32_t aAppUnitsPerDevPixel
,
4722 gfxFloat aScaleX
= 1.0,
4723 gfxFloat aScaleY
= 1.0) {
4724 gfxPoint scaledBlurStdDev
=
4725 ComputeBlurStdDev(aBlurRadius
, aAppUnitsPerDevPixel
, aScaleX
, aScaleY
);
4726 return gfxAlphaBoxBlur::CalculateBlurRadius(scaledBlurStdDev
);
4732 gfxContext
* nsContextBoxBlur::Init(const nsRect
& aRect
, nscoord aSpreadRadius
,
4733 nscoord aBlurRadius
,
4734 int32_t aAppUnitsPerDevPixel
,
4735 gfxContext
* aDestinationCtx
,
4736 const nsRect
& aDirtyRect
,
4737 const gfxRect
* aSkipRect
, uint32_t aFlags
) {
4738 if (aRect
.IsEmpty()) {
4744 IntSize spreadRadius
;
4745 GetBlurAndSpreadRadius(aDestinationCtx
->GetDrawTarget(), aAppUnitsPerDevPixel
,
4746 aBlurRadius
, aSpreadRadius
, blurRadius
, spreadRadius
);
4748 mDestinationCtx
= aDestinationCtx
;
4750 // If not blurring, draw directly onto the destination device
4751 if (blurRadius
.width
<= 0 && blurRadius
.height
<= 0 &&
4752 spreadRadius
.width
<= 0 && spreadRadius
.height
<= 0 &&
4753 !(aFlags
& FORCE_MASK
)) {
4754 mContext
= aDestinationCtx
;
4758 // Convert from app units to device pixels
4759 gfxRect rect
= nsLayoutUtils::RectToGfxRect(aRect
, aAppUnitsPerDevPixel
);
4762 nsLayoutUtils::RectToGfxRect(aDirtyRect
, aAppUnitsPerDevPixel
);
4763 dirtyRect
.RoundOut();
4765 gfxMatrix transform
= aDestinationCtx
->CurrentMatrixDouble();
4766 rect
= transform
.TransformBounds(rect
);
4768 mPreTransformed
= !transform
.IsIdentity();
4770 // Create the temporary surface for blurring
4771 dirtyRect
= transform
.TransformBounds(dirtyRect
);
4772 bool useHardwareAccel
= !(aFlags
& DISABLE_HARDWARE_ACCELERATION_BLUR
);
4774 gfxRect skipRect
= transform
.TransformBounds(*aSkipRect
);
4776 mAlphaBoxBlur
.Init(aDestinationCtx
, rect
, spreadRadius
, blurRadius
,
4777 &dirtyRect
, &skipRect
, useHardwareAccel
);
4780 mAlphaBoxBlur
.Init(aDestinationCtx
, rect
, spreadRadius
, blurRadius
,
4781 &dirtyRect
, nullptr, useHardwareAccel
);
4783 mContext
= mOwnedContext
.get();
4786 // we don't need to blur if skipRect is equal to rect
4787 // and mContext will be nullptr
4788 mContext
->Multiply(transform
);
4793 void nsContextBoxBlur::DoPaint() {
4794 if (mContext
== mDestinationCtx
) {
4798 gfxContextMatrixAutoSaveRestore
saveMatrix(mDestinationCtx
);
4800 if (mPreTransformed
) {
4801 mDestinationCtx
->SetMatrix(Matrix());
4804 mAlphaBoxBlur
.Paint(mDestinationCtx
);
4807 gfxContext
* nsContextBoxBlur::GetContext() { return mContext
; }
4810 nsMargin
nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius
,
4811 int32_t aAppUnitsPerDevPixel
) {
4812 IntSize blurRadius
= ComputeBlurRadius(aBlurRadius
, aAppUnitsPerDevPixel
);
4815 result
.top
= result
.bottom
= blurRadius
.height
* aAppUnitsPerDevPixel
;
4816 result
.left
= result
.right
= blurRadius
.width
* aAppUnitsPerDevPixel
;
4821 void nsContextBoxBlur::BlurRectangle(
4822 gfxContext
* aDestinationCtx
, const nsRect
& aRect
,
4823 int32_t aAppUnitsPerDevPixel
, RectCornerRadii
* aCornerRadii
,
4824 nscoord aBlurRadius
, const sRGBColor
& aShadowColor
,
4825 const nsRect
& aDirtyRect
, const gfxRect
& aSkipRect
) {
4826 DrawTarget
& aDestDrawTarget
= *aDestinationCtx
->GetDrawTarget();
4828 if (aRect
.IsEmpty()) {
4832 Rect shadowGfxRect
= NSRectToRect(aRect
, aAppUnitsPerDevPixel
);
4834 if (aBlurRadius
<= 0) {
4835 ColorPattern
color(ToDeviceColor(aShadowColor
));
4837 RefPtr
<Path
> roundedRect
=
4838 MakePathForRoundedRect(aDestDrawTarget
, shadowGfxRect
, *aCornerRadii
);
4839 aDestDrawTarget
.Fill(roundedRect
, color
);
4841 aDestDrawTarget
.FillRect(shadowGfxRect
, color
);
4846 gfxFloat scaleX
= 1;
4847 gfxFloat scaleY
= 1;
4849 // Do blurs in device space when possible.
4850 // Chrome/Skia always does the blurs in device space
4851 // and will sometimes get incorrect results (e.g. rotated blurs)
4852 gfxMatrix transform
= aDestinationCtx
->CurrentMatrixDouble();
4853 // XXX: we could probably handle negative scales but for now it's easier just
4855 if (!transform
.HasNonAxisAlignedTransform() && transform
._11
> 0.0 &&
4856 transform
._22
> 0.0) {
4857 scaleX
= transform
._11
;
4858 scaleY
= transform
._22
;
4859 aDestinationCtx
->SetMatrix(Matrix());
4861 transform
= gfxMatrix();
4864 gfxPoint blurStdDev
=
4865 ComputeBlurStdDev(aBlurRadius
, aAppUnitsPerDevPixel
, scaleX
, scaleY
);
4868 nsLayoutUtils::RectToGfxRect(aDirtyRect
, aAppUnitsPerDevPixel
);
4869 dirtyRect
.RoundOut();
4871 gfxRect shadowThebesRect
=
4872 transform
.TransformBounds(ThebesRect(shadowGfxRect
));
4873 dirtyRect
= transform
.TransformBounds(dirtyRect
);
4874 gfxRect skipRect
= transform
.TransformBounds(aSkipRect
);
4877 aCornerRadii
->Scale(scaleX
, scaleY
);
4880 gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx
, shadowThebesRect
,
4881 aCornerRadii
, blurStdDev
, aShadowColor
,
4882 dirtyRect
, skipRect
);
4886 void nsContextBoxBlur::GetBlurAndSpreadRadius(
4887 DrawTarget
* aDestDrawTarget
, int32_t aAppUnitsPerDevPixel
,
4888 nscoord aBlurRadius
, nscoord aSpreadRadius
, IntSize
& aOutBlurRadius
,
4889 IntSize
& aOutSpreadRadius
, bool aConstrainSpreadRadius
) {
4890 // Do blurs in device space when possible.
4891 // Chrome/Skia always does the blurs in device space
4892 // and will sometimes get incorrect results (e.g. rotated blurs)
4893 Matrix transform
= aDestDrawTarget
->GetTransform();
4894 // XXX: we could probably handle negative scales but for now it's easier just
4896 gfxFloat scaleX
, scaleY
;
4897 if (transform
.HasNonAxisAlignedTransform() || transform
._11
<= 0.0 ||
4898 transform
._22
<= 0.0) {
4902 scaleX
= transform
._11
;
4903 scaleY
= transform
._22
;
4906 // compute a large or smaller blur radius
4908 ComputeBlurRadius(aBlurRadius
, aAppUnitsPerDevPixel
, scaleX
, scaleY
);
4910 IntSize(int32_t(aSpreadRadius
* scaleX
/ aAppUnitsPerDevPixel
),
4911 int32_t(aSpreadRadius
* scaleY
/ aAppUnitsPerDevPixel
));
4913 if (aConstrainSpreadRadius
) {
4914 aOutSpreadRadius
.width
=
4915 std::min(aOutSpreadRadius
.width
, int32_t(MAX_SPREAD_RADIUS
));
4916 aOutSpreadRadius
.height
=
4917 std::min(aOutSpreadRadius
.height
, int32_t(MAX_SPREAD_RADIUS
));
4922 bool nsContextBoxBlur::InsetBoxBlur(
4923 gfxContext
* aDestinationCtx
, Rect aDestinationRect
, Rect aShadowClipRect
,
4924 sRGBColor
& aShadowColor
, nscoord aBlurRadiusAppUnits
,
4925 nscoord aSpreadDistanceAppUnits
, int32_t aAppUnitsPerDevPixel
,
4926 bool aHasBorderRadius
, RectCornerRadii
& aInnerClipRectRadii
, Rect aSkipRect
,
4927 Point aShadowOffset
) {
4928 if (aDestinationRect
.IsEmpty()) {
4933 gfxContextAutoSaveRestore
autoRestore(aDestinationCtx
);
4936 IntSize spreadRadius
;
4937 // Convert the blur and spread radius to device pixels
4938 bool constrainSpreadRadius
= false;
4939 GetBlurAndSpreadRadius(aDestinationCtx
->GetDrawTarget(), aAppUnitsPerDevPixel
,
4940 aBlurRadiusAppUnits
, aSpreadDistanceAppUnits
,
4941 blurRadius
, spreadRadius
, constrainSpreadRadius
);
4943 // The blur and spread radius are scaled already, so scale all
4944 // input data to the blur. This way, we don't have to scale the min
4945 // inset blur to the invert of the dest context, then rescale it back
4946 // when we draw to the destination surface.
4947 auto scale
= aDestinationCtx
->CurrentMatrix().ScaleFactors();
4948 Matrix transform
= aDestinationCtx
->CurrentMatrix();
4950 // XXX: we could probably handle negative scales but for now it's easier just
4952 if (!transform
.HasNonAxisAlignedTransform() && transform
._11
> 0.0 &&
4953 transform
._22
> 0.0) {
4954 // If we don't have a rotation, we're pre-transforming all the rects.
4955 aDestinationCtx
->SetMatrix(Matrix());
4957 // Don't touch anything, we have a rotation.
4958 transform
= Matrix();
4961 Rect transformedDestRect
= transform
.TransformBounds(aDestinationRect
);
4962 Rect transformedShadowClipRect
= transform
.TransformBounds(aShadowClipRect
);
4963 Rect transformedSkipRect
= transform
.TransformBounds(aSkipRect
);
4965 transformedDestRect
.Round();
4966 transformedShadowClipRect
.Round();
4967 transformedSkipRect
.RoundIn();
4969 for (size_t i
= 0; i
< 4; i
++) {
4970 aInnerClipRectRadii
[i
].width
=
4971 std::floor(scale
.xScale
* aInnerClipRectRadii
[i
].width
);
4972 aInnerClipRectRadii
[i
].height
=
4973 std::floor(scale
.yScale
* aInnerClipRectRadii
[i
].height
);
4976 mAlphaBoxBlur
.BlurInsetBox(aDestinationCtx
, transformedDestRect
,
4977 transformedShadowClipRect
, blurRadius
,
4979 aHasBorderRadius
? &aInnerClipRectRadii
: nullptr,
4980 transformedSkipRect
, aShadowOffset
);