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