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/. */
6
7 /* utility functions for drawing borders and backgrounds */
8
9 #include <ctime>
10
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"
27
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"
65
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;
72
73 static int gFrameTreeLockCount = 0;
74
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 {
InlineBackgroundDataInlineBackgroundData80 InlineBackgroundData()
81 : mFrame(nullptr),
82 mLineContainer(nullptr),
83 mContinuationPoint(0),
84 mUnbrokenMeasure(0),
85 mLineContinuationPoint(0),
86 mPIStartBorderData{},
87 mBidiEnabled(false),
88 mVertical(false) {}
89
90 ~InlineBackgroundData() = default;
91
ResetInlineBackgroundData92 void Reset() {
93 mBoundingBox.SetRect(0, 0, 0, 0);
94 mContinuationPoint = mLineContinuationPoint = mUnbrokenMeasure = 0;
95 mFrame = mLineContainer = nullptr;
96 mPIStartBorderData.Reset();
97 }
98
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.
103 */
GetContinuousRectInlineBackgroundData104 nsRect GetContinuousRect(nsIFrame* aFrame) {
105 MOZ_ASSERT(static_cast<nsInlineFrame*>(do_QueryFrame(aFrame)));
106
107 SetFrame(aFrame);
108
109 nscoord pos; // an x coordinate if writing-mode is horizontal;
110 // y coordinate if vertical
111 if (mBidiEnabled) {
112 pos = mLineContinuationPoint;
113
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;
121
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;
133 }
134 inlineFrame = inlineFrame->GetPrevContinuation();
135 }
136
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;
146 }
147 inlineFrame = inlineFrame->GetNextContinuation();
148 }
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;
158 }
159 } else {
160 pos = mContinuationPoint;
161 }
162
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);
169 }
170
171 /**
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.
179 */
GetBorderContinuousRectInlineBackgroundData180 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);
191 }
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);
202 }
203 }
204 } else {
205 // ... and restore it when possible.
206 mPIStartBorderData.SetCoord(saved.mCoord);
207 }
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;
214 }
215 } else {
216 if (joinedBorderArea.x > mPIStartBorderData.mCoord) {
217 joinedBorderArea.x =
218 -(mUnbrokenMeasure + joinedBorderArea.x - aBorderArea.width);
219 } else {
220 joinedBorderArea.x -= mPIStartBorderData.mCoord;
221 }
222 }
223 return joinedBorderArea;
224 }
225
GetBoundingRectInlineBackgroundData226 nsRect GetBoundingRect(nsIFrame* aFrame) {
227 SetFrame(aFrame);
228
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);
237
238 return boundingBox;
239 }
240
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
ResetInlineBackgroundData::PhysicalInlineStartBorderData250 void Reset() {
251 mFrame = nullptr;
252 mIsValid = false;
253 }
SetCoordInlineBackgroundData::PhysicalInlineStartBorderData254 void SetCoord(nscoord aCoord) {
255 mCoord = aCoord;
256 mIsValid = true;
257 }
258 };
259
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;
269
SetFrameInlineBackgroundData270 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");
274
275 if (aFrame == mFrame) {
276 return;
277 }
278
279 nsIFrame* prevContinuation = GetPrevContinuation(aFrame);
280
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;
286 }
287
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;
292
293 // If this a new line, update mLineContinuationPoint.
294 if (mBidiEnabled &&
295 (aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) {
296 mLineContinuationPoint = mContinuationPoint;
297 }
298
299 mFrame = aFrame;
300 }
301
GetPrevContinuationInlineBackgroundData302 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?");
312 }
313 }
314 return prevCont;
315 }
316
GetNextContinuationInlineBackgroundData317 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?");
326 }
327 }
328 return nextCont;
329 }
330
InitInlineBackgroundData331 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();
340 }
341
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.");
346 }
347
348 mVertical = aFrame->GetWritingMode().IsVertical();
349
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;
359 }
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;
366 }
367 mUnbrokenMeasure += mVertical ? rect.height : rect.width;
368 mBoundingBox.UnionRect(mBoundingBox, rect);
369 inlineFrame = GetPrevContinuation(inlineFrame);
370 }
371
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;
380 }
381 nsRect rect = inlineFrame->GetRect();
382 mUnbrokenMeasure += mVertical ? rect.height : rect.width;
383 mBoundingBox.UnionRect(mBoundingBox, rect);
384 inlineFrame = GetNextContinuation(inlineFrame);
385 }
386
387 mFrame = aFrame;
388 }
389
AreOnSameLineInlineBackgroundData390 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();
401 }
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;
417 }
418 if (isDescendant1 || isDescendant2) {
419 return false;
420 }
421 }
422 MOZ_ASSERT_UNREACHABLE("None of the frames is a descendant of this rtc?");
423 }
424 MOZ_ASSERT_UNREACHABLE("Do we have any other type of line container?");
425 return false;
426 }
427 };
428
429 static InlineBackgroundData* gInlineBGData = nullptr;
430
431 // Initialize any static variables used by nsCSSRendering.
Init()432 void nsCSSRendering::Init() {
433 NS_ASSERTION(!gInlineBGData, "Init called twice");
434 gInlineBGData = new InlineBackgroundData();
435 }
436
437 // Clean up any global variables used by nsCSSRendering.
Shutdown()438 void nsCSSRendering::Shutdown() {
439 delete gInlineBGData;
440 gInlineBGData = nullptr;
441 }
442
443 /**
444 * Make a bevel color
445 */
MakeBevelColor(mozilla::Side whichSide,StyleBorderStyle style,nscolor aBorderColor)446 static nscolor MakeBevelColor(mozilla::Side whichSide, StyleBorderStyle style,
447 nscolor aBorderColor) {
448 nscolor colors[2];
449 nscolor theColor;
450
451 // Given a background color and a border color
452 // calculate the color used for the shading
453 NS_GetSpecial3DColors(colors, aBorderColor);
454
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;
471 }
472 }
473
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;
488 }
489 return theColor;
490 }
491
GetRadii(nsIFrame * aForFrame,const nsStyleBorder & aBorder,const nsRect & aOrigBorderArea,const nsRect & aBorderArea,nscoord aRadii[8])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);
504 }
505
506 return haveRoundedCorners;
507 }
508
GetRadii(nsIFrame * aForFrame,const nsStyleBorder & aBorder,const nsRect & aOrigBorderArea,const nsRect & aBorderArea,RectCornerRadii * aBgRadii)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);
515
516 if (haveRoundedCorners) {
517 auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
518 nsCSSRendering::ComputePixelRadii(radii, d2a, aBgRadii);
519 }
520 return haveRoundedCorners;
521 }
522
JoinBoxesForBlockAxisSlice(nsIFrame * aFrame,const nsRect & aBorderArea)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);
534 }
535 borderArea.BSize(wm) += bSize;
536 bSize = 0;
537 f = aFrame->GetPrevContinuation();
538 for (; f; f = f->GetPrevContinuation()) {
539 bSize += f->BSize(wm);
540 }
541 borderArea.BStart(wm) -= bSize;
542 borderArea.BSize(wm) += bSize;
543 return borderArea.GetPhysicalRect(wm, dummyContainerSize);
544 }
545
546 /**
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
551 */
552 enum InlineBoxOrder { eForBorder, eForBackground };
JoinBoxesForSlice(nsIFrame * aFrame,const nsRect & aBorderArea,InlineBoxOrder aOrder)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();
560 }
561 return JoinBoxesForBlockAxisSlice(aFrame, aBorderArea);
562 }
563
564 /* static */
IsBoxDecorationSlice(const nsStyleBorder & aStyleBorder)565 bool nsCSSRendering::IsBoxDecorationSlice(const nsStyleBorder& aStyleBorder) {
566 return aStyleBorder.mBoxDecorationBreak == StyleBoxDecorationBreak::Slice;
567 }
568
569 /* static */
BoxDecorationRectForBorder(nsIFrame * aFrame,const nsRect & aBorderArea,Sides aSkipSides,const nsStyleBorder * aStyleBorder)570 nsRect nsCSSRendering::BoxDecorationRectForBorder(
571 nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides,
572 const nsStyleBorder* aStyleBorder) {
573 if (!aStyleBorder) {
574 aStyleBorder = aFrame->StyleBorder();
575 }
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;
581 }
582
583 /* static */
BoxDecorationRectForBackground(nsIFrame * aFrame,const nsRect & aBorderArea,Sides aSkipSides,const nsStyleBorder * aStyleBorder)584 nsRect nsCSSRendering::BoxDecorationRectForBackground(
585 nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides,
586 const nsStyleBorder* aStyleBorder) {
587 if (!aStyleBorder) {
588 aStyleBorder = aFrame->StyleBorder();
589 }
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;
595 }
596
597 //----------------------------------------------------------------------
598 // Thebes Border Rendering Code Start
599
600 /*
601 * Compute the float-pixel radii that should be used for drawing
602 * this border/outline, given the various input bits.
603 */
604 /* static */
ComputePixelRadii(const nscoord * aAppUnitsRadii,nscoord aAppUnitsPerPixel,RectCornerRadii * oBorderRadii)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;
611 }
612
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]);
620 }
621
GetBorderIfVisited(const ComputedStyle & aStyle)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;
629 }
630
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);
637 }
638
639 return result;
640 }
641
PaintBorder(nsPresContext * aPresContext,gfxContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,ComputedStyle * aStyle,PaintBorderFlags aFlags,Sides aSkipSides)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);
647
648 Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle);
649 return PaintBorderWithStyleBorder(
650 aPresContext, aRenderingContext, aForFrame, aDirtyRect, aBorderArea,
651 visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aFlags, aSkipSides);
652 }
653
CreateBorderRenderer(nsPresContext * aPresContext,DrawTarget * aDrawTarget,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,ComputedStyle * aStyle,bool * aOutBorderIsEmpty,Sides 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);
663 }
664
CreateWebRenderCommandsForBorder(nsDisplayItem * aItem,nsIFrame * aForFrame,const nsRect & aBorderArea,mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const mozilla::layers::StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayListBuilder * aDisplayListBuilder)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()));
677 }
678
CreateWebRenderCommandsForNullBorder(nsDisplayItem * aItem,nsIFrame * aForFrame,const nsRect & aBorderArea,mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const mozilla::layers::StackingContextHelper & aSc,const nsStyleBorder & aStyleBorder)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);
693 }
694 }
695
CreateWebRenderCommandsForBorderWithStyleBorder(nsDisplayItem * aItem,nsIFrame * aForFrame,const nsRect & aBorderArea,mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const mozilla::layers::StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayListBuilder * aDisplayListBuilder,const nsStyleBorder & aStyleBorder)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;
710 }
711
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;
716 }
717
718 if (aStyleBorder.mBorderImageRepeatH == StyleBorderImageRepeat::Space ||
719 aStyleBorder.mBorderImageRepeatV == StyleBorderImageRepeat::Space) {
720 return ImgDrawResult::NOT_SUPPORTED;
721 }
722
723 uint32_t flags = 0;
724 if (aDisplayListBuilder->IsPaintingToWindow()) {
725 flags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
726 }
727 if (aDisplayListBuilder->ShouldSyncDecodeImages()) {
728 flags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
729 }
730
731 image::ImgDrawResult result;
732 Maybe<nsCSSBorderImageRenderer> bir =
733 nsCSSBorderImageRenderer::CreateBorderImageRenderer(
734 aForFrame->PresContext(), aForFrame, aBorderArea, aStyleBorder,
735 aItem->GetPaintRect(), aForFrame->GetSkipSides(), flags, &result);
736
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;
743 }
744
745 return bir->CreateWebRenderCommands(aItem, aForFrame, aBuilder, aResources,
746 aSc, aManager, aDisplayListBuilder);
747 }
748
ConstructBorderRenderer(nsPresContext * aPresContext,ComputedStyle * aStyle,DrawTarget * aDrawTarget,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,const nsStyleBorder & aStyleBorder,Sides aSkipSides,bool * aNeedsClip)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();
754
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);
761
762 PrintAsFormatString(" joinedBorderArea: %d %d %d %d\n", joinedBorderArea.x,
763 joinedBorderArea.y, joinedBorderArea.width,
764 joinedBorderArea.height);
765
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;
775 }
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);
789 }
790
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);
798
799 StyleBorderStyle borderStyles[4];
800 nscolor borderColors[4];
801
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);
806 }
807
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]));
812
813 return nsCSSBorderRenderer(
814 aPresContext, aDrawTarget, dirtyRect, joinedBorderAreaPx, borderStyles,
815 borderWidths, bgRadii, borderColors, !aForFrame->BackfaceIsHidden(),
816 *aNeedsClip ? Some(NSRectToRect(aBorderArea, oneDevPixel)) : Nothing());
817 }
818
PaintBorderWithStyleBorder(nsPresContext * aPresContext,gfxContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,const nsStyleBorder & aStyleBorder,ComputedStyle * aStyle,PaintBorderFlags aFlags,Sides aSkipSides)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();
825
826 PrintAsStringNewline("++ PaintBorder");
827
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.
836 }
837 }
838
839 if (!aStyleBorder.mBorderImageSource.IsNone()) {
840 ImgDrawResult result = ImgDrawResult::SUCCESS;
841
842 uint32_t irFlags = 0;
843 if (aFlags & PaintBorderFlags::SyncDecodeImages) {
844 irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
845 }
846
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);
859 }
860 }
861
862 ImgDrawResult result = ImgDrawResult::SUCCESS;
863
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;
869 }
870
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;
876 }
877
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));
886 }
887
888 br.DrawBorders();
889
890 if (needsClip) {
891 aDrawTarget.PopClip();
892 }
893
894 PrintAsStringNewline();
895
896 return result;
897 }
898
CreateBorderRendererWithStyleBorder(nsPresContext * aPresContext,DrawTarget * aDrawTarget,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,const nsStyleBorder & aStyleBorder,ComputedStyle * aStyle,bool * aOutBorderIsEmpty,Sides aSkipSides)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();
906 }
907 return CreateNullBorderRendererWithStyleBorder(
908 aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
909 aStyleBorder, aStyle, aOutBorderIsEmpty, aSkipSides);
910 }
911
912 Maybe<nsCSSBorderRenderer>
CreateNullBorderRendererWithStyleBorder(nsPresContext * aPresContext,DrawTarget * aDrawTarget,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,const nsStyleBorder & aStyleBorder,ComputedStyle * aStyle,bool * aOutBorderIsEmpty,Sides aSkipSides)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();
923 }
924 }
925
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;
932 }
933 return Nothing();
934 }
935
936 bool needsClip = false;
937 nsCSSBorderRenderer br = ConstructBorderRenderer(
938 aPresContext, aStyle, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
939 aStyleBorder, aSkipSides, &needsClip);
940 return Some(br);
941 }
942
943 Maybe<nsCSSBorderRenderer>
CreateBorderRendererForNonThemedOutline(nsPresContext * aPresContext,DrawTarget * aDrawTarget,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aInnerRect,ComputedStyle * aStyle)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();
952 }
953
954 const nscoord offset = ourOutline->mOutlineOffset.ToAppUnits();
955 nsRect innerRect = aInnerRect;
956 innerRect.Inflate(offset);
957
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();
965 }
966
967 nscoord width = ourOutline->GetOutlineWidth();
968
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
974 }
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();
980 }
981
982 RectCornerRadii outlineRadii;
983 nsRect outerRect = innerRect;
984 outerRect.Inflate(width);
985
986 const nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel();
987 Rect oRect(NSRectToRect(outerRect, oneDevPixel));
988
989 const Float outlineWidths[4] = {
990 Float(width) / oneDevPixel, Float(width) / oneDevPixel,
991 Float(width) / oneDevPixel, Float(width) / oneDevPixel};
992
993 // convert the radii
994 nscoord twipsRadii[8];
995
996 // get the radius for our outline
997 if (aForFrame->GetBorderRadii(twipsRadii)) {
998 RectCornerRadii innerRadii;
999 ComputePixelRadii(twipsRadii, oneDevPixel, &innerRadii);
1000
1001 Float devPixelOffset = aPresContext->AppUnitsToFloatDevPixels(offset);
1002 const Float widths[4] = {
1003 outlineWidths[0] + devPixelOffset, outlineWidths[1] + devPixelOffset,
1004 outlineWidths[2] + devPixelOffset, outlineWidths[3] + devPixelOffset};
1005 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outlineRadii);
1006 }
1007
1008 StyleBorderStyle outlineStyles[4] = {outlineStyle, outlineStyle, outlineStyle,
1009 outlineStyle};
1010
1011 // This handles treating the initial color as 'currentColor'; if we
1012 // ever want 'invert' back we'll need to do a bit of work here too.
1013 nscolor outlineColor =
1014 aStyle->GetVisitedDependentColor(&nsStyleOutline::mOutlineColor);
1015 nscolor outlineColors[4] = {outlineColor, outlineColor, outlineColor,
1016 outlineColor};
1017
1018 Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel);
1019
1020 return Some(nsCSSBorderRenderer(
1021 aPresContext, aDrawTarget, dirtyRect, oRect, outlineStyles, outlineWidths,
1022 outlineRadii, outlineColors, !aForFrame->BackfaceIsHidden(), Nothing()));
1023 }
1024
PaintNonThemedOutline(nsPresContext * aPresContext,gfxContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aInnerRect,ComputedStyle * aStyle)1025 void nsCSSRendering::PaintNonThemedOutline(nsPresContext* aPresContext,
1026 gfxContext& aRenderingContext,
1027 nsIFrame* aForFrame,
1028 const nsRect& aDirtyRect,
1029 const nsRect& aInnerRect,
1030 ComputedStyle* aStyle) {
1031 Maybe<nsCSSBorderRenderer> br = CreateBorderRendererForNonThemedOutline(
1032 aPresContext, aRenderingContext.GetDrawTarget(), aForFrame, aDirtyRect,
1033 aInnerRect, aStyle);
1034 if (!br) {
1035 return;
1036 }
1037
1038 // start drawing
1039 br->DrawBorders();
1040
1041 PrintAsStringNewline();
1042 }
1043
PaintFocus(nsPresContext * aPresContext,DrawTarget * aDrawTarget,const nsRect & aFocusRect,nscolor aColor)1044 void nsCSSRendering::PaintFocus(nsPresContext* aPresContext,
1045 DrawTarget* aDrawTarget,
1046 const nsRect& aFocusRect, nscolor aColor) {
1047 nscoord oneCSSPixel = nsPresContext::CSSPixelsToAppUnits(1);
1048 nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
1049
1050 Rect focusRect(NSRectToRect(aFocusRect, oneDevPixel));
1051
1052 RectCornerRadii focusRadii;
1053 {
1054 nscoord twipsRadii[8] = {0, 0, 0, 0, 0, 0, 0, 0};
1055 ComputePixelRadii(twipsRadii, oneDevPixel, &focusRadii);
1056 }
1057 Float focusWidths[4] = {
1058 Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel,
1059 Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel};
1060
1061 StyleBorderStyle focusStyles[4] = {
1062 StyleBorderStyle::Dotted, StyleBorderStyle::Dotted,
1063 StyleBorderStyle::Dotted, StyleBorderStyle::Dotted};
1064 nscolor focusColors[4] = {aColor, aColor, aColor, aColor};
1065
1066 // Because this renders a dotted border, the background color
1067 // should not be used. Therefore, we provide a value that will
1068 // be blatantly wrong if it ever does get used. (If this becomes
1069 // something that CSS can style, this function will then have access
1070 // to a ComputedStyle and can use the same logic that PaintBorder
1071 // and PaintOutline do.)
1072 //
1073 // WebRender layers-free mode don't use PaintFocus function. Just assign
1074 // the backface-visibility to true for this case.
1075 nsCSSBorderRenderer br(aPresContext, aDrawTarget, focusRect, focusRect,
1076 focusStyles, focusWidths, focusRadii, focusColors,
1077 true, Nothing());
1078 br.DrawBorders();
1079
1080 PrintAsStringNewline();
1081 }
1082
1083 // Thebes Border Rendering Code End
1084 //----------------------------------------------------------------------
1085
1086 //----------------------------------------------------------------------
1087
1088 /**
1089 * Helper for ComputeObjectAnchorPoint; parameters are the same as for
1090 * that function, except they're for a single coordinate / a single size
1091 * dimension. (so, x/width vs. y/height)
1092 */
ComputeObjectAnchorCoord(const LengthPercentage & aCoord,const nscoord aOriginBounds,const nscoord aImageSize,nscoord * aTopLeftCoord,nscoord * aAnchorPointCoord)1093 static void ComputeObjectAnchorCoord(const LengthPercentage& aCoord,
1094 const nscoord aOriginBounds,
1095 const nscoord aImageSize,
1096 nscoord* aTopLeftCoord,
1097 nscoord* aAnchorPointCoord) {
1098 nscoord extraSpace = aOriginBounds - aImageSize;
1099
1100 // The anchor-point doesn't care about our image's size; just the size
1101 // of the region we're rendering into.
1102 *aAnchorPointCoord = aCoord.Resolve(aOriginBounds, NSToCoordRoundWithClamp);
1103 // Adjust aTopLeftCoord by the specified % of the extra space.
1104 *aTopLeftCoord = aCoord.Resolve(extraSpace, NSToCoordRoundWithClamp);
1105 }
1106
ComputeObjectAnchorPoint(const Position & aPos,const nsSize & aOriginBounds,const nsSize & aImageSize,nsPoint * aTopLeft,nsPoint * aAnchorPoint)1107 void nsImageRenderer::ComputeObjectAnchorPoint(const Position& aPos,
1108 const nsSize& aOriginBounds,
1109 const nsSize& aImageSize,
1110 nsPoint* aTopLeft,
1111 nsPoint* aAnchorPoint) {
1112 ComputeObjectAnchorCoord(aPos.horizontal, aOriginBounds.width,
1113 aImageSize.width, &aTopLeft->x, &aAnchorPoint->x);
1114
1115 ComputeObjectAnchorCoord(aPos.vertical, aOriginBounds.height,
1116 aImageSize.height, &aTopLeft->y, &aAnchorPoint->y);
1117 }
1118
FindNonTransparentBackgroundFrame(nsIFrame * aFrame,bool aStopAtThemed)1119 auto nsCSSRendering::FindNonTransparentBackgroundFrame(nsIFrame* aFrame,
1120 bool aStopAtThemed)
1121 -> NonTransparentBackgroundFrame {
1122 NS_ASSERTION(aFrame,
1123 "Cannot find NonTransparentBackgroundFrame in a null frame");
1124
1125 for (nsIFrame* frame = aFrame; frame;
1126 frame = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame)) {
1127 // No need to call GetVisitedDependentColor because it always uses this
1128 // alpha component anyway.
1129 if (NS_GET_A(frame->StyleBackground()->BackgroundColor(frame))) {
1130 return {frame, false, false};
1131 }
1132
1133 if (aStopAtThemed && frame->IsThemed()) {
1134 return {frame, true, false};
1135 }
1136
1137 if (IsCanvasFrame(frame)) {
1138 nsIFrame* bgFrame = nullptr;
1139 if (FindBackgroundFrame(frame, &bgFrame) &&
1140 NS_GET_A(bgFrame->StyleBackground()->BackgroundColor(bgFrame))) {
1141 return {bgFrame, false, true};
1142 }
1143 }
1144 }
1145
1146 return {};
1147 }
1148
1149 // Returns true if aFrame is a canvas frame.
1150 // We need to treat the viewport as canvas because, even though
1151 // it does not actually paint a background, we need to get the right
1152 // background style so we correctly detect transparent documents.
IsCanvasFrame(const nsIFrame * aFrame)1153 bool nsCSSRendering::IsCanvasFrame(const nsIFrame* aFrame) {
1154 LayoutFrameType frameType = aFrame->Type();
1155 return frameType == LayoutFrameType::Canvas ||
1156 frameType == LayoutFrameType::XULRoot ||
1157 frameType == LayoutFrameType::PageContent ||
1158 frameType == LayoutFrameType::Viewport;
1159 }
1160
FindBackgroundStyleFrame(nsIFrame * aForFrame)1161 nsIFrame* nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame) {
1162 const nsStyleBackground* result = aForFrame->StyleBackground();
1163
1164 // Check if we need to do propagation from BODY rather than HTML.
1165 if (!result->IsTransparent(aForFrame)) {
1166 return aForFrame;
1167 }
1168
1169 nsIContent* content = aForFrame->GetContent();
1170 // The root element content can't be null. We wouldn't know what
1171 // frame to create for aFrame.
1172 // Use |OwnerDoc| so it works during destruction.
1173 if (!content) {
1174 return aForFrame;
1175 }
1176
1177 Document* document = content->OwnerDoc();
1178
1179 dom::Element* bodyContent = document->GetBodyElement();
1180 // We need to null check the body node (bug 118829) since
1181 // there are cases, thanks to the fix for bug 5569, where we
1182 // will reflow a document with no body. In particular, if a
1183 // SCRIPT element in the head blocks the parser and then has a
1184 // SCRIPT that does "document.location.href = 'foo'", then
1185 // nsParser::Terminate will call |DidBuildModel| methods
1186 // through to the content sink, which will call |StartLayout|
1187 // and thus |Initialize| on the pres shell. See bug 119351
1188 // for the ugly details.
1189 if (!bodyContent) {
1190 return aForFrame;
1191 }
1192
1193 nsIFrame* bodyFrame = bodyContent->GetPrimaryFrame();
1194 if (!bodyFrame) {
1195 return aForFrame;
1196 }
1197
1198 return nsLayoutUtils::GetStyleFrame(bodyFrame);
1199 }
1200
1201 /**
1202 * |FindBackground| finds the correct style data to use to paint the
1203 * background. It is responsible for handling the following two
1204 * statements in section 14.2 of CSS2:
1205 *
1206 * The background of the box generated by the root element covers the
1207 * entire canvas.
1208 *
1209 * For HTML documents, however, we recommend that authors specify the
1210 * background for the BODY element rather than the HTML element. User
1211 * agents should observe the following precedence rules to fill in the
1212 * background: if the value of the 'background' property for the HTML
1213 * element is different from 'transparent' then use it, else use the
1214 * value of the 'background' property for the BODY element. If the
1215 * resulting value is 'transparent', the rendering is undefined.
1216 *
1217 * Thus, in our implementation, it is responsible for ensuring that:
1218 * + we paint the correct background on the |nsCanvasFrame|,
1219 * |nsRootBoxFrame|, or |nsPageFrame|,
1220 * + we don't paint the background on the root element, and
1221 * + we don't paint the background on the BODY element in *some* cases,
1222 * and for SGML-based HTML documents only.
1223 *
1224 * |FindBackground| returns true if a background should be painted, and
1225 * the resulting ComputedStyle to use for the background information
1226 * will be filled in to |aBackground|.
1227 */
FindRootFrameBackground(nsIFrame * aForFrame)1228 ComputedStyle* nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame) {
1229 return FindBackgroundStyleFrame(aForFrame)->Style();
1230 }
1231
FindElementBackground(const nsIFrame * aForFrame,nsIFrame * aRootElementFrame)1232 inline bool FindElementBackground(const nsIFrame* aForFrame,
1233 nsIFrame* aRootElementFrame) {
1234 if (aForFrame == aRootElementFrame) {
1235 // We must have propagated our background to the viewport or canvas. Abort.
1236 return false;
1237 }
1238
1239 // Return true unless the frame is for a BODY element whose background
1240 // was propagated to the viewport.
1241
1242 nsIContent* content = aForFrame->GetContent();
1243 if (!content || content->NodeInfo()->NameAtom() != nsGkAtoms::body)
1244 return true; // not frame for a "body" element
1245 // It could be a non-HTML "body" element but that's OK, we'd fail the
1246 // bodyContent check below
1247
1248 if (aForFrame->Style()->GetPseudoType() != PseudoStyleType::NotPseudo) {
1249 return true; // A pseudo-element frame.
1250 }
1251
1252 // We should only look at the <html> background if we're in an HTML document
1253 Document* document = content->OwnerDoc();
1254
1255 dom::Element* bodyContent = document->GetBodyElement();
1256 if (bodyContent != content)
1257 return true; // this wasn't the background that was propagated
1258
1259 // This can be called even when there's no root element yet, during frame
1260 // construction, via nsLayoutUtils::FrameHasTransparency and
1261 // nsContainerFrame::SyncFrameViewProperties.
1262 if (!aRootElementFrame) {
1263 return true;
1264 }
1265
1266 const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground();
1267 return !htmlBG->IsTransparent(aRootElementFrame);
1268 }
1269
FindBackgroundFrame(const nsIFrame * aForFrame,nsIFrame ** aBackgroundFrame)1270 bool nsCSSRendering::FindBackgroundFrame(const nsIFrame* aForFrame,
1271 nsIFrame** aBackgroundFrame) {
1272 nsIFrame* rootElementFrame =
1273 aForFrame->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
1274 if (IsCanvasFrame(aForFrame)) {
1275 *aBackgroundFrame = FindCanvasBackgroundFrame(aForFrame, rootElementFrame);
1276 return true;
1277 }
1278
1279 *aBackgroundFrame = const_cast<nsIFrame*>(aForFrame);
1280 return FindElementBackground(aForFrame, rootElementFrame);
1281 }
1282
FindBackground(const nsIFrame * aForFrame,ComputedStyle ** aBackgroundSC)1283 bool nsCSSRendering::FindBackground(const nsIFrame* aForFrame,
1284 ComputedStyle** aBackgroundSC) {
1285 nsIFrame* backgroundFrame = nullptr;
1286 if (FindBackgroundFrame(aForFrame, &backgroundFrame)) {
1287 *aBackgroundSC = backgroundFrame->Style();
1288 return true;
1289 }
1290 return false;
1291 }
1292
BeginFrameTreesLocked()1293 void nsCSSRendering::BeginFrameTreesLocked() { ++gFrameTreeLockCount; }
1294
EndFrameTreesLocked()1295 void nsCSSRendering::EndFrameTreesLocked() {
1296 NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked");
1297 --gFrameTreeLockCount;
1298 if (gFrameTreeLockCount == 0) {
1299 gInlineBGData->Reset();
1300 }
1301 }
1302
HasBoxShadowNativeTheme(nsIFrame * aFrame,bool & aMaybeHasBorderRadius)1303 bool nsCSSRendering::HasBoxShadowNativeTheme(nsIFrame* aFrame,
1304 bool& aMaybeHasBorderRadius) {
1305 const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
1306 nsITheme::Transparency transparency;
1307 if (aFrame->IsThemed(styleDisplay, &transparency)) {
1308 aMaybeHasBorderRadius = false;
1309 // For opaque (rectangular) theme widgets we can take the generic
1310 // border-box path with border-radius disabled.
1311 return transparency != nsITheme::eOpaque;
1312 }
1313
1314 aMaybeHasBorderRadius = true;
1315 return false;
1316 }
1317
GetShadowColor(const StyleSimpleShadow & aShadow,nsIFrame * aFrame,float aOpacity)1318 gfx::sRGBColor nsCSSRendering::GetShadowColor(const StyleSimpleShadow& aShadow,
1319 nsIFrame* aFrame,
1320 float aOpacity) {
1321 // Get the shadow color; if not specified, use the foreground color
1322 nscolor shadowColor = aShadow.color.CalcColor(aFrame);
1323 sRGBColor color = sRGBColor::FromABGR(shadowColor);
1324 color.a *= aOpacity;
1325 return color;
1326 }
1327
GetShadowRect(const nsRect & aFrameArea,bool aNativeTheme,nsIFrame * aForFrame)1328 nsRect nsCSSRendering::GetShadowRect(const nsRect& aFrameArea,
1329 bool aNativeTheme, nsIFrame* aForFrame) {
1330 nsRect frameRect = aNativeTheme ? aForFrame->InkOverflowRectRelativeToSelf() +
1331 aFrameArea.TopLeft()
1332 : aFrameArea;
1333 Sides skipSides = aForFrame->GetSkipSides();
1334 frameRect = BoxDecorationRectForBorder(aForFrame, frameRect, skipSides);
1335
1336 // Explicitly do not need to account for the spread radius here
1337 // Webrender does it for us or PaintBoxShadow will for non-WR
1338 return frameRect;
1339 }
1340
GetBorderRadii(const nsRect & aFrameRect,const nsRect & aBorderRect,nsIFrame * aFrame,RectCornerRadii & aOutRadii)1341 bool nsCSSRendering::GetBorderRadii(const nsRect& aFrameRect,
1342 const nsRect& aBorderRect, nsIFrame* aFrame,
1343 RectCornerRadii& aOutRadii) {
1344 const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1);
1345 nscoord twipsRadii[8];
1346 NS_ASSERTION(
1347 aBorderRect.Size() == aFrame->VisualBorderRectRelativeToSelf().Size(),
1348 "unexpected size");
1349 nsSize sz = aFrameRect.Size();
1350 bool hasBorderRadius = aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
1351 if (hasBorderRadius) {
1352 ComputePixelRadii(twipsRadii, oneDevPixel, &aOutRadii);
1353 }
1354
1355 return hasBorderRadius;
1356 }
1357
PaintBoxShadowOuter(nsPresContext * aPresContext,gfxContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aFrameArea,const nsRect & aDirtyRect,float aOpacity)1358 void nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext,
1359 gfxContext& aRenderingContext,
1360 nsIFrame* aForFrame,
1361 const nsRect& aFrameArea,
1362 const nsRect& aDirtyRect,
1363 float aOpacity) {
1364 DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
1365 auto shadows = aForFrame->StyleEffects()->mBoxShadow.AsSpan();
1366 if (shadows.IsEmpty()) {
1367 return;
1368 }
1369
1370 bool hasBorderRadius;
1371 // mutually exclusive with hasBorderRadius
1372 bool nativeTheme = HasBoxShadowNativeTheme(aForFrame, hasBorderRadius);
1373 const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay();
1374
1375 nsRect frameRect = GetShadowRect(aFrameArea, nativeTheme, aForFrame);
1376
1377 // Get any border radius, since box-shadow must also have rounded corners if
1378 // the frame does.
1379 RectCornerRadii borderRadii;
1380 const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
1381 if (hasBorderRadius) {
1382 nscoord twipsRadii[8];
1383 NS_ASSERTION(
1384 aFrameArea.Size() == aForFrame->VisualBorderRectRelativeToSelf().Size(),
1385 "unexpected size");
1386 nsSize sz = frameRect.Size();
1387 hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
1388 if (hasBorderRadius) {
1389 ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii);
1390 }
1391 }
1392
1393 // We don't show anything that intersects with the frame we're blurring on. So
1394 // tell the blurrer not to do unnecessary work there.
1395 gfxRect skipGfxRect = ThebesRect(NSRectToRect(frameRect, oneDevPixel));
1396 skipGfxRect.Round();
1397 bool useSkipGfxRect = true;
1398 if (nativeTheme) {
1399 // Optimize non-leaf native-themed frames by skipping computing pixels
1400 // in the padding-box. We assume the padding-box is going to be painted
1401 // opaquely for non-leaf frames.
1402 // XXX this may not be a safe assumption; we should make this go away
1403 // by optimizing box-shadow drawing more for the cases where we don't have a
1404 // skip-rect.
1405 useSkipGfxRect = !aForFrame->IsLeaf();
1406 nsRect paddingRect =
1407 aForFrame->GetPaddingRectRelativeToSelf() + aFrameArea.TopLeft();
1408 skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, oneDevPixel);
1409 } else if (hasBorderRadius) {
1410 skipGfxRect.Deflate(gfxMargin(
1411 std::max(borderRadii[C_TL].height, borderRadii[C_TR].height), 0,
1412 std::max(borderRadii[C_BL].height, borderRadii[C_BR].height), 0));
1413 }
1414
1415 for (const StyleBoxShadow& shadow : Reversed(shadows)) {
1416 if (shadow.inset) {
1417 continue;
1418 }
1419
1420 nsRect shadowRect = frameRect;
1421 nsPoint shadowOffset(shadow.base.horizontal.ToAppUnits(),
1422 shadow.base.vertical.ToAppUnits());
1423 shadowRect.MoveBy(shadowOffset);
1424 nscoord shadowSpread = shadow.spread.ToAppUnits();
1425 if (!nativeTheme) {
1426 shadowRect.Inflate(shadowSpread);
1427 }
1428
1429 // shadowRect won't include the blur, so make an extra rect here that
1430 // includes the blur for use in the even-odd rule below.
1431 nsRect shadowRectPlusBlur = shadowRect;
1432 nscoord blurRadius = shadow.base.blur.ToAppUnits();
1433 shadowRectPlusBlur.Inflate(
1434 nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel));
1435
1436 Rect shadowGfxRectPlusBlur = NSRectToRect(shadowRectPlusBlur, oneDevPixel);
1437 shadowGfxRectPlusBlur.RoundOut();
1438 MaybeSnapToDevicePixels(shadowGfxRectPlusBlur, aDrawTarget, true);
1439
1440 sRGBColor gfxShadowColor = GetShadowColor(shadow.base, aForFrame, aOpacity);
1441
1442 if (nativeTheme) {
1443 nsContextBoxBlur blurringArea;
1444
1445 // When getting the widget shape from the native theme, we're going
1446 // to draw the widget into the shadow surface to create a mask.
1447 // We need to ensure that there actually *is* a shadow surface
1448 // and that we're not going to draw directly into aRenderingContext.
1449 gfxContext* shadowContext = blurringArea.Init(
1450 shadowRect, shadowSpread, blurRadius, oneDevPixel, &aRenderingContext,
1451 aDirtyRect, useSkipGfxRect ? &skipGfxRect : nullptr,
1452 nsContextBoxBlur::FORCE_MASK);
1453 if (!shadowContext) continue;
1454
1455 MOZ_ASSERT(shadowContext == blurringArea.GetContext());
1456
1457 aRenderingContext.Save();
1458 aRenderingContext.SetColor(gfxShadowColor);
1459
1460 // Draw the shape of the frame so it can be blurred. Recall how
1461 // nsContextBoxBlur doesn't make any temporary surfaces if blur is 0 and
1462 // it just returns the original surface? If we have no blur, we're
1463 // painting this fill on the actual content surface (aRenderingContext ==
1464 // shadowContext) which is why we set up the color and clip before doing
1465 // this.
1466
1467 // We don't clip the border-box from the shadow, nor any other box.
1468 // We assume that the native theme is going to paint over the shadow.
1469
1470 // Draw the widget shape
1471 gfxContextMatrixAutoSaveRestore save(shadowContext);
1472 gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
1473 shadowOffset, aPresContext->AppUnitsPerDevPixel());
1474 shadowContext->SetMatrixDouble(
1475 shadowContext->CurrentMatrixDouble().PreTranslate(devPixelOffset));
1476
1477 nsRect nativeRect = aDirtyRect;
1478 nativeRect.MoveBy(-shadowOffset);
1479 nativeRect.IntersectRect(frameRect, nativeRect);
1480 aPresContext->Theme()->DrawWidgetBackground(
1481 shadowContext, aForFrame, styleDisplay->EffectiveAppearance(),
1482 aFrameArea, nativeRect, nsITheme::DrawOverflow::No);
1483
1484 blurringArea.DoPaint();
1485 aRenderingContext.Restore();
1486 } else {
1487 aRenderingContext.Save();
1488
1489 {
1490 Rect innerClipRect = NSRectToRect(frameRect, oneDevPixel);
1491 if (!MaybeSnapToDevicePixels(innerClipRect, aDrawTarget, true)) {
1492 innerClipRect.Round();
1493 }
1494
1495 // Clip out the interior of the frame's border edge so that the shadow
1496 // is only painted outside that area.
1497 RefPtr<PathBuilder> builder =
1498 aDrawTarget.CreatePathBuilder(FillRule::FILL_EVEN_ODD);
1499 AppendRectToPath(builder, shadowGfxRectPlusBlur);
1500 if (hasBorderRadius) {
1501 AppendRoundedRectToPath(builder, innerClipRect, borderRadii);
1502 } else {
1503 AppendRectToPath(builder, innerClipRect);
1504 }
1505 RefPtr<Path> path = builder->Finish();
1506 aRenderingContext.Clip(path);
1507 }
1508
1509 // Clip the shadow so that we only get the part that applies to aForFrame.
1510 nsRect fragmentClip = shadowRectPlusBlur;
1511 Sides skipSides = aForFrame->GetSkipSides();
1512 if (!skipSides.IsEmpty()) {
1513 if (skipSides.Left()) {
1514 nscoord xmost = fragmentClip.XMost();
1515 fragmentClip.x = aFrameArea.x;
1516 fragmentClip.width = xmost - fragmentClip.x;
1517 }
1518 if (skipSides.Right()) {
1519 nscoord xmost = fragmentClip.XMost();
1520 nscoord overflow = xmost - aFrameArea.XMost();
1521 if (overflow > 0) {
1522 fragmentClip.width -= overflow;
1523 }
1524 }
1525 if (skipSides.Top()) {
1526 nscoord ymost = fragmentClip.YMost();
1527 fragmentClip.y = aFrameArea.y;
1528 fragmentClip.height = ymost - fragmentClip.y;
1529 }
1530 if (skipSides.Bottom()) {
1531 nscoord ymost = fragmentClip.YMost();
1532 nscoord overflow = ymost - aFrameArea.YMost();
1533 if (overflow > 0) {
1534 fragmentClip.height -= overflow;
1535 }
1536 }
1537 }
1538 fragmentClip = fragmentClip.Intersect(aDirtyRect);
1539 aRenderingContext.Clip(NSRectToSnappedRect(
1540 fragmentClip, aForFrame->PresContext()->AppUnitsPerDevPixel(),
1541 aDrawTarget));
1542
1543 RectCornerRadii clipRectRadii;
1544 if (hasBorderRadius) {
1545 Float spreadDistance = Float(shadowSpread / oneDevPixel);
1546
1547 Float borderSizes[4];
1548
1549 borderSizes[eSideLeft] = spreadDistance;
1550 borderSizes[eSideTop] = spreadDistance;
1551 borderSizes[eSideRight] = spreadDistance;
1552 borderSizes[eSideBottom] = spreadDistance;
1553
1554 nsCSSBorderRenderer::ComputeOuterRadii(borderRadii, borderSizes,
1555 &clipRectRadii);
1556 }
1557 nsContextBoxBlur::BlurRectangle(
1558 &aRenderingContext, shadowRect, oneDevPixel,
1559 hasBorderRadius ? &clipRectRadii : nullptr, blurRadius,
1560 gfxShadowColor, aDirtyRect, skipGfxRect);
1561 aRenderingContext.Restore();
1562 }
1563 }
1564 }
1565
GetBoxShadowInnerPaddingRect(nsIFrame * aFrame,const nsRect & aFrameArea)1566 nsRect nsCSSRendering::GetBoxShadowInnerPaddingRect(nsIFrame* aFrame,
1567 const nsRect& aFrameArea) {
1568 Sides skipSides = aFrame->GetSkipSides();
1569 nsRect frameRect = BoxDecorationRectForBorder(aFrame, aFrameArea, skipSides);
1570
1571 nsRect paddingRect = frameRect;
1572 nsMargin border = aFrame->GetUsedBorder();
1573 paddingRect.Deflate(border);
1574 return paddingRect;
1575 }
1576
ShouldPaintBoxShadowInner(nsIFrame * aFrame)1577 bool nsCSSRendering::ShouldPaintBoxShadowInner(nsIFrame* aFrame) {
1578 const Span<const StyleBoxShadow> shadows =
1579 aFrame->StyleEffects()->mBoxShadow.AsSpan();
1580 if (shadows.IsEmpty()) {
1581 return false;
1582 }
1583
1584 if (aFrame->IsThemed() && aFrame->GetContent() &&
1585 !nsContentUtils::IsChromeDoc(aFrame->GetContent()->GetComposedDoc())) {
1586 // There's no way of getting hold of a shape corresponding to a
1587 // "padding-box" for native-themed widgets, so just don't draw
1588 // inner box-shadows for them. But we allow chrome to paint inner
1589 // box shadows since chrome can be aware of the platform theme.
1590 return false;
1591 }
1592
1593 return true;
1594 }
1595
GetShadowInnerRadii(nsIFrame * aFrame,const nsRect & aFrameArea,RectCornerRadii & aOutInnerRadii)1596 bool nsCSSRendering::GetShadowInnerRadii(nsIFrame* aFrame,
1597 const nsRect& aFrameArea,
1598 RectCornerRadii& aOutInnerRadii) {
1599 // Get any border radius, since box-shadow must also have rounded corners
1600 // if the frame does.
1601 nscoord twipsRadii[8];
1602 nsRect frameRect =
1603 BoxDecorationRectForBorder(aFrame, aFrameArea, aFrame->GetSkipSides());
1604 nsSize sz = frameRect.Size();
1605 nsMargin border = aFrame->GetUsedBorder();
1606 aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
1607 const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1);
1608
1609 RectCornerRadii borderRadii;
1610
1611 const bool hasBorderRadius =
1612 GetBorderRadii(frameRect, aFrameArea, aFrame, borderRadii);
1613
1614 if (hasBorderRadius) {
1615 ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii);
1616
1617 Float borderSizes[4] = {
1618 Float(border.top) / oneDevPixel, Float(border.right) / oneDevPixel,
1619 Float(border.bottom) / oneDevPixel, Float(border.left) / oneDevPixel};
1620 nsCSSBorderRenderer::ComputeInnerRadii(borderRadii, borderSizes,
1621 &aOutInnerRadii);
1622 }
1623
1624 return hasBorderRadius;
1625 }
1626
PaintBoxShadowInner(nsPresContext * aPresContext,gfxContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aFrameArea)1627 void nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext,
1628 gfxContext& aRenderingContext,
1629 nsIFrame* aForFrame,
1630 const nsRect& aFrameArea) {
1631 if (!ShouldPaintBoxShadowInner(aForFrame)) {
1632 return;
1633 }
1634
1635 const Span<const StyleBoxShadow> shadows =
1636 aForFrame->StyleEffects()->mBoxShadow.AsSpan();
1637 NS_ASSERTION(
1638 aForFrame->IsFieldSetFrame() || aFrameArea.Size() == aForFrame->GetSize(),
1639 "unexpected size");
1640
1641 nsRect paddingRect = GetBoxShadowInnerPaddingRect(aForFrame, aFrameArea);
1642
1643 RectCornerRadii innerRadii;
1644 bool hasBorderRadius = GetShadowInnerRadii(aForFrame, aFrameArea, innerRadii);
1645
1646 const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
1647
1648 for (const StyleBoxShadow& shadow : Reversed(shadows)) {
1649 if (!shadow.inset) {
1650 continue;
1651 }
1652
1653 // shadowPaintRect: the area to paint on the temp surface
1654 // shadowClipRect: the area on the temporary surface within shadowPaintRect
1655 // that we will NOT paint in
1656 nscoord blurRadius = shadow.base.blur.ToAppUnits();
1657 nsMargin blurMargin =
1658 nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel);
1659 nsRect shadowPaintRect = paddingRect;
1660 shadowPaintRect.Inflate(blurMargin);
1661
1662 // Round the spread radius to device pixels (by truncation).
1663 // This mostly matches what we do for borders, except that we don't round
1664 // up values between zero and one device pixels to one device pixel.
1665 // This way of rounding is symmetric around zero, which makes sense for
1666 // the spread radius.
1667 int32_t spreadDistance = shadow.spread.ToAppUnits() / oneDevPixel;
1668 nscoord spreadDistanceAppUnits =
1669 aPresContext->DevPixelsToAppUnits(spreadDistance);
1670
1671 nsRect shadowClipRect = paddingRect;
1672 shadowClipRect.MoveBy(shadow.base.horizontal.ToAppUnits(),
1673 shadow.base.vertical.ToAppUnits());
1674 shadowClipRect.Deflate(spreadDistanceAppUnits, spreadDistanceAppUnits);
1675
1676 Rect shadowClipGfxRect = NSRectToRect(shadowClipRect, oneDevPixel);
1677 shadowClipGfxRect.Round();
1678
1679 RectCornerRadii clipRectRadii;
1680 if (hasBorderRadius) {
1681 // Calculate the radii the inner clipping rect will have
1682 Float borderSizes[4] = {0, 0, 0, 0};
1683
1684 // See PaintBoxShadowOuter and bug 514670
1685 if (innerRadii[C_TL].width > 0 || innerRadii[C_BL].width > 0) {
1686 borderSizes[eSideLeft] = spreadDistance;
1687 }
1688
1689 if (innerRadii[C_TL].height > 0 || innerRadii[C_TR].height > 0) {
1690 borderSizes[eSideTop] = spreadDistance;
1691 }
1692
1693 if (innerRadii[C_TR].width > 0 || innerRadii[C_BR].width > 0) {
1694 borderSizes[eSideRight] = spreadDistance;
1695 }
1696
1697 if (innerRadii[C_BL].height > 0 || innerRadii[C_BR].height > 0) {
1698 borderSizes[eSideBottom] = spreadDistance;
1699 }
1700
1701 nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes,
1702 &clipRectRadii);
1703 }
1704
1705 // Set the "skip rect" to the area within the frame that we don't paint in,
1706 // including after blurring.
1707 nsRect skipRect = shadowClipRect;
1708 skipRect.Deflate(blurMargin);
1709 gfxRect skipGfxRect = nsLayoutUtils::RectToGfxRect(skipRect, oneDevPixel);
1710 if (hasBorderRadius) {
1711 skipGfxRect.Deflate(gfxMargin(
1712 std::max(clipRectRadii[C_TL].height, clipRectRadii[C_TR].height), 0,
1713 std::max(clipRectRadii[C_BL].height, clipRectRadii[C_BR].height), 0));
1714 }
1715
1716 // When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area
1717 // unchanged. And by construction the gfxSkipRect is not touched by the
1718 // rendered shadow (even after blurring), so those pixels must be completely
1719 // transparent in the shadow, so drawing them changes nothing.
1720 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
1721
1722 // Clip the context to the area of the frame's padding rect, so no part of
1723 // the shadow is painted outside. Also cut out anything beyond where the
1724 // inset shadow will be.
1725 Rect shadowGfxRect = NSRectToRect(paddingRect, oneDevPixel);
1726 shadowGfxRect.Round();
1727
1728 sRGBColor shadowColor = GetShadowColor(shadow.base, aForFrame, 1.0);
1729 aRenderingContext.Save();
1730
1731 // This clips the outside border radius.
1732 // clipRectRadii is the border radius inside the inset shadow.
1733 if (hasBorderRadius) {
1734 RefPtr<Path> roundedRect =
1735 MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii);
1736 aRenderingContext.Clip(roundedRect);
1737 } else {
1738 aRenderingContext.Clip(shadowGfxRect);
1739 }
1740
1741 nsContextBoxBlur insetBoxBlur;
1742 gfxRect destRect =
1743 nsLayoutUtils::RectToGfxRect(shadowPaintRect, oneDevPixel);
1744 Point shadowOffset(shadow.base.horizontal.ToAppUnits() / oneDevPixel,
1745 shadow.base.vertical.ToAppUnits() / oneDevPixel);
1746
1747 insetBoxBlur.InsetBoxBlur(
1748 &aRenderingContext, ToRect(destRect), shadowClipGfxRect, shadowColor,
1749 blurRadius, spreadDistanceAppUnits, oneDevPixel, hasBorderRadius,
1750 clipRectRadii, ToRect(skipGfxRect), shadowOffset);
1751 aRenderingContext.Restore();
1752 }
1753 }
1754
1755 /* static */
ForAllLayers(nsPresContext & aPresCtx,const nsRect & aDirtyRect,const nsRect & aBorderArea,nsIFrame * aFrame,uint32_t aPaintFlags,float aOpacity)1756 nsCSSRendering::PaintBGParams nsCSSRendering::PaintBGParams::ForAllLayers(
1757 nsPresContext& aPresCtx, const nsRect& aDirtyRect,
1758 const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags,
1759 float aOpacity) {
1760 MOZ_ASSERT(aFrame);
1761
1762 PaintBGParams result(aPresCtx, aDirtyRect, aBorderArea, aFrame, aPaintFlags,
1763 -1, CompositionOp::OP_OVER, aOpacity);
1764
1765 return result;
1766 }
1767
1768 /* static */
ForSingleLayer(nsPresContext & aPresCtx,const nsRect & aDirtyRect,const nsRect & aBorderArea,nsIFrame * aFrame,uint32_t aPaintFlags,int32_t aLayer,CompositionOp aCompositionOp,float aOpacity)1769 nsCSSRendering::PaintBGParams nsCSSRendering::PaintBGParams::ForSingleLayer(
1770 nsPresContext& aPresCtx, const nsRect& aDirtyRect,
1771 const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags,
1772 int32_t aLayer, CompositionOp aCompositionOp, float aOpacity) {
1773 MOZ_ASSERT(aFrame && (aLayer != -1));
1774
1775 PaintBGParams result(aPresCtx, aDirtyRect, aBorderArea, aFrame, aPaintFlags,
1776 aLayer, aCompositionOp, aOpacity);
1777
1778 return result;
1779 }
1780
PaintStyleImageLayer(const PaintBGParams & aParams,gfxContext & aRenderingCtx)1781 ImgDrawResult nsCSSRendering::PaintStyleImageLayer(const PaintBGParams& aParams,
1782 gfxContext& aRenderingCtx) {
1783 AUTO_PROFILER_LABEL("nsCSSRendering::PaintStyleImageLayer", GRAPHICS);
1784
1785 MOZ_ASSERT(aParams.frame,
1786 "Frame is expected to be provided to PaintStyleImageLayer");
1787
1788 ComputedStyle* sc;
1789 if (!FindBackground(aParams.frame, &sc)) {
1790 // We don't want to bail out if moz-appearance is set on a root
1791 // node. If it has a parent content node, bail because it's not
1792 // a root, otherwise keep going in order to let the theme stuff
1793 // draw the background. The canvas really should be drawing the
1794 // bg, but there's no way to hook that up via css.
1795 if (!aParams.frame->StyleDisplay()->HasAppearance()) {
1796 return ImgDrawResult::SUCCESS;
1797 }
1798
1799 nsIContent* content = aParams.frame->GetContent();
1800 if (!content || content->GetParent()) {
1801 return ImgDrawResult::SUCCESS;
1802 }
1803
1804 sc = aParams.frame->Style();
1805 }
1806
1807 return PaintStyleImageLayerWithSC(aParams, aRenderingCtx, sc,
1808 *aParams.frame->StyleBorder());
1809 }
1810
CanBuildWebRenderDisplayItemsForStyleImageLayer(LayerManager * aManager,nsPresContext & aPresCtx,nsIFrame * aFrame,const nsStyleBackground * aBackgroundStyle,int32_t aLayer,uint32_t aPaintFlags)1811 bool nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer(
1812 LayerManager* aManager, nsPresContext& aPresCtx, nsIFrame* aFrame,
1813 const nsStyleBackground* aBackgroundStyle, int32_t aLayer,
1814 uint32_t aPaintFlags) {
1815 if (!aBackgroundStyle) {
1816 return false;
1817 }
1818
1819 MOZ_ASSERT(aFrame && aLayer >= 0 &&
1820 (uint32_t)aLayer < aBackgroundStyle->mImage.mLayers.Length());
1821
1822 // We cannot draw native themed backgrounds
1823 StyleAppearance appearance = aFrame->StyleDisplay()->EffectiveAppearance();
1824 if (appearance != StyleAppearance::None) {
1825 nsITheme* theme = aPresCtx.Theme();
1826 if (theme->ThemeSupportsWidget(&aPresCtx, aFrame, appearance)) {
1827 return false;
1828 }
1829 }
1830
1831 // We only support painting gradients and image for a single style image
1832 // layer, and we don't support crop-rects.
1833 const auto& styleImage =
1834 aBackgroundStyle->mImage.mLayers[aLayer].mImage.FinalImage();
1835 if (styleImage.IsImageRequestType()) {
1836 if (styleImage.IsRect()) {
1837 return false;
1838 }
1839
1840 imgRequestProxy* requestProxy = styleImage.GetImageRequest();
1841 if (!requestProxy) {
1842 return false;
1843 }
1844
1845 uint32_t imageFlags = imgIContainer::FLAG_NONE;
1846 if (aPaintFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) {
1847 imageFlags |= imgIContainer::FLAG_SYNC_DECODE;
1848 }
1849
1850 nsCOMPtr<imgIContainer> srcImage;
1851 requestProxy->GetImage(getter_AddRefs(srcImage));
1852 if (!srcImage ||
1853 !srcImage->IsImageContainerAvailable(aManager, imageFlags)) {
1854 return false;
1855 }
1856
1857 return true;
1858 }
1859
1860 if (styleImage.IsGradient()) {
1861 return true;
1862 }
1863
1864 return false;
1865 }
1866
BuildWebRenderDisplayItemsForStyleImageLayer(const PaintBGParams & aParams,mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const mozilla::layers::StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayItem * aItem)1867 ImgDrawResult nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer(
1868 const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder,
1869 mozilla::wr::IpcResourceUpdateQueue& aResources,
1870 const mozilla::layers::StackingContextHelper& aSc,
1871 mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem) {
1872 MOZ_ASSERT(aParams.frame,
1873 "Frame is expected to be provided to "
1874 "BuildWebRenderDisplayItemsForStyleImageLayer");
1875
1876 ComputedStyle* sc;
1877 if (!FindBackground(aParams.frame, &sc)) {
1878 // We don't want to bail out if moz-appearance is set on a root
1879 // node. If it has a parent content node, bail because it's not
1880 // a root, otherwise keep going in order to let the theme stuff
1881 // draw the background. The canvas really should be drawing the
1882 // bg, but there's no way to hook that up via css.
1883 if (!aParams.frame->StyleDisplay()->HasAppearance()) {
1884 return ImgDrawResult::SUCCESS;
1885 }
1886
1887 nsIContent* content = aParams.frame->GetContent();
1888 if (!content || content->GetParent()) {
1889 return ImgDrawResult::SUCCESS;
1890 }
1891
1892 sc = aParams.frame->Style();
1893 }
1894 return BuildWebRenderDisplayItemsForStyleImageLayerWithSC(
1895 aParams, aBuilder, aResources, aSc, aManager, aItem, sc,
1896 *aParams.frame->StyleBorder());
1897 }
1898
IsOpaqueBorderEdge(const nsStyleBorder & aBorder,mozilla::Side aSide)1899 static bool IsOpaqueBorderEdge(const nsStyleBorder& aBorder,
1900 mozilla::Side aSide) {
1901 if (aBorder.GetComputedBorder().Side(aSide) == 0) return true;
1902 switch (aBorder.GetBorderStyle(aSide)) {
1903 case StyleBorderStyle::Solid:
1904 case StyleBorderStyle::Groove:
1905 case StyleBorderStyle::Ridge:
1906 case StyleBorderStyle::Inset:
1907 case StyleBorderStyle::Outset:
1908 break;
1909 default:
1910 return false;
1911 }
1912
1913 // If we're using a border image, assume it's not fully opaque,
1914 // because we may not even have the image loaded at this point, and
1915 // even if we did, checking whether the relevant tile is fully
1916 // opaque would be too much work.
1917 if (!aBorder.mBorderImageSource.IsNone()) {
1918 return false;
1919 }
1920
1921 StyleColor color = aBorder.BorderColorFor(aSide);
1922 // We don't know the foreground color here, so if it's being used
1923 // we must assume it might be transparent.
1924 return !color.MaybeTransparent();
1925 }
1926
1927 /**
1928 * Returns true if all border edges are either missing or opaque.
1929 */
IsOpaqueBorder(const nsStyleBorder & aBorder)1930 static bool IsOpaqueBorder(const nsStyleBorder& aBorder) {
1931 for (const auto i : mozilla::AllPhysicalSides()) {
1932 if (!IsOpaqueBorderEdge(aBorder, i)) {
1933 return false;
1934 }
1935 }
1936 return true;
1937 }
1938
SetupDirtyRects(const nsRect & aBGClipArea,const nsRect & aCallerDirtyRect,nscoord aAppUnitsPerPixel,nsRect * aDirtyRect,gfxRect * aDirtyRectGfx)1939 static inline void SetupDirtyRects(const nsRect& aBGClipArea,
1940 const nsRect& aCallerDirtyRect,
1941 nscoord aAppUnitsPerPixel,
1942 /* OUT: */
1943 nsRect* aDirtyRect, gfxRect* aDirtyRectGfx) {
1944 aDirtyRect->IntersectRect(aBGClipArea, aCallerDirtyRect);
1945
1946 // Compute the Thebes equivalent of the dirtyRect.
1947 *aDirtyRectGfx = nsLayoutUtils::RectToGfxRect(*aDirtyRect, aAppUnitsPerPixel);
1948 NS_WARNING_ASSERTION(aDirtyRect->IsEmpty() || !aDirtyRectGfx->IsEmpty(),
1949 "converted dirty rect should not be empty");
1950 MOZ_ASSERT(!aDirtyRect->IsEmpty() || aDirtyRectGfx->IsEmpty(),
1951 "second should be empty if first is");
1952 }
1953
IsSVGStyleGeometryBox(StyleGeometryBox aBox)1954 static bool IsSVGStyleGeometryBox(StyleGeometryBox aBox) {
1955 return (aBox == StyleGeometryBox::FillBox ||
1956 aBox == StyleGeometryBox::StrokeBox ||
1957 aBox == StyleGeometryBox::ViewBox);
1958 }
1959
IsHTMLStyleGeometryBox(StyleGeometryBox aBox)1960 static bool IsHTMLStyleGeometryBox(StyleGeometryBox aBox) {
1961 return (aBox == StyleGeometryBox::ContentBox ||
1962 aBox == StyleGeometryBox::PaddingBox ||
1963 aBox == StyleGeometryBox::BorderBox ||
1964 aBox == StyleGeometryBox::MarginBox);
1965 }
1966
ComputeBoxValue(nsIFrame * aForFrame,StyleGeometryBox aBox)1967 static StyleGeometryBox ComputeBoxValue(nsIFrame* aForFrame,
1968 StyleGeometryBox aBox) {
1969 if (!aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
1970 // For elements with associated CSS layout box, the values fill-box,
1971 // stroke-box and view-box compute to the initial value of mask-clip.
1972 if (IsSVGStyleGeometryBox(aBox)) {
1973 return StyleGeometryBox::BorderBox;
1974 }
1975 } else {
1976 // For SVG elements without associated CSS layout box, the values
1977 // content-box, padding-box, border-box and margin-box compute to fill-box.
1978 if (IsHTMLStyleGeometryBox(aBox)) {
1979 return StyleGeometryBox::FillBox;
1980 }
1981 }
1982
1983 return aBox;
1984 }
1985
IsValid() const1986 bool nsCSSRendering::ImageLayerClipState::IsValid() const {
1987 // mDirtyRectInDevPx comes from mDirtyRectInAppUnits. mDirtyRectInAppUnits
1988 // can not be empty if mDirtyRectInDevPx is not.
1989 if (!mDirtyRectInDevPx.IsEmpty() && mDirtyRectInAppUnits.IsEmpty()) {
1990 return false;
1991 }
1992
1993 if (mHasRoundedCorners == mClippedRadii.IsEmpty()) {
1994 return false;
1995 }
1996
1997 return true;
1998 }
1999
2000 /* static */
GetImageLayerClip(const nsStyleImageLayers::Layer & aLayer,nsIFrame * aForFrame,const nsStyleBorder & aBorder,const nsRect & aBorderArea,const nsRect & aCallerDirtyRect,bool aWillPaintBorder,nscoord aAppUnitsPerPixel,ImageLayerClipState * aClipState)2001 void nsCSSRendering::GetImageLayerClip(
2002 const nsStyleImageLayers::Layer& aLayer, nsIFrame* aForFrame,
2003 const nsStyleBorder& aBorder, const nsRect& aBorderArea,
2004 const nsRect& aCallerDirtyRect, bool aWillPaintBorder,
2005 nscoord aAppUnitsPerPixel,
2006 /* out */ ImageLayerClipState* aClipState) {
2007 StyleGeometryBox layerClip = ComputeBoxValue(aForFrame, aLayer.mClip);
2008 if (IsSVGStyleGeometryBox(layerClip)) {
2009 MOZ_ASSERT(aForFrame->IsFrameOfType(nsIFrame::eSVG) &&
2010 !aForFrame->IsSVGOuterSVGFrame());
2011
2012 // The coordinate space of clipArea is svg user space.
2013 nsRect clipArea = nsLayoutUtils::ComputeGeometryBox(aForFrame, layerClip);
2014
2015 nsRect strokeBox = (layerClip == StyleGeometryBox::StrokeBox)
2016 ? clipArea
2017 : nsLayoutUtils::ComputeGeometryBox(
2018 aForFrame, StyleGeometryBox::StrokeBox);
2019 nsRect clipAreaRelativeToStrokeBox = clipArea - strokeBox.TopLeft();
2020
2021 // aBorderArea is the stroke-box area in a coordinate space defined by
2022 // the caller. This coordinate space can be svg user space of aForFrame,
2023 // the space of aForFrame's reference-frame, or anything else.
2024 //
2025 // Which coordinate space chosen for aBorderArea is not matter. What
2026 // matter is to ensure returning aClipState->mBGClipArea in the consistent
2027 // coordiante space with aBorderArea. So we evaluate the position of clip
2028 // area base on the position of aBorderArea here.
2029 aClipState->mBGClipArea =
2030 clipAreaRelativeToStrokeBox + aBorderArea.TopLeft();
2031
2032 SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect,
2033 aAppUnitsPerPixel, &aClipState->mDirtyRectInAppUnits,
2034 &aClipState->mDirtyRectInDevPx);
2035 MOZ_ASSERT(aClipState->IsValid());
2036 return;
2037 }
2038
2039 if (layerClip == StyleGeometryBox::NoClip) {
2040 aClipState->mBGClipArea = aCallerDirtyRect;
2041
2042 SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect,
2043 aAppUnitsPerPixel, &aClipState->mDirtyRectInAppUnits,
2044 &aClipState->mDirtyRectInDevPx);
2045 MOZ_ASSERT(aClipState->IsValid());
2046 return;
2047 }
2048
2049 MOZ_ASSERT(!aForFrame->IsFrameOfType(nsIFrame::eSVG) ||
2050 aForFrame->IsSVGOuterSVGFrame());
2051
2052 // Compute the outermost boundary of the area that might be painted.
2053 // Same coordinate space as aBorderArea.
2054 Sides skipSides = aForFrame->GetSkipSides();
2055 nsRect clipBorderArea =
2056 BoxDecorationRectForBorder(aForFrame, aBorderArea, skipSides, &aBorder);
2057
2058 bool haveRoundedCorners = false;
2059 LayoutFrameType fType = aForFrame->Type();
2060 if (fType != LayoutFrameType::TableColGroup &&
2061 fType != LayoutFrameType::TableCol &&
2062 fType != LayoutFrameType::TableRow &&
2063 fType != LayoutFrameType::TableRowGroup) {
2064 haveRoundedCorners = GetRadii(aForFrame, aBorder, aBorderArea,
2065 clipBorderArea, aClipState->mRadii);
2066 }
2067 bool isSolidBorder = aWillPaintBorder && IsOpaqueBorder(aBorder);
2068 if (isSolidBorder && layerClip == StyleGeometryBox::BorderBox) {
2069 // If we have rounded corners, we need to inflate the background
2070 // drawing area a bit to avoid seams between the border and
2071 // background.
2072 layerClip = haveRoundedCorners ? StyleGeometryBox::MozAlmostPadding
2073 : StyleGeometryBox::PaddingBox;
2074 }
2075
2076 aClipState->mBGClipArea = clipBorderArea;
2077
2078 if (aForFrame->IsScrollFrame() &&
2079 StyleImageLayerAttachment::Local == aLayer.mAttachment) {
2080 // As of this writing, this is still in discussion in the CSS Working Group
2081 // http://lists.w3.org/Archives/Public/www-style/2013Jul/0250.html
2082
2083 // The rectangle for 'background-clip' scrolls with the content,
2084 // but the background is also clipped at a non-scrolling 'padding-box'
2085 // like the content. (See below.)
2086 // Therefore, only 'content-box' makes a difference here.
2087 if (layerClip == StyleGeometryBox::ContentBox) {
2088 nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
2089 // Clip at a rectangle attached to the scrolled content.
2090 aClipState->mHasAdditionalBGClipArea = true;
2091 aClipState->mAdditionalBGClipArea =
2092 nsRect(aClipState->mBGClipArea.TopLeft() +
2093 scrollableFrame->GetScrolledFrame()->GetPosition()
2094 // For the dir=rtl case:
2095 + scrollableFrame->GetScrollRange().TopLeft(),
2096 scrollableFrame->GetScrolledRect().Size());
2097 nsMargin padding = aForFrame->GetUsedPadding();
2098 // padding-bottom is ignored on scrollable frames:
2099 // https://bugzilla.mozilla.org/show_bug.cgi?id=748518
2100 padding.bottom = 0;
2101 padding.ApplySkipSides(skipSides);
2102 aClipState->mAdditionalBGClipArea.Deflate(padding);
2103 }
2104
2105 // Also clip at a non-scrolling, rounded-corner 'padding-box',
2106 // same as the scrolled content because of the 'overflow' property.
2107 layerClip = StyleGeometryBox::PaddingBox;
2108 }
2109
2110 // See the comment of StyleGeometryBox::Margin.
2111 // Hitting this assertion means we decide to turn on margin-box support for
2112 // positioned mask from CSS parser and style system. In this case, you
2113 // should *inflate* mBGClipArea by the margin returning from
2114 // aForFrame->GetUsedMargin() in the code chunk bellow.
2115 MOZ_ASSERT(layerClip != StyleGeometryBox::MarginBox,
2116 "StyleGeometryBox::MarginBox rendering is not supported yet.\n");
2117
2118 if (layerClip != StyleGeometryBox::BorderBox &&
2119 layerClip != StyleGeometryBox::Text) {
2120 nsMargin border = aForFrame->GetUsedBorder();
2121 if (layerClip == StyleGeometryBox::MozAlmostPadding) {
2122 // Reduce |border| by 1px (device pixels) on all sides, if
2123 // possible, so that we don't get antialiasing seams between the
2124 // {background|mask} and border.
2125 border.top = std::max(0, border.top - aAppUnitsPerPixel);
2126 border.right = std::max(0, border.right - aAppUnitsPerPixel);
2127 border.bottom = std::max(0, border.bottom - aAppUnitsPerPixel);
2128 border.left = std::max(0, border.left - aAppUnitsPerPixel);
2129 } else if (layerClip != StyleGeometryBox::PaddingBox) {
2130 NS_ASSERTION(layerClip == StyleGeometryBox::ContentBox,
2131 "unexpected background-clip");
2132 border += aForFrame->GetUsedPadding();
2133 }
2134 border.ApplySkipSides(skipSides);
2135 aClipState->mBGClipArea.Deflate(border);
2136
2137 if (haveRoundedCorners) {
2138 nsIFrame::InsetBorderRadii(aClipState->mRadii, border);
2139 }
2140 }
2141
2142 if (haveRoundedCorners) {
2143 auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
2144 nsCSSRendering::ComputePixelRadii(aClipState->mRadii, d2a,
2145 &aClipState->mClippedRadii);
2146 aClipState->mHasRoundedCorners = !aClipState->mClippedRadii.IsEmpty();
2147 }
2148
2149 if (!haveRoundedCorners && aClipState->mHasAdditionalBGClipArea) {
2150 // Do the intersection here to account for the fast path(?) below.
2151 aClipState->mBGClipArea =
2152 aClipState->mBGClipArea.Intersect(aClipState->mAdditionalBGClipArea);
2153 aClipState->mHasAdditionalBGClipArea = false;
2154 }
2155
2156 SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, aAppUnitsPerPixel,
2157 &aClipState->mDirtyRectInAppUnits,
2158 &aClipState->mDirtyRectInDevPx);
2159
2160 MOZ_ASSERT(aClipState->IsValid());
2161 }
2162
SetupImageLayerClip(nsCSSRendering::ImageLayerClipState & aClipState,gfxContext * aCtx,nscoord aAppUnitsPerPixel,gfxContextAutoSaveRestore * aAutoSR)2163 static void SetupImageLayerClip(nsCSSRendering::ImageLayerClipState& aClipState,
2164 gfxContext* aCtx, nscoord aAppUnitsPerPixel,
2165 gfxContextAutoSaveRestore* aAutoSR) {
2166 if (aClipState.mDirtyRectInDevPx.IsEmpty()) {
2167 // Our caller won't draw anything under this condition, so no need
2168 // to set more up.
2169 return;
2170 }
2171
2172 if (aClipState.mCustomClip) {
2173 // We don't support custom clips and rounded corners, arguably a bug, but
2174 // table painting seems to depend on it.
2175 return;
2176 }
2177
2178 // If we have rounded corners, clip all subsequent drawing to the
2179 // rounded rectangle defined by bgArea and bgRadii (we don't know
2180 // whether the rounded corners intrude on the dirtyRect or not).
2181 // Do not do this if we have a caller-provided clip rect --
2182 // as above with bgArea, arguably a bug, but table painting seems
2183 // to depend on it.
2184
2185 if (aClipState.mHasAdditionalBGClipArea) {
2186 gfxRect bgAreaGfx = nsLayoutUtils::RectToGfxRect(
2187 aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
2188 bgAreaGfx.Round();
2189 gfxUtils::ConditionRect(bgAreaGfx);
2190
2191 aAutoSR->EnsureSaved(aCtx);
2192 aCtx->SnappedClip(bgAreaGfx);
2193 }
2194
2195 if (aClipState.mHasRoundedCorners) {
2196 Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
2197 bgAreaGfx.Round();
2198
2199 if (bgAreaGfx.IsEmpty()) {
2200 // I think it's become possible to hit this since
2201 // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
2202 NS_WARNING("converted background area should not be empty");
2203 // Make our caller not do anything.
2204 aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0));
2205 return;
2206 }
2207
2208 aAutoSR->EnsureSaved(aCtx);
2209
2210 RefPtr<Path> roundedRect = MakePathForRoundedRect(
2211 *aCtx->GetDrawTarget(), bgAreaGfx, aClipState.mClippedRadii);
2212 aCtx->Clip(roundedRect);
2213 }
2214 }
2215
DrawBackgroundColor(nsCSSRendering::ImageLayerClipState & aClipState,gfxContext * aCtx,nscoord aAppUnitsPerPixel)2216 static void DrawBackgroundColor(nsCSSRendering::ImageLayerClipState& aClipState,
2217 gfxContext* aCtx, nscoord aAppUnitsPerPixel) {
2218 if (aClipState.mDirtyRectInDevPx.IsEmpty()) {
2219 // Our caller won't draw anything under this condition, so no need
2220 // to set more up.
2221 return;
2222 }
2223
2224 DrawTarget* drawTarget = aCtx->GetDrawTarget();
2225
2226 // We don't support custom clips and rounded corners, arguably a bug, but
2227 // table painting seems to depend on it.
2228 if (!aClipState.mHasRoundedCorners || aClipState.mCustomClip) {
2229 aCtx->NewPath();
2230 aCtx->SnappedRectangle(aClipState.mDirtyRectInDevPx);
2231 aCtx->Fill();
2232 return;
2233 }
2234
2235 Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
2236 bgAreaGfx.Round();
2237
2238 if (bgAreaGfx.IsEmpty()) {
2239 // I think it's become possible to hit this since
2240 // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
2241 NS_WARNING("converted background area should not be empty");
2242 // Make our caller not do anything.
2243 aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0));
2244 return;
2245 }
2246
2247 aCtx->Save();
2248 gfxRect dirty = ThebesRect(bgAreaGfx).Intersect(aClipState.mDirtyRectInDevPx);
2249
2250 aCtx->SnappedClip(dirty);
2251
2252 if (aClipState.mHasAdditionalBGClipArea) {
2253 gfxRect bgAdditionalAreaGfx = nsLayoutUtils::RectToGfxRect(
2254 aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
2255 bgAdditionalAreaGfx.Round();
2256 gfxUtils::ConditionRect(bgAdditionalAreaGfx);
2257 aCtx->SnappedClip(bgAdditionalAreaGfx);
2258 }
2259
2260 RefPtr<Path> roundedRect =
2261 MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii);
2262 aCtx->SetPath(roundedRect);
2263 aCtx->Fill();
2264 aCtx->Restore();
2265 }
2266
2267 enum class ScrollbarColorKind {
2268 Thumb,
2269 Track,
2270 };
2271
CalcScrollbarColor(nsIFrame * aFrame,ScrollbarColorKind aKind)2272 static Maybe<nscolor> CalcScrollbarColor(nsIFrame* aFrame,
2273 ScrollbarColorKind aKind) {
2274 ComputedStyle* scrollbarStyle = nsLayoutUtils::StyleForScrollbar(aFrame);
2275 const auto& colors = scrollbarStyle->StyleUI()->mScrollbarColor;
2276 if (colors.IsAuto()) {
2277 return Nothing();
2278 }
2279 const auto& color = aKind == ScrollbarColorKind::Thumb
2280 ? colors.AsColors().thumb
2281 : colors.AsColors().track;
2282 return Some(color.CalcColor(*scrollbarStyle));
2283 }
2284
GetBackgroundColor(nsIFrame * aFrame,ComputedStyle * aStyle)2285 static nscolor GetBackgroundColor(nsIFrame* aFrame, ComputedStyle* aStyle) {
2286 switch (aStyle->StyleDisplay()->EffectiveAppearance()) {
2287 case StyleAppearance::ScrollbarthumbVertical:
2288 case StyleAppearance::ScrollbarthumbHorizontal: {
2289 if (Maybe<nscolor> overrideColor =
2290 CalcScrollbarColor(aFrame, ScrollbarColorKind::Thumb)) {
2291 return *overrideColor;
2292 }
2293 break;
2294 }
2295 case StyleAppearance::ScrollbarVertical:
2296 case StyleAppearance::ScrollbarHorizontal:
2297 case StyleAppearance::Scrollcorner: {
2298 if (Maybe<nscolor> overrideColor =
2299 CalcScrollbarColor(aFrame, ScrollbarColorKind::Track)) {
2300 return *overrideColor;
2301 }
2302 break;
2303 }
2304 default:
2305 break;
2306 }
2307 return aStyle->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
2308 }
2309
DetermineBackgroundColor(nsPresContext * aPresContext,ComputedStyle * aStyle,nsIFrame * aFrame,bool & aDrawBackgroundImage,bool & aDrawBackgroundColor)2310 nscolor nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext,
2311 ComputedStyle* aStyle,
2312 nsIFrame* aFrame,
2313 bool& aDrawBackgroundImage,
2314 bool& aDrawBackgroundColor) {
2315 auto shouldPaint = aFrame->ComputeShouldPaintBackground();
2316 aDrawBackgroundImage = shouldPaint.mImage;
2317 aDrawBackgroundColor = shouldPaint.mColor;
2318
2319 const nsStyleBackground* bg = aStyle->StyleBackground();
2320 nscolor bgColor;
2321 if (aDrawBackgroundColor) {
2322 bgColor = GetBackgroundColor(aFrame, aStyle);
2323 if (NS_GET_A(bgColor) == 0) {
2324 aDrawBackgroundColor = false;
2325 }
2326 } else {
2327 // If GetBackgroundColorDraw() is false, we are still expected to
2328 // draw color in the background of any frame that's not completely
2329 // transparent, but we are expected to use white instead of whatever
2330 // color was specified.
2331 bgColor = NS_RGB(255, 255, 255);
2332 if (aDrawBackgroundImage || !bg->IsTransparent(aStyle)) {
2333 aDrawBackgroundColor = true;
2334 } else {
2335 bgColor = NS_RGBA(0, 0, 0, 0);
2336 }
2337 }
2338
2339 // We can skip painting the background color if a background image is opaque.
2340 nsStyleImageLayers::Repeat repeat = bg->BottomLayer().mRepeat;
2341 bool xFullRepeat = repeat.mXRepeat == StyleImageLayerRepeat::Repeat ||
2342 repeat.mXRepeat == StyleImageLayerRepeat::Round;
2343 bool yFullRepeat = repeat.mYRepeat == StyleImageLayerRepeat::Repeat ||
2344 repeat.mYRepeat == StyleImageLayerRepeat::Round;
2345 if (aDrawBackgroundColor && xFullRepeat && yFullRepeat &&
2346 bg->BottomLayer().mImage.IsOpaque() &&
2347 bg->BottomLayer().mBlendMode == StyleBlend::Normal) {
2348 aDrawBackgroundColor = false;
2349 }
2350
2351 return bgColor;
2352 }
2353
DetermineCompositionOp(const nsCSSRendering::PaintBGParams & aParams,const nsStyleImageLayers & aLayers,uint32_t aLayerIndex)2354 static CompositionOp DetermineCompositionOp(
2355 const nsCSSRendering::PaintBGParams& aParams,
2356 const nsStyleImageLayers& aLayers, uint32_t aLayerIndex) {
2357 if (aParams.layer >= 0) {
2358 // When drawing a single layer, use the specified composition op.
2359 return aParams.compositionOp;
2360 }
2361
2362 const nsStyleImageLayers::Layer& layer = aLayers.mLayers[aLayerIndex];
2363 // When drawing all layers, get the compositon op from each image layer.
2364 if (aParams.paintFlags & nsCSSRendering::PAINTBG_MASK_IMAGE) {
2365 // Always using OP_OVER mode while drawing the bottom mask layer.
2366 if (aLayerIndex == (aLayers.mImageCount - 1)) {
2367 return CompositionOp::OP_OVER;
2368 }
2369
2370 return nsCSSRendering::GetGFXCompositeMode(layer.mComposite);
2371 }
2372
2373 return nsCSSRendering::GetGFXBlendMode(layer.mBlendMode);
2374 }
2375
PaintStyleImageLayerWithSC(const PaintBGParams & aParams,gfxContext & aRenderingCtx,ComputedStyle * aBackgroundSC,const nsStyleBorder & aBorder)2376 ImgDrawResult nsCSSRendering::PaintStyleImageLayerWithSC(
2377 const PaintBGParams& aParams, gfxContext& aRenderingCtx,
2378 ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) {
2379 MOZ_ASSERT(aParams.frame,
2380 "Frame is expected to be provided to PaintStyleImageLayerWithSC");
2381
2382 // If we're drawing all layers, aCompositonOp is ignored, so make sure that
2383 // it was left at its default value.
2384 MOZ_ASSERT(aParams.layer != -1 ||
2385 aParams.compositionOp == CompositionOp::OP_OVER);
2386
2387 // Check to see if we have an appearance defined. If so, we let the theme
2388 // renderer draw the background and bail out.
2389 // XXXzw this ignores aParams.bgClipRect.
2390 StyleAppearance appearance =
2391 aParams.frame->StyleDisplay()->EffectiveAppearance();
2392 if (appearance != StyleAppearance::None) {
2393 nsITheme* theme = aParams.presCtx.Theme();
2394 if (theme->ThemeSupportsWidget(&aParams.presCtx, aParams.frame,
2395 appearance)) {
2396 nsRect drawing(aParams.borderArea);
2397 theme->GetWidgetOverflow(aParams.presCtx.DeviceContext(), aParams.frame,
2398 appearance, &drawing);
2399 drawing.IntersectRect(drawing, aParams.dirtyRect);
2400 theme->DrawWidgetBackground(&aRenderingCtx, aParams.frame, appearance,
2401 aParams.borderArea, drawing);
2402 return ImgDrawResult::SUCCESS;
2403 }
2404 }
2405
2406 // For canvas frames (in the CSS sense) we draw the background color using
2407 // a solid color item that gets added in nsLayoutUtils::PaintFrame,
2408 // or nsSubDocumentFrame::BuildDisplayList (bug 488242). (The solid
2409 // color may be moved into nsDisplayCanvasBackground by
2410 // PresShell::AddCanvasBackgroundColorItem(), and painted by
2411 // nsDisplayCanvasBackground directly.) Either way we don't need to
2412 // paint the background color here.
2413 bool isCanvasFrame = IsCanvasFrame(aParams.frame);
2414 const bool paintMask = aParams.paintFlags & PAINTBG_MASK_IMAGE;
2415
2416 // Determine whether we are drawing background images and/or
2417 // background colors.
2418 bool drawBackgroundImage = true;
2419 bool drawBackgroundColor = !paintMask;
2420 nscolor bgColor = NS_RGBA(0, 0, 0, 0);
2421 if (!paintMask) {
2422 bgColor =
2423 DetermineBackgroundColor(&aParams.presCtx, aBackgroundSC, aParams.frame,
2424 drawBackgroundImage, drawBackgroundColor);
2425 }
2426
2427 // Masks shouldn't be suppressed for print.
2428 MOZ_ASSERT_IF(paintMask, drawBackgroundImage);
2429
2430 const nsStyleImageLayers& layers =
2431 paintMask ? aBackgroundSC->StyleSVGReset()->mMask
2432 : aBackgroundSC->StyleBackground()->mImage;
2433 // If we're drawing a specific layer, we don't want to draw the
2434 // background color.
2435 if (drawBackgroundColor && aParams.layer >= 0) {
2436 drawBackgroundColor = false;
2437 }
2438
2439 // At this point, drawBackgroundImage and drawBackgroundColor are
2440 // true if and only if we are actually supposed to paint an image or
2441 // color into aDirtyRect, respectively.
2442 if (!drawBackgroundImage && !drawBackgroundColor) {
2443 return ImgDrawResult::SUCCESS;
2444 }
2445
2446 // The 'bgClipArea' (used only by the image tiling logic, far below)
2447 // is the caller-provided aParams.bgClipRect if any, or else the area
2448 // determined by the value of 'background-clip' in
2449 // SetupCurrentBackgroundClip. (Arguably it should be the
2450 // intersection, but that breaks the table painter -- in particular,
2451 // taking the intersection breaks reftests/bugs/403249-1[ab].)
2452 nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel();
2453 ImageLayerClipState clipState;
2454 if (aParams.bgClipRect) {
2455 clipState.mBGClipArea = *aParams.bgClipRect;
2456 clipState.mCustomClip = true;
2457 clipState.mHasRoundedCorners = false;
2458 SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel,
2459 &clipState.mDirtyRectInAppUnits,
2460 &clipState.mDirtyRectInDevPx);
2461 } else {
2462 GetImageLayerClip(layers.BottomLayer(), aParams.frame, aBorder,
2463 aParams.borderArea, aParams.dirtyRect,
2464 (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
2465 appUnitsPerPixel, &clipState);
2466 }
2467
2468 // If we might be using a background color, go ahead and set it now.
2469 if (drawBackgroundColor && !isCanvasFrame) {
2470 aRenderingCtx.SetColor(sRGBColor::FromABGR(bgColor));
2471 }
2472
2473 // If there is no background image, draw a color. (If there is
2474 // neither a background image nor a color, we wouldn't have gotten
2475 // this far.)
2476 if (!drawBackgroundImage) {
2477 if (!isCanvasFrame) {
2478 DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel);
2479 }
2480 return ImgDrawResult::SUCCESS;
2481 }
2482
2483 if (layers.mImageCount < 1) {
2484 // Return if there are no background layers, all work from this point
2485 // onwards happens iteratively on these.
2486 return ImgDrawResult::SUCCESS;
2487 }
2488
2489 MOZ_ASSERT((aParams.layer < 0) ||
2490 (layers.mImageCount > uint32_t(aParams.layer)));
2491
2492 // The background color is rendered over the entire dirty area,
2493 // even if the image isn't.
2494 if (drawBackgroundColor && !isCanvasFrame) {
2495 DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel);
2496 }
2497
2498 // Compute the outermost boundary of the area that might be painted.
2499 // Same coordinate space as aParams.borderArea & aParams.bgClipRect.
2500 Sides skipSides = aParams.frame->GetSkipSides();
2501 nsRect paintBorderArea = BoxDecorationRectForBackground(
2502 aParams.frame, aParams.borderArea, skipSides, &aBorder);
2503 nsRect clipBorderArea = BoxDecorationRectForBorder(
2504 aParams.frame, aParams.borderArea, skipSides, &aBorder);
2505
2506 ImgDrawResult result = ImgDrawResult::SUCCESS;
2507 StyleGeometryBox currentBackgroundClip = StyleGeometryBox::BorderBox;
2508 const bool drawAllLayers = (aParams.layer < 0);
2509 uint32_t count = drawAllLayers
2510 ? layers.mImageCount // iterate all image layers.
2511 : layers.mImageCount -
2512 aParams.layer; // iterate from the bottom layer to
2513 // the 'aParams.layer-th' layer.
2514 NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE(
2515 i, layers, layers.mImageCount - 1, count) {
2516 // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved(ctx)
2517 // in the cases we need it.
2518 gfxContextAutoSaveRestore autoSR;
2519 const nsStyleImageLayers::Layer& layer = layers.mLayers[i];
2520
2521 ImageLayerClipState currentLayerClipState = clipState;
2522 if (!aParams.bgClipRect) {
2523 bool isBottomLayer = (i == layers.mImageCount - 1);
2524 if (currentBackgroundClip != layer.mClip || isBottomLayer) {
2525 currentBackgroundClip = layer.mClip;
2526 if (!isBottomLayer) {
2527 currentLayerClipState = {};
2528 // For the bottom layer, we already called GetImageLayerClip above
2529 // and it stored its results in clipState.
2530 GetImageLayerClip(layer, aParams.frame, aBorder, aParams.borderArea,
2531 aParams.dirtyRect,
2532 (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
2533 appUnitsPerPixel, ¤tLayerClipState);
2534 }
2535 SetupImageLayerClip(currentLayerClipState, &aRenderingCtx,
2536 appUnitsPerPixel, &autoSR);
2537 if (!clipBorderArea.IsEqualEdges(aParams.borderArea)) {
2538 // We're drawing the background for the joined continuation boxes
2539 // so we need to clip that to the slice that we want for this
2540 // frame.
2541 gfxRect clip = nsLayoutUtils::RectToGfxRect(aParams.borderArea,
2542 appUnitsPerPixel);
2543 autoSR.EnsureSaved(&aRenderingCtx);
2544 aRenderingCtx.SnappedClip(clip);
2545 }
2546 }
2547 }
2548
2549 // Skip the following layer preparing and painting code if the current
2550 // layer is not selected for drawing.
2551 if (aParams.layer >= 0 && i != (uint32_t)aParams.layer) {
2552 continue;
2553 }
2554 nsBackgroundLayerState state = PrepareImageLayer(
2555 &aParams.presCtx, aParams.frame, aParams.paintFlags, paintBorderArea,
2556 currentLayerClipState.mBGClipArea, layer, nullptr);
2557 result &= state.mImageRenderer.PrepareResult();
2558
2559 // Skip the layer painting code if we found the dirty region is empty.
2560 if (currentLayerClipState.mDirtyRectInDevPx.IsEmpty()) {
2561 continue;
2562 }
2563
2564 if (!state.mFillArea.IsEmpty()) {
2565 CompositionOp co = DetermineCompositionOp(aParams, layers, i);
2566 if (co != CompositionOp::OP_OVER) {
2567 NS_ASSERTION(aRenderingCtx.CurrentOp() == CompositionOp::OP_OVER,
2568 "It is assumed the initial op is OP_OVER, when it is "
2569 "restored later");
2570 aRenderingCtx.SetOp(co);
2571 }
2572
2573 result &= state.mImageRenderer.DrawLayer(
2574 &aParams.presCtx, aRenderingCtx, state.mDestArea, state.mFillArea,
2575 state.mAnchor + paintBorderArea.TopLeft(),
2576 currentLayerClipState.mDirtyRectInAppUnits, state.mRepeatSize,
2577 aParams.opacity);
2578
2579 if (co != CompositionOp::OP_OVER) {
2580 aRenderingCtx.SetOp(CompositionOp::OP_OVER);
2581 }
2582 }
2583 }
2584
2585 return result;
2586 }
2587
2588 ImgDrawResult
BuildWebRenderDisplayItemsForStyleImageLayerWithSC(const PaintBGParams & aParams,mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const mozilla::layers::StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayItem * aItem,ComputedStyle * aBackgroundSC,const nsStyleBorder & aBorder)2589 nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayerWithSC(
2590 const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder,
2591 mozilla::wr::IpcResourceUpdateQueue& aResources,
2592 const mozilla::layers::StackingContextHelper& aSc,
2593 mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem,
2594 ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) {
2595 MOZ_ASSERT(!(aParams.paintFlags & PAINTBG_MASK_IMAGE));
2596
2597 nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel();
2598 ImageLayerClipState clipState;
2599
2600 clipState.mBGClipArea = *aParams.bgClipRect;
2601 clipState.mCustomClip = true;
2602 clipState.mHasRoundedCorners = false;
2603 SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel,
2604 &clipState.mDirtyRectInAppUnits,
2605 &clipState.mDirtyRectInDevPx);
2606
2607 // Compute the outermost boundary of the area that might be painted.
2608 // Same coordinate space as aParams.borderArea & aParams.bgClipRect.
2609 Sides skipSides = aParams.frame->GetSkipSides();
2610 nsRect paintBorderArea = BoxDecorationRectForBackground(
2611 aParams.frame, aParams.borderArea, skipSides, &aBorder);
2612
2613 const nsStyleImageLayers& layers = aBackgroundSC->StyleBackground()->mImage;
2614 const nsStyleImageLayers::Layer& layer = layers.mLayers[aParams.layer];
2615
2616 // Skip the following layer painting code if we found the dirty region is
2617 // empty or the current layer is not selected for drawing.
2618 if (clipState.mDirtyRectInDevPx.IsEmpty()) {
2619 return ImgDrawResult::SUCCESS;
2620 }
2621
2622 ImgDrawResult result = ImgDrawResult::SUCCESS;
2623 nsBackgroundLayerState state =
2624 PrepareImageLayer(&aParams.presCtx, aParams.frame, aParams.paintFlags,
2625 paintBorderArea, clipState.mBGClipArea, layer, nullptr);
2626 result &= state.mImageRenderer.PrepareResult();
2627
2628 if (!state.mFillArea.IsEmpty()) {
2629 return state.mImageRenderer.BuildWebRenderDisplayItemsForLayer(
2630 &aParams.presCtx, aBuilder, aResources, aSc, aManager, aItem,
2631 state.mDestArea, state.mFillArea,
2632 state.mAnchor + paintBorderArea.TopLeft(),
2633 clipState.mDirtyRectInAppUnits, state.mRepeatSize, aParams.opacity);
2634 }
2635
2636 return result;
2637 }
2638
ComputeImageLayerPositioningArea(nsPresContext * aPresContext,nsIFrame * aForFrame,const nsRect & aBorderArea,const nsStyleImageLayers::Layer & aLayer,nsIFrame ** aAttachedToFrame,bool * aOutIsTransformedFixed)2639 nsRect nsCSSRendering::ComputeImageLayerPositioningArea(
2640 nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea,
2641 const nsStyleImageLayers::Layer& aLayer, nsIFrame** aAttachedToFrame,
2642 bool* aOutIsTransformedFixed) {
2643 // Compute {background|mask} origin area relative to aBorderArea now as we
2644 // may need it to compute the effective image size for a CSS gradient.
2645 nsRect positionArea;
2646
2647 StyleGeometryBox layerOrigin = ComputeBoxValue(aForFrame, aLayer.mOrigin);
2648
2649 if (IsSVGStyleGeometryBox(layerOrigin)) {
2650 MOZ_ASSERT(aForFrame->IsFrameOfType(nsIFrame::eSVG) &&
2651 !aForFrame->IsSVGOuterSVGFrame());
2652 *aAttachedToFrame = aForFrame;
2653
2654 positionArea = nsLayoutUtils::ComputeGeometryBox(aForFrame, layerOrigin);
2655
2656 nsPoint toStrokeBoxOffset = nsPoint(0, 0);
2657 if (layerOrigin != StyleGeometryBox::StrokeBox) {
2658 nsRect strokeBox = nsLayoutUtils::ComputeGeometryBox(
2659 aForFrame, StyleGeometryBox::StrokeBox);
2660 toStrokeBoxOffset = positionArea.TopLeft() - strokeBox.TopLeft();
2661 }
2662
2663 // For SVG frames, the return value is relative to the stroke box
2664 return nsRect(toStrokeBoxOffset, positionArea.Size());
2665 }
2666
2667 MOZ_ASSERT(!aForFrame->IsFrameOfType(nsIFrame::eSVG) ||
2668 aForFrame->IsSVGOuterSVGFrame());
2669
2670 LayoutFrameType frameType = aForFrame->Type();
2671 nsIFrame* geometryFrame = aForFrame;
2672 if (MOZ_UNLIKELY(frameType == LayoutFrameType::Scroll &&
2673 StyleImageLayerAttachment::Local == aLayer.mAttachment)) {
2674 nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
2675 positionArea = nsRect(scrollableFrame->GetScrolledFrame()->GetPosition()
2676 // For the dir=rtl case:
2677 + scrollableFrame->GetScrollRange().TopLeft(),
2678 scrollableFrame->GetScrolledRect().Size());
2679 // The ScrolledRect’s size does not include the borders or scrollbars,
2680 // reverse the handling of background-origin
2681 // compared to the common case below.
2682 if (layerOrigin == StyleGeometryBox::BorderBox) {
2683 nsMargin border = geometryFrame->GetUsedBorder();
2684 border.ApplySkipSides(geometryFrame->GetSkipSides());
2685 positionArea.Inflate(border);
2686 positionArea.Inflate(scrollableFrame->GetActualScrollbarSizes());
2687 } else if (layerOrigin != StyleGeometryBox::PaddingBox) {
2688 nsMargin padding = geometryFrame->GetUsedPadding();
2689 padding.ApplySkipSides(geometryFrame->GetSkipSides());
2690 positionArea.Deflate(padding);
2691 NS_ASSERTION(layerOrigin == StyleGeometryBox::ContentBox,
2692 "unknown background-origin value");
2693 }
2694 *aAttachedToFrame = aForFrame;
2695 return positionArea;
2696 }
2697
2698 if (MOZ_UNLIKELY(frameType == LayoutFrameType::Canvas)) {
2699 geometryFrame = aForFrame->PrincipalChildList().FirstChild();
2700 // geometryFrame might be null if this canvas is a page created
2701 // as an overflow container (e.g. the in-flow content has already
2702 // finished and this page only displays the continuations of
2703 // absolutely positioned content).
2704 if (geometryFrame) {
2705 positionArea =
2706 nsPlaceholderFrame::GetRealFrameFor(geometryFrame)->GetRect();
2707 }
2708 } else {
2709 positionArea = nsRect(nsPoint(0, 0), aBorderArea.Size());
2710 }
2711
2712 // See the comment of StyleGeometryBox::MarginBox.
2713 // Hitting this assertion means we decide to turn on margin-box support for
2714 // positioned mask from CSS parser and style system. In this case, you
2715 // should *inflate* positionArea by the margin returning from
2716 // geometryFrame->GetUsedMargin() in the code chunk bellow.
2717 MOZ_ASSERT(aLayer.mOrigin != StyleGeometryBox::MarginBox,
2718 "StyleGeometryBox::MarginBox rendering is not supported yet.\n");
2719
2720 // {background|mask} images are tiled over the '{background|mask}-clip' area
2721 // but the origin of the tiling is based on the '{background|mask}-origin'
2722 // area.
2723 if (layerOrigin != StyleGeometryBox::BorderBox && geometryFrame) {
2724 nsMargin border = geometryFrame->GetUsedBorder();
2725 if (layerOrigin != StyleGeometryBox::PaddingBox) {
2726 border += geometryFrame->GetUsedPadding();
2727 NS_ASSERTION(layerOrigin == StyleGeometryBox::ContentBox,
2728 "unknown background-origin value");
2729 }
2730 positionArea.Deflate(border);
2731 }
2732
2733 nsIFrame* attachedToFrame = aForFrame;
2734 if (StyleImageLayerAttachment::Fixed == aLayer.mAttachment) {
2735 // If it's a fixed background attachment, then the image is placed
2736 // relative to the viewport, which is the area of the root frame
2737 // in a screen context or the page content frame in a print context.
2738 attachedToFrame = aPresContext->PresShell()->GetRootFrame();
2739 NS_ASSERTION(attachedToFrame, "no root frame");
2740 nsIFrame* pageContentFrame = nullptr;
2741 if (aPresContext->IsPaginated()) {
2742 pageContentFrame = nsLayoutUtils::GetClosestFrameOfType(
2743 aForFrame, LayoutFrameType::PageContent);
2744 if (pageContentFrame) {
2745 attachedToFrame = pageContentFrame;
2746 }
2747 // else this is an embedded shell and its root frame is what we want
2748 }
2749
2750 // If the background is affected by a transform, treat is as if it
2751 // wasn't fixed.
2752 if (nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame)) {
2753 attachedToFrame = aForFrame;
2754 *aOutIsTransformedFixed = true;
2755 } else {
2756 // Set the background positioning area to the viewport's area
2757 // (relative to aForFrame)
2758 positionArea = nsRect(-aForFrame->GetOffsetTo(attachedToFrame),
2759 attachedToFrame->GetSize());
2760
2761 if (!pageContentFrame) {
2762 // Subtract the size of scrollbars.
2763 nsIScrollableFrame* scrollableFrame =
2764 aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
2765 if (scrollableFrame) {
2766 nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes();
2767 positionArea.Deflate(scrollbars);
2768 }
2769 }
2770 }
2771 }
2772 *aAttachedToFrame = attachedToFrame;
2773
2774 return positionArea;
2775 }
2776
2777 /* static */
ComputeRoundedSize(nscoord aCurrentSize,nscoord aPositioningSize)2778 nscoord nsCSSRendering::ComputeRoundedSize(nscoord aCurrentSize,
2779 nscoord aPositioningSize) {
2780 float repeatCount = NS_roundf(float(aPositioningSize) / float(aCurrentSize));
2781 if (repeatCount < 1.0f) {
2782 return aPositioningSize;
2783 }
2784 return nscoord(NS_lround(float(aPositioningSize) / repeatCount));
2785 }
2786
2787 // Apply the CSS image sizing algorithm as it applies to background images.
2788 // See http://www.w3.org/TR/css3-background/#the-background-size .
2789 // aIntrinsicSize is the size that the background image 'would like to be'.
2790 // It can be found by calling nsImageRenderer::ComputeIntrinsicSize.
ComputeDrawnSizeForBackground(const CSSSizeOrRatio & aIntrinsicSize,const nsSize & aBgPositioningArea,const StyleBackgroundSize & aLayerSize,StyleImageLayerRepeat aXRepeat,StyleImageLayerRepeat aYRepeat)2791 static nsSize ComputeDrawnSizeForBackground(
2792 const CSSSizeOrRatio& aIntrinsicSize, const nsSize& aBgPositioningArea,
2793 const StyleBackgroundSize& aLayerSize, StyleImageLayerRepeat aXRepeat,
2794 StyleImageLayerRepeat aYRepeat) {
2795 nsSize imageSize;
2796
2797 // Size is dictated by cover or contain rules.
2798 if (aLayerSize.IsContain() || aLayerSize.IsCover()) {
2799 nsImageRenderer::FitType fitType = aLayerSize.IsCover()
2800 ? nsImageRenderer::COVER
2801 : nsImageRenderer::CONTAIN;
2802 imageSize = nsImageRenderer::ComputeConstrainedSize(
2803 aBgPositioningArea, aIntrinsicSize.mRatio, fitType);
2804 } else {
2805 MOZ_ASSERT(aLayerSize.IsExplicitSize());
2806 const auto& width = aLayerSize.explicit_size.width;
2807 const auto& height = aLayerSize.explicit_size.height;
2808 // No cover/contain constraint, use default algorithm.
2809 CSSSizeOrRatio specifiedSize;
2810 if (width.IsLengthPercentage()) {
2811 specifiedSize.SetWidth(
2812 width.AsLengthPercentage().Resolve(aBgPositioningArea.width));
2813 }
2814 if (height.IsLengthPercentage()) {
2815 specifiedSize.SetHeight(
2816 height.AsLengthPercentage().Resolve(aBgPositioningArea.height));
2817 }
2818
2819 imageSize = nsImageRenderer::ComputeConcreteSize(
2820 specifiedSize, aIntrinsicSize, aBgPositioningArea);
2821 }
2822
2823 // See https://www.w3.org/TR/css3-background/#background-size .
2824 // "If 'background-repeat' is 'round' for one (or both) dimensions, there is a
2825 // second
2826 // step. The UA must scale the image in that dimension (or both dimensions)
2827 // so that it fits a whole number of times in the background positioning
2828 // area."
2829 // "If 'background-repeat' is 'round' for one dimension only and if
2830 // 'background-size'
2831 // is 'auto' for the other dimension, then there is a third step: that other
2832 // dimension is scaled so that the original aspect ratio is restored."
2833 bool isRepeatRoundInBothDimensions =
2834 aXRepeat == StyleImageLayerRepeat::Round &&
2835 aYRepeat == StyleImageLayerRepeat::Round;
2836
2837 // Calculate the rounded size only if the background-size computation
2838 // returned a correct size for the image.
2839 if (imageSize.width && aXRepeat == StyleImageLayerRepeat::Round) {
2840 imageSize.width = nsCSSRendering::ComputeRoundedSize(
2841 imageSize.width, aBgPositioningArea.width);
2842 if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() &&
2843 aLayerSize.explicit_size.height.IsAuto()) {
2844 // Restore intrinsic ratio
2845 if (aIntrinsicSize.mRatio) {
2846 imageSize.height =
2847 aIntrinsicSize.mRatio.Inverted().ApplyTo(imageSize.width);
2848 }
2849 }
2850 }
2851
2852 // Calculate the rounded size only if the background-size computation
2853 // returned a correct size for the image.
2854 if (imageSize.height && aYRepeat == StyleImageLayerRepeat::Round) {
2855 imageSize.height = nsCSSRendering::ComputeRoundedSize(
2856 imageSize.height, aBgPositioningArea.height);
2857 if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() &&
2858 aLayerSize.explicit_size.width.IsAuto()) {
2859 // Restore intrinsic ratio
2860 if (aIntrinsicSize.mRatio) {
2861 imageSize.width = aIntrinsicSize.mRatio.ApplyTo(imageSize.height);
2862 }
2863 }
2864 }
2865
2866 return imageSize;
2867 }
2868
2869 /* ComputeSpacedRepeatSize
2870 * aImageDimension: the image width/height
2871 * aAvailableSpace: the background positioning area width/height
2872 * aRepeat: determine whether the image is repeated
2873 * Returns the image size plus gap size of app units for use as spacing
2874 */
ComputeSpacedRepeatSize(nscoord aImageDimension,nscoord aAvailableSpace,bool & aRepeat)2875 static nscoord ComputeSpacedRepeatSize(nscoord aImageDimension,
2876 nscoord aAvailableSpace, bool& aRepeat) {
2877 float ratio = static_cast<float>(aAvailableSpace) / aImageDimension;
2878
2879 if (ratio < 2.0f) { // If you can't repeat at least twice, then don't repeat.
2880 aRepeat = false;
2881 return aImageDimension;
2882 }
2883
2884 aRepeat = true;
2885 return (aAvailableSpace - aImageDimension) / (NSToIntFloor(ratio) - 1);
2886 }
2887
2888 /* static */
ComputeBorderSpacedRepeatSize(nscoord aImageDimension,nscoord aAvailableSpace,nscoord & aSpace)2889 nscoord nsCSSRendering::ComputeBorderSpacedRepeatSize(nscoord aImageDimension,
2890 nscoord aAvailableSpace,
2891 nscoord& aSpace) {
2892 int32_t count = aImageDimension ? (aAvailableSpace / aImageDimension) : 0;
2893 aSpace = (aAvailableSpace - aImageDimension * count) / (count + 1);
2894 return aSpace + aImageDimension;
2895 }
2896
PrepareImageLayer(nsPresContext * aPresContext,nsIFrame * aForFrame,uint32_t aFlags,const nsRect & aBorderArea,const nsRect & aBGClipRect,const nsStyleImageLayers::Layer & aLayer,bool * aOutIsTransformedFixed)2897 nsBackgroundLayerState nsCSSRendering::PrepareImageLayer(
2898 nsPresContext* aPresContext, nsIFrame* aForFrame, uint32_t aFlags,
2899 const nsRect& aBorderArea, const nsRect& aBGClipRect,
2900 const nsStyleImageLayers::Layer& aLayer, bool* aOutIsTransformedFixed) {
2901 /*
2902 * The properties we need to keep in mind when drawing style image
2903 * layers are:
2904 *
2905 * background-image/ mask-image
2906 * background-repeat/ mask-repeat
2907 * background-attachment
2908 * background-position/ mask-position
2909 * background-clip/ mask-clip
2910 * background-origin/ mask-origin
2911 * background-size/ mask-size
2912 * background-blend-mode
2913 * box-decoration-break
2914 * mask-mode
2915 * mask-composite
2916 *
2917 * (background-color applies to the entire element and not to individual
2918 * layers, so it is irrelevant to this method.)
2919 *
2920 * These properties have the following dependencies upon each other when
2921 * determining rendering:
2922 *
2923 * background-image/ mask-image
2924 * no dependencies
2925 * background-repeat/ mask-repeat
2926 * no dependencies
2927 * background-attachment
2928 * no dependencies
2929 * background-position/ mask-position
2930 * depends upon background-size/mask-size (for the image's scaled size)
2931 * and background-break (for the background positioning area)
2932 * background-clip/ mask-clip
2933 * no dependencies
2934 * background-origin/ mask-origin
2935 * depends upon background-attachment (only in the case where that value
2936 * is 'fixed')
2937 * background-size/ mask-size
2938 * depends upon box-decoration-break (for the background positioning area
2939 * for resolving percentages), background-image (for the image's intrinsic
2940 * size), background-repeat (if that value is 'round'), and
2941 * background-origin (for the background painting area, when
2942 * background-repeat is 'round')
2943 * background-blend-mode
2944 * no dependencies
2945 * mask-mode
2946 * no dependencies
2947 * mask-composite
2948 * no dependencies
2949 * box-decoration-break
2950 * no dependencies
2951 *
2952 * As a result of only-if dependencies we don't strictly do a topological
2953 * sort of the above properties when processing, but it's pretty close to one:
2954 *
2955 * background-clip/mask-clip (by caller)
2956 * background-image/ mask-image
2957 * box-decoration-break, background-origin/ mask origin
2958 * background-attachment (postfix for background-origin if 'fixed')
2959 * background-size/ mask-size
2960 * background-position/ mask-position
2961 * background-repeat/ mask-repeat
2962 */
2963
2964 uint32_t irFlags = 0;
2965 if (aFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) {
2966 irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
2967 }
2968 if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) {
2969 irFlags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
2970 }
2971 if (aFlags & nsCSSRendering::PAINTBG_HIGH_QUALITY_SCALING) {
2972 irFlags |= nsImageRenderer::FLAG_HIGH_QUALITY_SCALING;
2973 }
2974
2975 nsBackgroundLayerState state(aForFrame, &aLayer.mImage, irFlags);
2976 if (!state.mImageRenderer.PrepareImage()) {
2977 // There's no image or it's not ready to be painted.
2978 if (aOutIsTransformedFixed &&
2979 StyleImageLayerAttachment::Fixed == aLayer.mAttachment) {
2980 nsIFrame* attachedToFrame = aPresContext->PresShell()->GetRootFrame();
2981 NS_ASSERTION(attachedToFrame, "no root frame");
2982 nsIFrame* pageContentFrame = nullptr;
2983 if (aPresContext->IsPaginated()) {
2984 pageContentFrame = nsLayoutUtils::GetClosestFrameOfType(
2985 aForFrame, LayoutFrameType::PageContent);
2986 if (pageContentFrame) {
2987 attachedToFrame = pageContentFrame;
2988 }
2989 // else this is an embedded shell and its root frame is what we want
2990 }
2991
2992 *aOutIsTransformedFixed =
2993 nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame);
2994 }
2995 return state;
2996 }
2997
2998 // The frame to which the background is attached
2999 nsIFrame* attachedToFrame = aForFrame;
3000 // Is the background marked 'fixed', but affected by a transform?
3001 bool transformedFixed = false;
3002 // Compute background origin area relative to aBorderArea now as we may need
3003 // it to compute the effective image size for a CSS gradient.
3004 nsRect positionArea = ComputeImageLayerPositioningArea(
3005 aPresContext, aForFrame, aBorderArea, aLayer, &attachedToFrame,
3006 &transformedFixed);
3007 if (aOutIsTransformedFixed) {
3008 *aOutIsTransformedFixed = transformedFixed;
3009 }
3010
3011 // For background-attachment:fixed backgrounds, we'll override the area
3012 // where the background can be drawn to the viewport.
3013 nsRect bgClipRect = aBGClipRect;
3014
3015 if (StyleImageLayerAttachment::Fixed == aLayer.mAttachment &&
3016 !transformedFixed && (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW)) {
3017 bgClipRect = positionArea + aBorderArea.TopLeft();
3018 }
3019
3020 StyleImageLayerRepeat repeatX = aLayer.mRepeat.mXRepeat;
3021 StyleImageLayerRepeat repeatY = aLayer.mRepeat.mYRepeat;
3022
3023 // Scale the image as specified for background-size and background-repeat.
3024 // Also as required for proper background positioning when background-position
3025 // is defined with percentages.
3026 CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize();
3027 nsSize bgPositionSize = positionArea.Size();
3028 nsSize imageSize = ComputeDrawnSizeForBackground(
3029 intrinsicSize, bgPositionSize, aLayer.mSize, repeatX, repeatY);
3030
3031 if (imageSize.width <= 0 || imageSize.height <= 0) return state;
3032
3033 state.mImageRenderer.SetPreferredSize(intrinsicSize, imageSize);
3034
3035 // Compute the anchor point.
3036 //
3037 // relative to aBorderArea.TopLeft() (which is where the top-left
3038 // of aForFrame's border-box will be rendered)
3039 nsPoint imageTopLeft;
3040
3041 // Compute the position of the background now that the background's size is
3042 // determined.
3043 nsImageRenderer::ComputeObjectAnchorPoint(aLayer.mPosition, bgPositionSize,
3044 imageSize, &imageTopLeft,
3045 &state.mAnchor);
3046 state.mRepeatSize = imageSize;
3047 if (repeatX == StyleImageLayerRepeat::Space) {
3048 bool isRepeat;
3049 state.mRepeatSize.width = ComputeSpacedRepeatSize(
3050 imageSize.width, bgPositionSize.width, isRepeat);
3051 if (isRepeat) {
3052 imageTopLeft.x = 0;
3053 state.mAnchor.x = 0;
3054 } else {
3055 repeatX = StyleImageLayerRepeat::NoRepeat;
3056 }
3057 }
3058
3059 if (repeatY == StyleImageLayerRepeat::Space) {
3060 bool isRepeat;
3061 state.mRepeatSize.height = ComputeSpacedRepeatSize(
3062 imageSize.height, bgPositionSize.height, isRepeat);
3063 if (isRepeat) {
3064 imageTopLeft.y = 0;
3065 state.mAnchor.y = 0;
3066 } else {
3067 repeatY = StyleImageLayerRepeat::NoRepeat;
3068 }
3069 }
3070
3071 imageTopLeft += positionArea.TopLeft();
3072 state.mAnchor += positionArea.TopLeft();
3073 state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize);
3074 state.mFillArea = state.mDestArea;
3075
3076 ExtendMode repeatMode = ExtendMode::CLAMP;
3077 if (repeatX == StyleImageLayerRepeat::Repeat ||
3078 repeatX == StyleImageLayerRepeat::Round ||
3079 repeatX == StyleImageLayerRepeat::Space) {
3080 state.mFillArea.x = bgClipRect.x;
3081 state.mFillArea.width = bgClipRect.width;
3082 repeatMode = ExtendMode::REPEAT_X;
3083 }
3084 if (repeatY == StyleImageLayerRepeat::Repeat ||
3085 repeatY == StyleImageLayerRepeat::Round ||
3086 repeatY == StyleImageLayerRepeat::Space) {
3087 state.mFillArea.y = bgClipRect.y;
3088 state.mFillArea.height = bgClipRect.height;
3089
3090 /***
3091 * We're repeating on the X axis already,
3092 * so if we have to repeat in the Y axis,
3093 * we really need to repeat in both directions.
3094 */
3095 if (repeatMode == ExtendMode::REPEAT_X) {
3096 repeatMode = ExtendMode::REPEAT;
3097 } else {
3098 repeatMode = ExtendMode::REPEAT_Y;
3099 }
3100 }
3101 state.mImageRenderer.SetExtendMode(repeatMode);
3102 state.mImageRenderer.SetMaskOp(aLayer.mMaskMode);
3103
3104 state.mFillArea.IntersectRect(state.mFillArea, bgClipRect);
3105
3106 return state;
3107 }
3108
GetBackgroundLayerRect(nsPresContext * aPresContext,nsIFrame * aForFrame,const nsRect & aBorderArea,const nsRect & aClipRect,const nsStyleImageLayers::Layer & aLayer,uint32_t aFlags)3109 nsRect nsCSSRendering::GetBackgroundLayerRect(
3110 nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea,
3111 const nsRect& aClipRect, const nsStyleImageLayers::Layer& aLayer,
3112 uint32_t aFlags) {
3113 Sides skipSides = aForFrame->GetSkipSides();
3114 nsRect borderArea =
3115 BoxDecorationRectForBackground(aForFrame, aBorderArea, skipSides);
3116 nsBackgroundLayerState state = PrepareImageLayer(
3117 aPresContext, aForFrame, aFlags, borderArea, aClipRect, aLayer);
3118 return state.mFillArea;
3119 }
3120
3121 // Begin table border-collapsing section
3122 // These functions were written to not disrupt the normal ones and yet satisfy
3123 // some additional requirements At some point, all functions should be unified
3124 // to include the additional functionality that these provide
3125
RoundIntToPixel(nscoord aValue,nscoord aOneDevPixel,bool aRoundDown=false)3126 static nscoord RoundIntToPixel(nscoord aValue, nscoord aOneDevPixel,
3127 bool aRoundDown = false) {
3128 if (aOneDevPixel <= 0) {
3129 // We must be rendering to a device that has a resolution greater than
3130 // one device pixel!
3131 // In that case, aValue is as accurate as it's going to get.
3132 return aValue;
3133 }
3134
3135 nscoord halfPixel = NSToCoordRound(aOneDevPixel / 2.0f);
3136 nscoord extra = aValue % aOneDevPixel;
3137 nscoord finalValue = (!aRoundDown && (extra >= halfPixel))
3138 ? aValue + (aOneDevPixel - extra)
3139 : aValue - extra;
3140 return finalValue;
3141 }
3142
RoundFloatToPixel(float aValue,nscoord aOneDevPixel,bool aRoundDown=false)3143 static nscoord RoundFloatToPixel(float aValue, nscoord aOneDevPixel,
3144 bool aRoundDown = false) {
3145 return RoundIntToPixel(NSToCoordRound(aValue), aOneDevPixel, aRoundDown);
3146 }
3147
SetPoly(const Rect & aRect,Point * poly)3148 static void SetPoly(const Rect& aRect, Point* poly) {
3149 poly[0].x = aRect.x;
3150 poly[0].y = aRect.y;
3151 poly[1].x = aRect.x + aRect.width;
3152 poly[1].y = aRect.y;
3153 poly[2].x = aRect.x + aRect.width;
3154 poly[2].y = aRect.y + aRect.height;
3155 poly[3].x = aRect.x;
3156 poly[3].y = aRect.y + aRect.height;
3157 }
3158
DrawDashedSegment(DrawTarget & aDrawTarget,nsRect aRect,nscoord aDashLength,nscolor aColor,int32_t aAppUnitsPerDevPixel,bool aHorizontal)3159 static void DrawDashedSegment(DrawTarget& aDrawTarget, nsRect aRect,
3160 nscoord aDashLength, nscolor aColor,
3161 int32_t aAppUnitsPerDevPixel, bool aHorizontal) {
3162 ColorPattern color(ToDeviceColor(aColor));
3163 DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
3164 StrokeOptions strokeOptions;
3165
3166 Float dash[2];
3167 dash[0] = Float(aDashLength) / aAppUnitsPerDevPixel;
3168 dash[1] = dash[0];
3169
3170 strokeOptions.mDashPattern = dash;
3171 strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
3172
3173 if (aHorizontal) {
3174 nsPoint left = (aRect.TopLeft() + aRect.BottomLeft()) / 2;
3175 nsPoint right = (aRect.TopRight() + aRect.BottomRight()) / 2;
3176 strokeOptions.mLineWidth = Float(aRect.height) / aAppUnitsPerDevPixel;
3177 StrokeLineWithSnapping(left, right, aAppUnitsPerDevPixel, aDrawTarget,
3178 color, strokeOptions, drawOptions);
3179 } else {
3180 nsPoint top = (aRect.TopLeft() + aRect.TopRight()) / 2;
3181 nsPoint bottom = (aRect.BottomLeft() + aRect.BottomRight()) / 2;
3182 strokeOptions.mLineWidth = Float(aRect.width) / aAppUnitsPerDevPixel;
3183 StrokeLineWithSnapping(top, bottom, aAppUnitsPerDevPixel, aDrawTarget,
3184 color, strokeOptions, drawOptions);
3185 }
3186 }
3187
DrawSolidBorderSegment(DrawTarget & aDrawTarget,nsRect aRect,nscolor aColor,int32_t aAppUnitsPerDevPixel,mozilla::Side aStartBevelSide=mozilla::eSideTop,nscoord aStartBevelOffset=0,mozilla::Side aEndBevelSide=mozilla::eSideTop,nscoord aEndBevelOffset=0)3188 static void DrawSolidBorderSegment(
3189 DrawTarget& aDrawTarget, nsRect aRect, nscolor aColor,
3190 int32_t aAppUnitsPerDevPixel,
3191 mozilla::Side aStartBevelSide = mozilla::eSideTop,
3192 nscoord aStartBevelOffset = 0,
3193 mozilla::Side aEndBevelSide = mozilla::eSideTop,
3194 nscoord aEndBevelOffset = 0) {
3195 ColorPattern color(ToDeviceColor(aColor));
3196 DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
3197
3198 nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel);
3199 // We don't need to bevel single pixel borders
3200 if ((aRect.width == oneDevPixel) || (aRect.height == oneDevPixel) ||
3201 ((0 == aStartBevelOffset) && (0 == aEndBevelOffset))) {
3202 // simple rectangle
3203 aDrawTarget.FillRect(
3204 NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget), color,
3205 drawOptions);
3206 } else {
3207 // polygon with beveling
3208 Point poly[4];
3209 SetPoly(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget),
3210 poly);
3211
3212 Float startBevelOffset =
3213 NSAppUnitsToFloatPixels(aStartBevelOffset, aAppUnitsPerDevPixel);
3214 switch (aStartBevelSide) {
3215 case eSideTop:
3216 poly[0].x += startBevelOffset;
3217 break;
3218 case eSideBottom:
3219 poly[3].x += startBevelOffset;
3220 break;
3221 case eSideRight:
3222 poly[1].y += startBevelOffset;
3223 break;
3224 case eSideLeft:
3225 poly[0].y += startBevelOffset;
3226 }
3227
3228 Float endBevelOffset =
3229 NSAppUnitsToFloatPixels(aEndBevelOffset, aAppUnitsPerDevPixel);
3230 switch (aEndBevelSide) {
3231 case eSideTop:
3232 poly[1].x -= endBevelOffset;
3233 break;
3234 case eSideBottom:
3235 poly[2].x -= endBevelOffset;
3236 break;
3237 case eSideRight:
3238 poly[2].y -= endBevelOffset;
3239 break;
3240 case eSideLeft:
3241 poly[3].y -= endBevelOffset;
3242 }
3243
3244 RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
3245 builder->MoveTo(poly[0]);
3246 builder->LineTo(poly[1]);
3247 builder->LineTo(poly[2]);
3248 builder->LineTo(poly[3]);
3249 builder->Close();
3250 RefPtr<Path> path = builder->Finish();
3251 aDrawTarget.Fill(path, color, drawOptions);
3252 }
3253 }
3254
GetDashInfo(nscoord aBorderLength,nscoord aDashLength,nscoord aOneDevPixel,int32_t & aNumDashSpaces,nscoord & aStartDashLength,nscoord & aEndDashLength)3255 static void GetDashInfo(nscoord aBorderLength, nscoord aDashLength,
3256 nscoord aOneDevPixel, int32_t& aNumDashSpaces,
3257 nscoord& aStartDashLength, nscoord& aEndDashLength) {
3258 aNumDashSpaces = 0;
3259 if (aStartDashLength + aDashLength + aEndDashLength >= aBorderLength) {
3260 aStartDashLength = aBorderLength;
3261 aEndDashLength = 0;
3262 } else {
3263 aNumDashSpaces =
3264 (aBorderLength - aDashLength) / (2 * aDashLength); // round down
3265 nscoord extra = aBorderLength - aStartDashLength - aEndDashLength -
3266 (((2 * aNumDashSpaces) - 1) * aDashLength);
3267 if (extra > 0) {
3268 nscoord half = RoundIntToPixel(extra / 2, aOneDevPixel);
3269 aStartDashLength += half;
3270 aEndDashLength += (extra - half);
3271 }
3272 }
3273 }
3274
DrawTableBorderSegment(DrawTarget & aDrawTarget,StyleBorderStyle aBorderStyle,nscolor aBorderColor,const nsRect & aBorder,int32_t aAppUnitsPerDevPixel,mozilla::Side aStartBevelSide,nscoord aStartBevelOffset,mozilla::Side aEndBevelSide,nscoord aEndBevelOffset)3275 void nsCSSRendering::DrawTableBorderSegment(
3276 DrawTarget& aDrawTarget, StyleBorderStyle aBorderStyle,
3277 nscolor aBorderColor, const nsRect& aBorder, int32_t aAppUnitsPerDevPixel,
3278 mozilla::Side aStartBevelSide, nscoord aStartBevelOffset,
3279 mozilla::Side aEndBevelSide, nscoord aEndBevelOffset) {
3280 bool horizontal =
3281 ((eSideTop == aStartBevelSide) || (eSideBottom == aStartBevelSide));
3282 nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel);
3283
3284 if ((oneDevPixel >= aBorder.width) || (oneDevPixel >= aBorder.height) ||
3285 (StyleBorderStyle::Dashed == aBorderStyle) ||
3286 (StyleBorderStyle::Dotted == aBorderStyle)) {
3287 // no beveling for 1 pixel border, dash or dot
3288 aStartBevelOffset = 0;
3289 aEndBevelOffset = 0;
3290 }
3291
3292 switch (aBorderStyle) {
3293 case StyleBorderStyle::None:
3294 case StyleBorderStyle::Hidden:
3295 // NS_ASSERTION(false, "style of none or hidden");
3296 break;
3297 case StyleBorderStyle::Dotted:
3298 case StyleBorderStyle::Dashed: {
3299 nscoord dashLength =
3300 (StyleBorderStyle::Dashed == aBorderStyle) ? DASH_LENGTH : DOT_LENGTH;
3301 // make the dash length proportional to the border thickness
3302 dashLength *= (horizontal) ? aBorder.height : aBorder.width;
3303 // make the min dash length for the ends 1/2 the dash length
3304 nscoord minDashLength =
3305 (StyleBorderStyle::Dashed == aBorderStyle)
3306 ? RoundFloatToPixel(((float)dashLength) / 2.0f,
3307 aAppUnitsPerDevPixel)
3308 : dashLength;
3309 minDashLength = std::max(minDashLength, oneDevPixel);
3310 nscoord numDashSpaces = 0;
3311 nscoord startDashLength = minDashLength;
3312 nscoord endDashLength = minDashLength;
3313 if (horizontal) {
3314 GetDashInfo(aBorder.width, dashLength, aAppUnitsPerDevPixel,
3315 numDashSpaces, startDashLength, endDashLength);
3316 nsRect rect(aBorder.x, aBorder.y, startDashLength, aBorder.height);
3317 DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
3318 aAppUnitsPerDevPixel);
3319
3320 rect.x += startDashLength + dashLength;
3321 rect.width =
3322 aBorder.width - (startDashLength + endDashLength + dashLength);
3323 DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor,
3324 aAppUnitsPerDevPixel, horizontal);
3325
3326 rect.x += rect.width;
3327 rect.width = endDashLength;
3328 DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
3329 aAppUnitsPerDevPixel);
3330 } else {
3331 GetDashInfo(aBorder.height, dashLength, aAppUnitsPerDevPixel,
3332 numDashSpaces, startDashLength, endDashLength);
3333 nsRect rect(aBorder.x, aBorder.y, aBorder.width, startDashLength);
3334 DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
3335 aAppUnitsPerDevPixel);
3336
3337 rect.y += rect.height + dashLength;
3338 rect.height =
3339 aBorder.height - (startDashLength + endDashLength + dashLength);
3340 DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor,
3341 aAppUnitsPerDevPixel, horizontal);
3342
3343 rect.y += rect.height;
3344 rect.height = endDashLength;
3345 DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
3346 aAppUnitsPerDevPixel);
3347 }
3348 } break;
3349 default:
3350 AutoTArray<SolidBeveledBorderSegment, 3> segments;
3351 GetTableBorderSolidSegments(
3352 segments, aBorderStyle, aBorderColor, aBorder, aAppUnitsPerDevPixel,
3353 aStartBevelSide, aStartBevelOffset, aEndBevelSide, aEndBevelOffset);
3354 for (const auto& segment : segments) {
3355 DrawSolidBorderSegment(
3356 aDrawTarget, segment.mRect, segment.mColor, aAppUnitsPerDevPixel,
3357 segment.mStartBevel.mSide, segment.mStartBevel.mOffset,
3358 segment.mEndBevel.mSide, segment.mEndBevel.mOffset);
3359 }
3360 break;
3361 }
3362 }
3363
GetTableBorderSolidSegments(nsTArray<SolidBeveledBorderSegment> & aSegments,StyleBorderStyle aBorderStyle,nscolor aBorderColor,const nsRect & aBorder,int32_t aAppUnitsPerDevPixel,mozilla::Side aStartBevelSide,nscoord aStartBevelOffset,mozilla::Side aEndBevelSide,nscoord aEndBevelOffset)3364 void nsCSSRendering::GetTableBorderSolidSegments(
3365 nsTArray<SolidBeveledBorderSegment>& aSegments,
3366 StyleBorderStyle aBorderStyle, nscolor aBorderColor, const nsRect& aBorder,
3367 int32_t aAppUnitsPerDevPixel, mozilla::Side aStartBevelSide,
3368 nscoord aStartBevelOffset, mozilla::Side aEndBevelSide,
3369 nscoord aEndBevelOffset) {
3370 const bool horizontal =
3371 eSideTop == aStartBevelSide || eSideBottom == aStartBevelSide;
3372 const nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel);
3373
3374 switch (aBorderStyle) {
3375 case StyleBorderStyle::None:
3376 case StyleBorderStyle::Hidden:
3377 return;
3378 case StyleBorderStyle::Dotted:
3379 case StyleBorderStyle::Dashed:
3380 MOZ_ASSERT_UNREACHABLE("Caller should have checked");
3381 return;
3382 case StyleBorderStyle::Groove:
3383 case StyleBorderStyle::Ridge:
3384 if ((horizontal && (oneDevPixel >= aBorder.height)) ||
3385 (!horizontal && (oneDevPixel >= aBorder.width))) {
3386 aSegments.AppendElement(
3387 SolidBeveledBorderSegment{aBorder,
3388 aBorderColor,
3389 {aStartBevelSide, aStartBevelOffset},
3390 {aEndBevelSide, aEndBevelOffset}});
3391 } else {
3392 nscoord startBevel =
3393 (aStartBevelOffset > 0)
3394 ? RoundFloatToPixel(0.5f * (float)aStartBevelOffset,
3395 aAppUnitsPerDevPixel, true)
3396 : 0;
3397 nscoord endBevel =
3398 (aEndBevelOffset > 0)
3399 ? RoundFloatToPixel(0.5f * (float)aEndBevelOffset,
3400 aAppUnitsPerDevPixel, true)
3401 : 0;
3402 mozilla::Side ridgeGrooveSide = (horizontal) ? eSideTop : eSideLeft;
3403 // FIXME: In theory, this should use the visited-dependent
3404 // background color, but I don't care.
3405 nscolor bevelColor =
3406 MakeBevelColor(ridgeGrooveSide, aBorderStyle, aBorderColor);
3407 nsRect rect(aBorder);
3408 nscoord half;
3409 if (horizontal) { // top, bottom
3410 half = RoundFloatToPixel(0.5f * (float)aBorder.height,
3411 aAppUnitsPerDevPixel);
3412 rect.height = half;
3413 if (eSideTop == aStartBevelSide) {
3414 rect.x += startBevel;
3415 rect.width -= startBevel;
3416 }
3417 if (eSideTop == aEndBevelSide) {
3418 rect.width -= endBevel;
3419 }
3420 aSegments.AppendElement(
3421 SolidBeveledBorderSegment{rect,
3422 bevelColor,
3423 {aStartBevelSide, startBevel},
3424 {aEndBevelSide, endBevel}});
3425 } else { // left, right
3426 half = RoundFloatToPixel(0.5f * (float)aBorder.width,
3427 aAppUnitsPerDevPixel);
3428 rect.width = half;
3429 if (eSideLeft == aStartBevelSide) {
3430 rect.y += startBevel;
3431 rect.height -= startBevel;
3432 }
3433 if (eSideLeft == aEndBevelSide) {
3434 rect.height -= endBevel;
3435 }
3436 aSegments.AppendElement(
3437 SolidBeveledBorderSegment{rect,
3438 bevelColor,
3439 {aStartBevelSide, startBevel},
3440 {aEndBevelSide, endBevel}});
3441 }
3442
3443 rect = aBorder;
3444 ridgeGrooveSide =
3445 (eSideTop == ridgeGrooveSide) ? eSideBottom : eSideRight;
3446 // FIXME: In theory, this should use the visited-dependent
3447 // background color, but I don't care.
3448 bevelColor =
3449 MakeBevelColor(ridgeGrooveSide, aBorderStyle, aBorderColor);
3450 if (horizontal) {
3451 rect.y = rect.y + half;
3452 rect.height = aBorder.height - half;
3453 if (eSideBottom == aStartBevelSide) {
3454 rect.x += startBevel;
3455 rect.width -= startBevel;
3456 }
3457 if (eSideBottom == aEndBevelSide) {
3458 rect.width -= endBevel;
3459 }
3460 aSegments.AppendElement(
3461 SolidBeveledBorderSegment{rect,
3462 bevelColor,
3463 {aStartBevelSide, startBevel},
3464 {aEndBevelSide, endBevel}});
3465 } else {
3466 rect.x = rect.x + half;
3467 rect.width = aBorder.width - half;
3468 if (eSideRight == aStartBevelSide) {
3469 rect.y += aStartBevelOffset - startBevel;
3470 rect.height -= startBevel;
3471 }
3472 if (eSideRight == aEndBevelSide) {
3473 rect.height -= endBevel;
3474 }
3475 aSegments.AppendElement(
3476 SolidBeveledBorderSegment{rect,
3477 bevelColor,
3478 {aStartBevelSide, startBevel},
3479 {aEndBevelSide, endBevel}});
3480 }
3481 }
3482 break;
3483 case StyleBorderStyle::Double:
3484 // We can only do "double" borders if the thickness of the border
3485 // is more than 2px. Otherwise, we fall through to painting a
3486 // solid border.
3487 if ((aBorder.width > 2 * oneDevPixel || horizontal) &&
3488 (aBorder.height > 2 * oneDevPixel || !horizontal)) {
3489 nscoord startBevel =
3490 (aStartBevelOffset > 0)
3491 ? RoundFloatToPixel(0.333333f * (float)aStartBevelOffset,
3492 aAppUnitsPerDevPixel)
3493 : 0;
3494 nscoord endBevel =
3495 (aEndBevelOffset > 0)
3496 ? RoundFloatToPixel(0.333333f * (float)aEndBevelOffset,
3497 aAppUnitsPerDevPixel)
3498 : 0;
3499 if (horizontal) { // top, bottom
3500 nscoord thirdHeight = RoundFloatToPixel(
3501 0.333333f * (float)aBorder.height, aAppUnitsPerDevPixel);
3502
3503 // draw the top line or rect
3504 nsRect topRect(aBorder.x, aBorder.y, aBorder.width, thirdHeight);
3505 if (eSideTop == aStartBevelSide) {
3506 topRect.x += aStartBevelOffset - startBevel;
3507 topRect.width -= aStartBevelOffset - startBevel;
3508 }
3509 if (eSideTop == aEndBevelSide) {
3510 topRect.width -= aEndBevelOffset - endBevel;
3511 }
3512
3513 aSegments.AppendElement(
3514 SolidBeveledBorderSegment{topRect,
3515 aBorderColor,
3516 {aStartBevelSide, startBevel},
3517 {aEndBevelSide, endBevel}});
3518
3519 // draw the botom line or rect
3520 nscoord heightOffset = aBorder.height - thirdHeight;
3521 nsRect bottomRect(aBorder.x, aBorder.y + heightOffset, aBorder.width,
3522 aBorder.height - heightOffset);
3523 if (eSideBottom == aStartBevelSide) {
3524 bottomRect.x += aStartBevelOffset - startBevel;
3525 bottomRect.width -= aStartBevelOffset - startBevel;
3526 }
3527 if (eSideBottom == aEndBevelSide) {
3528 bottomRect.width -= aEndBevelOffset - endBevel;
3529 }
3530 aSegments.AppendElement(
3531 SolidBeveledBorderSegment{bottomRect,
3532 aBorderColor,
3533 {aStartBevelSide, startBevel},
3534 {aEndBevelSide, endBevel}});
3535 } else { // left, right
3536 nscoord thirdWidth = RoundFloatToPixel(
3537 0.333333f * (float)aBorder.width, aAppUnitsPerDevPixel);
3538
3539 nsRect leftRect(aBorder.x, aBorder.y, thirdWidth, aBorder.height);
3540 if (eSideLeft == aStartBevelSide) {
3541 leftRect.y += aStartBevelOffset - startBevel;
3542 leftRect.height -= aStartBevelOffset - startBevel;
3543 }
3544 if (eSideLeft == aEndBevelSide) {
3545 leftRect.height -= aEndBevelOffset - endBevel;
3546 }
3547
3548 aSegments.AppendElement(
3549 SolidBeveledBorderSegment{leftRect,
3550 aBorderColor,
3551 {aStartBevelSide, startBevel},
3552 {aEndBevelSide, endBevel}});
3553
3554 nscoord widthOffset = aBorder.width - thirdWidth;
3555 nsRect rightRect(aBorder.x + widthOffset, aBorder.y,
3556 aBorder.width - widthOffset, aBorder.height);
3557 if (eSideRight == aStartBevelSide) {
3558 rightRect.y += aStartBevelOffset - startBevel;
3559 rightRect.height -= aStartBevelOffset - startBevel;
3560 }
3561 if (eSideRight == aEndBevelSide) {
3562 rightRect.height -= aEndBevelOffset - endBevel;
3563 }
3564 aSegments.AppendElement(
3565 SolidBeveledBorderSegment{rightRect,
3566 aBorderColor,
3567 {aStartBevelSide, startBevel},
3568 {aEndBevelSide, endBevel}});
3569 }
3570 break;
3571 }
3572 // else fall through to solid
3573 [[fallthrough]];
3574 case StyleBorderStyle::Solid:
3575 aSegments.AppendElement(
3576 SolidBeveledBorderSegment{aBorder,
3577 aBorderColor,
3578 {aStartBevelSide, aStartBevelOffset},
3579 {aEndBevelSide, aEndBevelOffset}});
3580 break;
3581 case StyleBorderStyle::Outset:
3582 case StyleBorderStyle::Inset:
3583 MOZ_ASSERT_UNREACHABLE(
3584 "inset, outset should have been converted to groove, ridge");
3585 break;
3586 }
3587 }
3588
3589 // End table border-collapsing section
3590
ExpandPaintingRectForDecorationLine(nsIFrame * aFrame,const uint8_t aStyle,const Rect & aClippedRect,const Float aICoordInFrame,const Float aCycleLength,bool aVertical)3591 Rect nsCSSRendering::ExpandPaintingRectForDecorationLine(
3592 nsIFrame* aFrame, const uint8_t aStyle, const Rect& aClippedRect,
3593 const Float aICoordInFrame, const Float aCycleLength, bool aVertical) {
3594 switch (aStyle) {
3595 case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
3596 case NS_STYLE_TEXT_DECORATION_STYLE_DASHED:
3597 case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
3598 break;
3599 default:
3600 NS_ERROR("Invalid style was specified");
3601 return aClippedRect;
3602 }
3603
3604 nsBlockFrame* block = nullptr;
3605 // Note that when we paint the decoration lines in relative positioned
3606 // box, we should paint them like all of the boxes are positioned as static.
3607 nscoord framePosInBlockAppUnits = 0;
3608 for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
3609 block = do_QueryFrame(f);
3610 if (block) {
3611 break;
3612 }
3613 framePosInBlockAppUnits +=
3614 aVertical ? f->GetNormalPosition().y : f->GetNormalPosition().x;
3615 }
3616
3617 NS_ENSURE_TRUE(block, aClippedRect);
3618
3619 nsPresContext* pc = aFrame->PresContext();
3620 Float framePosInBlock =
3621 Float(pc->AppUnitsToGfxUnits(framePosInBlockAppUnits));
3622 int32_t rectPosInBlock = int32_t(NS_round(framePosInBlock + aICoordInFrame));
3623 int32_t extraStartEdge =
3624 rectPosInBlock - (rectPosInBlock / int32_t(aCycleLength) * aCycleLength);
3625 Rect rect(aClippedRect);
3626 if (aVertical) {
3627 rect.y -= extraStartEdge;
3628 rect.height += extraStartEdge;
3629 } else {
3630 rect.x -= extraStartEdge;
3631 rect.width += extraStartEdge;
3632 }
3633 return rect;
3634 }
3635
3636 // Converts a GfxFont to an SkFont
3637 // Either returns true if it was successful, or false if something went wrong
GetSkFontFromGfxFont(DrawTarget & aDrawTarget,gfxFont * aFont,SkFont & aSkFont)3638 static bool GetSkFontFromGfxFont(DrawTarget& aDrawTarget, gfxFont* aFont,
3639 SkFont& aSkFont) {
3640 RefPtr<ScaledFont> scaledFont = aFont->GetScaledFont(&aDrawTarget);
3641 if (!scaledFont) {
3642 return false;
3643 }
3644
3645 ScaledFontBase* fontBase = static_cast<ScaledFontBase*>(scaledFont.get());
3646
3647 SkTypeface* typeface = fontBase->GetSkTypeface();
3648 if (!typeface) {
3649 return false;
3650 }
3651
3652 aSkFont = SkFont(sk_ref_sp(typeface), SkFloatToScalar(fontBase->GetSize()));
3653 return true;
3654 }
3655
3656 // Computes data used to position the decoration line within a
3657 // SkTextBlob, data is returned through aBounds
GetPositioning(const nsCSSRendering::PaintDecorationLineParams & aParams,const Rect & aRect,Float aOneCSSPixel,Float aCenterBaselineOffset,SkScalar aBounds[])3658 static void GetPositioning(
3659 const nsCSSRendering::PaintDecorationLineParams& aParams, const Rect& aRect,
3660 Float aOneCSSPixel, Float aCenterBaselineOffset, SkScalar aBounds[]) {
3661 /**
3662 * How Positioning in Skia Works
3663 * Take the letter "n" for example
3664 * We set textPos as 0, 0
3665 * This is represented in Skia like so (not to scale)
3666 * ^
3667 * -10px | _ __
3668 * | | '_ \
3669 * -5px | | | | |
3670 * y-axis | |_| |_|
3671 * (0,0) ----------------------->
3672 * | 5px 10px
3673 * 5px |
3674 * |
3675 * 10px |
3676 * v
3677 * 0 on the x axis is a line that touches the bottom of the n
3678 * (0,0) is the bottom left-hand corner of the n character
3679 * Moving "up" from the n is going in a negative y direction
3680 * Moving "down" from the n is going in a positive y direction
3681 *
3682 * The intercepts that are returned in this arrangement will be
3683 * offset by the original point it starts at. (This happens in
3684 * the SkipInk function below).
3685 *
3686 * In Skia, text MUST be laid out such that the next character
3687 * in the RunBuffer is further along the x-axis than the previous
3688 * character, otherwise there is undefined/strange behavior.
3689 */
3690
3691 Float rectThickness = aParams.vertical ? aRect.Width() : aRect.Height();
3692
3693 // the upper and lower lines/edges of the under or over line
3694 SkScalar upperLine, lowerLine;
3695 if (aParams.decoration == mozilla::StyleTextDecorationLine::OVERLINE) {
3696 lowerLine =
3697 -aParams.offset + aParams.defaultLineThickness - aCenterBaselineOffset;
3698 upperLine = lowerLine - rectThickness;
3699 } else {
3700 // underlines in vertical text are offset from the center of
3701 // the text, and not the baseline
3702 // Skia sets the text at it's baseline so we have to offset it
3703 // for text in vertical-* writing modes
3704 upperLine = -aParams.offset - aCenterBaselineOffset;
3705 lowerLine = upperLine + rectThickness;
3706 }
3707
3708 // set up the bounds, add in a little padding to the thickness of the line
3709 // (unless the line is <= 1 CSS pixel thick)
3710 Float lineThicknessPadding = aParams.lineSize.height > aOneCSSPixel
3711 ? 0.25f * aParams.lineSize.height
3712 : 0;
3713 // don't allow padding greater than 0.75 CSS pixel
3714 lineThicknessPadding = std::min(lineThicknessPadding, 0.75f * aOneCSSPixel);
3715 aBounds[0] = upperLine - lineThicknessPadding;
3716 aBounds[1] = lowerLine + lineThicknessPadding;
3717 }
3718
3719 // positions an individual glyph according to the given offset
GlyphPosition(const gfxTextRun::DetailedGlyph & aGlyph,const SkPoint & aTextPos,int32_t aAppUnitsPerDevPixel)3720 static SkPoint GlyphPosition(const gfxTextRun::DetailedGlyph& aGlyph,
3721 const SkPoint& aTextPos,
3722 int32_t aAppUnitsPerDevPixel) {
3723 SkPoint point = {aGlyph.mOffset.x, aGlyph.mOffset.y};
3724
3725 // convert to device pixels
3726 point.fX /= (float)aAppUnitsPerDevPixel;
3727 point.fY /= (float)aAppUnitsPerDevPixel;
3728
3729 // add offsets
3730 point.fX += aTextPos.fX;
3731 point.fY += aTextPos.fY;
3732 return point;
3733 }
3734
3735 // returns a count of all the glyphs that will be rendered
3736 // excludes ligature continuations, includes the number of individual
3737 // glyph records. This includes the number of DetailedGlyphs that a single
3738 // CompressedGlyph record points to. This function is necessary because Skia
3739 // needs the total length of glyphs to add to it's run buffer before it creates
3740 // the RunBuffer object, and this cannot be resized later.
CountAllGlyphs(const gfxTextRun * aTextRun,const gfxTextRun::CompressedGlyph * aCompressedGlyph,uint32_t aStringStart,uint32_t aStringEnd)3741 static uint32_t CountAllGlyphs(
3742 const gfxTextRun* aTextRun,
3743 const gfxTextRun::CompressedGlyph* aCompressedGlyph, uint32_t aStringStart,
3744 uint32_t aStringEnd) {
3745 uint32_t totalGlyphCount = 0;
3746
3747 for (const gfxTextRun::CompressedGlyph* cg = aCompressedGlyph + aStringStart;
3748 cg < aCompressedGlyph + aStringEnd; ++cg) {
3749 totalGlyphCount += cg->IsSimpleGlyph() ? 1 : cg->GetGlyphCount();
3750 }
3751
3752 return totalGlyphCount;
3753 }
3754
AddDetailedGlyph(const SkTextBlobBuilder::RunBuffer & aRunBuffer,const gfxTextRun::DetailedGlyph & aGlyph,int aIndex,float aAppUnitsPerDevPixel,SkPoint & aTextPos)3755 static void AddDetailedGlyph(const SkTextBlobBuilder::RunBuffer& aRunBuffer,
3756 const gfxTextRun::DetailedGlyph& aGlyph,
3757 int aIndex, float aAppUnitsPerDevPixel,
3758 SkPoint& aTextPos) {
3759 // add glyph ID to the run buffer at i
3760 aRunBuffer.glyphs[aIndex] = aGlyph.mGlyphID;
3761
3762 // position the glyph correctly using the detailed offsets
3763 SkPoint position = GlyphPosition(aGlyph, aTextPos, aAppUnitsPerDevPixel);
3764 aRunBuffer.pos[2 * aIndex] = position.fX;
3765 aRunBuffer.pos[(2 * aIndex) + 1] = position.fY;
3766
3767 // increase aTextPos.fx by the advance
3768 aTextPos.fX += ((float)aGlyph.mAdvance / aAppUnitsPerDevPixel);
3769 }
3770
AddSimpleGlyph(const SkTextBlobBuilder::RunBuffer & aRunBuffer,const gfxTextRun::CompressedGlyph & aGlyph,int aIndex,float aAppUnitsPerDevPixel,SkPoint & aTextPos)3771 static void AddSimpleGlyph(const SkTextBlobBuilder::RunBuffer& aRunBuffer,
3772 const gfxTextRun::CompressedGlyph& aGlyph,
3773 int aIndex, float aAppUnitsPerDevPixel,
3774 SkPoint& aTextPos) {
3775 aRunBuffer.glyphs[aIndex] = aGlyph.GetSimpleGlyph();
3776
3777 // simple glyphs are offset from 0, so we'll just use textPos
3778 aRunBuffer.pos[2 * aIndex] = aTextPos.fX;
3779 aRunBuffer.pos[(2 * aIndex) + 1] = aTextPos.fY;
3780
3781 // increase aTextPos.fX by the advance
3782 aTextPos.fX += ((float)aGlyph.GetSimpleAdvance() / aAppUnitsPerDevPixel);
3783 }
3784
3785 // Sets up a Skia TextBlob of the specified font, text position, and made up of
3786 // the glyphs between aStringStart and aStringEnd. Handles RTL and LTR text
3787 // and positions each glyph within the text blob
CreateTextBlob(const gfxTextRun * aTextRun,const gfxTextRun::CompressedGlyph * aCompressedGlyph,const SkFont & aFont,const gfxTextRun::PropertyProvider::Spacing * aSpacing,uint32_t aStringStart,uint32_t aStringEnd,float aAppUnitsPerDevPixel,SkPoint & aTextPos,int32_t & aSpacingOffset)3788 static sk_sp<const SkTextBlob> CreateTextBlob(
3789 const gfxTextRun* aTextRun,
3790 const gfxTextRun::CompressedGlyph* aCompressedGlyph, const SkFont& aFont,
3791 const gfxTextRun::PropertyProvider::Spacing* aSpacing,
3792 uint32_t aStringStart, uint32_t aStringEnd, float aAppUnitsPerDevPixel,
3793 SkPoint& aTextPos, int32_t& aSpacingOffset) {
3794 // allocate space for the run buffer, then fill it with the glyphs
3795 uint32_t len =
3796 CountAllGlyphs(aTextRun, aCompressedGlyph, aStringStart, aStringEnd);
3797 if (len <= 0) {
3798 return nullptr;
3799 }
3800
3801 SkTextBlobBuilder builder;
3802 const SkTextBlobBuilder::RunBuffer& run = builder.allocRunPos(aFont, len);
3803
3804 // RTL text should be read in by glyph starting at aStringEnd - 1 down until
3805 // aStringStart.
3806 bool isRTL = aTextRun->IsRightToLeft();
3807 uint32_t currIndex = isRTL ? aStringEnd - 1 : aStringStart; // textRun index
3808 // currIndex will be advanced by |step| until it reaches |limit|, which is the
3809 // final index to be handled (NOT one beyond the final index)
3810 int step = isRTL ? -1 : 1;
3811 uint32_t limit = isRTL ? aStringStart : aStringEnd - 1;
3812
3813 uint32_t i = 0; // index into the SkTextBlob we're building
3814 while (true) {
3815 // Loop exit test is below, just before we update currIndex.
3816 aTextPos.fX +=
3817 isRTL ? aSpacing[aSpacingOffset].mAfter / aAppUnitsPerDevPixel
3818 : aSpacing[aSpacingOffset].mBefore / aAppUnitsPerDevPixel;
3819
3820 if (aCompressedGlyph[currIndex].IsSimpleGlyph()) {
3821 MOZ_ASSERT(i < len, "glyph count error!");
3822 AddSimpleGlyph(run, aCompressedGlyph[currIndex], i, aAppUnitsPerDevPixel,
3823 aTextPos);
3824 i++;
3825 } else {
3826 // if it's detailed, potentially add multiple into run.glyphs
3827 uint32_t count = aCompressedGlyph[currIndex].GetGlyphCount();
3828 if (count > 0) {
3829 gfxTextRun::DetailedGlyph* detailGlyph =
3830 aTextRun->GetDetailedGlyphs(currIndex);
3831 for (uint32_t d = isRTL ? count - 1 : 0; count; count--, d += step) {
3832 MOZ_ASSERT(i < len, "glyph count error!");
3833 AddDetailedGlyph(run, detailGlyph[d], i, aAppUnitsPerDevPixel,
3834 aTextPos);
3835 i++;
3836 }
3837 }
3838 }
3839 aTextPos.fX += isRTL
3840 ? aSpacing[aSpacingOffset].mBefore / aAppUnitsPerDevPixel
3841 : aSpacing[aSpacingOffset].mAfter / aAppUnitsPerDevPixel;
3842 aSpacingOffset += step;
3843
3844 if (currIndex == limit) {
3845 break;
3846 }
3847 currIndex += step;
3848 }
3849
3850 MOZ_ASSERT(i == len, "glyph count error!");
3851
3852 return builder.make();
3853 }
3854
3855 // Given a TextBlob, the bounding lines, and the set of current intercepts this
3856 // function adds the intercepts for the current TextBlob into the given set of
3857 // previoulsy calculated intercepts. This set is either of length 0, or a
3858 // multiple of 2 (since every intersection with a piece of text results in two
3859 // intercepts: entering/exiting)
GetTextIntercepts(const sk_sp<const SkTextBlob> & aBlob,const SkScalar aBounds[],nsTArray<SkScalar> & aIntercepts)3860 static void GetTextIntercepts(const sk_sp<const SkTextBlob>& aBlob,
3861 const SkScalar aBounds[],
3862 nsTArray<SkScalar>& aIntercepts) {
3863 // https://skia.org/user/api/SkTextBlob_Reference#Text_Blob_Text_Intercepts
3864 int count = aBlob->getIntercepts(aBounds, nullptr);
3865 if (count < 2) {
3866 return;
3867 }
3868 aBlob->getIntercepts(aBounds, aIntercepts.AppendElements(count));
3869 }
3870
3871 // This function, given a set of intercepts that represent each intersection
3872 // between an under/overline and text, makes a series of calls to
3873 // PaintDecorationLineInternal that paints a series of clip rects which
3874 // implement the text-decoration-skip-ink property
3875 // Logic for where to place each clipped rect, and the length of each rect is
3876 // included here
SkipInk(nsIFrame * aFrame,DrawTarget & aDrawTarget,const nsCSSRendering::PaintDecorationLineParams & aParams,const nsTArray<SkScalar> & aIntercepts,Float aPadding,Rect & aRect)3877 static void SkipInk(nsIFrame* aFrame, DrawTarget& aDrawTarget,
3878 const nsCSSRendering::PaintDecorationLineParams& aParams,
3879 const nsTArray<SkScalar>& aIntercepts, Float aPadding,
3880 Rect& aRect) {
3881 nsCSSRendering::PaintDecorationLineParams clipParams = aParams;
3882 int length = aIntercepts.Length();
3883
3884 SkScalar startIntercept = 0;
3885 SkScalar endIntercept = 0;
3886
3887 // keep track of the direction we are drawing the clipped rects in
3888 // for sideways text, our intercepts from the first glyph are actually
3889 // decreasing (towards the top edge of the page), so we use a negative
3890 // direction
3891 Float dir = 1.0f;
3892 Float lineStart = aParams.vertical ? aParams.pt.y : aParams.pt.x;
3893 Float lineEnd = lineStart + aParams.lineSize.width;
3894 if (aParams.sidewaysLeft) {
3895 dir = -1.0f;
3896 std::swap(lineStart, lineEnd);
3897 }
3898
3899 for (int i = 0; i <= length; i += 2) {
3900 // handle start/end edge cases and set up general case
3901 startIntercept = (i > 0) ? (dir * aIntercepts[i - 1]) + lineStart
3902 : lineStart - (dir * aPadding);
3903 endIntercept = (i < length) ? (dir * aIntercepts[i]) + lineStart
3904 : lineEnd + (dir * aPadding);
3905
3906 // remove padding at both ends for width
3907 // the start of the line is calculated so the padding removes just
3908 // enough so that the line starts at its normal position
3909 clipParams.lineSize.width =
3910 (dir * (endIntercept - startIntercept)) - (2.0 * aPadding);
3911
3912 // Don't draw decoration lines that have a smaller width than 1, or half
3913 // the line-end padding dimension.
3914 if (clipParams.lineSize.width < std::max(aPadding * 0.5, 1.0)) {
3915 continue;
3916 }
3917
3918 // Start the line right after the intercept's location plus room for
3919 // padding; snap the rect edges to device pixels for consistent rendering
3920 // of dots across separate fragments of a dotted line.
3921 if (aParams.vertical) {
3922 clipParams.pt.y = aParams.sidewaysLeft ? endIntercept + aPadding
3923 : startIntercept + aPadding;
3924 aRect.y = std::floor(clipParams.pt.y + 0.5);
3925 aRect.SetBottomEdge(
3926 std::floor(clipParams.pt.y + clipParams.lineSize.width + 0.5));
3927 } else {
3928 clipParams.pt.x = startIntercept + aPadding;
3929 aRect.x = std::floor(clipParams.pt.x + 0.5);
3930 aRect.SetRightEdge(
3931 std::floor(clipParams.pt.x + clipParams.lineSize.width + 0.5));
3932 }
3933
3934 nsCSSRendering::PaintDecorationLineInternal(aFrame, aDrawTarget, clipParams,
3935 aRect);
3936 }
3937 }
3938
PaintDecorationLine(nsIFrame * aFrame,DrawTarget & aDrawTarget,const PaintDecorationLineParams & aParams)3939 void nsCSSRendering::PaintDecorationLine(
3940 nsIFrame* aFrame, DrawTarget& aDrawTarget,
3941 const PaintDecorationLineParams& aParams) {
3942 NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
3943 "aStyle is none");
3944
3945 Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams));
3946 if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) {
3947 return;
3948 }
3949
3950 if (aParams.decoration != StyleTextDecorationLine::UNDERLINE &&
3951 aParams.decoration != StyleTextDecorationLine::OVERLINE &&
3952 aParams.decoration != StyleTextDecorationLine::LINE_THROUGH) {
3953 MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
3954 return;
3955 }
3956
3957 // Check if decoration line will skip past ascenders/descenders
3958 // text-decoration-skip-ink only applies to overlines/underlines
3959 mozilla::StyleTextDecorationSkipInk skipInk =
3960 aFrame->StyleText()->mTextDecorationSkipInk;
3961 bool skipInkEnabled =
3962 skipInk != mozilla::StyleTextDecorationSkipInk::None &&
3963 aParams.decoration != StyleTextDecorationLine::LINE_THROUGH;
3964
3965 if (!skipInkEnabled || aParams.glyphRange.Length() == 0) {
3966 PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
3967 return;
3968 }
3969
3970 // check if the frame is a text frame or not
3971 nsTextFrame* textFrame = nullptr;
3972 if (aFrame->IsTextFrame()) {
3973 textFrame = static_cast<nsTextFrame*>(aFrame);
3974 } else {
3975 PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
3976 return;
3977 }
3978
3979 // get text run and current text offset (for line wrapping)
3980 gfxTextRun* textRun =
3981 textFrame->GetTextRun(nsTextFrame::TextRunType::eInflated);
3982
3983 // used for conversions from app units to device pixels
3984 int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
3985
3986 // pointer to the array of glyphs for this TextRun
3987 gfxTextRun::CompressedGlyph* characterGlyphs = textRun->GetCharacterGlyphs();
3988
3989 // get positioning info
3990 SkPoint textPos = {0, aParams.baselineOffset};
3991 SkScalar bounds[] = {0, 0};
3992 Float oneCSSPixel = aFrame->PresContext()->CSSPixelsToDevPixels(1.0f);
3993 if (!textRun->UseCenterBaseline()) {
3994 GetPositioning(aParams, rect, oneCSSPixel, 0, bounds);
3995 }
3996
3997 // array for the text intercepts
3998 AutoTArray<SkScalar, 256> intercepts;
3999
4000 // array for spacing data
4001 AutoTArray<gfxTextRun::PropertyProvider::Spacing, 64> spacing;
4002 spacing.SetLength(aParams.glyphRange.Length());
4003 if (aParams.provider != nullptr) {
4004 aParams.provider->GetSpacing(aParams.glyphRange, spacing.Elements());
4005 }
4006
4007 // loop through each glyph run
4008 // in most cases there will only be one
4009 bool isRTL = textRun->IsRightToLeft();
4010 int32_t spacingOffset = isRTL ? aParams.glyphRange.Length() - 1 : 0;
4011 gfxTextRun::GlyphRunIterator iter(textRun, aParams.glyphRange, isRTL);
4012
4013 // For any glyph run where we don't actually do skipping, we'll need to
4014 // advance the current position by its width.
4015 // (For runs we do process, CreateTextBlob will update the position.)
4016 auto currentGlyphRunAdvance = [&]() {
4017 return textRun->GetAdvanceWidth(
4018 gfxTextRun::Range(iter.GetStringStart(), iter.GetStringEnd()),
4019 aParams.provider) /
4020 appUnitsPerDevPixel;
4021 };
4022
4023 while (iter.NextRun()) {
4024 if (iter.GetGlyphRun()->mOrientation ==
4025 mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT ||
4026 (iter.GetGlyphRun()->mIsCJK &&
4027 skipInk == mozilla::StyleTextDecorationSkipInk::Auto)) {
4028 // We don't support upright text in vertical modes currently
4029 // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1572294),
4030 // but we do need to update textPos so that following runs will be
4031 // correctly positioned.
4032 // We also don't apply skip-ink to CJK text runs because many fonts
4033 // have an underline that looks really bad if this is done
4034 // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1573249),
4035 // when skip-ink is set to 'auto'.
4036 textPos.fX += currentGlyphRunAdvance();
4037 continue;
4038 }
4039
4040 gfxFont* font = iter.GetGlyphRun()->mFont;
4041 // Don't try to apply skip-ink to 'sbix' fonts like Apple Color Emoji,
4042 // because old macOS (10.9) may crash trying to retrieve glyph paths
4043 // that don't exist.
4044 if (font->GetFontEntry()->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) {
4045 textPos.fX += currentGlyphRunAdvance();
4046 continue;
4047 }
4048
4049 // get a Skia version of the glyph run's font
4050 SkFont skiafont;
4051 if (!GetSkFontFromGfxFont(aDrawTarget, font, skiafont)) {
4052 PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
4053 return;
4054 }
4055
4056 // Create a text blob with correctly positioned glyphs. This also updates
4057 // textPos.fX with the advance of the glyphs.
4058 sk_sp<const SkTextBlob> textBlob =
4059 CreateTextBlob(textRun, characterGlyphs, skiafont, spacing.Elements(),
4060 iter.GetStringStart(), iter.GetStringEnd(),
4061 (float)appUnitsPerDevPixel, textPos, spacingOffset);
4062
4063 if (!textBlob) {
4064 textPos.fX += currentGlyphRunAdvance();
4065 continue;
4066 }
4067
4068 if (textRun->UseCenterBaseline()) {
4069 // writing modes that use a center baseline need to be adjusted on a
4070 // font-by-font basis since Skia lines up the text on a alphabetic
4071 // baseline, but for some vertical-* writing modes the offset is from the
4072 // center.
4073 gfxFont::Metrics metrics = font->GetMetrics(nsFontMetrics::eHorizontal);
4074 Float centerToBaseline = (metrics.emAscent - metrics.emDescent) / 2.0f;
4075 GetPositioning(aParams, rect, oneCSSPixel, centerToBaseline, bounds);
4076 }
4077
4078 // compute the text intercepts that need to be skipped
4079 GetTextIntercepts(textBlob, bounds, intercepts);
4080 }
4081 bool needsSkipInk = intercepts.Length() > 0;
4082
4083 if (needsSkipInk) {
4084 // Padding between glyph intercepts and the decoration line: we use the
4085 // decoration line thickness, clamped to a minimum of 1px and a maximum
4086 // of 0.2em.
4087 Float padding =
4088 std::min(std::max(aParams.lineSize.height, oneCSSPixel),
4089 Float(textRun->GetFontGroup()->GetStyle()->size / 5.0));
4090 SkipInk(aFrame, aDrawTarget, aParams, intercepts, padding, rect);
4091 } else {
4092 PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
4093 }
4094 }
4095
PaintDecorationLineInternal(nsIFrame * aFrame,DrawTarget & aDrawTarget,const PaintDecorationLineParams & aParams,Rect aRect)4096 void nsCSSRendering::PaintDecorationLineInternal(
4097 nsIFrame* aFrame, DrawTarget& aDrawTarget,
4098 const PaintDecorationLineParams& aParams, Rect aRect) {
4099 Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
4100
4101 DeviceColor color = ToDeviceColor(aParams.color);
4102 ColorPattern colorPat(color);
4103 StrokeOptions strokeOptions(lineThickness);
4104 DrawOptions drawOptions;
4105
4106 Float dash[2];
4107
4108 AutoPopClips autoPopClips(&aDrawTarget);
4109
4110 mozilla::layout::TextDrawTarget* textDrawer = nullptr;
4111 if (aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT) {
4112 textDrawer = static_cast<mozilla::layout::TextDrawTarget*>(&aDrawTarget);
4113 }
4114
4115 switch (aParams.style) {
4116 case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
4117 case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE:
4118 break;
4119 case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
4120 autoPopClips.PushClipRect(aRect);
4121 Float dashWidth = lineThickness * DOT_LENGTH * DASH_LENGTH;
4122 dash[0] = dashWidth;
4123 dash[1] = dashWidth;
4124 strokeOptions.mDashPattern = dash;
4125 strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
4126 strokeOptions.mLineCap = CapStyle::BUTT;
4127 aRect = ExpandPaintingRectForDecorationLine(
4128 aFrame, aParams.style, aRect, aParams.icoordInFrame, dashWidth * 2,
4129 aParams.vertical);
4130 // We should continue to draw the last dash even if it is not in the rect.
4131 aRect.width += dashWidth;
4132 break;
4133 }
4134 case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED: {
4135 autoPopClips.PushClipRect(aRect);
4136 Float dashWidth = lineThickness * DOT_LENGTH;
4137 if (lineThickness > 2.0) {
4138 dash[0] = 0.f;
4139 dash[1] = dashWidth * 2.f;
4140 strokeOptions.mLineCap = CapStyle::ROUND;
4141 } else {
4142 dash[0] = dashWidth;
4143 dash[1] = dashWidth;
4144 }
4145 strokeOptions.mDashPattern = dash;
4146 strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
4147 aRect = ExpandPaintingRectForDecorationLine(
4148 aFrame, aParams.style, aRect, aParams.icoordInFrame, dashWidth * 2,
4149 aParams.vertical);
4150 // We should continue to draw the last dot even if it is not in the rect.
4151 aRect.width += dashWidth;
4152 break;
4153 }
4154 case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
4155 autoPopClips.PushClipRect(aRect);
4156 if (lineThickness > 2.0) {
4157 drawOptions.mAntialiasMode = AntialiasMode::SUBPIXEL;
4158 } else {
4159 // Don't use anti-aliasing here. Because looks like lighter color wavy
4160 // line at this case. And probably, users don't think the
4161 // non-anti-aliased wavy line is not pretty.
4162 drawOptions.mAntialiasMode = AntialiasMode::NONE;
4163 }
4164 break;
4165 default:
4166 NS_ERROR("Invalid style value!");
4167 return;
4168 }
4169
4170 // The block-direction position should be set to the middle of the line.
4171 if (aParams.vertical) {
4172 aRect.x += lineThickness / 2;
4173 } else {
4174 aRect.y += lineThickness / 2;
4175 }
4176
4177 switch (aParams.style) {
4178 case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
4179 case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
4180 case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
4181 Point p1 = aRect.TopLeft();
4182 Point p2 = aParams.vertical ? aRect.BottomLeft() : aRect.TopRight();
4183 if (textDrawer) {
4184 textDrawer->AppendDecoration(p1, p2, lineThickness, aParams.vertical,
4185 color, aParams.style);
4186 } else {
4187 aDrawTarget.StrokeLine(p1, p2, colorPat, strokeOptions, drawOptions);
4188 }
4189 return;
4190 }
4191 case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE: {
4192 /**
4193 * We are drawing double line as:
4194 *
4195 * +-------------------------------------------+
4196 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4197 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4198 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4199 * | |
4200 * | |
4201 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4202 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4203 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4204 * +-------------------------------------------+
4205 */
4206 Point p1a = aRect.TopLeft();
4207 Point p2a = aParams.vertical ? aRect.BottomLeft() : aRect.TopRight();
4208
4209 if (aParams.vertical) {
4210 aRect.width -= lineThickness;
4211 } else {
4212 aRect.height -= lineThickness;
4213 }
4214
4215 Point p1b = aParams.vertical ? aRect.TopRight() : aRect.BottomLeft();
4216 Point p2b = aRect.BottomRight();
4217
4218 if (textDrawer) {
4219 textDrawer->AppendDecoration(p1a, p2a, lineThickness, aParams.vertical,
4220 color,
4221 NS_STYLE_TEXT_DECORATION_STYLE_SOLID);
4222 textDrawer->AppendDecoration(p1b, p2b, lineThickness, aParams.vertical,
4223 color,
4224 NS_STYLE_TEXT_DECORATION_STYLE_SOLID);
4225 } else {
4226 aDrawTarget.StrokeLine(p1a, p2a, colorPat, strokeOptions, drawOptions);
4227 aDrawTarget.StrokeLine(p1b, p2b, colorPat, strokeOptions, drawOptions);
4228 }
4229 return;
4230 }
4231 case NS_STYLE_TEXT_DECORATION_STYLE_WAVY: {
4232 /**
4233 * We are drawing wavy line as:
4234 *
4235 * P: Path, X: Painted pixel
4236 *
4237 * +---------------------------------------+
4238 * XX|X XXXXXX XXXXXX |
4239 * PP|PX XPPPPPPX XPPPPPPX | ^
4240 * XX|XPX XPXXXXXXPX XPXXXXXXPX| |
4241 * | XPX XPX XPX XPX XP|X |adv
4242 * | XPXXXXXXPX XPXXXXXXPX X|PX |
4243 * | XPPPPPPX XPPPPPPX |XPX v
4244 * | XXXXXX XXXXXX | XX
4245 * +---------------------------------------+
4246 * <---><---> ^
4247 * adv flatLengthAtVertex rightMost
4248 *
4249 * 1. Always starts from top-left of the drawing area, however, we need
4250 * to draw the line from outside of the rect. Because the start
4251 * point of the line is not good style if we draw from inside it.
4252 * 2. First, draw horizontal line from outside the rect to top-left of
4253 * the rect;
4254 * 3. Goes down to bottom of the area at 45 degrees.
4255 * 4. Slides to right horizontaly, see |flatLengthAtVertex|.
4256 * 5. Goes up to top of the area at 45 degrees.
4257 * 6. Slides to right horizontaly.
4258 * 7. Repeat from 2 until reached to right-most edge of the area.
4259 *
4260 * In the vertical case, swap horizontal and vertical coordinates and
4261 * directions in the above description.
4262 */
4263
4264 Float& rectICoord = aParams.vertical ? aRect.y : aRect.x;
4265 Float& rectISize = aParams.vertical ? aRect.height : aRect.width;
4266 const Float rectBSize = aParams.vertical ? aRect.width : aRect.height;
4267
4268 const Float adv = rectBSize - lineThickness;
4269 const Float flatLengthAtVertex =
4270 std::max((lineThickness - 1.0) * 2.0, 1.0);
4271
4272 // Align the start of wavy lines to the nearest ancestor block.
4273 const Float cycleLength = 2 * (adv + flatLengthAtVertex);
4274 aRect = ExpandPaintingRectForDecorationLine(
4275 aFrame, aParams.style, aRect, aParams.icoordInFrame, cycleLength,
4276 aParams.vertical);
4277
4278 if (textDrawer) {
4279 // Undo attempted centering
4280 Float& rectBCoord = aParams.vertical ? aRect.x : aRect.y;
4281 rectBCoord -= lineThickness / 2;
4282
4283 textDrawer->AppendWavyDecoration(aRect, lineThickness, aParams.vertical,
4284 color);
4285 return;
4286 }
4287
4288 // figure out if we can trim whole cycles from the left and right edges
4289 // of the line, to try and avoid creating an unnecessarily long and
4290 // complex path (but don't do this for webrender, )
4291 const Float dirtyRectICoord =
4292 aParams.vertical ? aParams.dirtyRect.y : aParams.dirtyRect.x;
4293 int32_t skipCycles = floor((dirtyRectICoord - rectICoord) / cycleLength);
4294 if (skipCycles > 0) {
4295 rectICoord += skipCycles * cycleLength;
4296 rectISize -= skipCycles * cycleLength;
4297 }
4298
4299 rectICoord += lineThickness / 2.0;
4300
4301 Point pt(aRect.TopLeft());
4302 Float& ptICoord = aParams.vertical ? pt.y : pt.x;
4303 Float& ptBCoord = aParams.vertical ? pt.x : pt.y;
4304 if (aParams.vertical) {
4305 ptBCoord += adv;
4306 }
4307 Float iCoordLimit = ptICoord + rectISize + lineThickness;
4308
4309 const Float dirtyRectIMost = aParams.vertical ? aParams.dirtyRect.YMost()
4310 : aParams.dirtyRect.XMost();
4311 skipCycles = floor((iCoordLimit - dirtyRectIMost) / cycleLength);
4312 if (skipCycles > 0) {
4313 iCoordLimit -= skipCycles * cycleLength;
4314 }
4315
4316 RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
4317 RefPtr<Path> path;
4318
4319 ptICoord -= lineThickness;
4320 builder->MoveTo(pt); // 1
4321
4322 ptICoord = rectICoord;
4323 builder->LineTo(pt); // 2
4324
4325 // In vertical mode, to go "down" relative to the text we need to
4326 // decrease the block coordinate, whereas in horizontal we increase
4327 // it. So the sense of this flag is effectively inverted.
4328 bool goDown = !aParams.vertical;
4329 uint32_t iter = 0;
4330 while (ptICoord < iCoordLimit) {
4331 if (++iter > 1000) {
4332 // stroke the current path and start again, to avoid pathological
4333 // behavior in cairo with huge numbers of path segments
4334 path = builder->Finish();
4335 aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions);
4336 builder = aDrawTarget.CreatePathBuilder();
4337 builder->MoveTo(pt);
4338 iter = 0;
4339 }
4340 ptICoord += adv;
4341 ptBCoord += goDown ? adv : -adv;
4342
4343 builder->LineTo(pt); // 3 and 5
4344
4345 ptICoord += flatLengthAtVertex;
4346 builder->LineTo(pt); // 4 and 6
4347
4348 goDown = !goDown;
4349 }
4350 path = builder->Finish();
4351 aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions);
4352 return;
4353 }
4354 default:
4355 NS_ERROR("Invalid style value!");
4356 }
4357 }
4358
DecorationLineToPath(const PaintDecorationLineParams & aParams)4359 Rect nsCSSRendering::DecorationLineToPath(
4360 const PaintDecorationLineParams& aParams) {
4361 NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
4362 "aStyle is none");
4363
4364 Rect path; // To benefit from RVO, we return this from all return points
4365
4366 Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams));
4367 if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) {
4368 return path;
4369 }
4370
4371 if (aParams.decoration != StyleTextDecorationLine::UNDERLINE &&
4372 aParams.decoration != StyleTextDecorationLine::OVERLINE &&
4373 aParams.decoration != StyleTextDecorationLine::LINE_THROUGH) {
4374 MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
4375 return path;
4376 }
4377
4378 if (aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_SOLID) {
4379 // For the moment, we support only solid text decorations.
4380 return path;
4381 }
4382
4383 Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
4384
4385 // The block-direction position should be set to the middle of the line.
4386 if (aParams.vertical) {
4387 rect.x += lineThickness / 2;
4388 path = Rect(rect.TopLeft() - Point(lineThickness / 2, 0.0),
4389 Size(lineThickness, rect.Height()));
4390 } else {
4391 rect.y += lineThickness / 2;
4392 path = Rect(rect.TopLeft() - Point(0.0, lineThickness / 2),
4393 Size(rect.Width(), lineThickness));
4394 }
4395
4396 return path;
4397 }
4398
GetTextDecorationRect(nsPresContext * aPresContext,const DecorationRectParams & aParams)4399 nsRect nsCSSRendering::GetTextDecorationRect(
4400 nsPresContext* aPresContext, const DecorationRectParams& aParams) {
4401 NS_ASSERTION(aPresContext, "aPresContext is null");
4402 NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
4403 "aStyle is none");
4404
4405 gfxRect rect = GetTextDecorationRectInternal(Point(0, 0), aParams);
4406 // The rect values are already rounded to nearest device pixels.
4407 nsRect r;
4408 r.x = aPresContext->GfxUnitsToAppUnits(rect.X());
4409 r.y = aPresContext->GfxUnitsToAppUnits(rect.Y());
4410 r.width = aPresContext->GfxUnitsToAppUnits(rect.Width());
4411 r.height = aPresContext->GfxUnitsToAppUnits(rect.Height());
4412 return r;
4413 }
4414
GetTextDecorationRectInternal(const Point & aPt,const DecorationRectParams & aParams)4415 gfxRect nsCSSRendering::GetTextDecorationRectInternal(
4416 const Point& aPt, const DecorationRectParams& aParams) {
4417 NS_ASSERTION(aParams.style <= NS_STYLE_TEXT_DECORATION_STYLE_WAVY,
4418 "Invalid aStyle value");
4419
4420 if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_NONE)
4421 return gfxRect(0, 0, 0, 0);
4422
4423 bool canLiftUnderline = aParams.descentLimit >= 0.0;
4424
4425 gfxFloat iCoord = aParams.vertical ? aPt.y : aPt.x;
4426 gfxFloat bCoord = aParams.vertical ? aPt.x : aPt.y;
4427
4428 // 'left' and 'right' are relative to the line, so for vertical writing modes
4429 // they will actually become top and bottom of the rendered line.
4430 // Similarly, aLineSize.width and .height are actually length and thickness
4431 // of the line, which runs horizontally or vertically according to aVertical.
4432 const gfxFloat left = floor(iCoord + 0.5),
4433 right = floor(iCoord + aParams.lineSize.width + 0.5);
4434
4435 // We compute |r| as if for a horizontal text run, and then swap vertical
4436 // and horizontal coordinates at the end if vertical was requested.
4437 gfxRect r(left, 0, right - left, 0);
4438
4439 gfxFloat lineThickness = NS_round(aParams.lineSize.height);
4440 lineThickness = std::max(lineThickness, 1.0);
4441 gfxFloat defaultLineThickness = NS_round(aParams.defaultLineThickness);
4442 defaultLineThickness = std::max(defaultLineThickness, 1.0);
4443
4444 gfxFloat ascent = NS_round(aParams.ascent);
4445 gfxFloat descentLimit = floor(aParams.descentLimit);
4446
4447 gfxFloat suggestedMaxRectHeight =
4448 std::max(std::min(ascent, descentLimit), 1.0);
4449 r.height = lineThickness;
4450 if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE) {
4451 /**
4452 * We will draw double line as:
4453 *
4454 * +-------------------------------------------+
4455 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4456 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4457 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4458 * | | ^
4459 * | | | gap
4460 * | | v
4461 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4462 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4463 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4464 * +-------------------------------------------+
4465 */
4466 gfxFloat gap = NS_round(lineThickness / 2.0);
4467 gap = std::max(gap, 1.0);
4468 r.height = lineThickness * 2.0 + gap;
4469 if (canLiftUnderline) {
4470 if (r.Height() > suggestedMaxRectHeight) {
4471 // Don't shrink the line height, because the thickness has some meaning.
4472 // We can just shrink the gap at this time.
4473 r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0 + 1.0);
4474 }
4475 }
4476 } else if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_WAVY) {
4477 /**
4478 * We will draw wavy line as:
4479 *
4480 * +-------------------------------------------+
4481 * |XXXXX XXXXXX XXXXXX | ^
4482 * |XXXXXX XXXXXXXX XXXXXXXX | | lineThickness
4483 * |XXXXXXX XXXXXXXXXX XXXXXXXXXX| v
4484 * | XXX XXX XXX XXX XX|
4485 * | XXXXXXXXXX XXXXXXXXXX X|
4486 * | XXXXXXXX XXXXXXXX |
4487 * | XXXXXX XXXXXX |
4488 * +-------------------------------------------+
4489 */
4490 r.height = lineThickness > 2.0 ? lineThickness * 4.0 : lineThickness * 3.0;
4491 if (canLiftUnderline) {
4492 if (r.Height() > suggestedMaxRectHeight) {
4493 // Don't shrink the line height even if there is not enough space,
4494 // because the thickness has some meaning. E.g., the 1px wavy line and
4495 // 2px wavy line can be used for different meaning in IME selections
4496 // at same time.
4497 r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0);
4498 }
4499 }
4500 }
4501
4502 gfxFloat baseline = floor(bCoord + aParams.ascent + 0.5);
4503
4504 // Calculate adjusted offset based on writing-mode/orientation and thickness
4505 // of decoration line. The input value aParams.offset is the nominal position
4506 // (offset from baseline) where we would draw a single, infinitely-thin line;
4507 // but for a wavy or double line, we'll need to move the bounding rect of the
4508 // decoration outwards from the baseline so that an underline remains below
4509 // the glyphs, and an overline above them, despite the increased block-dir
4510 // extent of the decoration.
4511 //
4512 // So adjustments by r.Height() are used to make the wider line styles (wavy
4513 // and double) "grow" in the appropriate direction compared to the basic
4514 // single line.
4515 //
4516 // Note that at this point, the decoration rect is being calculated in line-
4517 // relative coordinates, where 'x' is line-rightwards, and 'y' is line-
4518 // upwards. We'll swap them to be physical coords at the end.
4519 gfxFloat offset = 0.0;
4520
4521 if (aParams.decoration == StyleTextDecorationLine::UNDERLINE) {
4522 offset = aParams.offset;
4523 if (canLiftUnderline) {
4524 if (descentLimit < -offset + r.Height()) {
4525 // If we can ignore the offset and the decoration line is overflowing,
4526 // we should align the bottom edge of the decoration line rect if it's
4527 // possible. Otherwise, we should lift up the top edge of the rect as
4528 // far as possible.
4529 gfxFloat offsetBottomAligned = -descentLimit + r.Height();
4530 gfxFloat offsetTopAligned = 0.0;
4531 offset = std::min(offsetBottomAligned, offsetTopAligned);
4532 }
4533 }
4534 } else if (aParams.decoration == StyleTextDecorationLine::OVERLINE) {
4535 // For overline, we adjust the offset by defaultlineThickness (the default
4536 // thickness of a single decoration line) because empirically it looks
4537 // better to draw the overline just inside rather than outside the font's
4538 // ascent, which is what nsTextFrame passes as aParams.offset (as fonts
4539 // don't provide an explicit overline-offset).
4540 offset = aParams.offset - defaultLineThickness + r.Height();
4541 } else if (aParams.decoration == StyleTextDecorationLine::LINE_THROUGH) {
4542 // To maintain a consistent mid-point for line-through decorations,
4543 // we adjust the offset by half of the decoration rect's height.
4544 gfxFloat extra = floor(r.Height() / 2.0 + 0.5);
4545 extra = std::max(extra, lineThickness);
4546 // computes offset for when user specifies a decoration width since
4547 // aParams.offset is derived from the font metric's line height
4548 gfxFloat decorationThicknessOffset =
4549 (lineThickness - defaultLineThickness) / 2.0;
4550 offset = aParams.offset - lineThickness + extra + decorationThicknessOffset;
4551 } else {
4552 MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
4553 }
4554
4555 // Convert line-relative coordinate system (x = line-right, y = line-up)
4556 // to physical coords, and move the decoration rect to the calculated
4557 // offset from baseline.
4558 if (aParams.vertical) {
4559 std::swap(r.x, r.y);
4560 std::swap(r.width, r.height);
4561 // line-upwards in vertical mode = physical-right, so we /add/ offset
4562 // to baseline. Except in sideways-lr mode, where line-upwards will be
4563 // physical leftwards.
4564 if (aParams.sidewaysLeft) {
4565 r.x = baseline - floor(offset + 0.5);
4566 } else {
4567 r.x = baseline + floor(offset - r.Width() + 0.5);
4568 }
4569 } else {
4570 // line-upwards in horizontal mode = physical-up, but our physical coord
4571 // system works downwards, so we /subtract/ offset from baseline.
4572 r.y = baseline - floor(offset + 0.5);
4573 }
4574
4575 return r;
4576 }
4577
4578 #define MAX_BLUR_RADIUS 300
4579 #define MAX_SPREAD_RADIUS 50
4580
ComputeBlurStdDev(nscoord aBlurRadius,int32_t aAppUnitsPerDevPixel,gfxFloat aScaleX,gfxFloat aScaleY)4581 static inline gfxPoint ComputeBlurStdDev(nscoord aBlurRadius,
4582 int32_t aAppUnitsPerDevPixel,
4583 gfxFloat aScaleX, gfxFloat aScaleY) {
4584 // http://dev.w3.org/csswg/css3-background/#box-shadow says that the
4585 // standard deviation of the blur should be half the given blur value.
4586 gfxFloat blurStdDev = gfxFloat(aBlurRadius) / gfxFloat(aAppUnitsPerDevPixel);
4587
4588 return gfxPoint(
4589 std::min((blurStdDev * aScaleX), gfxFloat(MAX_BLUR_RADIUS)) / 2.0,
4590 std::min((blurStdDev * aScaleY), gfxFloat(MAX_BLUR_RADIUS)) / 2.0);
4591 }
4592
ComputeBlurRadius(nscoord aBlurRadius,int32_t aAppUnitsPerDevPixel,gfxFloat aScaleX=1.0,gfxFloat aScaleY=1.0)4593 static inline IntSize ComputeBlurRadius(nscoord aBlurRadius,
4594 int32_t aAppUnitsPerDevPixel,
4595 gfxFloat aScaleX = 1.0,
4596 gfxFloat aScaleY = 1.0) {
4597 gfxPoint scaledBlurStdDev =
4598 ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, aScaleX, aScaleY);
4599 return gfxAlphaBoxBlur::CalculateBlurRadius(scaledBlurStdDev);
4600 }
4601
4602 // -----
4603 // nsContextBoxBlur
4604 // -----
Init(const nsRect & aRect,nscoord aSpreadRadius,nscoord aBlurRadius,int32_t aAppUnitsPerDevPixel,gfxContext * aDestinationCtx,const nsRect & aDirtyRect,const gfxRect * aSkipRect,uint32_t aFlags)4605 gfxContext* nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius,
4606 nscoord aBlurRadius,
4607 int32_t aAppUnitsPerDevPixel,
4608 gfxContext* aDestinationCtx,
4609 const nsRect& aDirtyRect,
4610 const gfxRect* aSkipRect, uint32_t aFlags) {
4611 if (aRect.IsEmpty()) {
4612 mContext = nullptr;
4613 return nullptr;
4614 }
4615
4616 IntSize blurRadius;
4617 IntSize spreadRadius;
4618 GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
4619 aBlurRadius, aSpreadRadius, blurRadius, spreadRadius);
4620
4621 mDestinationCtx = aDestinationCtx;
4622
4623 // If not blurring, draw directly onto the destination device
4624 if (blurRadius.width <= 0 && blurRadius.height <= 0 &&
4625 spreadRadius.width <= 0 && spreadRadius.height <= 0 &&
4626 !(aFlags & FORCE_MASK)) {
4627 mContext = aDestinationCtx;
4628 return mContext;
4629 }
4630
4631 // Convert from app units to device pixels
4632 gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel);
4633
4634 gfxRect dirtyRect =
4635 nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
4636 dirtyRect.RoundOut();
4637
4638 gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble();
4639 rect = transform.TransformBounds(rect);
4640
4641 mPreTransformed = !transform.IsIdentity();
4642
4643 // Create the temporary surface for blurring
4644 dirtyRect = transform.TransformBounds(dirtyRect);
4645 bool useHardwareAccel = !(aFlags & DISABLE_HARDWARE_ACCELERATION_BLUR);
4646 if (aSkipRect) {
4647 gfxRect skipRect = transform.TransformBounds(*aSkipRect);
4648 mContext =
4649 mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius,
4650 &dirtyRect, &skipRect, useHardwareAccel);
4651 } else {
4652 mContext =
4653 mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius,
4654 &dirtyRect, nullptr, useHardwareAccel);
4655 }
4656
4657 if (mContext) {
4658 // we don't need to blur if skipRect is equal to rect
4659 // and mContext will be nullptr
4660 mContext->Multiply(transform);
4661 }
4662 return mContext;
4663 }
4664
DoPaint()4665 void nsContextBoxBlur::DoPaint() {
4666 if (mContext == mDestinationCtx) {
4667 return;
4668 }
4669
4670 gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx);
4671
4672 if (mPreTransformed) {
4673 mDestinationCtx->SetMatrix(Matrix());
4674 }
4675
4676 mAlphaBoxBlur.Paint(mDestinationCtx);
4677 }
4678
GetContext()4679 gfxContext* nsContextBoxBlur::GetContext() { return mContext; }
4680
4681 /* static */
GetBlurRadiusMargin(nscoord aBlurRadius,int32_t aAppUnitsPerDevPixel)4682 nsMargin nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius,
4683 int32_t aAppUnitsPerDevPixel) {
4684 IntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel);
4685
4686 nsMargin result;
4687 result.top = result.bottom = blurRadius.height * aAppUnitsPerDevPixel;
4688 result.left = result.right = blurRadius.width * aAppUnitsPerDevPixel;
4689 return result;
4690 }
4691
4692 /* static */
BlurRectangle(gfxContext * aDestinationCtx,const nsRect & aRect,int32_t aAppUnitsPerDevPixel,RectCornerRadii * aCornerRadii,nscoord aBlurRadius,const sRGBColor & aShadowColor,const nsRect & aDirtyRect,const gfxRect & aSkipRect)4693 void nsContextBoxBlur::BlurRectangle(
4694 gfxContext* aDestinationCtx, const nsRect& aRect,
4695 int32_t aAppUnitsPerDevPixel, RectCornerRadii* aCornerRadii,
4696 nscoord aBlurRadius, const sRGBColor& aShadowColor,
4697 const nsRect& aDirtyRect, const gfxRect& aSkipRect) {
4698 DrawTarget& aDestDrawTarget = *aDestinationCtx->GetDrawTarget();
4699
4700 if (aRect.IsEmpty()) {
4701 return;
4702 }
4703
4704 Rect shadowGfxRect = NSRectToRect(aRect, aAppUnitsPerDevPixel);
4705
4706 if (aBlurRadius <= 0) {
4707 ColorPattern color(ToDeviceColor(aShadowColor));
4708 if (aCornerRadii) {
4709 RefPtr<Path> roundedRect =
4710 MakePathForRoundedRect(aDestDrawTarget, shadowGfxRect, *aCornerRadii);
4711 aDestDrawTarget.Fill(roundedRect, color);
4712 } else {
4713 aDestDrawTarget.FillRect(shadowGfxRect, color);
4714 }
4715 return;
4716 }
4717
4718 gfxFloat scaleX = 1;
4719 gfxFloat scaleY = 1;
4720
4721 // Do blurs in device space when possible.
4722 // Chrome/Skia always does the blurs in device space
4723 // and will sometimes get incorrect results (e.g. rotated blurs)
4724 gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble();
4725 // XXX: we could probably handle negative scales but for now it's easier just
4726 // to fallback
4727 if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 &&
4728 transform._22 > 0.0) {
4729 scaleX = transform._11;
4730 scaleY = transform._22;
4731 aDestinationCtx->SetMatrix(Matrix());
4732 } else {
4733 transform = gfxMatrix();
4734 }
4735
4736 gfxPoint blurStdDev =
4737 ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
4738
4739 gfxRect dirtyRect =
4740 nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
4741 dirtyRect.RoundOut();
4742
4743 gfxRect shadowThebesRect =
4744 transform.TransformBounds(ThebesRect(shadowGfxRect));
4745 dirtyRect = transform.TransformBounds(dirtyRect);
4746 gfxRect skipRect = transform.TransformBounds(aSkipRect);
4747
4748 if (aCornerRadii) {
4749 aCornerRadii->Scale(scaleX, scaleY);
4750 }
4751
4752 gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx, shadowThebesRect,
4753 aCornerRadii, blurStdDev, aShadowColor,
4754 dirtyRect, skipRect);
4755 }
4756
4757 /* static */
GetBlurAndSpreadRadius(DrawTarget * aDestDrawTarget,int32_t aAppUnitsPerDevPixel,nscoord aBlurRadius,nscoord aSpreadRadius,IntSize & aOutBlurRadius,IntSize & aOutSpreadRadius,bool aConstrainSpreadRadius)4758 void nsContextBoxBlur::GetBlurAndSpreadRadius(
4759 DrawTarget* aDestDrawTarget, int32_t aAppUnitsPerDevPixel,
4760 nscoord aBlurRadius, nscoord aSpreadRadius, IntSize& aOutBlurRadius,
4761 IntSize& aOutSpreadRadius, bool aConstrainSpreadRadius) {
4762 // Do blurs in device space when possible.
4763 // Chrome/Skia always does the blurs in device space
4764 // and will sometimes get incorrect results (e.g. rotated blurs)
4765 Matrix transform = aDestDrawTarget->GetTransform();
4766 // XXX: we could probably handle negative scales but for now it's easier just
4767 // to fallback
4768 gfxFloat scaleX, scaleY;
4769 if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 ||
4770 transform._22 <= 0.0) {
4771 scaleX = 1;
4772 scaleY = 1;
4773 } else {
4774 scaleX = transform._11;
4775 scaleY = transform._22;
4776 }
4777
4778 // compute a large or smaller blur radius
4779 aOutBlurRadius =
4780 ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
4781 aOutSpreadRadius =
4782 IntSize(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel),
4783 int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel));
4784
4785 if (aConstrainSpreadRadius) {
4786 aOutSpreadRadius.width =
4787 std::min(aOutSpreadRadius.width, int32_t(MAX_SPREAD_RADIUS));
4788 aOutSpreadRadius.height =
4789 std::min(aOutSpreadRadius.height, int32_t(MAX_SPREAD_RADIUS));
4790 }
4791 }
4792
4793 /* static */
InsetBoxBlur(gfxContext * aDestinationCtx,Rect aDestinationRect,Rect aShadowClipRect,sRGBColor & aShadowColor,nscoord aBlurRadiusAppUnits,nscoord aSpreadDistanceAppUnits,int32_t aAppUnitsPerDevPixel,bool aHasBorderRadius,RectCornerRadii & aInnerClipRectRadii,Rect aSkipRect,Point aShadowOffset)4794 bool nsContextBoxBlur::InsetBoxBlur(
4795 gfxContext* aDestinationCtx, Rect aDestinationRect, Rect aShadowClipRect,
4796 sRGBColor& aShadowColor, nscoord aBlurRadiusAppUnits,
4797 nscoord aSpreadDistanceAppUnits, int32_t aAppUnitsPerDevPixel,
4798 bool aHasBorderRadius, RectCornerRadii& aInnerClipRectRadii, Rect aSkipRect,
4799 Point aShadowOffset) {
4800 if (aDestinationRect.IsEmpty()) {
4801 mContext = nullptr;
4802 return false;
4803 }
4804
4805 gfxContextAutoSaveRestore autoRestore(aDestinationCtx);
4806
4807 IntSize blurRadius;
4808 IntSize spreadRadius;
4809 // Convert the blur and spread radius to device pixels
4810 bool constrainSpreadRadius = false;
4811 GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
4812 aBlurRadiusAppUnits, aSpreadDistanceAppUnits,
4813 blurRadius, spreadRadius, constrainSpreadRadius);
4814
4815 // The blur and spread radius are scaled already, so scale all
4816 // input data to the blur. This way, we don't have to scale the min
4817 // inset blur to the invert of the dest context, then rescale it back
4818 // when we draw to the destination surface.
4819 gfx::Size scale = aDestinationCtx->CurrentMatrix().ScaleFactors();
4820 Matrix transform = aDestinationCtx->CurrentMatrix();
4821
4822 // XXX: we could probably handle negative scales but for now it's easier just
4823 // to fallback
4824 if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 &&
4825 transform._22 > 0.0) {
4826 // If we don't have a rotation, we're pre-transforming all the rects.
4827 aDestinationCtx->SetMatrix(Matrix());
4828 } else {
4829 // Don't touch anything, we have a rotation.
4830 transform = Matrix();
4831 }
4832
4833 Rect transformedDestRect = transform.TransformBounds(aDestinationRect);
4834 Rect transformedShadowClipRect = transform.TransformBounds(aShadowClipRect);
4835 Rect transformedSkipRect = transform.TransformBounds(aSkipRect);
4836
4837 transformedDestRect.Round();
4838 transformedShadowClipRect.Round();
4839 transformedSkipRect.RoundIn();
4840
4841 for (size_t i = 0; i < 4; i++) {
4842 aInnerClipRectRadii[i].width =
4843 std::floor(scale.width * aInnerClipRectRadii[i].width);
4844 aInnerClipRectRadii[i].height =
4845 std::floor(scale.height * aInnerClipRectRadii[i].height);
4846 }
4847
4848 mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect,
4849 transformedShadowClipRect, blurRadius,
4850 aShadowColor,
4851 aHasBorderRadius ? &aInnerClipRectRadii : nullptr,
4852 transformedSkipRect, aShadowOffset);
4853 return true;
4854 }
4855