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 */
11 #include "gfx2DGlue.h"
12 #include "gfxContext.h"
13 #include "mozilla/ArrayUtils.h"
14 #include "mozilla/ComputedStyle.h"
15 #include "mozilla/DebugOnly.h"
16 #include "mozilla/StaticPrefs_layout.h"
17 #include "mozilla/gfx/2D.h"
18 #include "mozilla/gfx/Helpers.h"
19 #include "mozilla/gfx/PathHelpers.h"
20 #include "mozilla/HashFunctions.h"
21 #include "mozilla/MathAlgorithms.h"
22 #include "mozilla/PresShell.h"
23 #include "mozilla/SVGImageContext.h"
25 #include "ScaledFontBase.h"
26 #include "skia/include/core/SkTextBlob.h"
28 #include "BorderConsts.h"
29 #include "nsStyleConsts.h"
30 #include "nsPresContext.h"
32 #include "nsIFrameInlines.h"
35 #include "nsFrameManager.h"
36 #include "nsGkAtoms.h"
37 #include "nsCSSAnonBoxes.h"
38 #include "nsIContent.h"
39 #include "mozilla/dom/DocumentInlines.h"
40 #include "nsIScrollableFrame.h"
41 #include "imgIContainer.h"
43 #include "nsCSSRendering.h"
44 #include "nsCSSColorUtils.h"
46 #include "nsLayoutUtils.h"
47 #include "nsBlockFrame.h"
48 #include "nsStyleStructInlines.h"
49 #include "nsCSSFrameConstructor.h"
50 #include "nsCSSProps.h"
51 #include "nsContentUtils.h"
52 #include "gfxDrawable.h"
53 #include "nsCSSRenderingBorders.h"
54 #include "mozilla/css/ImageLoader.h"
55 #include "ImageContainer.h"
56 #include "mozilla/ProfilerLabels.h"
57 #include "mozilla/StaticPrefs_layout.h"
58 #include "mozilla/Telemetry.h"
60 #include "gfxGradientCache.h"
61 #include "nsInlineFrame.h"
62 #include "nsRubyTextContainerFrame.h"
64 #include "TextDrawTarget.h"
66 using namespace mozilla
;
67 using namespace mozilla::css
;
68 using namespace mozilla::gfx
;
69 using namespace mozilla::image
;
70 using mozilla::CSSSizeOrRatio
;
71 using mozilla::dom::Document
;
73 static int gFrameTreeLockCount
= 0;
75 // To avoid storing this data on nsInlineFrame (bloat) and to avoid
76 // recalculating this for each frame in a continuation (perf), hold
77 // a cache of various coordinate information that we need in order
78 // to paint inline backgrounds.
79 struct InlineBackgroundData
{
80 InlineBackgroundData()
82 mLineContainer(nullptr),
83 mContinuationPoint(0),
85 mLineContinuationPoint(0),
90 ~InlineBackgroundData() = default;
93 mBoundingBox
.SetRect(0, 0, 0, 0);
94 mContinuationPoint
= mLineContinuationPoint
= mUnbrokenMeasure
= 0;
95 mFrame
= mLineContainer
= nullptr;
96 mPIStartBorderData
.Reset();
100 * Return a continuous rect for (an inline) aFrame relative to the
101 * continuation that draws the left-most part of the background.
102 * This is used when painting backgrounds.
104 nsRect
GetContinuousRect(nsIFrame
* aFrame
) {
105 MOZ_ASSERT(static_cast<nsInlineFrame
*>(do_QueryFrame(aFrame
)));
109 nscoord pos
; // an x coordinate if writing-mode is horizontal;
110 // y coordinate if vertical
112 pos
= mLineContinuationPoint
;
114 // Scan continuations on the same line as aFrame and accumulate the widths
115 // of frames that are to the left (if this is an LTR block) or right
116 // (if it's RTL) of the current one.
117 bool isRtlBlock
= (mLineContainer
->StyleVisibility()->mDirection
==
118 StyleDirection::Rtl
);
119 nscoord curOffset
= mVertical
? aFrame
->GetOffsetTo(mLineContainer
).y
120 : aFrame
->GetOffsetTo(mLineContainer
).x
;
122 // If the continuation is fluid we know inlineFrame is not on the same
123 // line. If it's not fluid, we need to test further to be sure.
124 nsIFrame
* inlineFrame
= aFrame
->GetPrevContinuation();
125 while (inlineFrame
&& !inlineFrame
->GetNextInFlow() &&
126 AreOnSameLine(aFrame
, inlineFrame
)) {
127 nscoord frameOffset
= mVertical
128 ? inlineFrame
->GetOffsetTo(mLineContainer
).y
129 : inlineFrame
->GetOffsetTo(mLineContainer
).x
;
130 if (isRtlBlock
== (frameOffset
>= curOffset
)) {
131 pos
+= mVertical
? inlineFrame
->GetSize().height
132 : inlineFrame
->GetSize().width
;
134 inlineFrame
= inlineFrame
->GetPrevContinuation();
137 inlineFrame
= aFrame
->GetNextContinuation();
138 while (inlineFrame
&& !inlineFrame
->GetPrevInFlow() &&
139 AreOnSameLine(aFrame
, inlineFrame
)) {
140 nscoord frameOffset
= mVertical
141 ? inlineFrame
->GetOffsetTo(mLineContainer
).y
142 : inlineFrame
->GetOffsetTo(mLineContainer
).x
;
143 if (isRtlBlock
== (frameOffset
>= curOffset
)) {
144 pos
+= mVertical
? inlineFrame
->GetSize().height
145 : inlineFrame
->GetSize().width
;
147 inlineFrame
= inlineFrame
->GetNextContinuation();
150 // aFrame itself is also to the right of its left edge, so add its
152 pos
+= mVertical
? aFrame
->GetSize().height
: aFrame
->GetSize().width
;
153 // pos is now the distance from the left [top] edge of aFrame to the
154 // right [bottom] edge of the unbroken content. Change it to indicate
155 // the distance from the left [top] edge of the unbroken content to the
156 // left [top] edge of aFrame.
157 pos
= mUnbrokenMeasure
- pos
;
160 pos
= mContinuationPoint
;
163 // Assume background-origin: border and return a rect with offsets
164 // relative to (0,0). If we have a different background-origin,
165 // then our rect should be deflated appropriately by our caller.
167 ? nsRect(0, -pos
, mFrame
->GetSize().width
, mUnbrokenMeasure
)
168 : nsRect(-pos
, 0, mUnbrokenMeasure
, mFrame
->GetSize().height
);
172 * Return a continuous rect for (an inline) aFrame relative to the
173 * continuation that should draw the left[top]-border. This is used when
174 * painting borders and clipping backgrounds. This may NOT be the same
175 * continuous rect as for drawing backgrounds; the continuation with the
176 * left[top]-border might be somewhere in the middle of that rect (e.g. BIDI),
177 * in those cases we need the reverse background order starting at the
178 * left[top]-border continuation.
180 nsRect
GetBorderContinuousRect(nsIFrame
* aFrame
, nsRect aBorderArea
) {
181 // Calling GetContinuousRect(aFrame) here may lead to Reset/Init which
182 // resets our mPIStartBorderData so we save it ...
183 PhysicalInlineStartBorderData
saved(mPIStartBorderData
);
184 nsRect joinedBorderArea
= GetContinuousRect(aFrame
);
185 if (!saved
.mIsValid
|| saved
.mFrame
!= mPIStartBorderData
.mFrame
) {
186 if (aFrame
== mPIStartBorderData
.mFrame
) {
188 mPIStartBorderData
.SetCoord(joinedBorderArea
.y
);
190 mPIStartBorderData
.SetCoord(joinedBorderArea
.x
);
192 } else if (mPIStartBorderData
.mFrame
) {
193 // Copy data to a temporary object so that computing the
194 // continous rect here doesn't clobber our normal state.
195 InlineBackgroundData temp
= *this;
197 mPIStartBorderData
.SetCoord(
198 temp
.GetContinuousRect(mPIStartBorderData
.mFrame
).y
);
200 mPIStartBorderData
.SetCoord(
201 temp
.GetContinuousRect(mPIStartBorderData
.mFrame
).x
);
205 // ... and restore it when possible.
206 mPIStartBorderData
.SetCoord(saved
.mCoord
);
209 if (joinedBorderArea
.y
> mPIStartBorderData
.mCoord
) {
211 -(mUnbrokenMeasure
+ joinedBorderArea
.y
- aBorderArea
.height
);
213 joinedBorderArea
.y
-= mPIStartBorderData
.mCoord
;
216 if (joinedBorderArea
.x
> mPIStartBorderData
.mCoord
) {
218 -(mUnbrokenMeasure
+ joinedBorderArea
.x
- aBorderArea
.width
);
220 joinedBorderArea
.x
-= mPIStartBorderData
.mCoord
;
223 return joinedBorderArea
;
226 nsRect
GetBoundingRect(nsIFrame
* aFrame
) {
229 // Move the offsets relative to (0,0) which puts the bounding box into
230 // our coordinate system rather than our parent's. We do this by
231 // moving it the back distance from us to the bounding box.
232 // This also assumes background-origin: border, so our caller will
233 // need to deflate us if needed.
234 nsRect
boundingBox(mBoundingBox
);
235 nsPoint point
= mFrame
->GetPosition();
236 boundingBox
.MoveBy(-point
.x
, -point
.y
);
242 // This is a coordinate on the inline axis, but is not a true logical inline-
243 // coord because it is always measured from left to right (if horizontal) or
244 // from top to bottom (if vertical), ignoring any bidi RTL directionality.
245 // We'll call this "physical inline start", or PIStart for short.
246 struct PhysicalInlineStartBorderData
{
247 nsIFrame
* mFrame
; // the continuation that may have a left-border
248 nscoord mCoord
; // cached GetContinuousRect(mFrame).x or .y
249 bool mIsValid
; // true if mCoord is valid
254 void SetCoord(nscoord aCoord
) {
261 nsIFrame
* mLineContainer
;
263 nscoord mContinuationPoint
;
264 nscoord mUnbrokenMeasure
;
265 nscoord mLineContinuationPoint
;
266 PhysicalInlineStartBorderData mPIStartBorderData
;
270 void SetFrame(nsIFrame
* aFrame
) {
271 MOZ_ASSERT(aFrame
, "Need a frame");
272 NS_ASSERTION(gFrameTreeLockCount
> 0,
273 "Can't call this when frame tree is not locked");
275 if (aFrame
== mFrame
) {
279 nsIFrame
* prevContinuation
= GetPrevContinuation(aFrame
);
281 if (!prevContinuation
|| mFrame
!= prevContinuation
) {
282 // Ok, we've got the wrong frame. We have to start from scratch.
288 // Get our last frame's size and add its width to our continuation
289 // point before we cache the new frame.
290 mContinuationPoint
+=
291 mVertical
? mFrame
->GetSize().height
: mFrame
->GetSize().width
;
293 // If this a new line, update mLineContinuationPoint.
295 (aFrame
->GetPrevInFlow() || !AreOnSameLine(mFrame
, aFrame
))) {
296 mLineContinuationPoint
= mContinuationPoint
;
302 nsIFrame
* GetPrevContinuation(nsIFrame
* aFrame
) {
303 nsIFrame
* prevCont
= aFrame
->GetPrevContinuation();
304 if (!prevCont
&& aFrame
->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT
)) {
305 nsIFrame
* block
= aFrame
->GetProperty(nsIFrame::IBSplitPrevSibling());
307 // The {ib} properties are only stored on first continuations
308 NS_ASSERTION(!block
->GetPrevContinuation(),
309 "Incorrect value for IBSplitPrevSibling");
310 prevCont
= block
->GetProperty(nsIFrame::IBSplitPrevSibling());
311 NS_ASSERTION(prevCont
, "How did that happen?");
317 nsIFrame
* GetNextContinuation(nsIFrame
* aFrame
) {
318 nsIFrame
* nextCont
= aFrame
->GetNextContinuation();
319 if (!nextCont
&& aFrame
->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT
)) {
320 // The {ib} properties are only stored on first continuations
321 aFrame
= aFrame
->FirstContinuation();
322 nsIFrame
* block
= aFrame
->GetProperty(nsIFrame::IBSplitSibling());
324 nextCont
= block
->GetProperty(nsIFrame::IBSplitSibling());
325 NS_ASSERTION(nextCont
, "How did that happen?");
331 void Init(nsIFrame
* aFrame
) {
332 mPIStartBorderData
.Reset();
333 mBidiEnabled
= aFrame
->PresContext()->BidiEnabled();
335 // Find the line container frame
336 mLineContainer
= aFrame
;
337 while (mLineContainer
&&
338 mLineContainer
->IsFrameOfType(nsIFrame::eLineParticipant
)) {
339 mLineContainer
= mLineContainer
->GetParent();
342 MOZ_ASSERT(mLineContainer
, "Cannot find line containing frame.");
343 MOZ_ASSERT(mLineContainer
!= aFrame
,
344 "line container frame "
345 "should be an ancestor of the target frame.");
348 mVertical
= aFrame
->GetWritingMode().IsVertical();
350 // Start with the previous flow frame as our continuation point
351 // is the total of the widths of the previous frames.
352 nsIFrame
* inlineFrame
= GetPrevContinuation(aFrame
);
353 bool changedLines
= false;
354 while (inlineFrame
) {
355 if (!mPIStartBorderData
.mFrame
&&
356 !(mVertical
? inlineFrame
->GetSkipSides().Top()
357 : inlineFrame
->GetSkipSides().Left())) {
358 mPIStartBorderData
.mFrame
= inlineFrame
;
360 nsRect rect
= inlineFrame
->GetRect();
361 mContinuationPoint
+= mVertical
? rect
.height
: rect
.width
;
363 (changedLines
|| !AreOnSameLine(aFrame
, inlineFrame
))) {
364 mLineContinuationPoint
+= mVertical
? rect
.height
: rect
.width
;
367 mUnbrokenMeasure
+= mVertical
? rect
.height
: rect
.width
;
368 mBoundingBox
.UnionRect(mBoundingBox
, rect
);
369 inlineFrame
= GetPrevContinuation(inlineFrame
);
372 // Next add this frame and subsequent frames to the bounding box and
374 inlineFrame
= aFrame
;
375 while (inlineFrame
) {
376 if (!mPIStartBorderData
.mFrame
&&
377 !(mVertical
? inlineFrame
->GetSkipSides().Top()
378 : inlineFrame
->GetSkipSides().Left())) {
379 mPIStartBorderData
.mFrame
= inlineFrame
;
381 nsRect rect
= inlineFrame
->GetRect();
382 mUnbrokenMeasure
+= mVertical
? rect
.height
: rect
.width
;
383 mBoundingBox
.UnionRect(mBoundingBox
, rect
);
384 inlineFrame
= GetNextContinuation(inlineFrame
);
390 bool AreOnSameLine(nsIFrame
* aFrame1
, nsIFrame
* aFrame2
) {
391 if (nsBlockFrame
* blockFrame
= do_QueryFrame(mLineContainer
)) {
392 bool isValid1
, isValid2
;
393 nsBlockInFlowLineIterator
it1(blockFrame
, aFrame1
, &isValid1
);
394 nsBlockInFlowLineIterator
it2(blockFrame
, aFrame2
, &isValid2
);
395 return isValid1
&& isValid2
&&
396 // Make sure aFrame1 and aFrame2 are in the same continuation of
398 it1
.GetContainer() == it2
.GetContainer() &&
399 // And on the same line in it
400 it1
.GetLine().get() == it2
.GetLine().get();
402 if (nsRubyTextContainerFrame
* rtcFrame
= do_QueryFrame(mLineContainer
)) {
403 nsBlockFrame
* block
= nsLayoutUtils::FindNearestBlockAncestor(rtcFrame
);
404 // Ruby text container can only hold one line of text, so if they
405 // are in the same continuation, they are in the same line. Since
406 // ruby text containers are bidi isolate, they are never split for
407 // bidi reordering, which means being in different continuation
408 // indicates being in different lines.
409 for (nsIFrame
* frame
= rtcFrame
->FirstContinuation(); frame
;
410 frame
= frame
->GetNextContinuation()) {
412 nsLayoutUtils::IsProperAncestorFrame(frame
, aFrame1
, block
);
414 nsLayoutUtils::IsProperAncestorFrame(frame
, aFrame2
, block
);
415 if (isDescendant1
&& isDescendant2
) {
418 if (isDescendant1
|| isDescendant2
) {
422 MOZ_ASSERT_UNREACHABLE("None of the frames is a descendant of this rtc?");
424 MOZ_ASSERT_UNREACHABLE("Do we have any other type of line container?");
429 static InlineBackgroundData
* gInlineBGData
= nullptr;
431 // Initialize any static variables used by nsCSSRendering.
432 void nsCSSRendering::Init() {
433 NS_ASSERTION(!gInlineBGData
, "Init called twice");
434 gInlineBGData
= new InlineBackgroundData();
437 // Clean up any global variables used by nsCSSRendering.
438 void nsCSSRendering::Shutdown() {
439 delete gInlineBGData
;
440 gInlineBGData
= nullptr;
446 static nscolor
MakeBevelColor(mozilla::Side whichSide
, StyleBorderStyle style
,
447 nscolor aBorderColor
) {
451 // Given a background color and a border color
452 // calculate the color used for the shading
453 NS_GetSpecial3DColors(colors
, aBorderColor
);
455 if ((style
== StyleBorderStyle::Outset
) ||
456 (style
== StyleBorderStyle::Ridge
)) {
457 // Flip colors for these two border styles
460 whichSide
= eSideTop
;
463 whichSide
= eSideLeft
;
466 whichSide
= eSideBottom
;
469 whichSide
= eSideRight
;
476 theColor
= colors
[1];
479 theColor
= colors
[1];
482 theColor
= colors
[0];
486 theColor
= colors
[0];
492 static bool GetRadii(nsIFrame
* aForFrame
, const nsStyleBorder
& aBorder
,
493 const nsRect
& aOrigBorderArea
, const nsRect
& aBorderArea
,
495 bool haveRoundedCorners
;
496 nsSize sz
= aBorderArea
.Size();
497 nsSize frameSize
= aForFrame
->GetSize();
498 if (&aBorder
== aForFrame
->StyleBorder() &&
499 frameSize
== aOrigBorderArea
.Size()) {
500 haveRoundedCorners
= aForFrame
->GetBorderRadii(sz
, sz
, Sides(), aRadii
);
502 haveRoundedCorners
= nsIFrame::ComputeBorderRadii(
503 aBorder
.mBorderRadius
, frameSize
, sz
, Sides(), aRadii
);
506 return haveRoundedCorners
;
509 static bool GetRadii(nsIFrame
* aForFrame
, const nsStyleBorder
& aBorder
,
510 const nsRect
& aOrigBorderArea
, const nsRect
& aBorderArea
,
511 RectCornerRadii
* aBgRadii
) {
513 bool haveRoundedCorners
=
514 GetRadii(aForFrame
, aBorder
, aOrigBorderArea
, aBorderArea
, radii
);
516 if (haveRoundedCorners
) {
517 auto d2a
= aForFrame
->PresContext()->AppUnitsPerDevPixel();
518 nsCSSRendering::ComputePixelRadii(radii
, d2a
, aBgRadii
);
520 return haveRoundedCorners
;
523 static nsRect
JoinBoxesForBlockAxisSlice(nsIFrame
* aFrame
,
524 const nsRect
& aBorderArea
) {
525 // Inflate the block-axis size as if our continuations were laid out
526 // adjacent in that axis. Note that we don't touch the inline size.
527 const auto wm
= aFrame
->GetWritingMode();
528 const nsSize dummyContainerSize
;
529 LogicalRect
borderArea(wm
, aBorderArea
, dummyContainerSize
);
531 nsIFrame
* f
= aFrame
->GetNextContinuation();
532 for (; f
; f
= f
->GetNextContinuation()) {
533 bSize
+= f
->BSize(wm
);
535 borderArea
.BSize(wm
) += bSize
;
537 f
= aFrame
->GetPrevContinuation();
538 for (; f
; f
= f
->GetPrevContinuation()) {
539 bSize
+= f
->BSize(wm
);
541 borderArea
.BStart(wm
) -= bSize
;
542 borderArea
.BSize(wm
) += bSize
;
543 return borderArea
.GetPhysicalRect(wm
, dummyContainerSize
);
547 * Inflate aBorderArea which is relative to aFrame's origin to calculate
548 * a hypothetical non-split frame area for all the continuations.
549 * See "Joining Boxes for 'slice'" in
550 * http://dev.w3.org/csswg/css-break/#break-decoration
552 enum InlineBoxOrder
{ eForBorder
, eForBackground
};
553 static nsRect
JoinBoxesForSlice(nsIFrame
* aFrame
, const nsRect
& aBorderArea
,
554 InlineBoxOrder aOrder
) {
555 if (static_cast<nsInlineFrame
*>(do_QueryFrame(aFrame
))) {
556 return (aOrder
== eForBorder
557 ? gInlineBGData
->GetBorderContinuousRect(aFrame
, aBorderArea
)
558 : gInlineBGData
->GetContinuousRect(aFrame
)) +
559 aBorderArea
.TopLeft();
561 return JoinBoxesForBlockAxisSlice(aFrame
, aBorderArea
);
565 bool nsCSSRendering::IsBoxDecorationSlice(const nsStyleBorder
& aStyleBorder
) {
566 return aStyleBorder
.mBoxDecorationBreak
== StyleBoxDecorationBreak::Slice
;
570 nsRect
nsCSSRendering::BoxDecorationRectForBorder(
571 nsIFrame
* aFrame
, const nsRect
& aBorderArea
, Sides aSkipSides
,
572 const nsStyleBorder
* aStyleBorder
) {
574 aStyleBorder
= aFrame
->StyleBorder();
576 // If aSkipSides.IsEmpty() then there are no continuations, or it's
577 // a ::first-letter that wants all border sides on the first continuation.
578 return IsBoxDecorationSlice(*aStyleBorder
) && !aSkipSides
.IsEmpty()
579 ? ::JoinBoxesForSlice(aFrame
, aBorderArea
, eForBorder
)
584 nsRect
nsCSSRendering::BoxDecorationRectForBackground(
585 nsIFrame
* aFrame
, const nsRect
& aBorderArea
, Sides aSkipSides
,
586 const nsStyleBorder
* aStyleBorder
) {
588 aStyleBorder
= aFrame
->StyleBorder();
590 // If aSkipSides.IsEmpty() then there are no continuations, or it's
591 // a ::first-letter that wants all border sides on the first continuation.
592 return IsBoxDecorationSlice(*aStyleBorder
) && !aSkipSides
.IsEmpty()
593 ? ::JoinBoxesForSlice(aFrame
, aBorderArea
, eForBackground
)
597 //----------------------------------------------------------------------
598 // Thebes Border Rendering Code Start
601 * Compute the float-pixel radii that should be used for drawing
602 * this border/outline, given the various input bits.
605 void nsCSSRendering::ComputePixelRadii(const nscoord
* aAppUnitsRadii
,
606 nscoord aAppUnitsPerPixel
,
607 RectCornerRadii
* oBorderRadii
) {
609 for (const auto corner
: mozilla::AllPhysicalHalfCorners()) {
610 radii
[corner
] = Float(aAppUnitsRadii
[corner
]) / aAppUnitsPerPixel
;
613 (*oBorderRadii
)[C_TL
] = Size(radii
[eCornerTopLeftX
], radii
[eCornerTopLeftY
]);
614 (*oBorderRadii
)[C_TR
] =
615 Size(radii
[eCornerTopRightX
], radii
[eCornerTopRightY
]);
616 (*oBorderRadii
)[C_BR
] =
617 Size(radii
[eCornerBottomRightX
], radii
[eCornerBottomRightY
]);
618 (*oBorderRadii
)[C_BL
] =
619 Size(radii
[eCornerBottomLeftX
], radii
[eCornerBottomLeftY
]);
622 static Maybe
<nsStyleBorder
> GetBorderIfVisited(const ComputedStyle
& aStyle
) {
623 Maybe
<nsStyleBorder
> result
;
624 // Don't check RelevantLinkVisited here, since we want to take the
625 // same amount of time whether or not it's true.
626 const ComputedStyle
* styleIfVisited
= aStyle
.GetStyleIfVisited();
627 if (MOZ_LIKELY(!styleIfVisited
)) {
631 result
.emplace(*aStyle
.StyleBorder());
632 auto& newBorder
= result
.ref();
633 for (const auto side
: mozilla::AllPhysicalSides()) {
634 nscolor color
= aStyle
.GetVisitedDependentColor(
635 nsStyleBorder::BorderColorFieldFor(side
));
636 newBorder
.BorderColorFor(side
) = StyleColor::FromColor(color
);
642 ImgDrawResult
nsCSSRendering::PaintBorder(
643 nsPresContext
* aPresContext
, gfxContext
& aRenderingContext
,
644 nsIFrame
* aForFrame
, const nsRect
& aDirtyRect
, const nsRect
& aBorderArea
,
645 ComputedStyle
* aStyle
, PaintBorderFlags aFlags
, Sides aSkipSides
) {
646 AUTO_PROFILER_LABEL("nsCSSRendering::PaintBorder", GRAPHICS
);
648 Maybe
<nsStyleBorder
> visitedBorder
= GetBorderIfVisited(*aStyle
);
649 return PaintBorderWithStyleBorder(
650 aPresContext
, aRenderingContext
, aForFrame
, aDirtyRect
, aBorderArea
,
651 visitedBorder
.refOr(*aStyle
->StyleBorder()), aStyle
, aFlags
, aSkipSides
);
654 Maybe
<nsCSSBorderRenderer
> nsCSSRendering::CreateBorderRenderer(
655 nsPresContext
* aPresContext
, DrawTarget
* aDrawTarget
, nsIFrame
* aForFrame
,
656 const nsRect
& aDirtyRect
, const nsRect
& aBorderArea
, ComputedStyle
* aStyle
,
657 bool* aOutBorderIsEmpty
, Sides aSkipSides
) {
658 Maybe
<nsStyleBorder
> visitedBorder
= GetBorderIfVisited(*aStyle
);
659 return CreateBorderRendererWithStyleBorder(
660 aPresContext
, aDrawTarget
, aForFrame
, aDirtyRect
, aBorderArea
,
661 visitedBorder
.refOr(*aStyle
->StyleBorder()), aStyle
, aOutBorderIsEmpty
,
665 ImgDrawResult
nsCSSRendering::CreateWebRenderCommandsForBorder(
666 nsDisplayItem
* aItem
, nsIFrame
* aForFrame
, const nsRect
& aBorderArea
,
667 mozilla::wr::DisplayListBuilder
& aBuilder
,
668 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
669 const mozilla::layers::StackingContextHelper
& aSc
,
670 mozilla::layers::RenderRootStateManager
* aManager
,
671 nsDisplayListBuilder
* aDisplayListBuilder
) {
672 const auto* style
= aForFrame
->Style();
673 Maybe
<nsStyleBorder
> visitedBorder
= GetBorderIfVisited(*style
);
674 return nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
675 aItem
, aForFrame
, aBorderArea
, aBuilder
, aResources
, aSc
, aManager
,
676 aDisplayListBuilder
, visitedBorder
.refOr(*style
->StyleBorder()));
679 void nsCSSRendering::CreateWebRenderCommandsForNullBorder(
680 nsDisplayItem
* aItem
, nsIFrame
* aForFrame
, const nsRect
& aBorderArea
,
681 mozilla::wr::DisplayListBuilder
& aBuilder
,
682 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
683 const mozilla::layers::StackingContextHelper
& aSc
,
684 const nsStyleBorder
& aStyleBorder
) {
685 bool borderIsEmpty
= false;
686 Maybe
<nsCSSBorderRenderer
> br
=
687 nsCSSRendering::CreateNullBorderRendererWithStyleBorder(
688 aForFrame
->PresContext(), nullptr, aForFrame
, nsRect(), aBorderArea
,
689 aStyleBorder
, aForFrame
->Style(), &borderIsEmpty
,
690 aForFrame
->GetSkipSides());
691 if (!borderIsEmpty
&& br
) {
692 br
->CreateWebRenderCommands(aItem
, aBuilder
, aResources
, aSc
);
696 ImgDrawResult
nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
697 nsDisplayItem
* aItem
, nsIFrame
* aForFrame
, const nsRect
& aBorderArea
,
698 mozilla::wr::DisplayListBuilder
& aBuilder
,
699 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
700 const mozilla::layers::StackingContextHelper
& aSc
,
701 mozilla::layers::RenderRootStateManager
* aManager
,
702 nsDisplayListBuilder
* aDisplayListBuilder
,
703 const nsStyleBorder
& aStyleBorder
) {
704 auto& borderImage
= aStyleBorder
.mBorderImageSource
;
705 // First try to create commands for simple borders.
706 if (borderImage
.IsNone()) {
707 CreateWebRenderCommandsForNullBorder(
708 aItem
, aForFrame
, aBorderArea
, aBuilder
, aResources
, aSc
, aStyleBorder
);
709 return ImgDrawResult::SUCCESS
;
712 // Next we try image and gradient borders. Gradients are not supported at
714 if (!borderImage
.IsImageRequestType()) {
715 return ImgDrawResult::NOT_SUPPORTED
;
718 if (aStyleBorder
.mBorderImageRepeatH
== StyleBorderImageRepeat::Space
||
719 aStyleBorder
.mBorderImageRepeatV
== StyleBorderImageRepeat::Space
) {
720 return ImgDrawResult::NOT_SUPPORTED
;
724 if (aDisplayListBuilder
->IsPaintingToWindow()) {
725 flags
|= nsImageRenderer::FLAG_PAINTING_TO_WINDOW
;
727 if (aDisplayListBuilder
->ShouldSyncDecodeImages()) {
728 flags
|= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES
;
731 image::ImgDrawResult result
;
732 Maybe
<nsCSSBorderImageRenderer
> bir
=
733 nsCSSBorderImageRenderer::CreateBorderImageRenderer(
734 aForFrame
->PresContext(), aForFrame
, aBorderArea
, aStyleBorder
,
735 aItem
->GetPaintRect(), aForFrame
->GetSkipSides(), flags
, &result
);
738 // We aren't ready. Try to fallback to the null border image if present but
739 // return the draw result for the border image renderer.
740 CreateWebRenderCommandsForNullBorder(
741 aItem
, aForFrame
, aBorderArea
, aBuilder
, aResources
, aSc
, aStyleBorder
);
745 return bir
->CreateWebRenderCommands(aItem
, aForFrame
, aBuilder
, aResources
,
746 aSc
, aManager
, aDisplayListBuilder
);
749 static nsCSSBorderRenderer
ConstructBorderRenderer(
750 nsPresContext
* aPresContext
, ComputedStyle
* aStyle
, DrawTarget
* aDrawTarget
,
751 nsIFrame
* aForFrame
, const nsRect
& aDirtyRect
, const nsRect
& aBorderArea
,
752 const nsStyleBorder
& aStyleBorder
, Sides aSkipSides
, bool* aNeedsClip
) {
753 nsMargin border
= aStyleBorder
.GetComputedBorder();
755 // Compute the outermost boundary of the area that might be painted.
756 // Same coordinate space as aBorderArea & aBGClipRect.
757 nsRect joinedBorderArea
= nsCSSRendering::BoxDecorationRectForBorder(
758 aForFrame
, aBorderArea
, aSkipSides
, &aStyleBorder
);
759 RectCornerRadii bgRadii
;
760 ::GetRadii(aForFrame
, aStyleBorder
, aBorderArea
, joinedBorderArea
, &bgRadii
);
762 PrintAsFormatString(" joinedBorderArea: %d %d %d %d\n", joinedBorderArea
.x
,
763 joinedBorderArea
.y
, joinedBorderArea
.width
,
764 joinedBorderArea
.height
);
767 if (nsCSSRendering::IsBoxDecorationSlice(aStyleBorder
)) {
768 if (joinedBorderArea
.IsEqualEdges(aBorderArea
)) {
769 // No need for a clip, just skip the sides we don't want.
770 border
.ApplySkipSides(aSkipSides
);
772 // We're drawing borders around the joined continuation boxes so we need
773 // to clip that to the slice that we want for this frame.
777 MOZ_ASSERT(joinedBorderArea
.IsEqualEdges(aBorderArea
),
778 "Should use aBorderArea for box-decoration-break:clone");
780 aForFrame
->GetSkipSides().IsEmpty() ||
781 aForFrame
->IsTrueOverflowContainer() ||
782 aForFrame
->IsColumnSetFrame(), // a little broader than column-rule
783 "Should not skip sides for box-decoration-break:clone except "
784 "::first-letter/line continuations or other frame types that "
785 "don't have borders but those shouldn't reach this point. "
786 "Overflow containers do reach this point though, as does "
787 "column-rule drawing (which always involves a columnset).");
788 border
.ApplySkipSides(aSkipSides
);
791 // Convert to dev pixels.
792 nscoord oneDevPixel
= aPresContext
->DevPixelsToAppUnits(1);
793 Rect joinedBorderAreaPx
= NSRectToRect(joinedBorderArea
, oneDevPixel
);
794 Float borderWidths
[4] = {
795 Float(border
.top
) / oneDevPixel
, Float(border
.right
) / oneDevPixel
,
796 Float(border
.bottom
) / oneDevPixel
, Float(border
.left
) / oneDevPixel
};
797 Rect dirtyRect
= NSRectToRect(aDirtyRect
, oneDevPixel
);
799 StyleBorderStyle borderStyles
[4];
800 nscolor borderColors
[4];
802 // pull out styles, colors
803 for (const auto i
: mozilla::AllPhysicalSides()) {
804 borderStyles
[i
] = aStyleBorder
.GetBorderStyle(i
);
805 borderColors
[i
] = aStyleBorder
.BorderColorFor(i
).CalcColor(*aStyle
);
809 " borderStyles: %d %d %d %d\n", static_cast<int>(borderStyles
[0]),
810 static_cast<int>(borderStyles
[1]), static_cast<int>(borderStyles
[2]),
811 static_cast<int>(borderStyles
[3]));
813 return nsCSSBorderRenderer(
814 aPresContext
, aDrawTarget
, dirtyRect
, joinedBorderAreaPx
, borderStyles
,
815 borderWidths
, bgRadii
, borderColors
, !aForFrame
->BackfaceIsHidden(),
816 *aNeedsClip
? Some(NSRectToRect(aBorderArea
, oneDevPixel
)) : Nothing());
819 ImgDrawResult
nsCSSRendering::PaintBorderWithStyleBorder(
820 nsPresContext
* aPresContext
, gfxContext
& aRenderingContext
,
821 nsIFrame
* aForFrame
, const nsRect
& aDirtyRect
, const nsRect
& aBorderArea
,
822 const nsStyleBorder
& aStyleBorder
, ComputedStyle
* aStyle
,
823 PaintBorderFlags aFlags
, Sides aSkipSides
) {
824 DrawTarget
& aDrawTarget
= *aRenderingContext
.GetDrawTarget();
826 PrintAsStringNewline("++ PaintBorder");
828 // Check to see if we have an appearance defined. If so, we let the theme
829 // renderer draw the border. DO not get the data from aForFrame, since the
830 // passed in ComputedStyle may be different! Always use |aStyle|!
831 StyleAppearance appearance
= aStyle
->StyleDisplay()->EffectiveAppearance();
832 if (appearance
!= StyleAppearance::None
) {
833 nsITheme
* theme
= aPresContext
->Theme();
834 if (theme
->ThemeSupportsWidget(aPresContext
, aForFrame
, appearance
)) {
835 return ImgDrawResult::SUCCESS
; // Let the theme handle it.
839 if (!aStyleBorder
.mBorderImageSource
.IsNone()) {
840 ImgDrawResult result
= ImgDrawResult::SUCCESS
;
842 uint32_t irFlags
= 0;
843 if (aFlags
& PaintBorderFlags::SyncDecodeImages
) {
844 irFlags
|= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES
;
847 // Creating the border image renderer will request a decode, and we rely on
849 Maybe
<nsCSSBorderImageRenderer
> renderer
=
850 nsCSSBorderImageRenderer::CreateBorderImageRenderer(
851 aPresContext
, aForFrame
, aBorderArea
, aStyleBorder
, aDirtyRect
,
852 aSkipSides
, irFlags
, &result
);
853 // renderer was created successfully, which means border image is ready to
856 MOZ_ASSERT(result
== ImgDrawResult::SUCCESS
);
857 return renderer
->DrawBorderImage(aPresContext
, aRenderingContext
,
858 aForFrame
, aDirtyRect
);
862 ImgDrawResult result
= ImgDrawResult::SUCCESS
;
864 // If we had a border-image, but it wasn't loaded, then we should return
865 // ImgDrawResult::NOT_READY; we'll want to try again if we do a paint with
866 // sync decoding enabled.
867 if (!aStyleBorder
.mBorderImageSource
.IsNone()) {
868 result
= ImgDrawResult::NOT_READY
;
871 nsMargin border
= aStyleBorder
.GetComputedBorder();
872 if (0 == border
.left
&& 0 == border
.right
&& 0 == border
.top
&&
873 0 == border
.bottom
) {
878 bool needsClip
= false;
879 nsCSSBorderRenderer br
= ConstructBorderRenderer(
880 aPresContext
, aStyle
, &aDrawTarget
, aForFrame
, aDirtyRect
, aBorderArea
,
881 aStyleBorder
, aSkipSides
, &needsClip
);
883 aDrawTarget
.PushClipRect(NSRectToSnappedRect(
884 aBorderArea
, aForFrame
->PresContext()->AppUnitsPerDevPixel(),
891 aDrawTarget
.PopClip();
894 PrintAsStringNewline();
899 Maybe
<nsCSSBorderRenderer
> nsCSSRendering::CreateBorderRendererWithStyleBorder(
900 nsPresContext
* aPresContext
, DrawTarget
* aDrawTarget
, nsIFrame
* aForFrame
,
901 const nsRect
& aDirtyRect
, const nsRect
& aBorderArea
,
902 const nsStyleBorder
& aStyleBorder
, ComputedStyle
* aStyle
,
903 bool* aOutBorderIsEmpty
, Sides aSkipSides
) {
904 if (!aStyleBorder
.mBorderImageSource
.IsNone()) {
907 return CreateNullBorderRendererWithStyleBorder(
908 aPresContext
, aDrawTarget
, aForFrame
, aDirtyRect
, aBorderArea
,
909 aStyleBorder
, aStyle
, aOutBorderIsEmpty
, aSkipSides
);
912 Maybe
<nsCSSBorderRenderer
>
913 nsCSSRendering::CreateNullBorderRendererWithStyleBorder(
914 nsPresContext
* aPresContext
, DrawTarget
* aDrawTarget
, nsIFrame
* aForFrame
,
915 const nsRect
& aDirtyRect
, const nsRect
& aBorderArea
,
916 const nsStyleBorder
& aStyleBorder
, ComputedStyle
* aStyle
,
917 bool* aOutBorderIsEmpty
, Sides aSkipSides
) {
918 StyleAppearance appearance
= aStyle
->StyleDisplay()->EffectiveAppearance();
919 if (appearance
!= StyleAppearance::None
) {
920 nsITheme
* theme
= aPresContext
->Theme();
921 if (theme
->ThemeSupportsWidget(aPresContext
, aForFrame
, appearance
)) {
926 nsMargin border
= aStyleBorder
.GetComputedBorder();
927 if (0 == border
.left
&& 0 == border
.right
&& 0 == border
.top
&&
928 0 == border
.bottom
) {
930 if (aOutBorderIsEmpty
) {
931 *aOutBorderIsEmpty
= true;
936 bool needsClip
= false;
937 nsCSSBorderRenderer br
= ConstructBorderRenderer(
938 aPresContext
, aStyle
, aDrawTarget
, aForFrame
, aDirtyRect
, aBorderArea
,
939 aStyleBorder
, aSkipSides
, &needsClip
);
943 Maybe
<nsCSSBorderRenderer
>
944 nsCSSRendering::CreateBorderRendererForNonThemedOutline(
945 nsPresContext
* aPresContext
, DrawTarget
* aDrawTarget
, nsIFrame
* aForFrame
,
946 const nsRect
& aDirtyRect
, const nsRect
& aInnerRect
, ComputedStyle
* aStyle
) {
947 // Get our ComputedStyle's color struct.
948 const nsStyleOutline
* ourOutline
= aStyle
->StyleOutline();
949 if (!ourOutline
->ShouldPaintOutline()) {
954 const nscoord offset
= ourOutline
->mOutlineOffset
.ToAppUnits();
955 nsRect innerRect
= aInnerRect
;
956 innerRect
.Inflate(offset
);
958 // If the dirty rect is completely inside the border area (e.g., only the
959 // content is being painted), then we can skip out now
960 // XXX this isn't exactly true for rounded borders, where the inside curves
961 // may encroach into the content area. A safer calculation would be to
962 // shorten insideRect by the radius one each side before performing this test.
963 if (innerRect
.Contains(aDirtyRect
)) {
967 nscoord width
= ourOutline
->GetOutlineWidth();
969 StyleBorderStyle outlineStyle
;
970 // Themed outlines are handled by our callers, if supported.
971 if (ourOutline
->mOutlineStyle
.IsAuto()) {
973 return Nothing(); // empty outline
975 // http://dev.w3.org/csswg/css-ui/#outline
976 // "User agents may treat 'auto' as 'solid'."
977 outlineStyle
= StyleBorderStyle::Solid
;
979 outlineStyle
= ourOutline
->mOutlineStyle
.AsBorderStyle();
982 RectCornerRadii outlineRadii
;
983 nsRect outerRect
= innerRect
;
984 outerRect
.Inflate(width
);
986 const nscoord oneDevPixel
= aPresContext
->AppUnitsPerDevPixel();
987 Rect
oRect(NSRectToRect(outerRect
, oneDevPixel
));
989 const Float outlineWidths
[4] = {
990 Float(width
) / oneDevPixel
, Float(width
) / oneDevPixel
,
991 Float(width
) / oneDevPixel
, Float(width
) / oneDevPixel
};
994 nscoord twipsRadii
[8];
996 // get the radius for our outline
997 if (StaticPrefs::layout_css_outline_follows_border_radius_enabled()) {
998 if (aForFrame
->GetBorderRadii(twipsRadii
)) {
999 RectCornerRadii innerRadii
;
1000 ComputePixelRadii(twipsRadii
, oneDevPixel
, &innerRadii
);
1002 Float devPixelOffset
= aPresContext
->AppUnitsToFloatDevPixels(offset
);
1003 const Float widths
[4] = {
1004 outlineWidths
[0] + devPixelOffset
, outlineWidths
[1] + devPixelOffset
,
1005 outlineWidths
[2] + devPixelOffset
, outlineWidths
[3] + devPixelOffset
};
1006 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii
, widths
, &outlineRadii
);
1009 nsIFrame::ComputeBorderRadii(ourOutline
->mOutlineRadius
,
1010 aForFrame
->GetSize(), outerRect
.Size(),
1011 Sides(), twipsRadii
);
1012 ComputePixelRadii(twipsRadii
, oneDevPixel
, &outlineRadii
);
1015 StyleBorderStyle outlineStyles
[4] = {outlineStyle
, outlineStyle
, outlineStyle
,
1018 // This handles treating the initial color as 'currentColor'; if we
1019 // ever want 'invert' back we'll need to do a bit of work here too.
1020 nscolor outlineColor
=
1021 aStyle
->GetVisitedDependentColor(&nsStyleOutline::mOutlineColor
);
1022 nscolor outlineColors
[4] = {outlineColor
, outlineColor
, outlineColor
,
1025 Rect dirtyRect
= NSRectToRect(aDirtyRect
, oneDevPixel
);
1027 return Some(nsCSSBorderRenderer(
1028 aPresContext
, aDrawTarget
, dirtyRect
, oRect
, outlineStyles
, outlineWidths
,
1029 outlineRadii
, outlineColors
, !aForFrame
->BackfaceIsHidden(), Nothing()));
1032 void nsCSSRendering::PaintNonThemedOutline(nsPresContext
* aPresContext
,
1033 gfxContext
& aRenderingContext
,
1034 nsIFrame
* aForFrame
,
1035 const nsRect
& aDirtyRect
,
1036 const nsRect
& aInnerRect
,
1037 ComputedStyle
* aStyle
) {
1038 Maybe
<nsCSSBorderRenderer
> br
= CreateBorderRendererForNonThemedOutline(
1039 aPresContext
, aRenderingContext
.GetDrawTarget(), aForFrame
, aDirtyRect
,
1040 aInnerRect
, aStyle
);
1048 PrintAsStringNewline();
1051 void nsCSSRendering::PaintFocus(nsPresContext
* aPresContext
,
1052 DrawTarget
* aDrawTarget
,
1053 const nsRect
& aFocusRect
, nscolor aColor
) {
1054 nscoord oneCSSPixel
= nsPresContext::CSSPixelsToAppUnits(1);
1055 nscoord oneDevPixel
= aPresContext
->DevPixelsToAppUnits(1);
1057 Rect
focusRect(NSRectToRect(aFocusRect
, oneDevPixel
));
1059 RectCornerRadii focusRadii
;
1061 nscoord twipsRadii
[8] = {0, 0, 0, 0, 0, 0, 0, 0};
1062 ComputePixelRadii(twipsRadii
, oneDevPixel
, &focusRadii
);
1064 Float focusWidths
[4] = {
1065 Float(oneCSSPixel
) / oneDevPixel
, Float(oneCSSPixel
) / oneDevPixel
,
1066 Float(oneCSSPixel
) / oneDevPixel
, Float(oneCSSPixel
) / oneDevPixel
};
1068 StyleBorderStyle focusStyles
[4] = {
1069 StyleBorderStyle::Dotted
, StyleBorderStyle::Dotted
,
1070 StyleBorderStyle::Dotted
, StyleBorderStyle::Dotted
};
1071 nscolor focusColors
[4] = {aColor
, aColor
, aColor
, aColor
};
1073 // Because this renders a dotted border, the background color
1074 // should not be used. Therefore, we provide a value that will
1075 // be blatantly wrong if it ever does get used. (If this becomes
1076 // something that CSS can style, this function will then have access
1077 // to a ComputedStyle and can use the same logic that PaintBorder
1078 // and PaintOutline do.)
1080 // WebRender layers-free mode don't use PaintFocus function. Just assign
1081 // the backface-visibility to true for this case.
1082 nsCSSBorderRenderer
br(aPresContext
, aDrawTarget
, focusRect
, focusRect
,
1083 focusStyles
, focusWidths
, focusRadii
, focusColors
,
1087 PrintAsStringNewline();
1090 // Thebes Border Rendering Code End
1091 //----------------------------------------------------------------------
1093 //----------------------------------------------------------------------
1096 * Helper for ComputeObjectAnchorPoint; parameters are the same as for
1097 * that function, except they're for a single coordinate / a single size
1098 * dimension. (so, x/width vs. y/height)
1100 static void ComputeObjectAnchorCoord(const LengthPercentage
& aCoord
,
1101 const nscoord aOriginBounds
,
1102 const nscoord aImageSize
,
1103 nscoord
* aTopLeftCoord
,
1104 nscoord
* aAnchorPointCoord
) {
1105 nscoord extraSpace
= aOriginBounds
- aImageSize
;
1107 // The anchor-point doesn't care about our image's size; just the size
1108 // of the region we're rendering into.
1109 *aAnchorPointCoord
= aCoord
.Resolve(aOriginBounds
, NSToCoordRoundWithClamp
);
1110 // Adjust aTopLeftCoord by the specified % of the extra space.
1111 *aTopLeftCoord
= aCoord
.Resolve(extraSpace
, NSToCoordRoundWithClamp
);
1114 void nsImageRenderer::ComputeObjectAnchorPoint(const Position
& aPos
,
1115 const nsSize
& aOriginBounds
,
1116 const nsSize
& aImageSize
,
1118 nsPoint
* aAnchorPoint
) {
1119 ComputeObjectAnchorCoord(aPos
.horizontal
, aOriginBounds
.width
,
1120 aImageSize
.width
, &aTopLeft
->x
, &aAnchorPoint
->x
);
1122 ComputeObjectAnchorCoord(aPos
.vertical
, aOriginBounds
.height
,
1123 aImageSize
.height
, &aTopLeft
->y
, &aAnchorPoint
->y
);
1126 auto nsCSSRendering::FindNonTransparentBackgroundFrame(nsIFrame
* aFrame
,
1128 -> NonTransparentBackgroundFrame
{
1129 NS_ASSERTION(aFrame
,
1130 "Cannot find NonTransparentBackgroundFrame in a null frame");
1132 for (nsIFrame
* frame
= aFrame
; frame
;
1133 frame
= nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame
)) {
1134 // No need to call GetVisitedDependentColor because it always uses this
1135 // alpha component anyway.
1136 if (NS_GET_A(frame
->StyleBackground()->BackgroundColor(frame
))) {
1137 return {frame
, false, false};
1140 if (aStopAtThemed
&& frame
->IsThemed()) {
1141 return {frame
, true, false};
1144 if (IsCanvasFrame(frame
)) {
1145 nsIFrame
* bgFrame
= nullptr;
1146 if (FindBackgroundFrame(frame
, &bgFrame
) &&
1147 NS_GET_A(bgFrame
->StyleBackground()->BackgroundColor(bgFrame
))) {
1148 return {bgFrame
, false, true};
1156 // Returns true if aFrame is a canvas frame.
1157 // We need to treat the viewport as canvas because, even though
1158 // it does not actually paint a background, we need to get the right
1159 // background style so we correctly detect transparent documents.
1160 bool nsCSSRendering::IsCanvasFrame(const nsIFrame
* aFrame
) {
1161 LayoutFrameType frameType
= aFrame
->Type();
1162 return frameType
== LayoutFrameType::Canvas
||
1163 frameType
== LayoutFrameType::XULRoot
||
1164 frameType
== LayoutFrameType::PageContent
||
1165 frameType
== LayoutFrameType::Viewport
;
1168 nsIFrame
* nsCSSRendering::FindBackgroundStyleFrame(nsIFrame
* aForFrame
) {
1169 const nsStyleBackground
* result
= aForFrame
->StyleBackground();
1171 // Check if we need to do propagation from BODY rather than HTML.
1172 if (!result
->IsTransparent(aForFrame
)) {
1176 nsIContent
* content
= aForFrame
->GetContent();
1177 // The root element content can't be null. We wouldn't know what
1178 // frame to create for aFrame.
1179 // Use |OwnerDoc| so it works during destruction.
1184 Document
* document
= content
->OwnerDoc();
1186 dom::Element
* bodyContent
= document
->GetBodyElement();
1187 // We need to null check the body node (bug 118829) since
1188 // there are cases, thanks to the fix for bug 5569, where we
1189 // will reflow a document with no body. In particular, if a
1190 // SCRIPT element in the head blocks the parser and then has a
1191 // SCRIPT that does "document.location.href = 'foo'", then
1192 // nsParser::Terminate will call |DidBuildModel| methods
1193 // through to the content sink, which will call |StartLayout|
1194 // and thus |Initialize| on the pres shell. See bug 119351
1195 // for the ugly details.
1200 nsIFrame
* bodyFrame
= bodyContent
->GetPrimaryFrame();
1205 return nsLayoutUtils::GetStyleFrame(bodyFrame
);
1209 * |FindBackground| finds the correct style data to use to paint the
1210 * background. It is responsible for handling the following two
1211 * statements in section 14.2 of CSS2:
1213 * The background of the box generated by the root element covers the
1216 * For HTML documents, however, we recommend that authors specify the
1217 * background for the BODY element rather than the HTML element. User
1218 * agents should observe the following precedence rules to fill in the
1219 * background: if the value of the 'background' property for the HTML
1220 * element is different from 'transparent' then use it, else use the
1221 * value of the 'background' property for the BODY element. If the
1222 * resulting value is 'transparent', the rendering is undefined.
1224 * Thus, in our implementation, it is responsible for ensuring that:
1225 * + we paint the correct background on the |nsCanvasFrame|,
1226 * |nsRootBoxFrame|, or |nsPageFrame|,
1227 * + we don't paint the background on the root element, and
1228 * + we don't paint the background on the BODY element in *some* cases,
1229 * and for SGML-based HTML documents only.
1231 * |FindBackground| returns true if a background should be painted, and
1232 * the resulting ComputedStyle to use for the background information
1233 * will be filled in to |aBackground|.
1235 ComputedStyle
* nsCSSRendering::FindRootFrameBackground(nsIFrame
* aForFrame
) {
1236 return FindBackgroundStyleFrame(aForFrame
)->Style();
1239 inline bool FindElementBackground(const nsIFrame
* aForFrame
,
1240 nsIFrame
* aRootElementFrame
) {
1241 if (aForFrame
== aRootElementFrame
) {
1242 // We must have propagated our background to the viewport or canvas. Abort.
1246 // Return true unless the frame is for a BODY element whose background
1247 // was propagated to the viewport.
1249 nsIContent
* content
= aForFrame
->GetContent();
1250 if (!content
|| content
->NodeInfo()->NameAtom() != nsGkAtoms::body
)
1251 return true; // not frame for a "body" element
1252 // It could be a non-HTML "body" element but that's OK, we'd fail the
1253 // bodyContent check below
1255 if (aForFrame
->Style()->GetPseudoType() != PseudoStyleType::NotPseudo
) {
1256 return true; // A pseudo-element frame.
1259 // We should only look at the <html> background if we're in an HTML document
1260 Document
* document
= content
->OwnerDoc();
1262 dom::Element
* bodyContent
= document
->GetBodyElement();
1263 if (bodyContent
!= content
)
1264 return true; // this wasn't the background that was propagated
1266 // This can be called even when there's no root element yet, during frame
1267 // construction, via nsLayoutUtils::FrameHasTransparency and
1268 // nsContainerFrame::SyncFrameViewProperties.
1269 if (!aRootElementFrame
) {
1273 const nsStyleBackground
* htmlBG
= aRootElementFrame
->StyleBackground();
1274 return !htmlBG
->IsTransparent(aRootElementFrame
);
1277 bool nsCSSRendering::FindBackgroundFrame(const nsIFrame
* aForFrame
,
1278 nsIFrame
** aBackgroundFrame
) {
1279 nsIFrame
* rootElementFrame
=
1280 aForFrame
->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
1281 if (IsCanvasFrame(aForFrame
)) {
1282 *aBackgroundFrame
= FindCanvasBackgroundFrame(aForFrame
, rootElementFrame
);
1286 *aBackgroundFrame
= const_cast<nsIFrame
*>(aForFrame
);
1287 return FindElementBackground(aForFrame
, rootElementFrame
);
1290 bool nsCSSRendering::FindBackground(const nsIFrame
* aForFrame
,
1291 ComputedStyle
** aBackgroundSC
) {
1292 nsIFrame
* backgroundFrame
= nullptr;
1293 if (FindBackgroundFrame(aForFrame
, &backgroundFrame
)) {
1294 *aBackgroundSC
= backgroundFrame
->Style();
1300 void nsCSSRendering::BeginFrameTreesLocked() { ++gFrameTreeLockCount
; }
1302 void nsCSSRendering::EndFrameTreesLocked() {
1303 NS_ASSERTION(gFrameTreeLockCount
> 0, "Unbalanced EndFrameTreeLocked");
1304 --gFrameTreeLockCount
;
1305 if (gFrameTreeLockCount
== 0) {
1306 gInlineBGData
->Reset();
1310 bool nsCSSRendering::HasBoxShadowNativeTheme(nsIFrame
* aFrame
,
1311 bool& aMaybeHasBorderRadius
) {
1312 const nsStyleDisplay
* styleDisplay
= aFrame
->StyleDisplay();
1313 nsITheme::Transparency transparency
;
1314 if (aFrame
->IsThemed(styleDisplay
, &transparency
)) {
1315 aMaybeHasBorderRadius
= false;
1316 // For opaque (rectangular) theme widgets we can take the generic
1317 // border-box path with border-radius disabled.
1318 return transparency
!= nsITheme::eOpaque
;
1321 aMaybeHasBorderRadius
= true;
1325 gfx::sRGBColor
nsCSSRendering::GetShadowColor(const StyleSimpleShadow
& aShadow
,
1328 // Get the shadow color; if not specified, use the foreground color
1329 nscolor shadowColor
= aShadow
.color
.CalcColor(aFrame
);
1330 sRGBColor color
= sRGBColor::FromABGR(shadowColor
);
1331 color
.a
*= aOpacity
;
1335 nsRect
nsCSSRendering::GetShadowRect(const nsRect
& aFrameArea
,
1336 bool aNativeTheme
, nsIFrame
* aForFrame
) {
1337 nsRect frameRect
= aNativeTheme
? aForFrame
->InkOverflowRectRelativeToSelf() +
1338 aFrameArea
.TopLeft()
1340 Sides skipSides
= aForFrame
->GetSkipSides();
1341 frameRect
= BoxDecorationRectForBorder(aForFrame
, frameRect
, skipSides
);
1343 // Explicitly do not need to account for the spread radius here
1344 // Webrender does it for us or PaintBoxShadow will for non-WR
1348 bool nsCSSRendering::GetBorderRadii(const nsRect
& aFrameRect
,
1349 const nsRect
& aBorderRect
, nsIFrame
* aFrame
,
1350 RectCornerRadii
& aOutRadii
) {
1351 const nscoord oneDevPixel
= aFrame
->PresContext()->DevPixelsToAppUnits(1);
1352 nscoord twipsRadii
[8];
1354 aBorderRect
.Size() == aFrame
->VisualBorderRectRelativeToSelf().Size(),
1356 nsSize sz
= aFrameRect
.Size();
1357 bool hasBorderRadius
= aFrame
->GetBorderRadii(sz
, sz
, Sides(), twipsRadii
);
1358 if (hasBorderRadius
) {
1359 ComputePixelRadii(twipsRadii
, oneDevPixel
, &aOutRadii
);
1362 return hasBorderRadius
;
1365 void nsCSSRendering::PaintBoxShadowOuter(nsPresContext
* aPresContext
,
1366 gfxContext
& aRenderingContext
,
1367 nsIFrame
* aForFrame
,
1368 const nsRect
& aFrameArea
,
1369 const nsRect
& aDirtyRect
,
1371 DrawTarget
& aDrawTarget
= *aRenderingContext
.GetDrawTarget();
1372 auto shadows
= aForFrame
->StyleEffects()->mBoxShadow
.AsSpan();
1373 if (shadows
.IsEmpty()) {
1377 bool hasBorderRadius
;
1378 // mutually exclusive with hasBorderRadius
1379 bool nativeTheme
= HasBoxShadowNativeTheme(aForFrame
, hasBorderRadius
);
1380 const nsStyleDisplay
* styleDisplay
= aForFrame
->StyleDisplay();
1382 nsRect frameRect
= GetShadowRect(aFrameArea
, nativeTheme
, aForFrame
);
1384 // Get any border radius, since box-shadow must also have rounded corners if
1386 RectCornerRadii borderRadii
;
1387 const nscoord oneDevPixel
= aPresContext
->DevPixelsToAppUnits(1);
1388 if (hasBorderRadius
) {
1389 nscoord twipsRadii
[8];
1391 aFrameArea
.Size() == aForFrame
->VisualBorderRectRelativeToSelf().Size(),
1393 nsSize sz
= frameRect
.Size();
1394 hasBorderRadius
= aForFrame
->GetBorderRadii(sz
, sz
, Sides(), twipsRadii
);
1395 if (hasBorderRadius
) {
1396 ComputePixelRadii(twipsRadii
, oneDevPixel
, &borderRadii
);
1400 // We don't show anything that intersects with the frame we're blurring on. So
1401 // tell the blurrer not to do unnecessary work there.
1402 gfxRect skipGfxRect
= ThebesRect(NSRectToRect(frameRect
, oneDevPixel
));
1403 skipGfxRect
.Round();
1404 bool useSkipGfxRect
= true;
1406 // Optimize non-leaf native-themed frames by skipping computing pixels
1407 // in the padding-box. We assume the padding-box is going to be painted
1408 // opaquely for non-leaf frames.
1409 // XXX this may not be a safe assumption; we should make this go away
1410 // by optimizing box-shadow drawing more for the cases where we don't have a
1412 useSkipGfxRect
= !aForFrame
->IsLeaf();
1413 nsRect paddingRect
=
1414 aForFrame
->GetPaddingRectRelativeToSelf() + aFrameArea
.TopLeft();
1415 skipGfxRect
= nsLayoutUtils::RectToGfxRect(paddingRect
, oneDevPixel
);
1416 } else if (hasBorderRadius
) {
1417 skipGfxRect
.Deflate(gfxMargin(
1418 std::max(borderRadii
[C_TL
].height
, borderRadii
[C_TR
].height
), 0,
1419 std::max(borderRadii
[C_BL
].height
, borderRadii
[C_BR
].height
), 0));
1422 for (const StyleBoxShadow
& shadow
: Reversed(shadows
)) {
1427 nsRect shadowRect
= frameRect
;
1428 nsPoint
shadowOffset(shadow
.base
.horizontal
.ToAppUnits(),
1429 shadow
.base
.vertical
.ToAppUnits());
1430 shadowRect
.MoveBy(shadowOffset
);
1431 nscoord shadowSpread
= shadow
.spread
.ToAppUnits();
1433 shadowRect
.Inflate(shadowSpread
);
1436 // shadowRect won't include the blur, so make an extra rect here that
1437 // includes the blur for use in the even-odd rule below.
1438 nsRect shadowRectPlusBlur
= shadowRect
;
1439 nscoord blurRadius
= shadow
.base
.blur
.ToAppUnits();
1440 shadowRectPlusBlur
.Inflate(
1441 nsContextBoxBlur::GetBlurRadiusMargin(blurRadius
, oneDevPixel
));
1443 Rect shadowGfxRectPlusBlur
= NSRectToRect(shadowRectPlusBlur
, oneDevPixel
);
1444 shadowGfxRectPlusBlur
.RoundOut();
1445 MaybeSnapToDevicePixels(shadowGfxRectPlusBlur
, aDrawTarget
, true);
1447 sRGBColor gfxShadowColor
= GetShadowColor(shadow
.base
, aForFrame
, aOpacity
);
1450 nsContextBoxBlur blurringArea
;
1452 // When getting the widget shape from the native theme, we're going
1453 // to draw the widget into the shadow surface to create a mask.
1454 // We need to ensure that there actually *is* a shadow surface
1455 // and that we're not going to draw directly into aRenderingContext.
1456 gfxContext
* shadowContext
= blurringArea
.Init(
1457 shadowRect
, shadowSpread
, blurRadius
, oneDevPixel
, &aRenderingContext
,
1458 aDirtyRect
, useSkipGfxRect
? &skipGfxRect
: nullptr,
1459 nsContextBoxBlur::FORCE_MASK
);
1460 if (!shadowContext
) continue;
1462 MOZ_ASSERT(shadowContext
== blurringArea
.GetContext());
1464 aRenderingContext
.Save();
1465 aRenderingContext
.SetColor(gfxShadowColor
);
1467 // Draw the shape of the frame so it can be blurred. Recall how
1468 // nsContextBoxBlur doesn't make any temporary surfaces if blur is 0 and
1469 // it just returns the original surface? If we have no blur, we're
1470 // painting this fill on the actual content surface (aRenderingContext ==
1471 // shadowContext) which is why we set up the color and clip before doing
1474 // We don't clip the border-box from the shadow, nor any other box.
1475 // We assume that the native theme is going to paint over the shadow.
1477 // Draw the widget shape
1478 gfxContextMatrixAutoSaveRestore
save(shadowContext
);
1479 gfxPoint devPixelOffset
= nsLayoutUtils::PointToGfxPoint(
1480 shadowOffset
, aPresContext
->AppUnitsPerDevPixel());
1481 shadowContext
->SetMatrixDouble(
1482 shadowContext
->CurrentMatrixDouble().PreTranslate(devPixelOffset
));
1484 nsRect nativeRect
= aDirtyRect
;
1485 nativeRect
.MoveBy(-shadowOffset
);
1486 nativeRect
.IntersectRect(frameRect
, nativeRect
);
1487 aPresContext
->Theme()->DrawWidgetBackground(
1488 shadowContext
, aForFrame
, styleDisplay
->EffectiveAppearance(),
1489 aFrameArea
, nativeRect
, nsITheme::DrawOverflow::No
);
1491 blurringArea
.DoPaint();
1492 aRenderingContext
.Restore();
1494 aRenderingContext
.Save();
1497 Rect innerClipRect
= NSRectToRect(frameRect
, oneDevPixel
);
1498 if (!MaybeSnapToDevicePixels(innerClipRect
, aDrawTarget
, true)) {
1499 innerClipRect
.Round();
1502 // Clip out the interior of the frame's border edge so that the shadow
1503 // is only painted outside that area.
1504 RefPtr
<PathBuilder
> builder
=
1505 aDrawTarget
.CreatePathBuilder(FillRule::FILL_EVEN_ODD
);
1506 AppendRectToPath(builder
, shadowGfxRectPlusBlur
);
1507 if (hasBorderRadius
) {
1508 AppendRoundedRectToPath(builder
, innerClipRect
, borderRadii
);
1510 AppendRectToPath(builder
, innerClipRect
);
1512 RefPtr
<Path
> path
= builder
->Finish();
1513 aRenderingContext
.Clip(path
);
1516 // Clip the shadow so that we only get the part that applies to aForFrame.
1517 nsRect fragmentClip
= shadowRectPlusBlur
;
1518 Sides skipSides
= aForFrame
->GetSkipSides();
1519 if (!skipSides
.IsEmpty()) {
1520 if (skipSides
.Left()) {
1521 nscoord xmost
= fragmentClip
.XMost();
1522 fragmentClip
.x
= aFrameArea
.x
;
1523 fragmentClip
.width
= xmost
- fragmentClip
.x
;
1525 if (skipSides
.Right()) {
1526 nscoord xmost
= fragmentClip
.XMost();
1527 nscoord overflow
= xmost
- aFrameArea
.XMost();
1529 fragmentClip
.width
-= overflow
;
1532 if (skipSides
.Top()) {
1533 nscoord ymost
= fragmentClip
.YMost();
1534 fragmentClip
.y
= aFrameArea
.y
;
1535 fragmentClip
.height
= ymost
- fragmentClip
.y
;
1537 if (skipSides
.Bottom()) {
1538 nscoord ymost
= fragmentClip
.YMost();
1539 nscoord overflow
= ymost
- aFrameArea
.YMost();
1541 fragmentClip
.height
-= overflow
;
1545 fragmentClip
= fragmentClip
.Intersect(aDirtyRect
);
1546 aRenderingContext
.Clip(NSRectToSnappedRect(
1547 fragmentClip
, aForFrame
->PresContext()->AppUnitsPerDevPixel(),
1550 RectCornerRadii clipRectRadii
;
1551 if (hasBorderRadius
) {
1552 Float spreadDistance
= Float(shadowSpread
/ oneDevPixel
);
1554 Float borderSizes
[4];
1556 borderSizes
[eSideLeft
] = spreadDistance
;
1557 borderSizes
[eSideTop
] = spreadDistance
;
1558 borderSizes
[eSideRight
] = spreadDistance
;
1559 borderSizes
[eSideBottom
] = spreadDistance
;
1561 nsCSSBorderRenderer::ComputeOuterRadii(borderRadii
, borderSizes
,
1564 nsContextBoxBlur::BlurRectangle(
1565 &aRenderingContext
, shadowRect
, oneDevPixel
,
1566 hasBorderRadius
? &clipRectRadii
: nullptr, blurRadius
,
1567 gfxShadowColor
, aDirtyRect
, skipGfxRect
);
1568 aRenderingContext
.Restore();
1573 nsRect
nsCSSRendering::GetBoxShadowInnerPaddingRect(nsIFrame
* aFrame
,
1574 const nsRect
& aFrameArea
) {
1575 Sides skipSides
= aFrame
->GetSkipSides();
1576 nsRect frameRect
= BoxDecorationRectForBorder(aFrame
, aFrameArea
, skipSides
);
1578 nsRect paddingRect
= frameRect
;
1579 nsMargin border
= aFrame
->GetUsedBorder();
1580 paddingRect
.Deflate(border
);
1584 bool nsCSSRendering::ShouldPaintBoxShadowInner(nsIFrame
* aFrame
) {
1585 const Span
<const StyleBoxShadow
> shadows
=
1586 aFrame
->StyleEffects()->mBoxShadow
.AsSpan();
1587 if (shadows
.IsEmpty()) {
1591 if (aFrame
->IsThemed() && aFrame
->GetContent() &&
1592 !nsContentUtils::IsChromeDoc(aFrame
->GetContent()->GetComposedDoc())) {
1593 // There's no way of getting hold of a shape corresponding to a
1594 // "padding-box" for native-themed widgets, so just don't draw
1595 // inner box-shadows for them. But we allow chrome to paint inner
1596 // box shadows since chrome can be aware of the platform theme.
1603 bool nsCSSRendering::GetShadowInnerRadii(nsIFrame
* aFrame
,
1604 const nsRect
& aFrameArea
,
1605 RectCornerRadii
& aOutInnerRadii
) {
1606 // Get any border radius, since box-shadow must also have rounded corners
1607 // if the frame does.
1608 nscoord twipsRadii
[8];
1610 BoxDecorationRectForBorder(aFrame
, aFrameArea
, aFrame
->GetSkipSides());
1611 nsSize sz
= frameRect
.Size();
1612 nsMargin border
= aFrame
->GetUsedBorder();
1613 aFrame
->GetBorderRadii(sz
, sz
, Sides(), twipsRadii
);
1614 const nscoord oneDevPixel
= aFrame
->PresContext()->DevPixelsToAppUnits(1);
1616 RectCornerRadii borderRadii
;
1618 const bool hasBorderRadius
=
1619 GetBorderRadii(frameRect
, aFrameArea
, aFrame
, borderRadii
);
1621 if (hasBorderRadius
) {
1622 ComputePixelRadii(twipsRadii
, oneDevPixel
, &borderRadii
);
1624 Float borderSizes
[4] = {
1625 Float(border
.top
) / oneDevPixel
, Float(border
.right
) / oneDevPixel
,
1626 Float(border
.bottom
) / oneDevPixel
, Float(border
.left
) / oneDevPixel
};
1627 nsCSSBorderRenderer::ComputeInnerRadii(borderRadii
, borderSizes
,
1631 return hasBorderRadius
;
1634 void nsCSSRendering::PaintBoxShadowInner(nsPresContext
* aPresContext
,
1635 gfxContext
& aRenderingContext
,
1636 nsIFrame
* aForFrame
,
1637 const nsRect
& aFrameArea
) {
1638 if (!ShouldPaintBoxShadowInner(aForFrame
)) {
1642 const Span
<const StyleBoxShadow
> shadows
=
1643 aForFrame
->StyleEffects()->mBoxShadow
.AsSpan();
1645 aForFrame
->IsFieldSetFrame() || aFrameArea
.Size() == aForFrame
->GetSize(),
1648 nsRect paddingRect
= GetBoxShadowInnerPaddingRect(aForFrame
, aFrameArea
);
1650 RectCornerRadii innerRadii
;
1651 bool hasBorderRadius
= GetShadowInnerRadii(aForFrame
, aFrameArea
, innerRadii
);
1653 const nscoord oneDevPixel
= aPresContext
->DevPixelsToAppUnits(1);
1655 for (const StyleBoxShadow
& shadow
: Reversed(shadows
)) {
1656 if (!shadow
.inset
) {
1660 // shadowPaintRect: the area to paint on the temp surface
1661 // shadowClipRect: the area on the temporary surface within shadowPaintRect
1662 // that we will NOT paint in
1663 nscoord blurRadius
= shadow
.base
.blur
.ToAppUnits();
1664 nsMargin blurMargin
=
1665 nsContextBoxBlur::GetBlurRadiusMargin(blurRadius
, oneDevPixel
);
1666 nsRect shadowPaintRect
= paddingRect
;
1667 shadowPaintRect
.Inflate(blurMargin
);
1669 // Round the spread radius to device pixels (by truncation).
1670 // This mostly matches what we do for borders, except that we don't round
1671 // up values between zero and one device pixels to one device pixel.
1672 // This way of rounding is symmetric around zero, which makes sense for
1673 // the spread radius.
1674 int32_t spreadDistance
= shadow
.spread
.ToAppUnits() / oneDevPixel
;
1675 nscoord spreadDistanceAppUnits
=
1676 aPresContext
->DevPixelsToAppUnits(spreadDistance
);
1678 nsRect shadowClipRect
= paddingRect
;
1679 shadowClipRect
.MoveBy(shadow
.base
.horizontal
.ToAppUnits(),
1680 shadow
.base
.vertical
.ToAppUnits());
1681 shadowClipRect
.Deflate(spreadDistanceAppUnits
, spreadDistanceAppUnits
);
1683 Rect shadowClipGfxRect
= NSRectToRect(shadowClipRect
, oneDevPixel
);
1684 shadowClipGfxRect
.Round();
1686 RectCornerRadii clipRectRadii
;
1687 if (hasBorderRadius
) {
1688 // Calculate the radii the inner clipping rect will have
1689 Float borderSizes
[4] = {0, 0, 0, 0};
1691 // See PaintBoxShadowOuter and bug 514670
1692 if (innerRadii
[C_TL
].width
> 0 || innerRadii
[C_BL
].width
> 0) {
1693 borderSizes
[eSideLeft
] = spreadDistance
;
1696 if (innerRadii
[C_TL
].height
> 0 || innerRadii
[C_TR
].height
> 0) {
1697 borderSizes
[eSideTop
] = spreadDistance
;
1700 if (innerRadii
[C_TR
].width
> 0 || innerRadii
[C_BR
].width
> 0) {
1701 borderSizes
[eSideRight
] = spreadDistance
;
1704 if (innerRadii
[C_BL
].height
> 0 || innerRadii
[C_BR
].height
> 0) {
1705 borderSizes
[eSideBottom
] = spreadDistance
;
1708 nsCSSBorderRenderer::ComputeInnerRadii(innerRadii
, borderSizes
,
1712 // Set the "skip rect" to the area within the frame that we don't paint in,
1713 // including after blurring.
1714 nsRect skipRect
= shadowClipRect
;
1715 skipRect
.Deflate(blurMargin
);
1716 gfxRect skipGfxRect
= nsLayoutUtils::RectToGfxRect(skipRect
, oneDevPixel
);
1717 if (hasBorderRadius
) {
1718 skipGfxRect
.Deflate(gfxMargin(
1719 std::max(clipRectRadii
[C_TL
].height
, clipRectRadii
[C_TR
].height
), 0,
1720 std::max(clipRectRadii
[C_BL
].height
, clipRectRadii
[C_BR
].height
), 0));
1723 // When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area
1724 // unchanged. And by construction the gfxSkipRect is not touched by the
1725 // rendered shadow (even after blurring), so those pixels must be completely
1726 // transparent in the shadow, so drawing them changes nothing.
1727 DrawTarget
* drawTarget
= aRenderingContext
.GetDrawTarget();
1729 // Clip the context to the area of the frame's padding rect, so no part of
1730 // the shadow is painted outside. Also cut out anything beyond where the
1731 // inset shadow will be.
1732 Rect shadowGfxRect
= NSRectToRect(paddingRect
, oneDevPixel
);
1733 shadowGfxRect
.Round();
1735 sRGBColor shadowColor
= GetShadowColor(shadow
.base
, aForFrame
, 1.0);
1736 aRenderingContext
.Save();
1738 // This clips the outside border radius.
1739 // clipRectRadii is the border radius inside the inset shadow.
1740 if (hasBorderRadius
) {
1741 RefPtr
<Path
> roundedRect
=
1742 MakePathForRoundedRect(*drawTarget
, shadowGfxRect
, innerRadii
);
1743 aRenderingContext
.Clip(roundedRect
);
1745 aRenderingContext
.Clip(shadowGfxRect
);
1748 nsContextBoxBlur insetBoxBlur
;
1750 nsLayoutUtils::RectToGfxRect(shadowPaintRect
, oneDevPixel
);
1751 Point
shadowOffset(shadow
.base
.horizontal
.ToAppUnits() / oneDevPixel
,
1752 shadow
.base
.vertical
.ToAppUnits() / oneDevPixel
);
1754 insetBoxBlur
.InsetBoxBlur(
1755 &aRenderingContext
, ToRect(destRect
), shadowClipGfxRect
, shadowColor
,
1756 blurRadius
, spreadDistanceAppUnits
, oneDevPixel
, hasBorderRadius
,
1757 clipRectRadii
, ToRect(skipGfxRect
), shadowOffset
);
1758 aRenderingContext
.Restore();
1763 nsCSSRendering::PaintBGParams
nsCSSRendering::PaintBGParams::ForAllLayers(
1764 nsPresContext
& aPresCtx
, const nsRect
& aDirtyRect
,
1765 const nsRect
& aBorderArea
, nsIFrame
* aFrame
, uint32_t aPaintFlags
,
1769 PaintBGParams
result(aPresCtx
, aDirtyRect
, aBorderArea
, aFrame
, aPaintFlags
,
1770 -1, CompositionOp::OP_OVER
, aOpacity
);
1776 nsCSSRendering::PaintBGParams
nsCSSRendering::PaintBGParams::ForSingleLayer(
1777 nsPresContext
& aPresCtx
, const nsRect
& aDirtyRect
,
1778 const nsRect
& aBorderArea
, nsIFrame
* aFrame
, uint32_t aPaintFlags
,
1779 int32_t aLayer
, CompositionOp aCompositionOp
, float aOpacity
) {
1780 MOZ_ASSERT(aFrame
&& (aLayer
!= -1));
1782 PaintBGParams
result(aPresCtx
, aDirtyRect
, aBorderArea
, aFrame
, aPaintFlags
,
1783 aLayer
, aCompositionOp
, aOpacity
);
1788 ImgDrawResult
nsCSSRendering::PaintStyleImageLayer(const PaintBGParams
& aParams
,
1789 gfxContext
& aRenderingCtx
) {
1790 AUTO_PROFILER_LABEL("nsCSSRendering::PaintStyleImageLayer", GRAPHICS
);
1792 MOZ_ASSERT(aParams
.frame
,
1793 "Frame is expected to be provided to PaintStyleImageLayer");
1796 if (!FindBackground(aParams
.frame
, &sc
)) {
1797 // We don't want to bail out if moz-appearance is set on a root
1798 // node. If it has a parent content node, bail because it's not
1799 // a root, otherwise keep going in order to let the theme stuff
1800 // draw the background. The canvas really should be drawing the
1801 // bg, but there's no way to hook that up via css.
1802 if (!aParams
.frame
->StyleDisplay()->HasAppearance()) {
1803 return ImgDrawResult::SUCCESS
;
1806 nsIContent
* content
= aParams
.frame
->GetContent();
1807 if (!content
|| content
->GetParent()) {
1808 return ImgDrawResult::SUCCESS
;
1811 sc
= aParams
.frame
->Style();
1814 return PaintStyleImageLayerWithSC(aParams
, aRenderingCtx
, sc
,
1815 *aParams
.frame
->StyleBorder());
1818 bool nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer(
1819 LayerManager
* aManager
, nsPresContext
& aPresCtx
, nsIFrame
* aFrame
,
1820 const nsStyleBackground
* aBackgroundStyle
, int32_t aLayer
,
1821 uint32_t aPaintFlags
) {
1822 if (!aBackgroundStyle
) {
1826 MOZ_ASSERT(aFrame
&& aLayer
>= 0 &&
1827 (uint32_t)aLayer
< aBackgroundStyle
->mImage
.mLayers
.Length());
1829 // We cannot draw native themed backgrounds
1830 StyleAppearance appearance
= aFrame
->StyleDisplay()->EffectiveAppearance();
1831 if (appearance
!= StyleAppearance::None
) {
1832 nsITheme
* theme
= aPresCtx
.Theme();
1833 if (theme
->ThemeSupportsWidget(&aPresCtx
, aFrame
, appearance
)) {
1838 // We only support painting gradients and image for a single style image
1839 // layer, and we don't support crop-rects.
1840 const auto& styleImage
=
1841 aBackgroundStyle
->mImage
.mLayers
[aLayer
].mImage
.FinalImage();
1842 if (styleImage
.IsImageRequestType()) {
1843 if (styleImage
.IsRect()) {
1847 imgRequestProxy
* requestProxy
= styleImage
.GetImageRequest();
1848 if (!requestProxy
) {
1852 uint32_t imageFlags
= imgIContainer::FLAG_NONE
;
1853 if (aPaintFlags
& nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES
) {
1854 imageFlags
|= imgIContainer::FLAG_SYNC_DECODE
;
1857 nsCOMPtr
<imgIContainer
> srcImage
;
1858 requestProxy
->GetImage(getter_AddRefs(srcImage
));
1860 !srcImage
->IsImageContainerAvailable(aManager
, imageFlags
)) {
1867 if (styleImage
.IsGradient()) {
1874 ImgDrawResult
nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer(
1875 const PaintBGParams
& aParams
, mozilla::wr::DisplayListBuilder
& aBuilder
,
1876 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
1877 const mozilla::layers::StackingContextHelper
& aSc
,
1878 mozilla::layers::RenderRootStateManager
* aManager
, nsDisplayItem
* aItem
) {
1879 MOZ_ASSERT(aParams
.frame
,
1880 "Frame is expected to be provided to "
1881 "BuildWebRenderDisplayItemsForStyleImageLayer");
1884 if (!FindBackground(aParams
.frame
, &sc
)) {
1885 // We don't want to bail out if moz-appearance is set on a root
1886 // node. If it has a parent content node, bail because it's not
1887 // a root, otherwise keep going in order to let the theme stuff
1888 // draw the background. The canvas really should be drawing the
1889 // bg, but there's no way to hook that up via css.
1890 if (!aParams
.frame
->StyleDisplay()->HasAppearance()) {
1891 return ImgDrawResult::SUCCESS
;
1894 nsIContent
* content
= aParams
.frame
->GetContent();
1895 if (!content
|| content
->GetParent()) {
1896 return ImgDrawResult::SUCCESS
;
1899 sc
= aParams
.frame
->Style();
1901 return BuildWebRenderDisplayItemsForStyleImageLayerWithSC(
1902 aParams
, aBuilder
, aResources
, aSc
, aManager
, aItem
, sc
,
1903 *aParams
.frame
->StyleBorder());
1906 static bool IsOpaqueBorderEdge(const nsStyleBorder
& aBorder
,
1907 mozilla::Side aSide
) {
1908 if (aBorder
.GetComputedBorder().Side(aSide
) == 0) return true;
1909 switch (aBorder
.GetBorderStyle(aSide
)) {
1910 case StyleBorderStyle::Solid
:
1911 case StyleBorderStyle::Groove
:
1912 case StyleBorderStyle::Ridge
:
1913 case StyleBorderStyle::Inset
:
1914 case StyleBorderStyle::Outset
:
1920 // If we're using a border image, assume it's not fully opaque,
1921 // because we may not even have the image loaded at this point, and
1922 // even if we did, checking whether the relevant tile is fully
1923 // opaque would be too much work.
1924 if (!aBorder
.mBorderImageSource
.IsNone()) {
1928 StyleColor color
= aBorder
.BorderColorFor(aSide
);
1929 // We don't know the foreground color here, so if it's being used
1930 // we must assume it might be transparent.
1931 return !color
.MaybeTransparent();
1935 * Returns true if all border edges are either missing or opaque.
1937 static bool IsOpaqueBorder(const nsStyleBorder
& aBorder
) {
1938 for (const auto i
: mozilla::AllPhysicalSides()) {
1939 if (!IsOpaqueBorderEdge(aBorder
, i
)) {
1946 static inline void SetupDirtyRects(const nsRect
& aBGClipArea
,
1947 const nsRect
& aCallerDirtyRect
,
1948 nscoord aAppUnitsPerPixel
,
1950 nsRect
* aDirtyRect
, gfxRect
* aDirtyRectGfx
) {
1951 aDirtyRect
->IntersectRect(aBGClipArea
, aCallerDirtyRect
);
1953 // Compute the Thebes equivalent of the dirtyRect.
1954 *aDirtyRectGfx
= nsLayoutUtils::RectToGfxRect(*aDirtyRect
, aAppUnitsPerPixel
);
1955 NS_WARNING_ASSERTION(aDirtyRect
->IsEmpty() || !aDirtyRectGfx
->IsEmpty(),
1956 "converted dirty rect should not be empty");
1957 MOZ_ASSERT(!aDirtyRect
->IsEmpty() || aDirtyRectGfx
->IsEmpty(),
1958 "second should be empty if first is");
1961 static bool IsSVGStyleGeometryBox(StyleGeometryBox aBox
) {
1962 return (aBox
== StyleGeometryBox::FillBox
||
1963 aBox
== StyleGeometryBox::StrokeBox
||
1964 aBox
== StyleGeometryBox::ViewBox
);
1967 static bool IsHTMLStyleGeometryBox(StyleGeometryBox aBox
) {
1968 return (aBox
== StyleGeometryBox::ContentBox
||
1969 aBox
== StyleGeometryBox::PaddingBox
||
1970 aBox
== StyleGeometryBox::BorderBox
||
1971 aBox
== StyleGeometryBox::MarginBox
);
1974 static StyleGeometryBox
ComputeBoxValue(nsIFrame
* aForFrame
,
1975 StyleGeometryBox aBox
) {
1976 if (!aForFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
)) {
1977 // For elements with associated CSS layout box, the values fill-box,
1978 // stroke-box and view-box compute to the initial value of mask-clip.
1979 if (IsSVGStyleGeometryBox(aBox
)) {
1980 return StyleGeometryBox::BorderBox
;
1983 // For SVG elements without associated CSS layout box, the values
1984 // content-box, padding-box, border-box and margin-box compute to fill-box.
1985 if (IsHTMLStyleGeometryBox(aBox
)) {
1986 return StyleGeometryBox::FillBox
;
1993 bool nsCSSRendering::ImageLayerClipState::IsValid() const {
1994 // mDirtyRectInDevPx comes from mDirtyRectInAppUnits. mDirtyRectInAppUnits
1995 // can not be empty if mDirtyRectInDevPx is not.
1996 if (!mDirtyRectInDevPx
.IsEmpty() && mDirtyRectInAppUnits
.IsEmpty()) {
2000 if (mHasRoundedCorners
== mClippedRadii
.IsEmpty()) {
2008 void nsCSSRendering::GetImageLayerClip(
2009 const nsStyleImageLayers::Layer
& aLayer
, nsIFrame
* aForFrame
,
2010 const nsStyleBorder
& aBorder
, const nsRect
& aBorderArea
,
2011 const nsRect
& aCallerDirtyRect
, bool aWillPaintBorder
,
2012 nscoord aAppUnitsPerPixel
,
2013 /* out */ ImageLayerClipState
* aClipState
) {
2014 StyleGeometryBox layerClip
= ComputeBoxValue(aForFrame
, aLayer
.mClip
);
2015 if (IsSVGStyleGeometryBox(layerClip
)) {
2016 MOZ_ASSERT(aForFrame
->IsFrameOfType(nsIFrame::eSVG
) &&
2017 !aForFrame
->IsSVGOuterSVGFrame());
2019 // The coordinate space of clipArea is svg user space.
2020 nsRect clipArea
= nsLayoutUtils::ComputeGeometryBox(aForFrame
, layerClip
);
2022 nsRect strokeBox
= (layerClip
== StyleGeometryBox::StrokeBox
)
2024 : nsLayoutUtils::ComputeGeometryBox(
2025 aForFrame
, StyleGeometryBox::StrokeBox
);
2026 nsRect clipAreaRelativeToStrokeBox
= clipArea
- strokeBox
.TopLeft();
2028 // aBorderArea is the stroke-box area in a coordinate space defined by
2029 // the caller. This coordinate space can be svg user space of aForFrame,
2030 // the space of aForFrame's reference-frame, or anything else.
2032 // Which coordinate space chosen for aBorderArea is not matter. What
2033 // matter is to ensure returning aClipState->mBGClipArea in the consistent
2034 // coordiante space with aBorderArea. So we evaluate the position of clip
2035 // area base on the position of aBorderArea here.
2036 aClipState
->mBGClipArea
=
2037 clipAreaRelativeToStrokeBox
+ aBorderArea
.TopLeft();
2039 SetupDirtyRects(aClipState
->mBGClipArea
, aCallerDirtyRect
,
2040 aAppUnitsPerPixel
, &aClipState
->mDirtyRectInAppUnits
,
2041 &aClipState
->mDirtyRectInDevPx
);
2042 MOZ_ASSERT(aClipState
->IsValid());
2046 if (layerClip
== StyleGeometryBox::NoClip
) {
2047 aClipState
->mBGClipArea
= aCallerDirtyRect
;
2049 SetupDirtyRects(aClipState
->mBGClipArea
, aCallerDirtyRect
,
2050 aAppUnitsPerPixel
, &aClipState
->mDirtyRectInAppUnits
,
2051 &aClipState
->mDirtyRectInDevPx
);
2052 MOZ_ASSERT(aClipState
->IsValid());
2056 MOZ_ASSERT(!aForFrame
->IsFrameOfType(nsIFrame::eSVG
) ||
2057 aForFrame
->IsSVGOuterSVGFrame());
2059 // Compute the outermost boundary of the area that might be painted.
2060 // Same coordinate space as aBorderArea.
2061 Sides skipSides
= aForFrame
->GetSkipSides();
2062 nsRect clipBorderArea
=
2063 BoxDecorationRectForBorder(aForFrame
, aBorderArea
, skipSides
, &aBorder
);
2065 bool haveRoundedCorners
= false;
2066 LayoutFrameType fType
= aForFrame
->Type();
2067 if (fType
!= LayoutFrameType::TableColGroup
&&
2068 fType
!= LayoutFrameType::TableCol
&&
2069 fType
!= LayoutFrameType::TableRow
&&
2070 fType
!= LayoutFrameType::TableRowGroup
) {
2071 haveRoundedCorners
= GetRadii(aForFrame
, aBorder
, aBorderArea
,
2072 clipBorderArea
, aClipState
->mRadii
);
2074 bool isSolidBorder
= aWillPaintBorder
&& IsOpaqueBorder(aBorder
);
2075 if (isSolidBorder
&& layerClip
== StyleGeometryBox::BorderBox
) {
2076 // If we have rounded corners, we need to inflate the background
2077 // drawing area a bit to avoid seams between the border and
2079 layerClip
= haveRoundedCorners
? StyleGeometryBox::MozAlmostPadding
2080 : StyleGeometryBox::PaddingBox
;
2083 aClipState
->mBGClipArea
= clipBorderArea
;
2085 if (aForFrame
->IsScrollFrame() &&
2086 StyleImageLayerAttachment::Local
== aLayer
.mAttachment
) {
2087 // As of this writing, this is still in discussion in the CSS Working Group
2088 // http://lists.w3.org/Archives/Public/www-style/2013Jul/0250.html
2090 // The rectangle for 'background-clip' scrolls with the content,
2091 // but the background is also clipped at a non-scrolling 'padding-box'
2092 // like the content. (See below.)
2093 // Therefore, only 'content-box' makes a difference here.
2094 if (layerClip
== StyleGeometryBox::ContentBox
) {
2095 nsIScrollableFrame
* scrollableFrame
= do_QueryFrame(aForFrame
);
2096 // Clip at a rectangle attached to the scrolled content.
2097 aClipState
->mHasAdditionalBGClipArea
= true;
2098 aClipState
->mAdditionalBGClipArea
=
2099 nsRect(aClipState
->mBGClipArea
.TopLeft() +
2100 scrollableFrame
->GetScrolledFrame()->GetPosition()
2101 // For the dir=rtl case:
2102 + scrollableFrame
->GetScrollRange().TopLeft(),
2103 scrollableFrame
->GetScrolledRect().Size());
2104 nsMargin padding
= aForFrame
->GetUsedPadding();
2105 // padding-bottom is ignored on scrollable frames:
2106 // https://bugzilla.mozilla.org/show_bug.cgi?id=748518
2108 padding
.ApplySkipSides(skipSides
);
2109 aClipState
->mAdditionalBGClipArea
.Deflate(padding
);
2112 // Also clip at a non-scrolling, rounded-corner 'padding-box',
2113 // same as the scrolled content because of the 'overflow' property.
2114 layerClip
= StyleGeometryBox::PaddingBox
;
2117 // See the comment of StyleGeometryBox::Margin.
2118 // Hitting this assertion means we decide to turn on margin-box support for
2119 // positioned mask from CSS parser and style system. In this case, you
2120 // should *inflate* mBGClipArea by the margin returning from
2121 // aForFrame->GetUsedMargin() in the code chunk bellow.
2122 MOZ_ASSERT(layerClip
!= StyleGeometryBox::MarginBox
,
2123 "StyleGeometryBox::MarginBox rendering is not supported yet.\n");
2125 if (layerClip
!= StyleGeometryBox::BorderBox
&&
2126 layerClip
!= StyleGeometryBox::Text
) {
2127 nsMargin border
= aForFrame
->GetUsedBorder();
2128 if (layerClip
== StyleGeometryBox::MozAlmostPadding
) {
2129 // Reduce |border| by 1px (device pixels) on all sides, if
2130 // possible, so that we don't get antialiasing seams between the
2131 // {background|mask} and border.
2132 border
.top
= std::max(0, border
.top
- aAppUnitsPerPixel
);
2133 border
.right
= std::max(0, border
.right
- aAppUnitsPerPixel
);
2134 border
.bottom
= std::max(0, border
.bottom
- aAppUnitsPerPixel
);
2135 border
.left
= std::max(0, border
.left
- aAppUnitsPerPixel
);
2136 } else if (layerClip
!= StyleGeometryBox::PaddingBox
) {
2137 NS_ASSERTION(layerClip
== StyleGeometryBox::ContentBox
,
2138 "unexpected background-clip");
2139 border
+= aForFrame
->GetUsedPadding();
2141 border
.ApplySkipSides(skipSides
);
2142 aClipState
->mBGClipArea
.Deflate(border
);
2144 if (haveRoundedCorners
) {
2145 nsIFrame::InsetBorderRadii(aClipState
->mRadii
, border
);
2149 if (haveRoundedCorners
) {
2150 auto d2a
= aForFrame
->PresContext()->AppUnitsPerDevPixel();
2151 nsCSSRendering::ComputePixelRadii(aClipState
->mRadii
, d2a
,
2152 &aClipState
->mClippedRadii
);
2153 aClipState
->mHasRoundedCorners
= !aClipState
->mClippedRadii
.IsEmpty();
2156 if (!haveRoundedCorners
&& aClipState
->mHasAdditionalBGClipArea
) {
2157 // Do the intersection here to account for the fast path(?) below.
2158 aClipState
->mBGClipArea
=
2159 aClipState
->mBGClipArea
.Intersect(aClipState
->mAdditionalBGClipArea
);
2160 aClipState
->mHasAdditionalBGClipArea
= false;
2163 SetupDirtyRects(aClipState
->mBGClipArea
, aCallerDirtyRect
, aAppUnitsPerPixel
,
2164 &aClipState
->mDirtyRectInAppUnits
,
2165 &aClipState
->mDirtyRectInDevPx
);
2167 MOZ_ASSERT(aClipState
->IsValid());
2170 static void SetupImageLayerClip(nsCSSRendering::ImageLayerClipState
& aClipState
,
2171 gfxContext
* aCtx
, nscoord aAppUnitsPerPixel
,
2172 gfxContextAutoSaveRestore
* aAutoSR
) {
2173 if (aClipState
.mDirtyRectInDevPx
.IsEmpty()) {
2174 // Our caller won't draw anything under this condition, so no need
2179 if (aClipState
.mCustomClip
) {
2180 // We don't support custom clips and rounded corners, arguably a bug, but
2181 // table painting seems to depend on it.
2185 // If we have rounded corners, clip all subsequent drawing to the
2186 // rounded rectangle defined by bgArea and bgRadii (we don't know
2187 // whether the rounded corners intrude on the dirtyRect or not).
2188 // Do not do this if we have a caller-provided clip rect --
2189 // as above with bgArea, arguably a bug, but table painting seems
2192 if (aClipState
.mHasAdditionalBGClipArea
) {
2193 gfxRect bgAreaGfx
= nsLayoutUtils::RectToGfxRect(
2194 aClipState
.mAdditionalBGClipArea
, aAppUnitsPerPixel
);
2196 gfxUtils::ConditionRect(bgAreaGfx
);
2198 aAutoSR
->EnsureSaved(aCtx
);
2199 aCtx
->SnappedClip(bgAreaGfx
);
2202 if (aClipState
.mHasRoundedCorners
) {
2203 Rect bgAreaGfx
= NSRectToRect(aClipState
.mBGClipArea
, aAppUnitsPerPixel
);
2206 if (bgAreaGfx
.IsEmpty()) {
2207 // I think it's become possible to hit this since
2208 // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
2209 NS_WARNING("converted background area should not be empty");
2210 // Make our caller not do anything.
2211 aClipState
.mDirtyRectInDevPx
.SizeTo(gfxSize(0.0, 0.0));
2215 aAutoSR
->EnsureSaved(aCtx
);
2217 RefPtr
<Path
> roundedRect
= MakePathForRoundedRect(
2218 *aCtx
->GetDrawTarget(), bgAreaGfx
, aClipState
.mClippedRadii
);
2219 aCtx
->Clip(roundedRect
);
2223 static void DrawBackgroundColor(nsCSSRendering::ImageLayerClipState
& aClipState
,
2224 gfxContext
* aCtx
, nscoord aAppUnitsPerPixel
) {
2225 if (aClipState
.mDirtyRectInDevPx
.IsEmpty()) {
2226 // Our caller won't draw anything under this condition, so no need
2231 DrawTarget
* drawTarget
= aCtx
->GetDrawTarget();
2233 // We don't support custom clips and rounded corners, arguably a bug, but
2234 // table painting seems to depend on it.
2235 if (!aClipState
.mHasRoundedCorners
|| aClipState
.mCustomClip
) {
2237 aCtx
->SnappedRectangle(aClipState
.mDirtyRectInDevPx
);
2242 Rect bgAreaGfx
= NSRectToRect(aClipState
.mBGClipArea
, aAppUnitsPerPixel
);
2245 if (bgAreaGfx
.IsEmpty()) {
2246 // I think it's become possible to hit this since
2247 // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
2248 NS_WARNING("converted background area should not be empty");
2249 // Make our caller not do anything.
2250 aClipState
.mDirtyRectInDevPx
.SizeTo(gfxSize(0.0, 0.0));
2255 gfxRect dirty
= ThebesRect(bgAreaGfx
).Intersect(aClipState
.mDirtyRectInDevPx
);
2257 aCtx
->SnappedClip(dirty
);
2259 if (aClipState
.mHasAdditionalBGClipArea
) {
2260 gfxRect bgAdditionalAreaGfx
= nsLayoutUtils::RectToGfxRect(
2261 aClipState
.mAdditionalBGClipArea
, aAppUnitsPerPixel
);
2262 bgAdditionalAreaGfx
.Round();
2263 gfxUtils::ConditionRect(bgAdditionalAreaGfx
);
2264 aCtx
->SnappedClip(bgAdditionalAreaGfx
);
2267 RefPtr
<Path
> roundedRect
=
2268 MakePathForRoundedRect(*drawTarget
, bgAreaGfx
, aClipState
.mClippedRadii
);
2269 aCtx
->SetPath(roundedRect
);
2274 enum class ScrollbarColorKind
{
2279 static Maybe
<nscolor
> CalcScrollbarColor(nsIFrame
* aFrame
,
2280 ScrollbarColorKind aKind
) {
2281 ComputedStyle
* scrollbarStyle
= nsLayoutUtils::StyleForScrollbar(aFrame
);
2282 const auto& colors
= scrollbarStyle
->StyleUI()->mScrollbarColor
;
2283 if (colors
.IsAuto()) {
2286 const auto& color
= aKind
== ScrollbarColorKind::Thumb
2287 ? colors
.AsColors().thumb
2288 : colors
.AsColors().track
;
2289 return Some(color
.CalcColor(*scrollbarStyle
));
2292 static nscolor
GetBackgroundColor(nsIFrame
* aFrame
, ComputedStyle
* aStyle
) {
2293 switch (aStyle
->StyleDisplay()->EffectiveAppearance()) {
2294 case StyleAppearance::ScrollbarthumbVertical
:
2295 case StyleAppearance::ScrollbarthumbHorizontal
: {
2296 if (Maybe
<nscolor
> overrideColor
=
2297 CalcScrollbarColor(aFrame
, ScrollbarColorKind::Thumb
)) {
2298 return *overrideColor
;
2302 case StyleAppearance::ScrollbarVertical
:
2303 case StyleAppearance::ScrollbarHorizontal
:
2304 case StyleAppearance::Scrollcorner
: {
2305 if (Maybe
<nscolor
> overrideColor
=
2306 CalcScrollbarColor(aFrame
, ScrollbarColorKind::Track
)) {
2307 return *overrideColor
;
2314 return aStyle
->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor
);
2317 nscolor
nsCSSRendering::DetermineBackgroundColor(nsPresContext
* aPresContext
,
2318 ComputedStyle
* aStyle
,
2320 bool& aDrawBackgroundImage
,
2321 bool& aDrawBackgroundColor
) {
2322 auto shouldPaint
= aFrame
->ComputeShouldPaintBackground();
2323 aDrawBackgroundImage
= shouldPaint
.mImage
;
2324 aDrawBackgroundColor
= shouldPaint
.mColor
;
2326 const nsStyleBackground
* bg
= aStyle
->StyleBackground();
2328 if (aDrawBackgroundColor
) {
2329 bgColor
= GetBackgroundColor(aFrame
, aStyle
);
2330 if (NS_GET_A(bgColor
) == 0) {
2331 aDrawBackgroundColor
= false;
2334 // If GetBackgroundColorDraw() is false, we are still expected to
2335 // draw color in the background of any frame that's not completely
2336 // transparent, but we are expected to use white instead of whatever
2337 // color was specified.
2338 bgColor
= NS_RGB(255, 255, 255);
2339 if (aDrawBackgroundImage
|| !bg
->IsTransparent(aStyle
)) {
2340 aDrawBackgroundColor
= true;
2342 bgColor
= NS_RGBA(0, 0, 0, 0);
2346 // We can skip painting the background color if a background image is opaque.
2347 nsStyleImageLayers::Repeat repeat
= bg
->BottomLayer().mRepeat
;
2348 bool xFullRepeat
= repeat
.mXRepeat
== StyleImageLayerRepeat::Repeat
||
2349 repeat
.mXRepeat
== StyleImageLayerRepeat::Round
;
2350 bool yFullRepeat
= repeat
.mYRepeat
== StyleImageLayerRepeat::Repeat
||
2351 repeat
.mYRepeat
== StyleImageLayerRepeat::Round
;
2352 if (aDrawBackgroundColor
&& xFullRepeat
&& yFullRepeat
&&
2353 bg
->BottomLayer().mImage
.IsOpaque() &&
2354 bg
->BottomLayer().mBlendMode
== StyleBlend::Normal
) {
2355 aDrawBackgroundColor
= false;
2361 static CompositionOp
DetermineCompositionOp(
2362 const nsCSSRendering::PaintBGParams
& aParams
,
2363 const nsStyleImageLayers
& aLayers
, uint32_t aLayerIndex
) {
2364 if (aParams
.layer
>= 0) {
2365 // When drawing a single layer, use the specified composition op.
2366 return aParams
.compositionOp
;
2369 const nsStyleImageLayers::Layer
& layer
= aLayers
.mLayers
[aLayerIndex
];
2370 // When drawing all layers, get the compositon op from each image layer.
2371 if (aParams
.paintFlags
& nsCSSRendering::PAINTBG_MASK_IMAGE
) {
2372 // Always using OP_OVER mode while drawing the bottom mask layer.
2373 if (aLayerIndex
== (aLayers
.mImageCount
- 1)) {
2374 return CompositionOp::OP_OVER
;
2377 return nsCSSRendering::GetGFXCompositeMode(layer
.mComposite
);
2380 return nsCSSRendering::GetGFXBlendMode(layer
.mBlendMode
);
2383 ImgDrawResult
nsCSSRendering::PaintStyleImageLayerWithSC(
2384 const PaintBGParams
& aParams
, gfxContext
& aRenderingCtx
,
2385 ComputedStyle
* aBackgroundSC
, const nsStyleBorder
& aBorder
) {
2386 MOZ_ASSERT(aParams
.frame
,
2387 "Frame is expected to be provided to PaintStyleImageLayerWithSC");
2389 // If we're drawing all layers, aCompositonOp is ignored, so make sure that
2390 // it was left at its default value.
2391 MOZ_ASSERT(aParams
.layer
!= -1 ||
2392 aParams
.compositionOp
== CompositionOp::OP_OVER
);
2394 // Check to see if we have an appearance defined. If so, we let the theme
2395 // renderer draw the background and bail out.
2396 // XXXzw this ignores aParams.bgClipRect.
2397 StyleAppearance appearance
=
2398 aParams
.frame
->StyleDisplay()->EffectiveAppearance();
2399 if (appearance
!= StyleAppearance::None
) {
2400 nsITheme
* theme
= aParams
.presCtx
.Theme();
2401 if (theme
->ThemeSupportsWidget(&aParams
.presCtx
, aParams
.frame
,
2403 nsRect
drawing(aParams
.borderArea
);
2404 theme
->GetWidgetOverflow(aParams
.presCtx
.DeviceContext(), aParams
.frame
,
2405 appearance
, &drawing
);
2406 drawing
.IntersectRect(drawing
, aParams
.dirtyRect
);
2407 theme
->DrawWidgetBackground(&aRenderingCtx
, aParams
.frame
, appearance
,
2408 aParams
.borderArea
, drawing
);
2409 return ImgDrawResult::SUCCESS
;
2413 // For canvas frames (in the CSS sense) we draw the background color using
2414 // a solid color item that gets added in nsLayoutUtils::PaintFrame,
2415 // or nsSubDocumentFrame::BuildDisplayList (bug 488242). (The solid
2416 // color may be moved into nsDisplayCanvasBackground by
2417 // PresShell::AddCanvasBackgroundColorItem(), and painted by
2418 // nsDisplayCanvasBackground directly.) Either way we don't need to
2419 // paint the background color here.
2420 bool isCanvasFrame
= IsCanvasFrame(aParams
.frame
);
2421 const bool paintMask
= aParams
.paintFlags
& PAINTBG_MASK_IMAGE
;
2423 // Determine whether we are drawing background images and/or
2424 // background colors.
2425 bool drawBackgroundImage
= true;
2426 bool drawBackgroundColor
= !paintMask
;
2427 nscolor bgColor
= NS_RGBA(0, 0, 0, 0);
2430 DetermineBackgroundColor(&aParams
.presCtx
, aBackgroundSC
, aParams
.frame
,
2431 drawBackgroundImage
, drawBackgroundColor
);
2434 // Masks shouldn't be suppressed for print.
2435 MOZ_ASSERT_IF(paintMask
, drawBackgroundImage
);
2437 const nsStyleImageLayers
& layers
=
2438 paintMask
? aBackgroundSC
->StyleSVGReset()->mMask
2439 : aBackgroundSC
->StyleBackground()->mImage
;
2440 // If we're drawing a specific layer, we don't want to draw the
2441 // background color.
2442 if (drawBackgroundColor
&& aParams
.layer
>= 0) {
2443 drawBackgroundColor
= false;
2446 // At this point, drawBackgroundImage and drawBackgroundColor are
2447 // true if and only if we are actually supposed to paint an image or
2448 // color into aDirtyRect, respectively.
2449 if (!drawBackgroundImage
&& !drawBackgroundColor
) {
2450 return ImgDrawResult::SUCCESS
;
2453 // The 'bgClipArea' (used only by the image tiling logic, far below)
2454 // is the caller-provided aParams.bgClipRect if any, or else the area
2455 // determined by the value of 'background-clip' in
2456 // SetupCurrentBackgroundClip. (Arguably it should be the
2457 // intersection, but that breaks the table painter -- in particular,
2458 // taking the intersection breaks reftests/bugs/403249-1[ab].)
2459 nscoord appUnitsPerPixel
= aParams
.presCtx
.AppUnitsPerDevPixel();
2460 ImageLayerClipState clipState
;
2461 if (aParams
.bgClipRect
) {
2462 clipState
.mBGClipArea
= *aParams
.bgClipRect
;
2463 clipState
.mCustomClip
= true;
2464 clipState
.mHasRoundedCorners
= false;
2465 SetupDirtyRects(clipState
.mBGClipArea
, aParams
.dirtyRect
, appUnitsPerPixel
,
2466 &clipState
.mDirtyRectInAppUnits
,
2467 &clipState
.mDirtyRectInDevPx
);
2469 GetImageLayerClip(layers
.BottomLayer(), aParams
.frame
, aBorder
,
2470 aParams
.borderArea
, aParams
.dirtyRect
,
2471 (aParams
.paintFlags
& PAINTBG_WILL_PAINT_BORDER
),
2472 appUnitsPerPixel
, &clipState
);
2475 // If we might be using a background color, go ahead and set it now.
2476 if (drawBackgroundColor
&& !isCanvasFrame
) {
2477 aRenderingCtx
.SetColor(sRGBColor::FromABGR(bgColor
));
2480 // If there is no background image, draw a color. (If there is
2481 // neither a background image nor a color, we wouldn't have gotten
2483 if (!drawBackgroundImage
) {
2484 if (!isCanvasFrame
) {
2485 DrawBackgroundColor(clipState
, &aRenderingCtx
, appUnitsPerPixel
);
2487 return ImgDrawResult::SUCCESS
;
2490 if (layers
.mImageCount
< 1) {
2491 // Return if there are no background layers, all work from this point
2492 // onwards happens iteratively on these.
2493 return ImgDrawResult::SUCCESS
;
2496 MOZ_ASSERT((aParams
.layer
< 0) ||
2497 (layers
.mImageCount
> uint32_t(aParams
.layer
)));
2499 // The background color is rendered over the entire dirty area,
2500 // even if the image isn't.
2501 if (drawBackgroundColor
&& !isCanvasFrame
) {
2502 DrawBackgroundColor(clipState
, &aRenderingCtx
, appUnitsPerPixel
);
2505 // Compute the outermost boundary of the area that might be painted.
2506 // Same coordinate space as aParams.borderArea & aParams.bgClipRect.
2507 Sides skipSides
= aParams
.frame
->GetSkipSides();
2508 nsRect paintBorderArea
= BoxDecorationRectForBackground(
2509 aParams
.frame
, aParams
.borderArea
, skipSides
, &aBorder
);
2510 nsRect clipBorderArea
= BoxDecorationRectForBorder(
2511 aParams
.frame
, aParams
.borderArea
, skipSides
, &aBorder
);
2513 ImgDrawResult result
= ImgDrawResult::SUCCESS
;
2514 StyleGeometryBox currentBackgroundClip
= StyleGeometryBox::BorderBox
;
2515 const bool drawAllLayers
= (aParams
.layer
< 0);
2516 uint32_t count
= drawAllLayers
2517 ? layers
.mImageCount
// iterate all image layers.
2518 : layers
.mImageCount
-
2519 aParams
.layer
; // iterate from the bottom layer to
2520 // the 'aParams.layer-th' layer.
2521 NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE(
2522 i
, layers
, layers
.mImageCount
- 1, count
) {
2523 // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved(ctx)
2524 // in the cases we need it.
2525 gfxContextAutoSaveRestore autoSR
;
2526 const nsStyleImageLayers::Layer
& layer
= layers
.mLayers
[i
];
2528 ImageLayerClipState currentLayerClipState
= clipState
;
2529 if (!aParams
.bgClipRect
) {
2530 bool isBottomLayer
= (i
== layers
.mImageCount
- 1);
2531 if (currentBackgroundClip
!= layer
.mClip
|| isBottomLayer
) {
2532 currentBackgroundClip
= layer
.mClip
;
2533 if (!isBottomLayer
) {
2534 currentLayerClipState
= {};
2535 // For the bottom layer, we already called GetImageLayerClip above
2536 // and it stored its results in clipState.
2537 GetImageLayerClip(layer
, aParams
.frame
, aBorder
, aParams
.borderArea
,
2539 (aParams
.paintFlags
& PAINTBG_WILL_PAINT_BORDER
),
2540 appUnitsPerPixel
, ¤tLayerClipState
);
2542 SetupImageLayerClip(currentLayerClipState
, &aRenderingCtx
,
2543 appUnitsPerPixel
, &autoSR
);
2544 if (!clipBorderArea
.IsEqualEdges(aParams
.borderArea
)) {
2545 // We're drawing the background for the joined continuation boxes
2546 // so we need to clip that to the slice that we want for this
2548 gfxRect clip
= nsLayoutUtils::RectToGfxRect(aParams
.borderArea
,
2550 autoSR
.EnsureSaved(&aRenderingCtx
);
2551 aRenderingCtx
.SnappedClip(clip
);
2556 // Skip the following layer preparing and painting code if the current
2557 // layer is not selected for drawing.
2558 if (aParams
.layer
>= 0 && i
!= (uint32_t)aParams
.layer
) {
2561 nsBackgroundLayerState state
= PrepareImageLayer(
2562 &aParams
.presCtx
, aParams
.frame
, aParams
.paintFlags
, paintBorderArea
,
2563 currentLayerClipState
.mBGClipArea
, layer
, nullptr);
2564 result
&= state
.mImageRenderer
.PrepareResult();
2566 // Skip the layer painting code if we found the dirty region is empty.
2567 if (currentLayerClipState
.mDirtyRectInDevPx
.IsEmpty()) {
2571 if (!state
.mFillArea
.IsEmpty()) {
2572 CompositionOp co
= DetermineCompositionOp(aParams
, layers
, i
);
2573 if (co
!= CompositionOp::OP_OVER
) {
2574 NS_ASSERTION(aRenderingCtx
.CurrentOp() == CompositionOp::OP_OVER
,
2575 "It is assumed the initial op is OP_OVER, when it is "
2577 aRenderingCtx
.SetOp(co
);
2580 result
&= state
.mImageRenderer
.DrawLayer(
2581 &aParams
.presCtx
, aRenderingCtx
, state
.mDestArea
, state
.mFillArea
,
2582 state
.mAnchor
+ paintBorderArea
.TopLeft(),
2583 currentLayerClipState
.mDirtyRectInAppUnits
, state
.mRepeatSize
,
2586 if (co
!= CompositionOp::OP_OVER
) {
2587 aRenderingCtx
.SetOp(CompositionOp::OP_OVER
);
2596 nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayerWithSC(
2597 const PaintBGParams
& aParams
, mozilla::wr::DisplayListBuilder
& aBuilder
,
2598 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
2599 const mozilla::layers::StackingContextHelper
& aSc
,
2600 mozilla::layers::RenderRootStateManager
* aManager
, nsDisplayItem
* aItem
,
2601 ComputedStyle
* aBackgroundSC
, const nsStyleBorder
& aBorder
) {
2602 MOZ_ASSERT(!(aParams
.paintFlags
& PAINTBG_MASK_IMAGE
));
2604 nscoord appUnitsPerPixel
= aParams
.presCtx
.AppUnitsPerDevPixel();
2605 ImageLayerClipState clipState
;
2607 clipState
.mBGClipArea
= *aParams
.bgClipRect
;
2608 clipState
.mCustomClip
= true;
2609 clipState
.mHasRoundedCorners
= false;
2610 SetupDirtyRects(clipState
.mBGClipArea
, aParams
.dirtyRect
, appUnitsPerPixel
,
2611 &clipState
.mDirtyRectInAppUnits
,
2612 &clipState
.mDirtyRectInDevPx
);
2614 // Compute the outermost boundary of the area that might be painted.
2615 // Same coordinate space as aParams.borderArea & aParams.bgClipRect.
2616 Sides skipSides
= aParams
.frame
->GetSkipSides();
2617 nsRect paintBorderArea
= BoxDecorationRectForBackground(
2618 aParams
.frame
, aParams
.borderArea
, skipSides
, &aBorder
);
2620 const nsStyleImageLayers
& layers
= aBackgroundSC
->StyleBackground()->mImage
;
2621 const nsStyleImageLayers::Layer
& layer
= layers
.mLayers
[aParams
.layer
];
2623 // Skip the following layer painting code if we found the dirty region is
2624 // empty or the current layer is not selected for drawing.
2625 if (clipState
.mDirtyRectInDevPx
.IsEmpty()) {
2626 return ImgDrawResult::SUCCESS
;
2629 ImgDrawResult result
= ImgDrawResult::SUCCESS
;
2630 nsBackgroundLayerState state
=
2631 PrepareImageLayer(&aParams
.presCtx
, aParams
.frame
, aParams
.paintFlags
,
2632 paintBorderArea
, clipState
.mBGClipArea
, layer
, nullptr);
2633 result
&= state
.mImageRenderer
.PrepareResult();
2635 if (!state
.mFillArea
.IsEmpty()) {
2636 return state
.mImageRenderer
.BuildWebRenderDisplayItemsForLayer(
2637 &aParams
.presCtx
, aBuilder
, aResources
, aSc
, aManager
, aItem
,
2638 state
.mDestArea
, state
.mFillArea
,
2639 state
.mAnchor
+ paintBorderArea
.TopLeft(),
2640 clipState
.mDirtyRectInAppUnits
, state
.mRepeatSize
, aParams
.opacity
);
2646 nsRect
nsCSSRendering::ComputeImageLayerPositioningArea(
2647 nsPresContext
* aPresContext
, nsIFrame
* aForFrame
, const nsRect
& aBorderArea
,
2648 const nsStyleImageLayers::Layer
& aLayer
, nsIFrame
** aAttachedToFrame
,
2649 bool* aOutIsTransformedFixed
) {
2650 // Compute {background|mask} origin area relative to aBorderArea now as we
2651 // may need it to compute the effective image size for a CSS gradient.
2652 nsRect positionArea
;
2654 StyleGeometryBox layerOrigin
= ComputeBoxValue(aForFrame
, aLayer
.mOrigin
);
2656 if (IsSVGStyleGeometryBox(layerOrigin
)) {
2657 MOZ_ASSERT(aForFrame
->IsFrameOfType(nsIFrame::eSVG
) &&
2658 !aForFrame
->IsSVGOuterSVGFrame());
2659 *aAttachedToFrame
= aForFrame
;
2661 positionArea
= nsLayoutUtils::ComputeGeometryBox(aForFrame
, layerOrigin
);
2663 nsPoint toStrokeBoxOffset
= nsPoint(0, 0);
2664 if (layerOrigin
!= StyleGeometryBox::StrokeBox
) {
2665 nsRect strokeBox
= nsLayoutUtils::ComputeGeometryBox(
2666 aForFrame
, StyleGeometryBox::StrokeBox
);
2667 toStrokeBoxOffset
= positionArea
.TopLeft() - strokeBox
.TopLeft();
2670 // For SVG frames, the return value is relative to the stroke box
2671 return nsRect(toStrokeBoxOffset
, positionArea
.Size());
2674 MOZ_ASSERT(!aForFrame
->IsFrameOfType(nsIFrame::eSVG
) ||
2675 aForFrame
->IsSVGOuterSVGFrame());
2677 LayoutFrameType frameType
= aForFrame
->Type();
2678 nsIFrame
* geometryFrame
= aForFrame
;
2679 if (MOZ_UNLIKELY(frameType
== LayoutFrameType::Scroll
&&
2680 StyleImageLayerAttachment::Local
== aLayer
.mAttachment
)) {
2681 nsIScrollableFrame
* scrollableFrame
= do_QueryFrame(aForFrame
);
2682 positionArea
= nsRect(scrollableFrame
->GetScrolledFrame()->GetPosition()
2683 // For the dir=rtl case:
2684 + scrollableFrame
->GetScrollRange().TopLeft(),
2685 scrollableFrame
->GetScrolledRect().Size());
2686 // The ScrolledRect’s size does not include the borders or scrollbars,
2687 // reverse the handling of background-origin
2688 // compared to the common case below.
2689 if (layerOrigin
== StyleGeometryBox::BorderBox
) {
2690 nsMargin border
= geometryFrame
->GetUsedBorder();
2691 border
.ApplySkipSides(geometryFrame
->GetSkipSides());
2692 positionArea
.Inflate(border
);
2693 positionArea
.Inflate(scrollableFrame
->GetActualScrollbarSizes());
2694 } else if (layerOrigin
!= StyleGeometryBox::PaddingBox
) {
2695 nsMargin padding
= geometryFrame
->GetUsedPadding();
2696 padding
.ApplySkipSides(geometryFrame
->GetSkipSides());
2697 positionArea
.Deflate(padding
);
2698 NS_ASSERTION(layerOrigin
== StyleGeometryBox::ContentBox
,
2699 "unknown background-origin value");
2701 *aAttachedToFrame
= aForFrame
;
2702 return positionArea
;
2705 if (MOZ_UNLIKELY(frameType
== LayoutFrameType::Canvas
)) {
2706 geometryFrame
= aForFrame
->PrincipalChildList().FirstChild();
2707 // geometryFrame might be null if this canvas is a page created
2708 // as an overflow container (e.g. the in-flow content has already
2709 // finished and this page only displays the continuations of
2710 // absolutely positioned content).
2711 if (geometryFrame
) {
2713 nsPlaceholderFrame::GetRealFrameFor(geometryFrame
)->GetRect();
2716 positionArea
= nsRect(nsPoint(0, 0), aBorderArea
.Size());
2719 // See the comment of StyleGeometryBox::MarginBox.
2720 // Hitting this assertion means we decide to turn on margin-box support for
2721 // positioned mask from CSS parser and style system. In this case, you
2722 // should *inflate* positionArea by the margin returning from
2723 // geometryFrame->GetUsedMargin() in the code chunk bellow.
2724 MOZ_ASSERT(aLayer
.mOrigin
!= StyleGeometryBox::MarginBox
,
2725 "StyleGeometryBox::MarginBox rendering is not supported yet.\n");
2727 // {background|mask} images are tiled over the '{background|mask}-clip' area
2728 // but the origin of the tiling is based on the '{background|mask}-origin'
2730 if (layerOrigin
!= StyleGeometryBox::BorderBox
&& geometryFrame
) {
2731 nsMargin border
= geometryFrame
->GetUsedBorder();
2732 if (layerOrigin
!= StyleGeometryBox::PaddingBox
) {
2733 border
+= geometryFrame
->GetUsedPadding();
2734 NS_ASSERTION(layerOrigin
== StyleGeometryBox::ContentBox
,
2735 "unknown background-origin value");
2737 positionArea
.Deflate(border
);
2740 nsIFrame
* attachedToFrame
= aForFrame
;
2741 if (StyleImageLayerAttachment::Fixed
== aLayer
.mAttachment
) {
2742 // If it's a fixed background attachment, then the image is placed
2743 // relative to the viewport, which is the area of the root frame
2744 // in a screen context or the page content frame in a print context.
2745 attachedToFrame
= aPresContext
->PresShell()->GetRootFrame();
2746 NS_ASSERTION(attachedToFrame
, "no root frame");
2747 nsIFrame
* pageContentFrame
= nullptr;
2748 if (aPresContext
->IsPaginated()) {
2749 pageContentFrame
= nsLayoutUtils::GetClosestFrameOfType(
2750 aForFrame
, LayoutFrameType::PageContent
);
2751 if (pageContentFrame
) {
2752 attachedToFrame
= pageContentFrame
;
2754 // else this is an embedded shell and its root frame is what we want
2757 // If the background is affected by a transform, treat is as if it
2759 if (nsLayoutUtils::IsTransformed(aForFrame
, attachedToFrame
)) {
2760 attachedToFrame
= aForFrame
;
2761 *aOutIsTransformedFixed
= true;
2763 // Set the background positioning area to the viewport's area
2764 // (relative to aForFrame)
2765 positionArea
= nsRect(-aForFrame
->GetOffsetTo(attachedToFrame
),
2766 attachedToFrame
->GetSize());
2768 if (!pageContentFrame
) {
2769 // Subtract the size of scrollbars.
2770 nsIScrollableFrame
* scrollableFrame
=
2771 aPresContext
->PresShell()->GetRootScrollFrameAsScrollable();
2772 if (scrollableFrame
) {
2773 nsMargin scrollbars
= scrollableFrame
->GetActualScrollbarSizes();
2774 positionArea
.Deflate(scrollbars
);
2779 *aAttachedToFrame
= attachedToFrame
;
2781 return positionArea
;
2785 nscoord
nsCSSRendering::ComputeRoundedSize(nscoord aCurrentSize
,
2786 nscoord aPositioningSize
) {
2787 float repeatCount
= NS_roundf(float(aPositioningSize
) / float(aCurrentSize
));
2788 if (repeatCount
< 1.0f
) {
2789 return aPositioningSize
;
2791 return nscoord(NS_lround(float(aPositioningSize
) / repeatCount
));
2794 // Apply the CSS image sizing algorithm as it applies to background images.
2795 // See http://www.w3.org/TR/css3-background/#the-background-size .
2796 // aIntrinsicSize is the size that the background image 'would like to be'.
2797 // It can be found by calling nsImageRenderer::ComputeIntrinsicSize.
2798 static nsSize
ComputeDrawnSizeForBackground(
2799 const CSSSizeOrRatio
& aIntrinsicSize
, const nsSize
& aBgPositioningArea
,
2800 const StyleBackgroundSize
& aLayerSize
, StyleImageLayerRepeat aXRepeat
,
2801 StyleImageLayerRepeat aYRepeat
) {
2804 // Size is dictated by cover or contain rules.
2805 if (aLayerSize
.IsContain() || aLayerSize
.IsCover()) {
2806 nsImageRenderer::FitType fitType
= aLayerSize
.IsCover()
2807 ? nsImageRenderer::COVER
2808 : nsImageRenderer::CONTAIN
;
2809 imageSize
= nsImageRenderer::ComputeConstrainedSize(
2810 aBgPositioningArea
, aIntrinsicSize
.mRatio
, fitType
);
2812 MOZ_ASSERT(aLayerSize
.IsExplicitSize());
2813 const auto& width
= aLayerSize
.explicit_size
.width
;
2814 const auto& height
= aLayerSize
.explicit_size
.height
;
2815 // No cover/contain constraint, use default algorithm.
2816 CSSSizeOrRatio specifiedSize
;
2817 if (width
.IsLengthPercentage()) {
2818 specifiedSize
.SetWidth(
2819 width
.AsLengthPercentage().Resolve(aBgPositioningArea
.width
));
2821 if (height
.IsLengthPercentage()) {
2822 specifiedSize
.SetHeight(
2823 height
.AsLengthPercentage().Resolve(aBgPositioningArea
.height
));
2826 imageSize
= nsImageRenderer::ComputeConcreteSize(
2827 specifiedSize
, aIntrinsicSize
, aBgPositioningArea
);
2830 // See https://www.w3.org/TR/css3-background/#background-size .
2831 // "If 'background-repeat' is 'round' for one (or both) dimensions, there is a
2833 // step. The UA must scale the image in that dimension (or both dimensions)
2834 // so that it fits a whole number of times in the background positioning
2836 // "If 'background-repeat' is 'round' for one dimension only and if
2837 // 'background-size'
2838 // is 'auto' for the other dimension, then there is a third step: that other
2839 // dimension is scaled so that the original aspect ratio is restored."
2840 bool isRepeatRoundInBothDimensions
=
2841 aXRepeat
== StyleImageLayerRepeat::Round
&&
2842 aYRepeat
== StyleImageLayerRepeat::Round
;
2844 // Calculate the rounded size only if the background-size computation
2845 // returned a correct size for the image.
2846 if (imageSize
.width
&& aXRepeat
== StyleImageLayerRepeat::Round
) {
2847 imageSize
.width
= nsCSSRendering::ComputeRoundedSize(
2848 imageSize
.width
, aBgPositioningArea
.width
);
2849 if (!isRepeatRoundInBothDimensions
&& aLayerSize
.IsExplicitSize() &&
2850 aLayerSize
.explicit_size
.height
.IsAuto()) {
2851 // Restore intrinsic ratio
2852 if (aIntrinsicSize
.mRatio
) {
2854 aIntrinsicSize
.mRatio
.Inverted().ApplyTo(imageSize
.width
);
2859 // Calculate the rounded size only if the background-size computation
2860 // returned a correct size for the image.
2861 if (imageSize
.height
&& aYRepeat
== StyleImageLayerRepeat::Round
) {
2862 imageSize
.height
= nsCSSRendering::ComputeRoundedSize(
2863 imageSize
.height
, aBgPositioningArea
.height
);
2864 if (!isRepeatRoundInBothDimensions
&& aLayerSize
.IsExplicitSize() &&
2865 aLayerSize
.explicit_size
.width
.IsAuto()) {
2866 // Restore intrinsic ratio
2867 if (aIntrinsicSize
.mRatio
) {
2868 imageSize
.width
= aIntrinsicSize
.mRatio
.ApplyTo(imageSize
.height
);
2876 /* ComputeSpacedRepeatSize
2877 * aImageDimension: the image width/height
2878 * aAvailableSpace: the background positioning area width/height
2879 * aRepeat: determine whether the image is repeated
2880 * Returns the image size plus gap size of app units for use as spacing
2882 static nscoord
ComputeSpacedRepeatSize(nscoord aImageDimension
,
2883 nscoord aAvailableSpace
, bool& aRepeat
) {
2884 float ratio
= static_cast<float>(aAvailableSpace
) / aImageDimension
;
2886 if (ratio
< 2.0f
) { // If you can't repeat at least twice, then don't repeat.
2888 return aImageDimension
;
2892 return (aAvailableSpace
- aImageDimension
) / (NSToIntFloor(ratio
) - 1);
2896 nscoord
nsCSSRendering::ComputeBorderSpacedRepeatSize(nscoord aImageDimension
,
2897 nscoord aAvailableSpace
,
2899 int32_t count
= aImageDimension
? (aAvailableSpace
/ aImageDimension
) : 0;
2900 aSpace
= (aAvailableSpace
- aImageDimension
* count
) / (count
+ 1);
2901 return aSpace
+ aImageDimension
;
2904 nsBackgroundLayerState
nsCSSRendering::PrepareImageLayer(
2905 nsPresContext
* aPresContext
, nsIFrame
* aForFrame
, uint32_t aFlags
,
2906 const nsRect
& aBorderArea
, const nsRect
& aBGClipRect
,
2907 const nsStyleImageLayers::Layer
& aLayer
, bool* aOutIsTransformedFixed
) {
2909 * The properties we need to keep in mind when drawing style image
2912 * background-image/ mask-image
2913 * background-repeat/ mask-repeat
2914 * background-attachment
2915 * background-position/ mask-position
2916 * background-clip/ mask-clip
2917 * background-origin/ mask-origin
2918 * background-size/ mask-size
2919 * background-blend-mode
2920 * box-decoration-break
2924 * (background-color applies to the entire element and not to individual
2925 * layers, so it is irrelevant to this method.)
2927 * These properties have the following dependencies upon each other when
2928 * determining rendering:
2930 * background-image/ mask-image
2932 * background-repeat/ mask-repeat
2934 * background-attachment
2936 * background-position/ mask-position
2937 * depends upon background-size/mask-size (for the image's scaled size)
2938 * and background-break (for the background positioning area)
2939 * background-clip/ mask-clip
2941 * background-origin/ mask-origin
2942 * depends upon background-attachment (only in the case where that value
2944 * background-size/ mask-size
2945 * depends upon box-decoration-break (for the background positioning area
2946 * for resolving percentages), background-image (for the image's intrinsic
2947 * size), background-repeat (if that value is 'round'), and
2948 * background-origin (for the background painting area, when
2949 * background-repeat is 'round')
2950 * background-blend-mode
2956 * box-decoration-break
2959 * As a result of only-if dependencies we don't strictly do a topological
2960 * sort of the above properties when processing, but it's pretty close to one:
2962 * background-clip/mask-clip (by caller)
2963 * background-image/ mask-image
2964 * box-decoration-break, background-origin/ mask origin
2965 * background-attachment (postfix for background-origin if 'fixed')
2966 * background-size/ mask-size
2967 * background-position/ mask-position
2968 * background-repeat/ mask-repeat
2971 uint32_t irFlags
= 0;
2972 if (aFlags
& nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES
) {
2973 irFlags
|= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES
;
2975 if (aFlags
& nsCSSRendering::PAINTBG_TO_WINDOW
) {
2976 irFlags
|= nsImageRenderer::FLAG_PAINTING_TO_WINDOW
;
2978 if (aFlags
& nsCSSRendering::PAINTBG_HIGH_QUALITY_SCALING
) {
2979 irFlags
|= nsImageRenderer::FLAG_HIGH_QUALITY_SCALING
;
2982 nsBackgroundLayerState
state(aForFrame
, &aLayer
.mImage
, irFlags
);
2983 if (!state
.mImageRenderer
.PrepareImage()) {
2984 // There's no image or it's not ready to be painted.
2985 if (aOutIsTransformedFixed
&&
2986 StyleImageLayerAttachment::Fixed
== aLayer
.mAttachment
) {
2987 nsIFrame
* attachedToFrame
= aPresContext
->PresShell()->GetRootFrame();
2988 NS_ASSERTION(attachedToFrame
, "no root frame");
2989 nsIFrame
* pageContentFrame
= nullptr;
2990 if (aPresContext
->IsPaginated()) {
2991 pageContentFrame
= nsLayoutUtils::GetClosestFrameOfType(
2992 aForFrame
, LayoutFrameType::PageContent
);
2993 if (pageContentFrame
) {
2994 attachedToFrame
= pageContentFrame
;
2996 // else this is an embedded shell and its root frame is what we want
2999 *aOutIsTransformedFixed
=
3000 nsLayoutUtils::IsTransformed(aForFrame
, attachedToFrame
);
3005 // The frame to which the background is attached
3006 nsIFrame
* attachedToFrame
= aForFrame
;
3007 // Is the background marked 'fixed', but affected by a transform?
3008 bool transformedFixed
= false;
3009 // Compute background origin area relative to aBorderArea now as we may need
3010 // it to compute the effective image size for a CSS gradient.
3011 nsRect positionArea
= ComputeImageLayerPositioningArea(
3012 aPresContext
, aForFrame
, aBorderArea
, aLayer
, &attachedToFrame
,
3014 if (aOutIsTransformedFixed
) {
3015 *aOutIsTransformedFixed
= transformedFixed
;
3018 // For background-attachment:fixed backgrounds, we'll override the area
3019 // where the background can be drawn to the viewport.
3020 nsRect bgClipRect
= aBGClipRect
;
3022 if (StyleImageLayerAttachment::Fixed
== aLayer
.mAttachment
&&
3023 !transformedFixed
&& (aFlags
& nsCSSRendering::PAINTBG_TO_WINDOW
)) {
3024 bgClipRect
= positionArea
+ aBorderArea
.TopLeft();
3027 StyleImageLayerRepeat repeatX
= aLayer
.mRepeat
.mXRepeat
;
3028 StyleImageLayerRepeat repeatY
= aLayer
.mRepeat
.mYRepeat
;
3030 // Scale the image as specified for background-size and background-repeat.
3031 // Also as required for proper background positioning when background-position
3032 // is defined with percentages.
3033 CSSSizeOrRatio intrinsicSize
= state
.mImageRenderer
.ComputeIntrinsicSize();
3034 nsSize bgPositionSize
= positionArea
.Size();
3035 nsSize imageSize
= ComputeDrawnSizeForBackground(
3036 intrinsicSize
, bgPositionSize
, aLayer
.mSize
, repeatX
, repeatY
);
3038 if (imageSize
.width
<= 0 || imageSize
.height
<= 0) return state
;
3040 state
.mImageRenderer
.SetPreferredSize(intrinsicSize
, imageSize
);
3042 // Compute the anchor point.
3044 // relative to aBorderArea.TopLeft() (which is where the top-left
3045 // of aForFrame's border-box will be rendered)
3046 nsPoint imageTopLeft
;
3048 // Compute the position of the background now that the background's size is
3050 nsImageRenderer::ComputeObjectAnchorPoint(aLayer
.mPosition
, bgPositionSize
,
3051 imageSize
, &imageTopLeft
,
3053 state
.mRepeatSize
= imageSize
;
3054 if (repeatX
== StyleImageLayerRepeat::Space
) {
3056 state
.mRepeatSize
.width
= ComputeSpacedRepeatSize(
3057 imageSize
.width
, bgPositionSize
.width
, isRepeat
);
3060 state
.mAnchor
.x
= 0;
3062 repeatX
= StyleImageLayerRepeat::NoRepeat
;
3066 if (repeatY
== StyleImageLayerRepeat::Space
) {
3068 state
.mRepeatSize
.height
= ComputeSpacedRepeatSize(
3069 imageSize
.height
, bgPositionSize
.height
, isRepeat
);
3072 state
.mAnchor
.y
= 0;
3074 repeatY
= StyleImageLayerRepeat::NoRepeat
;
3078 imageTopLeft
+= positionArea
.TopLeft();
3079 state
.mAnchor
+= positionArea
.TopLeft();
3080 state
.mDestArea
= nsRect(imageTopLeft
+ aBorderArea
.TopLeft(), imageSize
);
3081 state
.mFillArea
= state
.mDestArea
;
3083 ExtendMode repeatMode
= ExtendMode::CLAMP
;
3084 if (repeatX
== StyleImageLayerRepeat::Repeat
||
3085 repeatX
== StyleImageLayerRepeat::Round
||
3086 repeatX
== StyleImageLayerRepeat::Space
) {
3087 state
.mFillArea
.x
= bgClipRect
.x
;
3088 state
.mFillArea
.width
= bgClipRect
.width
;
3089 repeatMode
= ExtendMode::REPEAT_X
;
3091 if (repeatY
== StyleImageLayerRepeat::Repeat
||
3092 repeatY
== StyleImageLayerRepeat::Round
||
3093 repeatY
== StyleImageLayerRepeat::Space
) {
3094 state
.mFillArea
.y
= bgClipRect
.y
;
3095 state
.mFillArea
.height
= bgClipRect
.height
;
3098 * We're repeating on the X axis already,
3099 * so if we have to repeat in the Y axis,
3100 * we really need to repeat in both directions.
3102 if (repeatMode
== ExtendMode::REPEAT_X
) {
3103 repeatMode
= ExtendMode::REPEAT
;
3105 repeatMode
= ExtendMode::REPEAT_Y
;
3108 state
.mImageRenderer
.SetExtendMode(repeatMode
);
3109 state
.mImageRenderer
.SetMaskOp(aLayer
.mMaskMode
);
3111 state
.mFillArea
.IntersectRect(state
.mFillArea
, bgClipRect
);
3116 nsRect
nsCSSRendering::GetBackgroundLayerRect(
3117 nsPresContext
* aPresContext
, nsIFrame
* aForFrame
, const nsRect
& aBorderArea
,
3118 const nsRect
& aClipRect
, const nsStyleImageLayers::Layer
& aLayer
,
3120 Sides skipSides
= aForFrame
->GetSkipSides();
3122 BoxDecorationRectForBackground(aForFrame
, aBorderArea
, skipSides
);
3123 nsBackgroundLayerState state
= PrepareImageLayer(
3124 aPresContext
, aForFrame
, aFlags
, borderArea
, aClipRect
, aLayer
);
3125 return state
.mFillArea
;
3128 // Begin table border-collapsing section
3129 // These functions were written to not disrupt the normal ones and yet satisfy
3130 // some additional requirements At some point, all functions should be unified
3131 // to include the additional functionality that these provide
3133 static nscoord
RoundIntToPixel(nscoord aValue
, nscoord aOneDevPixel
,
3134 bool aRoundDown
= false) {
3135 if (aOneDevPixel
<= 0) {
3136 // We must be rendering to a device that has a resolution greater than
3137 // one device pixel!
3138 // In that case, aValue is as accurate as it's going to get.
3142 nscoord halfPixel
= NSToCoordRound(aOneDevPixel
/ 2.0f
);
3143 nscoord extra
= aValue
% aOneDevPixel
;
3144 nscoord finalValue
= (!aRoundDown
&& (extra
>= halfPixel
))
3145 ? aValue
+ (aOneDevPixel
- extra
)
3150 static nscoord
RoundFloatToPixel(float aValue
, nscoord aOneDevPixel
,
3151 bool aRoundDown
= false) {
3152 return RoundIntToPixel(NSToCoordRound(aValue
), aOneDevPixel
, aRoundDown
);
3155 static void SetPoly(const Rect
& aRect
, Point
* poly
) {
3156 poly
[0].x
= aRect
.x
;
3157 poly
[0].y
= aRect
.y
;
3158 poly
[1].x
= aRect
.x
+ aRect
.width
;
3159 poly
[1].y
= aRect
.y
;
3160 poly
[2].x
= aRect
.x
+ aRect
.width
;
3161 poly
[2].y
= aRect
.y
+ aRect
.height
;
3162 poly
[3].x
= aRect
.x
;
3163 poly
[3].y
= aRect
.y
+ aRect
.height
;
3166 static void DrawDashedSegment(DrawTarget
& aDrawTarget
, nsRect aRect
,
3167 nscoord aDashLength
, nscolor aColor
,
3168 int32_t aAppUnitsPerDevPixel
, bool aHorizontal
) {
3169 ColorPattern
color(ToDeviceColor(aColor
));
3170 DrawOptions
drawOptions(1.f
, CompositionOp::OP_OVER
, AntialiasMode::NONE
);
3171 StrokeOptions strokeOptions
;
3174 dash
[0] = Float(aDashLength
) / aAppUnitsPerDevPixel
;
3177 strokeOptions
.mDashPattern
= dash
;
3178 strokeOptions
.mDashLength
= MOZ_ARRAY_LENGTH(dash
);
3181 nsPoint left
= (aRect
.TopLeft() + aRect
.BottomLeft()) / 2;
3182 nsPoint right
= (aRect
.TopRight() + aRect
.BottomRight()) / 2;
3183 strokeOptions
.mLineWidth
= Float(aRect
.height
) / aAppUnitsPerDevPixel
;
3184 StrokeLineWithSnapping(left
, right
, aAppUnitsPerDevPixel
, aDrawTarget
,
3185 color
, strokeOptions
, drawOptions
);
3187 nsPoint top
= (aRect
.TopLeft() + aRect
.TopRight()) / 2;
3188 nsPoint bottom
= (aRect
.BottomLeft() + aRect
.BottomRight()) / 2;
3189 strokeOptions
.mLineWidth
= Float(aRect
.width
) / aAppUnitsPerDevPixel
;
3190 StrokeLineWithSnapping(top
, bottom
, aAppUnitsPerDevPixel
, aDrawTarget
,
3191 color
, strokeOptions
, drawOptions
);
3195 static void DrawSolidBorderSegment(
3196 DrawTarget
& aDrawTarget
, nsRect aRect
, nscolor aColor
,
3197 int32_t aAppUnitsPerDevPixel
,
3198 mozilla::Side aStartBevelSide
= mozilla::eSideTop
,
3199 nscoord aStartBevelOffset
= 0,
3200 mozilla::Side aEndBevelSide
= mozilla::eSideTop
,
3201 nscoord aEndBevelOffset
= 0) {
3202 ColorPattern
color(ToDeviceColor(aColor
));
3203 DrawOptions
drawOptions(1.f
, CompositionOp::OP_OVER
, AntialiasMode::NONE
);
3205 nscoord oneDevPixel
= NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel
);
3206 // We don't need to bevel single pixel borders
3207 if ((aRect
.width
== oneDevPixel
) || (aRect
.height
== oneDevPixel
) ||
3208 ((0 == aStartBevelOffset
) && (0 == aEndBevelOffset
))) {
3210 aDrawTarget
.FillRect(
3211 NSRectToSnappedRect(aRect
, aAppUnitsPerDevPixel
, aDrawTarget
), color
,
3214 // polygon with beveling
3216 SetPoly(NSRectToSnappedRect(aRect
, aAppUnitsPerDevPixel
, aDrawTarget
),
3219 Float startBevelOffset
=
3220 NSAppUnitsToFloatPixels(aStartBevelOffset
, aAppUnitsPerDevPixel
);
3221 switch (aStartBevelSide
) {
3223 poly
[0].x
+= startBevelOffset
;
3226 poly
[3].x
+= startBevelOffset
;
3229 poly
[1].y
+= startBevelOffset
;
3232 poly
[0].y
+= startBevelOffset
;
3235 Float endBevelOffset
=
3236 NSAppUnitsToFloatPixels(aEndBevelOffset
, aAppUnitsPerDevPixel
);
3237 switch (aEndBevelSide
) {
3239 poly
[1].x
-= endBevelOffset
;
3242 poly
[2].x
-= endBevelOffset
;
3245 poly
[2].y
-= endBevelOffset
;
3248 poly
[3].y
-= endBevelOffset
;
3251 RefPtr
<PathBuilder
> builder
= aDrawTarget
.CreatePathBuilder();
3252 builder
->MoveTo(poly
[0]);
3253 builder
->LineTo(poly
[1]);
3254 builder
->LineTo(poly
[2]);
3255 builder
->LineTo(poly
[3]);
3257 RefPtr
<Path
> path
= builder
->Finish();
3258 aDrawTarget
.Fill(path
, color
, drawOptions
);
3262 static void GetDashInfo(nscoord aBorderLength
, nscoord aDashLength
,
3263 nscoord aOneDevPixel
, int32_t& aNumDashSpaces
,
3264 nscoord
& aStartDashLength
, nscoord
& aEndDashLength
) {
3266 if (aStartDashLength
+ aDashLength
+ aEndDashLength
>= aBorderLength
) {
3267 aStartDashLength
= aBorderLength
;
3271 (aBorderLength
- aDashLength
) / (2 * aDashLength
); // round down
3272 nscoord extra
= aBorderLength
- aStartDashLength
- aEndDashLength
-
3273 (((2 * aNumDashSpaces
) - 1) * aDashLength
);
3275 nscoord half
= RoundIntToPixel(extra
/ 2, aOneDevPixel
);
3276 aStartDashLength
+= half
;
3277 aEndDashLength
+= (extra
- half
);
3282 void nsCSSRendering::DrawTableBorderSegment(
3283 DrawTarget
& aDrawTarget
, StyleBorderStyle aBorderStyle
,
3284 nscolor aBorderColor
, const nsRect
& aBorder
, int32_t aAppUnitsPerDevPixel
,
3285 mozilla::Side aStartBevelSide
, nscoord aStartBevelOffset
,
3286 mozilla::Side aEndBevelSide
, nscoord aEndBevelOffset
) {
3288 ((eSideTop
== aStartBevelSide
) || (eSideBottom
== aStartBevelSide
));
3289 nscoord oneDevPixel
= NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel
);
3291 if ((oneDevPixel
>= aBorder
.width
) || (oneDevPixel
>= aBorder
.height
) ||
3292 (StyleBorderStyle::Dashed
== aBorderStyle
) ||
3293 (StyleBorderStyle::Dotted
== aBorderStyle
)) {
3294 // no beveling for 1 pixel border, dash or dot
3295 aStartBevelOffset
= 0;
3296 aEndBevelOffset
= 0;
3299 switch (aBorderStyle
) {
3300 case StyleBorderStyle::None
:
3301 case StyleBorderStyle::Hidden
:
3302 // NS_ASSERTION(false, "style of none or hidden");
3304 case StyleBorderStyle::Dotted
:
3305 case StyleBorderStyle::Dashed
: {
3306 nscoord dashLength
=
3307 (StyleBorderStyle::Dashed
== aBorderStyle
) ? DASH_LENGTH
: DOT_LENGTH
;
3308 // make the dash length proportional to the border thickness
3309 dashLength
*= (horizontal
) ? aBorder
.height
: aBorder
.width
;
3310 // make the min dash length for the ends 1/2 the dash length
3311 nscoord minDashLength
=
3312 (StyleBorderStyle::Dashed
== aBorderStyle
)
3313 ? RoundFloatToPixel(((float)dashLength
) / 2.0f
,
3314 aAppUnitsPerDevPixel
)
3316 minDashLength
= std::max(minDashLength
, oneDevPixel
);
3317 nscoord numDashSpaces
= 0;
3318 nscoord startDashLength
= minDashLength
;
3319 nscoord endDashLength
= minDashLength
;
3321 GetDashInfo(aBorder
.width
, dashLength
, aAppUnitsPerDevPixel
,
3322 numDashSpaces
, startDashLength
, endDashLength
);
3323 nsRect
rect(aBorder
.x
, aBorder
.y
, startDashLength
, aBorder
.height
);
3324 DrawSolidBorderSegment(aDrawTarget
, rect
, aBorderColor
,
3325 aAppUnitsPerDevPixel
);
3327 rect
.x
+= startDashLength
+ dashLength
;
3329 aBorder
.width
- (startDashLength
+ endDashLength
+ dashLength
);
3330 DrawDashedSegment(aDrawTarget
, rect
, dashLength
, aBorderColor
,
3331 aAppUnitsPerDevPixel
, horizontal
);
3333 rect
.x
+= rect
.width
;
3334 rect
.width
= endDashLength
;
3335 DrawSolidBorderSegment(aDrawTarget
, rect
, aBorderColor
,
3336 aAppUnitsPerDevPixel
);
3338 GetDashInfo(aBorder
.height
, dashLength
, aAppUnitsPerDevPixel
,
3339 numDashSpaces
, startDashLength
, endDashLength
);
3340 nsRect
rect(aBorder
.x
, aBorder
.y
, aBorder
.width
, startDashLength
);
3341 DrawSolidBorderSegment(aDrawTarget
, rect
, aBorderColor
,
3342 aAppUnitsPerDevPixel
);
3344 rect
.y
+= rect
.height
+ dashLength
;
3346 aBorder
.height
- (startDashLength
+ endDashLength
+ dashLength
);
3347 DrawDashedSegment(aDrawTarget
, rect
, dashLength
, aBorderColor
,
3348 aAppUnitsPerDevPixel
, horizontal
);
3350 rect
.y
+= rect
.height
;
3351 rect
.height
= endDashLength
;
3352 DrawSolidBorderSegment(aDrawTarget
, rect
, aBorderColor
,
3353 aAppUnitsPerDevPixel
);
3357 AutoTArray
<SolidBeveledBorderSegment
, 3> segments
;
3358 GetTableBorderSolidSegments(
3359 segments
, aBorderStyle
, aBorderColor
, aBorder
, aAppUnitsPerDevPixel
,
3360 aStartBevelSide
, aStartBevelOffset
, aEndBevelSide
, aEndBevelOffset
);
3361 for (const auto& segment
: segments
) {
3362 DrawSolidBorderSegment(
3363 aDrawTarget
, segment
.mRect
, segment
.mColor
, aAppUnitsPerDevPixel
,
3364 segment
.mStartBevel
.mSide
, segment
.mStartBevel
.mOffset
,
3365 segment
.mEndBevel
.mSide
, segment
.mEndBevel
.mOffset
);
3371 void nsCSSRendering::GetTableBorderSolidSegments(
3372 nsTArray
<SolidBeveledBorderSegment
>& aSegments
,
3373 StyleBorderStyle aBorderStyle
, nscolor aBorderColor
, const nsRect
& aBorder
,
3374 int32_t aAppUnitsPerDevPixel
, mozilla::Side aStartBevelSide
,
3375 nscoord aStartBevelOffset
, mozilla::Side aEndBevelSide
,
3376 nscoord aEndBevelOffset
) {
3377 const bool horizontal
=
3378 eSideTop
== aStartBevelSide
|| eSideBottom
== aStartBevelSide
;
3379 const nscoord oneDevPixel
= NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel
);
3381 switch (aBorderStyle
) {
3382 case StyleBorderStyle::None
:
3383 case StyleBorderStyle::Hidden
:
3385 case StyleBorderStyle::Dotted
:
3386 case StyleBorderStyle::Dashed
:
3387 MOZ_ASSERT_UNREACHABLE("Caller should have checked");
3389 case StyleBorderStyle::Groove
:
3390 case StyleBorderStyle::Ridge
:
3391 if ((horizontal
&& (oneDevPixel
>= aBorder
.height
)) ||
3392 (!horizontal
&& (oneDevPixel
>= aBorder
.width
))) {
3393 aSegments
.AppendElement(
3394 SolidBeveledBorderSegment
{aBorder
,
3396 {aStartBevelSide
, aStartBevelOffset
},
3397 {aEndBevelSide
, aEndBevelOffset
}});
3399 nscoord startBevel
=
3400 (aStartBevelOffset
> 0)
3401 ? RoundFloatToPixel(0.5f
* (float)aStartBevelOffset
,
3402 aAppUnitsPerDevPixel
, true)
3405 (aEndBevelOffset
> 0)
3406 ? RoundFloatToPixel(0.5f
* (float)aEndBevelOffset
,
3407 aAppUnitsPerDevPixel
, true)
3409 mozilla::Side ridgeGrooveSide
= (horizontal
) ? eSideTop
: eSideLeft
;
3410 // FIXME: In theory, this should use the visited-dependent
3411 // background color, but I don't care.
3412 nscolor bevelColor
=
3413 MakeBevelColor(ridgeGrooveSide
, aBorderStyle
, aBorderColor
);
3414 nsRect
rect(aBorder
);
3416 if (horizontal
) { // top, bottom
3417 half
= RoundFloatToPixel(0.5f
* (float)aBorder
.height
,
3418 aAppUnitsPerDevPixel
);
3420 if (eSideTop
== aStartBevelSide
) {
3421 rect
.x
+= startBevel
;
3422 rect
.width
-= startBevel
;
3424 if (eSideTop
== aEndBevelSide
) {
3425 rect
.width
-= endBevel
;
3427 aSegments
.AppendElement(
3428 SolidBeveledBorderSegment
{rect
,
3430 {aStartBevelSide
, startBevel
},
3431 {aEndBevelSide
, endBevel
}});
3432 } else { // left, right
3433 half
= RoundFloatToPixel(0.5f
* (float)aBorder
.width
,
3434 aAppUnitsPerDevPixel
);
3436 if (eSideLeft
== aStartBevelSide
) {
3437 rect
.y
+= startBevel
;
3438 rect
.height
-= startBevel
;
3440 if (eSideLeft
== aEndBevelSide
) {
3441 rect
.height
-= endBevel
;
3443 aSegments
.AppendElement(
3444 SolidBeveledBorderSegment
{rect
,
3446 {aStartBevelSide
, startBevel
},
3447 {aEndBevelSide
, endBevel
}});
3452 (eSideTop
== ridgeGrooveSide
) ? eSideBottom
: eSideRight
;
3453 // FIXME: In theory, this should use the visited-dependent
3454 // background color, but I don't care.
3456 MakeBevelColor(ridgeGrooveSide
, aBorderStyle
, aBorderColor
);
3458 rect
.y
= rect
.y
+ half
;
3459 rect
.height
= aBorder
.height
- half
;
3460 if (eSideBottom
== aStartBevelSide
) {
3461 rect
.x
+= startBevel
;
3462 rect
.width
-= startBevel
;
3464 if (eSideBottom
== aEndBevelSide
) {
3465 rect
.width
-= endBevel
;
3467 aSegments
.AppendElement(
3468 SolidBeveledBorderSegment
{rect
,
3470 {aStartBevelSide
, startBevel
},
3471 {aEndBevelSide
, endBevel
}});
3473 rect
.x
= rect
.x
+ half
;
3474 rect
.width
= aBorder
.width
- half
;
3475 if (eSideRight
== aStartBevelSide
) {
3476 rect
.y
+= aStartBevelOffset
- startBevel
;
3477 rect
.height
-= startBevel
;
3479 if (eSideRight
== aEndBevelSide
) {
3480 rect
.height
-= endBevel
;
3482 aSegments
.AppendElement(
3483 SolidBeveledBorderSegment
{rect
,
3485 {aStartBevelSide
, startBevel
},
3486 {aEndBevelSide
, endBevel
}});
3490 case StyleBorderStyle::Double
:
3491 // We can only do "double" borders if the thickness of the border
3492 // is more than 2px. Otherwise, we fall through to painting a
3494 if ((aBorder
.width
> 2 * oneDevPixel
|| horizontal
) &&
3495 (aBorder
.height
> 2 * oneDevPixel
|| !horizontal
)) {
3496 nscoord startBevel
=
3497 (aStartBevelOffset
> 0)
3498 ? RoundFloatToPixel(0.333333f
* (float)aStartBevelOffset
,
3499 aAppUnitsPerDevPixel
)
3502 (aEndBevelOffset
> 0)
3503 ? RoundFloatToPixel(0.333333f
* (float)aEndBevelOffset
,
3504 aAppUnitsPerDevPixel
)
3506 if (horizontal
) { // top, bottom
3507 nscoord thirdHeight
= RoundFloatToPixel(
3508 0.333333f
* (float)aBorder
.height
, aAppUnitsPerDevPixel
);
3510 // draw the top line or rect
3511 nsRect
topRect(aBorder
.x
, aBorder
.y
, aBorder
.width
, thirdHeight
);
3512 if (eSideTop
== aStartBevelSide
) {
3513 topRect
.x
+= aStartBevelOffset
- startBevel
;
3514 topRect
.width
-= aStartBevelOffset
- startBevel
;
3516 if (eSideTop
== aEndBevelSide
) {
3517 topRect
.width
-= aEndBevelOffset
- endBevel
;
3520 aSegments
.AppendElement(
3521 SolidBeveledBorderSegment
{topRect
,
3523 {aStartBevelSide
, startBevel
},
3524 {aEndBevelSide
, endBevel
}});
3526 // draw the botom line or rect
3527 nscoord heightOffset
= aBorder
.height
- thirdHeight
;
3528 nsRect
bottomRect(aBorder
.x
, aBorder
.y
+ heightOffset
, aBorder
.width
,
3529 aBorder
.height
- heightOffset
);
3530 if (eSideBottom
== aStartBevelSide
) {
3531 bottomRect
.x
+= aStartBevelOffset
- startBevel
;
3532 bottomRect
.width
-= aStartBevelOffset
- startBevel
;
3534 if (eSideBottom
== aEndBevelSide
) {
3535 bottomRect
.width
-= aEndBevelOffset
- endBevel
;
3537 aSegments
.AppendElement(
3538 SolidBeveledBorderSegment
{bottomRect
,
3540 {aStartBevelSide
, startBevel
},
3541 {aEndBevelSide
, endBevel
}});
3542 } else { // left, right
3543 nscoord thirdWidth
= RoundFloatToPixel(
3544 0.333333f
* (float)aBorder
.width
, aAppUnitsPerDevPixel
);
3546 nsRect
leftRect(aBorder
.x
, aBorder
.y
, thirdWidth
, aBorder
.height
);
3547 if (eSideLeft
== aStartBevelSide
) {
3548 leftRect
.y
+= aStartBevelOffset
- startBevel
;
3549 leftRect
.height
-= aStartBevelOffset
- startBevel
;
3551 if (eSideLeft
== aEndBevelSide
) {
3552 leftRect
.height
-= aEndBevelOffset
- endBevel
;
3555 aSegments
.AppendElement(
3556 SolidBeveledBorderSegment
{leftRect
,
3558 {aStartBevelSide
, startBevel
},
3559 {aEndBevelSide
, endBevel
}});
3561 nscoord widthOffset
= aBorder
.width
- thirdWidth
;
3562 nsRect
rightRect(aBorder
.x
+ widthOffset
, aBorder
.y
,
3563 aBorder
.width
- widthOffset
, aBorder
.height
);
3564 if (eSideRight
== aStartBevelSide
) {
3565 rightRect
.y
+= aStartBevelOffset
- startBevel
;
3566 rightRect
.height
-= aStartBevelOffset
- startBevel
;
3568 if (eSideRight
== aEndBevelSide
) {
3569 rightRect
.height
-= aEndBevelOffset
- endBevel
;
3571 aSegments
.AppendElement(
3572 SolidBeveledBorderSegment
{rightRect
,
3574 {aStartBevelSide
, startBevel
},
3575 {aEndBevelSide
, endBevel
}});
3579 // else fall through to solid
3581 case StyleBorderStyle::Solid
:
3582 aSegments
.AppendElement(
3583 SolidBeveledBorderSegment
{aBorder
,
3585 {aStartBevelSide
, aStartBevelOffset
},
3586 {aEndBevelSide
, aEndBevelOffset
}});
3588 case StyleBorderStyle::Outset
:
3589 case StyleBorderStyle::Inset
:
3590 MOZ_ASSERT_UNREACHABLE(
3591 "inset, outset should have been converted to groove, ridge");
3596 // End table border-collapsing section
3598 Rect
nsCSSRendering::ExpandPaintingRectForDecorationLine(
3599 nsIFrame
* aFrame
, const uint8_t aStyle
, const Rect
& aClippedRect
,
3600 const Float aICoordInFrame
, const Float aCycleLength
, bool aVertical
) {
3602 case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED
:
3603 case NS_STYLE_TEXT_DECORATION_STYLE_DASHED
:
3604 case NS_STYLE_TEXT_DECORATION_STYLE_WAVY
:
3607 NS_ERROR("Invalid style was specified");
3608 return aClippedRect
;
3611 nsBlockFrame
* block
= nullptr;
3612 // Note that when we paint the decoration lines in relative positioned
3613 // box, we should paint them like all of the boxes are positioned as static.
3614 nscoord framePosInBlockAppUnits
= 0;
3615 for (nsIFrame
* f
= aFrame
; f
; f
= f
->GetParent()) {
3616 block
= do_QueryFrame(f
);
3620 framePosInBlockAppUnits
+=
3621 aVertical
? f
->GetNormalPosition().y
: f
->GetNormalPosition().x
;
3624 NS_ENSURE_TRUE(block
, aClippedRect
);
3626 nsPresContext
* pc
= aFrame
->PresContext();
3627 Float framePosInBlock
=
3628 Float(pc
->AppUnitsToGfxUnits(framePosInBlockAppUnits
));
3629 int32_t rectPosInBlock
= int32_t(NS_round(framePosInBlock
+ aICoordInFrame
));
3630 int32_t extraStartEdge
=
3631 rectPosInBlock
- (rectPosInBlock
/ int32_t(aCycleLength
) * aCycleLength
);
3632 Rect
rect(aClippedRect
);
3634 rect
.y
-= extraStartEdge
;
3635 rect
.height
+= extraStartEdge
;
3637 rect
.x
-= extraStartEdge
;
3638 rect
.width
+= extraStartEdge
;
3643 // Converts a GfxFont to an SkFont
3644 // Either returns true if it was successful, or false if something went wrong
3645 static bool GetSkFontFromGfxFont(DrawTarget
& aDrawTarget
, gfxFont
* aFont
,
3647 RefPtr
<ScaledFont
> scaledFont
= aFont
->GetScaledFont(&aDrawTarget
);
3652 ScaledFontBase
* fontBase
= static_cast<ScaledFontBase
*>(scaledFont
.get());
3654 SkTypeface
* typeface
= fontBase
->GetSkTypeface();
3659 aSkFont
= SkFont(sk_ref_sp(typeface
), SkFloatToScalar(fontBase
->GetSize()));
3663 // Computes data used to position the decoration line within a
3664 // SkTextBlob, data is returned through aBounds
3665 static void GetPositioning(
3666 const nsCSSRendering::PaintDecorationLineParams
& aParams
, const Rect
& aRect
,
3667 Float aOneCSSPixel
, Float aCenterBaselineOffset
, SkScalar aBounds
[]) {
3669 * How Positioning in Skia Works
3670 * Take the letter "n" for example
3671 * We set textPos as 0, 0
3672 * This is represented in Skia like so (not to scale)
3678 * (0,0) ----------------------->
3684 * 0 on the x axis is a line that touches the bottom of the n
3685 * (0,0) is the bottom left-hand corner of the n character
3686 * Moving "up" from the n is going in a negative y direction
3687 * Moving "down" from the n is going in a positive y direction
3689 * The intercepts that are returned in this arrangement will be
3690 * offset by the original point it starts at. (This happens in
3691 * the SkipInk function below).
3693 * In Skia, text MUST be laid out such that the next character
3694 * in the RunBuffer is further along the x-axis than the previous
3695 * character, otherwise there is undefined/strange behavior.
3698 Float rectThickness
= aParams
.vertical
? aRect
.Width() : aRect
.Height();
3700 // the upper and lower lines/edges of the under or over line
3701 SkScalar upperLine
, lowerLine
;
3702 if (aParams
.decoration
== mozilla::StyleTextDecorationLine::OVERLINE
) {
3704 -aParams
.offset
+ aParams
.defaultLineThickness
- aCenterBaselineOffset
;
3705 upperLine
= lowerLine
- rectThickness
;
3707 // underlines in vertical text are offset from the center of
3708 // the text, and not the baseline
3709 // Skia sets the text at it's baseline so we have to offset it
3710 // for text in vertical-* writing modes
3711 upperLine
= -aParams
.offset
- aCenterBaselineOffset
;
3712 lowerLine
= upperLine
+ rectThickness
;
3715 // set up the bounds, add in a little padding to the thickness of the line
3716 // (unless the line is <= 1 CSS pixel thick)
3717 Float lineThicknessPadding
= aParams
.lineSize
.height
> aOneCSSPixel
3718 ? 0.25f
* aParams
.lineSize
.height
3720 // don't allow padding greater than 0.75 CSS pixel
3721 lineThicknessPadding
= std::min(lineThicknessPadding
, 0.75f
* aOneCSSPixel
);
3722 aBounds
[0] = upperLine
- lineThicknessPadding
;
3723 aBounds
[1] = lowerLine
+ lineThicknessPadding
;
3726 // positions an individual glyph according to the given offset
3727 static SkPoint
GlyphPosition(const gfxTextRun::DetailedGlyph
& aGlyph
,
3728 const SkPoint
& aTextPos
,
3729 int32_t aAppUnitsPerDevPixel
) {
3730 SkPoint point
= {aGlyph
.mOffset
.x
, aGlyph
.mOffset
.y
};
3732 // convert to device pixels
3733 point
.fX
/= (float)aAppUnitsPerDevPixel
;
3734 point
.fY
/= (float)aAppUnitsPerDevPixel
;
3737 point
.fX
+= aTextPos
.fX
;
3738 point
.fY
+= aTextPos
.fY
;
3742 // returns a count of all the glyphs that will be rendered
3743 // excludes ligature continuations, includes the number of individual
3744 // glyph records. This includes the number of DetailedGlyphs that a single
3745 // CompressedGlyph record points to. This function is necessary because Skia
3746 // needs the total length of glyphs to add to it's run buffer before it creates
3747 // the RunBuffer object, and this cannot be resized later.
3748 static uint32_t CountAllGlyphs(
3749 const gfxTextRun
* aTextRun
,
3750 const gfxTextRun::CompressedGlyph
* aCompressedGlyph
, uint32_t aStringStart
,
3751 uint32_t aStringEnd
) {
3752 uint32_t totalGlyphCount
= 0;
3754 for (const gfxTextRun::CompressedGlyph
* cg
= aCompressedGlyph
+ aStringStart
;
3755 cg
< aCompressedGlyph
+ aStringEnd
; ++cg
) {
3756 totalGlyphCount
+= cg
->IsSimpleGlyph() ? 1 : cg
->GetGlyphCount();
3759 return totalGlyphCount
;
3762 static void AddDetailedGlyph(const SkTextBlobBuilder::RunBuffer
& aRunBuffer
,
3763 const gfxTextRun::DetailedGlyph
& aGlyph
,
3764 int aIndex
, float aAppUnitsPerDevPixel
,
3765 SkPoint
& aTextPos
) {
3766 // add glyph ID to the run buffer at i
3767 aRunBuffer
.glyphs
[aIndex
] = aGlyph
.mGlyphID
;
3769 // position the glyph correctly using the detailed offsets
3770 SkPoint position
= GlyphPosition(aGlyph
, aTextPos
, aAppUnitsPerDevPixel
);
3771 aRunBuffer
.pos
[2 * aIndex
] = position
.fX
;
3772 aRunBuffer
.pos
[(2 * aIndex
) + 1] = position
.fY
;
3774 // increase aTextPos.fx by the advance
3775 aTextPos
.fX
+= ((float)aGlyph
.mAdvance
/ aAppUnitsPerDevPixel
);
3778 static void AddSimpleGlyph(const SkTextBlobBuilder::RunBuffer
& aRunBuffer
,
3779 const gfxTextRun::CompressedGlyph
& aGlyph
,
3780 int aIndex
, float aAppUnitsPerDevPixel
,
3781 SkPoint
& aTextPos
) {
3782 aRunBuffer
.glyphs
[aIndex
] = aGlyph
.GetSimpleGlyph();
3784 // simple glyphs are offset from 0, so we'll just use textPos
3785 aRunBuffer
.pos
[2 * aIndex
] = aTextPos
.fX
;
3786 aRunBuffer
.pos
[(2 * aIndex
) + 1] = aTextPos
.fY
;
3788 // increase aTextPos.fX by the advance
3789 aTextPos
.fX
+= ((float)aGlyph
.GetSimpleAdvance() / aAppUnitsPerDevPixel
);
3792 // Sets up a Skia TextBlob of the specified font, text position, and made up of
3793 // the glyphs between aStringStart and aStringEnd. Handles RTL and LTR text
3794 // and positions each glyph within the text blob
3795 static sk_sp
<const SkTextBlob
> CreateTextBlob(
3796 const gfxTextRun
* aTextRun
,
3797 const gfxTextRun::CompressedGlyph
* aCompressedGlyph
, const SkFont
& aFont
,
3798 const gfxTextRun::PropertyProvider::Spacing
* aSpacing
,
3799 uint32_t aStringStart
, uint32_t aStringEnd
, float aAppUnitsPerDevPixel
,
3800 SkPoint
& aTextPos
, int32_t& aSpacingOffset
) {
3801 // allocate space for the run buffer, then fill it with the glyphs
3803 CountAllGlyphs(aTextRun
, aCompressedGlyph
, aStringStart
, aStringEnd
);
3808 SkTextBlobBuilder builder
;
3809 const SkTextBlobBuilder::RunBuffer
& run
= builder
.allocRunPos(aFont
, len
);
3811 // RTL text should be read in by glyph starting at aStringEnd - 1 down until
3813 bool isRTL
= aTextRun
->IsRightToLeft();
3814 uint32_t currIndex
= isRTL
? aStringEnd
- 1 : aStringStart
; // textRun index
3815 // currIndex will be advanced by |step| until it reaches |limit|, which is the
3816 // final index to be handled (NOT one beyond the final index)
3817 int step
= isRTL
? -1 : 1;
3818 uint32_t limit
= isRTL
? aStringStart
: aStringEnd
- 1;
3820 uint32_t i
= 0; // index into the SkTextBlob we're building
3822 // Loop exit test is below, just before we update currIndex.
3824 isRTL
? aSpacing
[aSpacingOffset
].mAfter
/ aAppUnitsPerDevPixel
3825 : aSpacing
[aSpacingOffset
].mBefore
/ aAppUnitsPerDevPixel
;
3827 if (aCompressedGlyph
[currIndex
].IsSimpleGlyph()) {
3828 MOZ_ASSERT(i
< len
, "glyph count error!");
3829 AddSimpleGlyph(run
, aCompressedGlyph
[currIndex
], i
, aAppUnitsPerDevPixel
,
3833 // if it's detailed, potentially add multiple into run.glyphs
3834 uint32_t count
= aCompressedGlyph
[currIndex
].GetGlyphCount();
3836 gfxTextRun::DetailedGlyph
* detailGlyph
=
3837 aTextRun
->GetDetailedGlyphs(currIndex
);
3838 for (uint32_t d
= isRTL
? count
- 1 : 0; count
; count
--, d
+= step
) {
3839 MOZ_ASSERT(i
< len
, "glyph count error!");
3840 AddDetailedGlyph(run
, detailGlyph
[d
], i
, aAppUnitsPerDevPixel
,
3846 aTextPos
.fX
+= isRTL
3847 ? aSpacing
[aSpacingOffset
].mBefore
/ aAppUnitsPerDevPixel
3848 : aSpacing
[aSpacingOffset
].mAfter
/ aAppUnitsPerDevPixel
;
3849 aSpacingOffset
+= step
;
3851 if (currIndex
== limit
) {
3857 MOZ_ASSERT(i
== len
, "glyph count error!");
3859 return builder
.make();
3862 // Given a TextBlob, the bounding lines, and the set of current intercepts this
3863 // function adds the intercepts for the current TextBlob into the given set of
3864 // previoulsy calculated intercepts. This set is either of length 0, or a
3865 // multiple of 2 (since every intersection with a piece of text results in two
3866 // intercepts: entering/exiting)
3867 static void GetTextIntercepts(const sk_sp
<const SkTextBlob
>& aBlob
,
3868 const SkScalar aBounds
[],
3869 nsTArray
<SkScalar
>& aIntercepts
) {
3870 // https://skia.org/user/api/SkTextBlob_Reference#Text_Blob_Text_Intercepts
3871 int count
= aBlob
->getIntercepts(aBounds
, nullptr);
3875 aBlob
->getIntercepts(aBounds
, aIntercepts
.AppendElements(count
));
3878 // This function, given a set of intercepts that represent each intersection
3879 // between an under/overline and text, makes a series of calls to
3880 // PaintDecorationLineInternal that paints a series of clip rects which
3881 // implement the text-decoration-skip-ink property
3882 // Logic for where to place each clipped rect, and the length of each rect is
3884 static void SkipInk(nsIFrame
* aFrame
, DrawTarget
& aDrawTarget
,
3885 const nsCSSRendering::PaintDecorationLineParams
& aParams
,
3886 const nsTArray
<SkScalar
>& aIntercepts
, Float aPadding
,
3888 nsCSSRendering::PaintDecorationLineParams clipParams
= aParams
;
3889 int length
= aIntercepts
.Length();
3891 SkScalar startIntercept
= 0;
3892 SkScalar endIntercept
= 0;
3894 // keep track of the direction we are drawing the clipped rects in
3895 // for sideways text, our intercepts from the first glyph are actually
3896 // decreasing (towards the top edge of the page), so we use a negative
3899 Float lineStart
= aParams
.vertical
? aParams
.pt
.y
: aParams
.pt
.x
;
3900 Float lineEnd
= lineStart
+ aParams
.lineSize
.width
;
3901 if (aParams
.sidewaysLeft
) {
3903 std::swap(lineStart
, lineEnd
);
3906 for (int i
= 0; i
<= length
; i
+= 2) {
3907 // handle start/end edge cases and set up general case
3908 startIntercept
= (i
> 0) ? (dir
* aIntercepts
[i
- 1]) + lineStart
3909 : lineStart
- (dir
* aPadding
);
3910 endIntercept
= (i
< length
) ? (dir
* aIntercepts
[i
]) + lineStart
3911 : lineEnd
+ (dir
* aPadding
);
3913 // remove padding at both ends for width
3914 // the start of the line is calculated so the padding removes just
3915 // enough so that the line starts at its normal position
3916 clipParams
.lineSize
.width
=
3917 (dir
* (endIntercept
- startIntercept
)) - (2.0 * aPadding
);
3919 // Don't draw decoration lines that have a smaller width than 1, or half
3920 // the line-end padding dimension.
3921 if (clipParams
.lineSize
.width
< std::max(aPadding
* 0.5, 1.0)) {
3925 // Start the line right after the intercept's location plus room for
3926 // padding; snap the rect edges to device pixels for consistent rendering
3927 // of dots across separate fragments of a dotted line.
3928 if (aParams
.vertical
) {
3929 clipParams
.pt
.y
= aParams
.sidewaysLeft
? endIntercept
+ aPadding
3930 : startIntercept
+ aPadding
;
3931 aRect
.y
= std::floor(clipParams
.pt
.y
+ 0.5);
3932 aRect
.SetBottomEdge(
3933 std::floor(clipParams
.pt
.y
+ clipParams
.lineSize
.width
+ 0.5));
3935 clipParams
.pt
.x
= startIntercept
+ aPadding
;
3936 aRect
.x
= std::floor(clipParams
.pt
.x
+ 0.5);
3938 std::floor(clipParams
.pt
.x
+ clipParams
.lineSize
.width
+ 0.5));
3941 nsCSSRendering::PaintDecorationLineInternal(aFrame
, aDrawTarget
, clipParams
,
3946 void nsCSSRendering::PaintDecorationLine(
3947 nsIFrame
* aFrame
, DrawTarget
& aDrawTarget
,
3948 const PaintDecorationLineParams
& aParams
) {
3949 NS_ASSERTION(aParams
.style
!= NS_STYLE_TEXT_DECORATION_STYLE_NONE
,
3952 Rect rect
= ToRect(GetTextDecorationRectInternal(aParams
.pt
, aParams
));
3953 if (rect
.IsEmpty() || !rect
.Intersects(aParams
.dirtyRect
)) {
3957 if (aParams
.decoration
!= StyleTextDecorationLine::UNDERLINE
&&
3958 aParams
.decoration
!= StyleTextDecorationLine::OVERLINE
&&
3959 aParams
.decoration
!= StyleTextDecorationLine::LINE_THROUGH
) {
3960 MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
3964 // Check if decoration line will skip past ascenders/descenders
3965 // text-decoration-skip-ink only applies to overlines/underlines
3966 mozilla::StyleTextDecorationSkipInk skipInk
=
3967 aFrame
->StyleText()->mTextDecorationSkipInk
;
3968 bool skipInkEnabled
=
3969 skipInk
!= mozilla::StyleTextDecorationSkipInk::None
&&
3970 aParams
.decoration
!= StyleTextDecorationLine::LINE_THROUGH
;
3972 if (!skipInkEnabled
|| aParams
.glyphRange
.Length() == 0) {
3973 PaintDecorationLineInternal(aFrame
, aDrawTarget
, aParams
, rect
);
3977 // check if the frame is a text frame or not
3978 nsTextFrame
* textFrame
= nullptr;
3979 if (aFrame
->IsTextFrame()) {
3980 textFrame
= static_cast<nsTextFrame
*>(aFrame
);
3982 PaintDecorationLineInternal(aFrame
, aDrawTarget
, aParams
, rect
);
3986 // get text run and current text offset (for line wrapping)
3987 gfxTextRun
* textRun
=
3988 textFrame
->GetTextRun(nsTextFrame::TextRunType::eInflated
);
3990 // used for conversions from app units to device pixels
3991 int32_t appUnitsPerDevPixel
= aFrame
->PresContext()->AppUnitsPerDevPixel();
3993 // pointer to the array of glyphs for this TextRun
3994 gfxTextRun::CompressedGlyph
* characterGlyphs
= textRun
->GetCharacterGlyphs();
3996 // get positioning info
3997 SkPoint textPos
= {0, aParams
.baselineOffset
};
3998 SkScalar bounds
[] = {0, 0};
3999 Float oneCSSPixel
= aFrame
->PresContext()->CSSPixelsToDevPixels(1.0f
);
4000 if (!textRun
->UseCenterBaseline()) {
4001 GetPositioning(aParams
, rect
, oneCSSPixel
, 0, bounds
);
4004 // array for the text intercepts
4005 AutoTArray
<SkScalar
, 256> intercepts
;
4007 // array for spacing data
4008 AutoTArray
<gfxTextRun::PropertyProvider::Spacing
, 64> spacing
;
4009 spacing
.SetLength(aParams
.glyphRange
.Length());
4010 if (aParams
.provider
!= nullptr) {
4011 aParams
.provider
->GetSpacing(aParams
.glyphRange
, spacing
.Elements());
4014 // loop through each glyph run
4015 // in most cases there will only be one
4016 bool isRTL
= textRun
->IsRightToLeft();
4017 int32_t spacingOffset
= isRTL
? aParams
.glyphRange
.Length() - 1 : 0;
4018 gfxTextRun::GlyphRunIterator
iter(textRun
, aParams
.glyphRange
, isRTL
);
4020 // For any glyph run where we don't actually do skipping, we'll need to
4021 // advance the current position by its width.
4022 // (For runs we do process, CreateTextBlob will update the position.)
4023 auto currentGlyphRunAdvance
= [&]() {
4024 return textRun
->GetAdvanceWidth(
4025 gfxTextRun::Range(iter
.GetStringStart(), iter
.GetStringEnd()),
4027 appUnitsPerDevPixel
;
4030 while (iter
.NextRun()) {
4031 if (iter
.GetGlyphRun()->mOrientation
==
4032 mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
||
4033 (iter
.GetGlyphRun()->mIsCJK
&&
4034 skipInk
== mozilla::StyleTextDecorationSkipInk::Auto
)) {
4035 // We don't support upright text in vertical modes currently
4036 // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1572294),
4037 // but we do need to update textPos so that following runs will be
4038 // correctly positioned.
4039 // We also don't apply skip-ink to CJK text runs because many fonts
4040 // have an underline that looks really bad if this is done
4041 // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1573249),
4042 // when skip-ink is set to 'auto'.
4043 textPos
.fX
+= currentGlyphRunAdvance();
4047 gfxFont
* font
= iter
.GetGlyphRun()->mFont
;
4048 // Don't try to apply skip-ink to 'sbix' fonts like Apple Color Emoji,
4049 // because old macOS (10.9) may crash trying to retrieve glyph paths
4050 // that don't exist.
4051 if (font
->GetFontEntry()->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) {
4052 textPos
.fX
+= currentGlyphRunAdvance();
4056 // get a Skia version of the glyph run's font
4058 if (!GetSkFontFromGfxFont(aDrawTarget
, font
, skiafont
)) {
4059 PaintDecorationLineInternal(aFrame
, aDrawTarget
, aParams
, rect
);
4063 // Create a text blob with correctly positioned glyphs. This also updates
4064 // textPos.fX with the advance of the glyphs.
4065 sk_sp
<const SkTextBlob
> textBlob
=
4066 CreateTextBlob(textRun
, characterGlyphs
, skiafont
, spacing
.Elements(),
4067 iter
.GetStringStart(), iter
.GetStringEnd(),
4068 (float)appUnitsPerDevPixel
, textPos
, spacingOffset
);
4071 textPos
.fX
+= currentGlyphRunAdvance();
4075 if (textRun
->UseCenterBaseline()) {
4076 // writing modes that use a center baseline need to be adjusted on a
4077 // font-by-font basis since Skia lines up the text on a alphabetic
4078 // baseline, but for some vertical-* writing modes the offset is from the
4080 gfxFont::Metrics metrics
= font
->GetMetrics(nsFontMetrics::eHorizontal
);
4081 Float centerToBaseline
= (metrics
.emAscent
- metrics
.emDescent
) / 2.0f
;
4082 GetPositioning(aParams
, rect
, oneCSSPixel
, centerToBaseline
, bounds
);
4085 // compute the text intercepts that need to be skipped
4086 GetTextIntercepts(textBlob
, bounds
, intercepts
);
4088 bool needsSkipInk
= intercepts
.Length() > 0;
4091 // Padding between glyph intercepts and the decoration line: we use the
4092 // decoration line thickness, clamped to a minimum of 1px and a maximum
4095 std::min(std::max(aParams
.lineSize
.height
, oneCSSPixel
),
4096 Float(textRun
->GetFontGroup()->GetStyle()->size
/ 5.0));
4097 SkipInk(aFrame
, aDrawTarget
, aParams
, intercepts
, padding
, rect
);
4099 PaintDecorationLineInternal(aFrame
, aDrawTarget
, aParams
, rect
);
4103 void nsCSSRendering::PaintDecorationLineInternal(
4104 nsIFrame
* aFrame
, DrawTarget
& aDrawTarget
,
4105 const PaintDecorationLineParams
& aParams
, Rect aRect
) {
4106 Float lineThickness
= std::max(NS_round(aParams
.lineSize
.height
), 1.0);
4108 DeviceColor color
= ToDeviceColor(aParams
.color
);
4109 ColorPattern
colorPat(color
);
4110 StrokeOptions
strokeOptions(lineThickness
);
4111 DrawOptions drawOptions
;
4115 AutoPopClips
autoPopClips(&aDrawTarget
);
4117 mozilla::layout::TextDrawTarget
* textDrawer
= nullptr;
4118 if (aDrawTarget
.GetBackendType() == BackendType::WEBRENDER_TEXT
) {
4119 textDrawer
= static_cast<mozilla::layout::TextDrawTarget
*>(&aDrawTarget
);
4122 switch (aParams
.style
) {
4123 case NS_STYLE_TEXT_DECORATION_STYLE_SOLID
:
4124 case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE
:
4126 case NS_STYLE_TEXT_DECORATION_STYLE_DASHED
: {
4127 autoPopClips
.PushClipRect(aRect
);
4128 Float dashWidth
= lineThickness
* DOT_LENGTH
* DASH_LENGTH
;
4129 dash
[0] = dashWidth
;
4130 dash
[1] = dashWidth
;
4131 strokeOptions
.mDashPattern
= dash
;
4132 strokeOptions
.mDashLength
= MOZ_ARRAY_LENGTH(dash
);
4133 strokeOptions
.mLineCap
= CapStyle::BUTT
;
4134 aRect
= ExpandPaintingRectForDecorationLine(
4135 aFrame
, aParams
.style
, aRect
, aParams
.icoordInFrame
, dashWidth
* 2,
4137 // We should continue to draw the last dash even if it is not in the rect.
4138 aRect
.width
+= dashWidth
;
4141 case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED
: {
4142 autoPopClips
.PushClipRect(aRect
);
4143 Float dashWidth
= lineThickness
* DOT_LENGTH
;
4144 if (lineThickness
> 2.0) {
4146 dash
[1] = dashWidth
* 2.f
;
4147 strokeOptions
.mLineCap
= CapStyle::ROUND
;
4149 dash
[0] = dashWidth
;
4150 dash
[1] = dashWidth
;
4152 strokeOptions
.mDashPattern
= dash
;
4153 strokeOptions
.mDashLength
= MOZ_ARRAY_LENGTH(dash
);
4154 aRect
= ExpandPaintingRectForDecorationLine(
4155 aFrame
, aParams
.style
, aRect
, aParams
.icoordInFrame
, dashWidth
* 2,
4157 // We should continue to draw the last dot even if it is not in the rect.
4158 aRect
.width
+= dashWidth
;
4161 case NS_STYLE_TEXT_DECORATION_STYLE_WAVY
:
4162 autoPopClips
.PushClipRect(aRect
);
4163 if (lineThickness
> 2.0) {
4164 drawOptions
.mAntialiasMode
= AntialiasMode::SUBPIXEL
;
4166 // Don't use anti-aliasing here. Because looks like lighter color wavy
4167 // line at this case. And probably, users don't think the
4168 // non-anti-aliased wavy line is not pretty.
4169 drawOptions
.mAntialiasMode
= AntialiasMode::NONE
;
4173 NS_ERROR("Invalid style value!");
4177 // The block-direction position should be set to the middle of the line.
4178 if (aParams
.vertical
) {
4179 aRect
.x
+= lineThickness
/ 2;
4181 aRect
.y
+= lineThickness
/ 2;
4184 switch (aParams
.style
) {
4185 case NS_STYLE_TEXT_DECORATION_STYLE_SOLID
:
4186 case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED
:
4187 case NS_STYLE_TEXT_DECORATION_STYLE_DASHED
: {
4188 Point p1
= aRect
.TopLeft();
4189 Point p2
= aParams
.vertical
? aRect
.BottomLeft() : aRect
.TopRight();
4191 textDrawer
->AppendDecoration(p1
, p2
, lineThickness
, aParams
.vertical
,
4192 color
, aParams
.style
);
4194 aDrawTarget
.StrokeLine(p1
, p2
, colorPat
, strokeOptions
, drawOptions
);
4198 case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE
: {
4200 * We are drawing double line as:
4202 * +-------------------------------------------+
4203 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4204 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4205 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4208 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4209 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4210 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4211 * +-------------------------------------------+
4213 Point p1a
= aRect
.TopLeft();
4214 Point p2a
= aParams
.vertical
? aRect
.BottomLeft() : aRect
.TopRight();
4216 if (aParams
.vertical
) {
4217 aRect
.width
-= lineThickness
;
4219 aRect
.height
-= lineThickness
;
4222 Point p1b
= aParams
.vertical
? aRect
.TopRight() : aRect
.BottomLeft();
4223 Point p2b
= aRect
.BottomRight();
4226 textDrawer
->AppendDecoration(p1a
, p2a
, lineThickness
, aParams
.vertical
,
4228 NS_STYLE_TEXT_DECORATION_STYLE_SOLID
);
4229 textDrawer
->AppendDecoration(p1b
, p2b
, lineThickness
, aParams
.vertical
,
4231 NS_STYLE_TEXT_DECORATION_STYLE_SOLID
);
4233 aDrawTarget
.StrokeLine(p1a
, p2a
, colorPat
, strokeOptions
, drawOptions
);
4234 aDrawTarget
.StrokeLine(p1b
, p2b
, colorPat
, strokeOptions
, drawOptions
);
4238 case NS_STYLE_TEXT_DECORATION_STYLE_WAVY
: {
4240 * We are drawing wavy line as:
4242 * P: Path, X: Painted pixel
4244 * +---------------------------------------+
4245 * XX|X XXXXXX XXXXXX |
4246 * PP|PX XPPPPPPX XPPPPPPX | ^
4247 * XX|XPX XPXXXXXXPX XPXXXXXXPX| |
4248 * | XPX XPX XPX XPX XP|X |adv
4249 * | XPXXXXXXPX XPXXXXXXPX X|PX |
4250 * | XPPPPPPX XPPPPPPX |XPX v
4251 * | XXXXXX XXXXXX | XX
4252 * +---------------------------------------+
4254 * adv flatLengthAtVertex rightMost
4256 * 1. Always starts from top-left of the drawing area, however, we need
4257 * to draw the line from outside of the rect. Because the start
4258 * point of the line is not good style if we draw from inside it.
4259 * 2. First, draw horizontal line from outside the rect to top-left of
4261 * 3. Goes down to bottom of the area at 45 degrees.
4262 * 4. Slides to right horizontaly, see |flatLengthAtVertex|.
4263 * 5. Goes up to top of the area at 45 degrees.
4264 * 6. Slides to right horizontaly.
4265 * 7. Repeat from 2 until reached to right-most edge of the area.
4267 * In the vertical case, swap horizontal and vertical coordinates and
4268 * directions in the above description.
4271 Float
& rectICoord
= aParams
.vertical
? aRect
.y
: aRect
.x
;
4272 Float
& rectISize
= aParams
.vertical
? aRect
.height
: aRect
.width
;
4273 const Float rectBSize
= aParams
.vertical
? aRect
.width
: aRect
.height
;
4275 const Float adv
= rectBSize
- lineThickness
;
4276 const Float flatLengthAtVertex
=
4277 std::max((lineThickness
- 1.0) * 2.0, 1.0);
4279 // Align the start of wavy lines to the nearest ancestor block.
4280 const Float cycleLength
= 2 * (adv
+ flatLengthAtVertex
);
4281 aRect
= ExpandPaintingRectForDecorationLine(
4282 aFrame
, aParams
.style
, aRect
, aParams
.icoordInFrame
, cycleLength
,
4286 // Undo attempted centering
4287 Float
& rectBCoord
= aParams
.vertical
? aRect
.x
: aRect
.y
;
4288 rectBCoord
-= lineThickness
/ 2;
4290 textDrawer
->AppendWavyDecoration(aRect
, lineThickness
, aParams
.vertical
,
4295 // figure out if we can trim whole cycles from the left and right edges
4296 // of the line, to try and avoid creating an unnecessarily long and
4297 // complex path (but don't do this for webrender, )
4298 const Float dirtyRectICoord
=
4299 aParams
.vertical
? aParams
.dirtyRect
.y
: aParams
.dirtyRect
.x
;
4300 int32_t skipCycles
= floor((dirtyRectICoord
- rectICoord
) / cycleLength
);
4301 if (skipCycles
> 0) {
4302 rectICoord
+= skipCycles
* cycleLength
;
4303 rectISize
-= skipCycles
* cycleLength
;
4306 rectICoord
+= lineThickness
/ 2.0;
4308 Point
pt(aRect
.TopLeft());
4309 Float
& ptICoord
= aParams
.vertical
? pt
.y
: pt
.x
;
4310 Float
& ptBCoord
= aParams
.vertical
? pt
.x
: pt
.y
;
4311 if (aParams
.vertical
) {
4314 Float iCoordLimit
= ptICoord
+ rectISize
+ lineThickness
;
4316 const Float dirtyRectIMost
= aParams
.vertical
? aParams
.dirtyRect
.YMost()
4317 : aParams
.dirtyRect
.XMost();
4318 skipCycles
= floor((iCoordLimit
- dirtyRectIMost
) / cycleLength
);
4319 if (skipCycles
> 0) {
4320 iCoordLimit
-= skipCycles
* cycleLength
;
4323 RefPtr
<PathBuilder
> builder
= aDrawTarget
.CreatePathBuilder();
4326 ptICoord
-= lineThickness
;
4327 builder
->MoveTo(pt
); // 1
4329 ptICoord
= rectICoord
;
4330 builder
->LineTo(pt
); // 2
4332 // In vertical mode, to go "down" relative to the text we need to
4333 // decrease the block coordinate, whereas in horizontal we increase
4334 // it. So the sense of this flag is effectively inverted.
4335 bool goDown
= !aParams
.vertical
;
4337 while (ptICoord
< iCoordLimit
) {
4338 if (++iter
> 1000) {
4339 // stroke the current path and start again, to avoid pathological
4340 // behavior in cairo with huge numbers of path segments
4341 path
= builder
->Finish();
4342 aDrawTarget
.Stroke(path
, colorPat
, strokeOptions
, drawOptions
);
4343 builder
= aDrawTarget
.CreatePathBuilder();
4344 builder
->MoveTo(pt
);
4348 ptBCoord
+= goDown
? adv
: -adv
;
4350 builder
->LineTo(pt
); // 3 and 5
4352 ptICoord
+= flatLengthAtVertex
;
4353 builder
->LineTo(pt
); // 4 and 6
4357 path
= builder
->Finish();
4358 aDrawTarget
.Stroke(path
, colorPat
, strokeOptions
, drawOptions
);
4362 NS_ERROR("Invalid style value!");
4366 Rect
nsCSSRendering::DecorationLineToPath(
4367 const PaintDecorationLineParams
& aParams
) {
4368 NS_ASSERTION(aParams
.style
!= NS_STYLE_TEXT_DECORATION_STYLE_NONE
,
4371 Rect path
; // To benefit from RVO, we return this from all return points
4373 Rect rect
= ToRect(GetTextDecorationRectInternal(aParams
.pt
, aParams
));
4374 if (rect
.IsEmpty() || !rect
.Intersects(aParams
.dirtyRect
)) {
4378 if (aParams
.decoration
!= StyleTextDecorationLine::UNDERLINE
&&
4379 aParams
.decoration
!= StyleTextDecorationLine::OVERLINE
&&
4380 aParams
.decoration
!= StyleTextDecorationLine::LINE_THROUGH
) {
4381 MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
4385 if (aParams
.style
!= NS_STYLE_TEXT_DECORATION_STYLE_SOLID
) {
4386 // For the moment, we support only solid text decorations.
4390 Float lineThickness
= std::max(NS_round(aParams
.lineSize
.height
), 1.0);
4392 // The block-direction position should be set to the middle of the line.
4393 if (aParams
.vertical
) {
4394 rect
.x
+= lineThickness
/ 2;
4395 path
= Rect(rect
.TopLeft() - Point(lineThickness
/ 2, 0.0),
4396 Size(lineThickness
, rect
.Height()));
4398 rect
.y
+= lineThickness
/ 2;
4399 path
= Rect(rect
.TopLeft() - Point(0.0, lineThickness
/ 2),
4400 Size(rect
.Width(), lineThickness
));
4406 nsRect
nsCSSRendering::GetTextDecorationRect(
4407 nsPresContext
* aPresContext
, const DecorationRectParams
& aParams
) {
4408 NS_ASSERTION(aPresContext
, "aPresContext is null");
4409 NS_ASSERTION(aParams
.style
!= NS_STYLE_TEXT_DECORATION_STYLE_NONE
,
4412 gfxRect rect
= GetTextDecorationRectInternal(Point(0, 0), aParams
);
4413 // The rect values are already rounded to nearest device pixels.
4415 r
.x
= aPresContext
->GfxUnitsToAppUnits(rect
.X());
4416 r
.y
= aPresContext
->GfxUnitsToAppUnits(rect
.Y());
4417 r
.width
= aPresContext
->GfxUnitsToAppUnits(rect
.Width());
4418 r
.height
= aPresContext
->GfxUnitsToAppUnits(rect
.Height());
4422 gfxRect
nsCSSRendering::GetTextDecorationRectInternal(
4423 const Point
& aPt
, const DecorationRectParams
& aParams
) {
4424 NS_ASSERTION(aParams
.style
<= NS_STYLE_TEXT_DECORATION_STYLE_WAVY
,
4425 "Invalid aStyle value");
4427 if (aParams
.style
== NS_STYLE_TEXT_DECORATION_STYLE_NONE
)
4428 return gfxRect(0, 0, 0, 0);
4430 bool canLiftUnderline
= aParams
.descentLimit
>= 0.0;
4432 gfxFloat iCoord
= aParams
.vertical
? aPt
.y
: aPt
.x
;
4433 gfxFloat bCoord
= aParams
.vertical
? aPt
.x
: aPt
.y
;
4435 // 'left' and 'right' are relative to the line, so for vertical writing modes
4436 // they will actually become top and bottom of the rendered line.
4437 // Similarly, aLineSize.width and .height are actually length and thickness
4438 // of the line, which runs horizontally or vertically according to aVertical.
4439 const gfxFloat left
= floor(iCoord
+ 0.5),
4440 right
= floor(iCoord
+ aParams
.lineSize
.width
+ 0.5);
4442 // We compute |r| as if for a horizontal text run, and then swap vertical
4443 // and horizontal coordinates at the end if vertical was requested.
4444 gfxRect
r(left
, 0, right
- left
, 0);
4446 gfxFloat lineThickness
= NS_round(aParams
.lineSize
.height
);
4447 lineThickness
= std::max(lineThickness
, 1.0);
4448 gfxFloat defaultLineThickness
= NS_round(aParams
.defaultLineThickness
);
4449 defaultLineThickness
= std::max(defaultLineThickness
, 1.0);
4451 gfxFloat ascent
= NS_round(aParams
.ascent
);
4452 gfxFloat descentLimit
= floor(aParams
.descentLimit
);
4454 gfxFloat suggestedMaxRectHeight
=
4455 std::max(std::min(ascent
, descentLimit
), 1.0);
4456 r
.height
= lineThickness
;
4457 if (aParams
.style
== NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE
) {
4459 * We will draw double line as:
4461 * +-------------------------------------------+
4462 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4463 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4464 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4468 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4469 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4470 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4471 * +-------------------------------------------+
4473 gfxFloat gap
= NS_round(lineThickness
/ 2.0);
4474 gap
= std::max(gap
, 1.0);
4475 r
.height
= lineThickness
* 2.0 + gap
;
4476 if (canLiftUnderline
) {
4477 if (r
.Height() > suggestedMaxRectHeight
) {
4478 // Don't shrink the line height, because the thickness has some meaning.
4479 // We can just shrink the gap at this time.
4480 r
.height
= std::max(suggestedMaxRectHeight
, lineThickness
* 2.0 + 1.0);
4483 } else if (aParams
.style
== NS_STYLE_TEXT_DECORATION_STYLE_WAVY
) {
4485 * We will draw wavy line as:
4487 * +-------------------------------------------+
4488 * |XXXXX XXXXXX XXXXXX | ^
4489 * |XXXXXX XXXXXXXX XXXXXXXX | | lineThickness
4490 * |XXXXXXX XXXXXXXXXX XXXXXXXXXX| v
4491 * | XXX XXX XXX XXX XX|
4492 * | XXXXXXXXXX XXXXXXXXXX X|
4493 * | XXXXXXXX XXXXXXXX |
4495 * +-------------------------------------------+
4497 r
.height
= lineThickness
> 2.0 ? lineThickness
* 4.0 : lineThickness
* 3.0;
4498 if (canLiftUnderline
) {
4499 if (r
.Height() > suggestedMaxRectHeight
) {
4500 // Don't shrink the line height even if there is not enough space,
4501 // because the thickness has some meaning. E.g., the 1px wavy line and
4502 // 2px wavy line can be used for different meaning in IME selections
4504 r
.height
= std::max(suggestedMaxRectHeight
, lineThickness
* 2.0);
4509 gfxFloat baseline
= floor(bCoord
+ aParams
.ascent
+ 0.5);
4511 // Calculate adjusted offset based on writing-mode/orientation and thickness
4512 // of decoration line. The input value aParams.offset is the nominal position
4513 // (offset from baseline) where we would draw a single, infinitely-thin line;
4514 // but for a wavy or double line, we'll need to move the bounding rect of the
4515 // decoration outwards from the baseline so that an underline remains below
4516 // the glyphs, and an overline above them, despite the increased block-dir
4517 // extent of the decoration.
4519 // So adjustments by r.Height() are used to make the wider line styles (wavy
4520 // and double) "grow" in the appropriate direction compared to the basic
4523 // Note that at this point, the decoration rect is being calculated in line-
4524 // relative coordinates, where 'x' is line-rightwards, and 'y' is line-
4525 // upwards. We'll swap them to be physical coords at the end.
4526 gfxFloat offset
= 0.0;
4528 if (aParams
.decoration
== StyleTextDecorationLine::UNDERLINE
) {
4529 offset
= aParams
.offset
;
4530 if (canLiftUnderline
) {
4531 if (descentLimit
< -offset
+ r
.Height()) {
4532 // If we can ignore the offset and the decoration line is overflowing,
4533 // we should align the bottom edge of the decoration line rect if it's
4534 // possible. Otherwise, we should lift up the top edge of the rect as
4536 gfxFloat offsetBottomAligned
= -descentLimit
+ r
.Height();
4537 gfxFloat offsetTopAligned
= 0.0;
4538 offset
= std::min(offsetBottomAligned
, offsetTopAligned
);
4541 } else if (aParams
.decoration
== StyleTextDecorationLine::OVERLINE
) {
4542 // For overline, we adjust the offset by defaultlineThickness (the default
4543 // thickness of a single decoration line) because empirically it looks
4544 // better to draw the overline just inside rather than outside the font's
4545 // ascent, which is what nsTextFrame passes as aParams.offset (as fonts
4546 // don't provide an explicit overline-offset).
4547 offset
= aParams
.offset
- defaultLineThickness
+ r
.Height();
4548 } else if (aParams
.decoration
== StyleTextDecorationLine::LINE_THROUGH
) {
4549 // To maintain a consistent mid-point for line-through decorations,
4550 // we adjust the offset by half of the decoration rect's height.
4551 gfxFloat extra
= floor(r
.Height() / 2.0 + 0.5);
4552 extra
= std::max(extra
, lineThickness
);
4553 // computes offset for when user specifies a decoration width since
4554 // aParams.offset is derived from the font metric's line height
4555 gfxFloat decorationThicknessOffset
=
4556 (lineThickness
- defaultLineThickness
) / 2.0;
4557 offset
= aParams
.offset
- lineThickness
+ extra
+ decorationThicknessOffset
;
4559 MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
4562 // Convert line-relative coordinate system (x = line-right, y = line-up)
4563 // to physical coords, and move the decoration rect to the calculated
4564 // offset from baseline.
4565 if (aParams
.vertical
) {
4566 std::swap(r
.x
, r
.y
);
4567 std::swap(r
.width
, r
.height
);
4568 // line-upwards in vertical mode = physical-right, so we /add/ offset
4569 // to baseline. Except in sideways-lr mode, where line-upwards will be
4570 // physical leftwards.
4571 if (aParams
.sidewaysLeft
) {
4572 r
.x
= baseline
- floor(offset
+ 0.5);
4574 r
.x
= baseline
+ floor(offset
- r
.Width() + 0.5);
4577 // line-upwards in horizontal mode = physical-up, but our physical coord
4578 // system works downwards, so we /subtract/ offset from baseline.
4579 r
.y
= baseline
- floor(offset
+ 0.5);
4585 #define MAX_BLUR_RADIUS 300
4586 #define MAX_SPREAD_RADIUS 50
4588 static inline gfxPoint
ComputeBlurStdDev(nscoord aBlurRadius
,
4589 int32_t aAppUnitsPerDevPixel
,
4590 gfxFloat aScaleX
, gfxFloat aScaleY
) {
4591 // http://dev.w3.org/csswg/css3-background/#box-shadow says that the
4592 // standard deviation of the blur should be half the given blur value.
4593 gfxFloat blurStdDev
= gfxFloat(aBlurRadius
) / gfxFloat(aAppUnitsPerDevPixel
);
4596 std::min((blurStdDev
* aScaleX
), gfxFloat(MAX_BLUR_RADIUS
)) / 2.0,
4597 std::min((blurStdDev
* aScaleY
), gfxFloat(MAX_BLUR_RADIUS
)) / 2.0);
4600 static inline IntSize
ComputeBlurRadius(nscoord aBlurRadius
,
4601 int32_t aAppUnitsPerDevPixel
,
4602 gfxFloat aScaleX
= 1.0,
4603 gfxFloat aScaleY
= 1.0) {
4604 gfxPoint scaledBlurStdDev
=
4605 ComputeBlurStdDev(aBlurRadius
, aAppUnitsPerDevPixel
, aScaleX
, aScaleY
);
4606 return gfxAlphaBoxBlur::CalculateBlurRadius(scaledBlurStdDev
);
4612 gfxContext
* nsContextBoxBlur::Init(const nsRect
& aRect
, nscoord aSpreadRadius
,
4613 nscoord aBlurRadius
,
4614 int32_t aAppUnitsPerDevPixel
,
4615 gfxContext
* aDestinationCtx
,
4616 const nsRect
& aDirtyRect
,
4617 const gfxRect
* aSkipRect
, uint32_t aFlags
) {
4618 if (aRect
.IsEmpty()) {
4624 IntSize spreadRadius
;
4625 GetBlurAndSpreadRadius(aDestinationCtx
->GetDrawTarget(), aAppUnitsPerDevPixel
,
4626 aBlurRadius
, aSpreadRadius
, blurRadius
, spreadRadius
);
4628 mDestinationCtx
= aDestinationCtx
;
4630 // If not blurring, draw directly onto the destination device
4631 if (blurRadius
.width
<= 0 && blurRadius
.height
<= 0 &&
4632 spreadRadius
.width
<= 0 && spreadRadius
.height
<= 0 &&
4633 !(aFlags
& FORCE_MASK
)) {
4634 mContext
= aDestinationCtx
;
4638 // Convert from app units to device pixels
4639 gfxRect rect
= nsLayoutUtils::RectToGfxRect(aRect
, aAppUnitsPerDevPixel
);
4642 nsLayoutUtils::RectToGfxRect(aDirtyRect
, aAppUnitsPerDevPixel
);
4643 dirtyRect
.RoundOut();
4645 gfxMatrix transform
= aDestinationCtx
->CurrentMatrixDouble();
4646 rect
= transform
.TransformBounds(rect
);
4648 mPreTransformed
= !transform
.IsIdentity();
4650 // Create the temporary surface for blurring
4651 dirtyRect
= transform
.TransformBounds(dirtyRect
);
4652 bool useHardwareAccel
= !(aFlags
& DISABLE_HARDWARE_ACCELERATION_BLUR
);
4654 gfxRect skipRect
= transform
.TransformBounds(*aSkipRect
);
4656 mAlphaBoxBlur
.Init(aDestinationCtx
, rect
, spreadRadius
, blurRadius
,
4657 &dirtyRect
, &skipRect
, useHardwareAccel
);
4660 mAlphaBoxBlur
.Init(aDestinationCtx
, rect
, spreadRadius
, blurRadius
,
4661 &dirtyRect
, nullptr, useHardwareAccel
);
4665 // we don't need to blur if skipRect is equal to rect
4666 // and mContext will be nullptr
4667 mContext
->Multiply(transform
);
4672 void nsContextBoxBlur::DoPaint() {
4673 if (mContext
== mDestinationCtx
) {
4677 gfxContextMatrixAutoSaveRestore
saveMatrix(mDestinationCtx
);
4679 if (mPreTransformed
) {
4680 mDestinationCtx
->SetMatrix(Matrix());
4683 mAlphaBoxBlur
.Paint(mDestinationCtx
);
4686 gfxContext
* nsContextBoxBlur::GetContext() { return mContext
; }
4689 nsMargin
nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius
,
4690 int32_t aAppUnitsPerDevPixel
) {
4691 IntSize blurRadius
= ComputeBlurRadius(aBlurRadius
, aAppUnitsPerDevPixel
);
4694 result
.top
= result
.bottom
= blurRadius
.height
* aAppUnitsPerDevPixel
;
4695 result
.left
= result
.right
= blurRadius
.width
* aAppUnitsPerDevPixel
;
4700 void nsContextBoxBlur::BlurRectangle(
4701 gfxContext
* aDestinationCtx
, const nsRect
& aRect
,
4702 int32_t aAppUnitsPerDevPixel
, RectCornerRadii
* aCornerRadii
,
4703 nscoord aBlurRadius
, const sRGBColor
& aShadowColor
,
4704 const nsRect
& aDirtyRect
, const gfxRect
& aSkipRect
) {
4705 DrawTarget
& aDestDrawTarget
= *aDestinationCtx
->GetDrawTarget();
4707 if (aRect
.IsEmpty()) {
4711 Rect shadowGfxRect
= NSRectToRect(aRect
, aAppUnitsPerDevPixel
);
4713 if (aBlurRadius
<= 0) {
4714 ColorPattern
color(ToDeviceColor(aShadowColor
));
4716 RefPtr
<Path
> roundedRect
=
4717 MakePathForRoundedRect(aDestDrawTarget
, shadowGfxRect
, *aCornerRadii
);
4718 aDestDrawTarget
.Fill(roundedRect
, color
);
4720 aDestDrawTarget
.FillRect(shadowGfxRect
, color
);
4725 gfxFloat scaleX
= 1;
4726 gfxFloat scaleY
= 1;
4728 // Do blurs in device space when possible.
4729 // Chrome/Skia always does the blurs in device space
4730 // and will sometimes get incorrect results (e.g. rotated blurs)
4731 gfxMatrix transform
= aDestinationCtx
->CurrentMatrixDouble();
4732 // XXX: we could probably handle negative scales but for now it's easier just
4734 if (!transform
.HasNonAxisAlignedTransform() && transform
._11
> 0.0 &&
4735 transform
._22
> 0.0) {
4736 scaleX
= transform
._11
;
4737 scaleY
= transform
._22
;
4738 aDestinationCtx
->SetMatrix(Matrix());
4740 transform
= gfxMatrix();
4743 gfxPoint blurStdDev
=
4744 ComputeBlurStdDev(aBlurRadius
, aAppUnitsPerDevPixel
, scaleX
, scaleY
);
4747 nsLayoutUtils::RectToGfxRect(aDirtyRect
, aAppUnitsPerDevPixel
);
4748 dirtyRect
.RoundOut();
4750 gfxRect shadowThebesRect
=
4751 transform
.TransformBounds(ThebesRect(shadowGfxRect
));
4752 dirtyRect
= transform
.TransformBounds(dirtyRect
);
4753 gfxRect skipRect
= transform
.TransformBounds(aSkipRect
);
4756 aCornerRadii
->Scale(scaleX
, scaleY
);
4759 gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx
, shadowThebesRect
,
4760 aCornerRadii
, blurStdDev
, aShadowColor
,
4761 dirtyRect
, skipRect
);
4765 void nsContextBoxBlur::GetBlurAndSpreadRadius(
4766 DrawTarget
* aDestDrawTarget
, int32_t aAppUnitsPerDevPixel
,
4767 nscoord aBlurRadius
, nscoord aSpreadRadius
, IntSize
& aOutBlurRadius
,
4768 IntSize
& aOutSpreadRadius
, bool aConstrainSpreadRadius
) {
4769 // Do blurs in device space when possible.
4770 // Chrome/Skia always does the blurs in device space
4771 // and will sometimes get incorrect results (e.g. rotated blurs)
4772 Matrix transform
= aDestDrawTarget
->GetTransform();
4773 // XXX: we could probably handle negative scales but for now it's easier just
4775 gfxFloat scaleX
, scaleY
;
4776 if (transform
.HasNonAxisAlignedTransform() || transform
._11
<= 0.0 ||
4777 transform
._22
<= 0.0) {
4781 scaleX
= transform
._11
;
4782 scaleY
= transform
._22
;
4785 // compute a large or smaller blur radius
4787 ComputeBlurRadius(aBlurRadius
, aAppUnitsPerDevPixel
, scaleX
, scaleY
);
4789 IntSize(int32_t(aSpreadRadius
* scaleX
/ aAppUnitsPerDevPixel
),
4790 int32_t(aSpreadRadius
* scaleY
/ aAppUnitsPerDevPixel
));
4792 if (aConstrainSpreadRadius
) {
4793 aOutSpreadRadius
.width
=
4794 std::min(aOutSpreadRadius
.width
, int32_t(MAX_SPREAD_RADIUS
));
4795 aOutSpreadRadius
.height
=
4796 std::min(aOutSpreadRadius
.height
, int32_t(MAX_SPREAD_RADIUS
));
4801 bool nsContextBoxBlur::InsetBoxBlur(
4802 gfxContext
* aDestinationCtx
, Rect aDestinationRect
, Rect aShadowClipRect
,
4803 sRGBColor
& aShadowColor
, nscoord aBlurRadiusAppUnits
,
4804 nscoord aSpreadDistanceAppUnits
, int32_t aAppUnitsPerDevPixel
,
4805 bool aHasBorderRadius
, RectCornerRadii
& aInnerClipRectRadii
, Rect aSkipRect
,
4806 Point aShadowOffset
) {
4807 if (aDestinationRect
.IsEmpty()) {
4812 gfxContextAutoSaveRestore
autoRestore(aDestinationCtx
);
4815 IntSize spreadRadius
;
4816 // Convert the blur and spread radius to device pixels
4817 bool constrainSpreadRadius
= false;
4818 GetBlurAndSpreadRadius(aDestinationCtx
->GetDrawTarget(), aAppUnitsPerDevPixel
,
4819 aBlurRadiusAppUnits
, aSpreadDistanceAppUnits
,
4820 blurRadius
, spreadRadius
, constrainSpreadRadius
);
4822 // The blur and spread radius are scaled already, so scale all
4823 // input data to the blur. This way, we don't have to scale the min
4824 // inset blur to the invert of the dest context, then rescale it back
4825 // when we draw to the destination surface.
4826 gfx::Size scale
= aDestinationCtx
->CurrentMatrix().ScaleFactors();
4827 Matrix transform
= aDestinationCtx
->CurrentMatrix();
4829 // XXX: we could probably handle negative scales but for now it's easier just
4831 if (!transform
.HasNonAxisAlignedTransform() && transform
._11
> 0.0 &&
4832 transform
._22
> 0.0) {
4833 // If we don't have a rotation, we're pre-transforming all the rects.
4834 aDestinationCtx
->SetMatrix(Matrix());
4836 // Don't touch anything, we have a rotation.
4837 transform
= Matrix();
4840 Rect transformedDestRect
= transform
.TransformBounds(aDestinationRect
);
4841 Rect transformedShadowClipRect
= transform
.TransformBounds(aShadowClipRect
);
4842 Rect transformedSkipRect
= transform
.TransformBounds(aSkipRect
);
4844 transformedDestRect
.Round();
4845 transformedShadowClipRect
.Round();
4846 transformedSkipRect
.RoundIn();
4848 for (size_t i
= 0; i
< 4; i
++) {
4849 aInnerClipRectRadii
[i
].width
=
4850 std::floor(scale
.width
* aInnerClipRectRadii
[i
].width
);
4851 aInnerClipRectRadii
[i
].height
=
4852 std::floor(scale
.height
* aInnerClipRectRadii
[i
].height
);
4855 mAlphaBoxBlur
.BlurInsetBox(aDestinationCtx
, transformedDestRect
,
4856 transformedShadowClipRect
, blurRadius
,
4858 aHasBorderRadius
? &aInnerClipRectRadii
: nullptr,
4859 transformedSkipRect
, aShadowOffset
);