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