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