1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 // vim:cindent:ts=2:et:sw=2:
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 "mozilla/ArrayUtils.h"
13 #include "mozilla/DebugOnly.h"
14 #include "mozilla/gfx/2D.h"
15 #include "mozilla/gfx/Helpers.h"
16 #include "mozilla/gfx/PathHelpers.h"
17 #include "mozilla/HashFunctions.h"
18 #include "mozilla/MathAlgorithms.h"
19
20 #include "BorderConsts.h"
21 #include "nsISVGChildFrame.h"
22 #include "nsStyleConsts.h"
23 #include "nsPresContext.h"
24 #include "nsIFrame.h"
25 #include "nsPoint.h"
26 #include "nsRect.h"
27 #include "nsIPresShell.h"
28 #include "nsFrameManager.h"
29 #include "nsStyleContext.h"
30 #include "nsGkAtoms.h"
31 #include "nsCSSAnonBoxes.h"
32 #include "nsIContent.h"
33 #include "nsIDocumentInlines.h"
34 #include "nsIScrollableFrame.h"
35 #include "imgIRequest.h"
36 #include "imgIContainer.h"
37 #include "ImageOps.h"
38 #include "nsCSSRendering.h"
39 #include "nsCSSColorUtils.h"
40 #include "nsITheme.h"
41 #include "nsThemeConstants.h"
42 #include "nsLayoutUtils.h"
43 #include "nsBlockFrame.h"
44 #include "gfxContext.h"
45 #include "nsRenderingContext.h"
46 #include "nsStyleStructInlines.h"
47 #include "nsCSSFrameConstructor.h"
48 #include "nsCSSProps.h"
49 #include "nsContentUtils.h"
50 #include "nsSVGEffects.h"
51 #include "nsSVGIntegrationUtils.h"
52 #include "gfxDrawable.h"
53 #include "GeckoProfiler.h"
54 #include "nsCSSRenderingBorders.h"
55 #include "mozilla/css/ImageLoader.h"
56 #include "ImageContainer.h"
57 #include "mozilla/Telemetry.h"
58 #include "gfxUtils.h"
59 #include "gfxGradientCache.h"
60 #include "nsInlineFrame.h"
61 #include "nsRubyTextContainerFrame.h"
62 #include <algorithm>
63
64 using namespace mozilla;
65 using namespace mozilla::css;
66 using namespace mozilla::gfx;
67 using namespace mozilla::image;
68 using mozilla::CSSSizeOrRatio;
69
70 static int gFrameTreeLockCount = 0;
71
72 // To avoid storing this data on nsInlineFrame (bloat) and to avoid
73 // recalculating this for each frame in a continuation (perf), hold
74 // a cache of various coordinate information that we need in order
75 // to paint inline backgrounds.
76 struct InlineBackgroundData
77 {
InlineBackgroundDataInlineBackgroundData78 InlineBackgroundData()
79 : mFrame(nullptr), mLineContainer(nullptr)
80 {
81 }
82
~InlineBackgroundDataInlineBackgroundData83 ~InlineBackgroundData()
84 {
85 }
86
ResetInlineBackgroundData87 void Reset()
88 {
89 mBoundingBox.SetRect(0,0,0,0);
90 mContinuationPoint = mLineContinuationPoint = mUnbrokenMeasure = 0;
91 mFrame = mLineContainer = nullptr;
92 mPIStartBorderData.Reset();
93 }
94
95 /**
96 * Return a continuous rect for (an inline) aFrame relative to the
97 * continuation that draws the left-most part of the background.
98 * This is used when painting backgrounds.
99 */
GetContinuousRectInlineBackgroundData100 nsRect GetContinuousRect(nsIFrame* aFrame)
101 {
102 MOZ_ASSERT(static_cast<nsInlineFrame*>(do_QueryFrame(aFrame)));
103
104 SetFrame(aFrame);
105
106 nscoord pos; // an x coordinate if writing-mode is horizontal;
107 // y coordinate if vertical
108 if (mBidiEnabled) {
109 pos = mLineContinuationPoint;
110
111 // Scan continuations on the same line as aFrame and accumulate the widths
112 // of frames that are to the left (if this is an LTR block) or right
113 // (if it's RTL) of the current one.
114 bool isRtlBlock = (mLineContainer->StyleVisibility()->mDirection ==
115 NS_STYLE_DIRECTION_RTL);
116 nscoord curOffset = mVertical ? aFrame->GetOffsetTo(mLineContainer).y
117 : aFrame->GetOffsetTo(mLineContainer).x;
118
119 // If the continuation is fluid we know inlineFrame is not on the same line.
120 // If it's not fluid, we need to test further to be sure.
121 nsIFrame* inlineFrame = aFrame->GetPrevContinuation();
122 while (inlineFrame && !inlineFrame->GetNextInFlow() &&
123 AreOnSameLine(aFrame, inlineFrame)) {
124 nscoord frameOffset = mVertical
125 ? inlineFrame->GetOffsetTo(mLineContainer).y
126 : inlineFrame->GetOffsetTo(mLineContainer).x;
127 if (isRtlBlock == (frameOffset >= curOffset)) {
128 pos += mVertical
129 ? inlineFrame->GetSize().height
130 : inlineFrame->GetSize().width;
131 }
132 inlineFrame = inlineFrame->GetPrevContinuation();
133 }
134
135 inlineFrame = aFrame->GetNextContinuation();
136 while (inlineFrame && !inlineFrame->GetPrevInFlow() &&
137 AreOnSameLine(aFrame, inlineFrame)) {
138 nscoord frameOffset = mVertical
139 ? inlineFrame->GetOffsetTo(mLineContainer).y
140 : inlineFrame->GetOffsetTo(mLineContainer).x;
141 if (isRtlBlock == (frameOffset >= curOffset)) {
142 pos += mVertical
143 ? inlineFrame->GetSize().height
144 : inlineFrame->GetSize().width;
145 }
146 inlineFrame = inlineFrame->GetNextContinuation();
147 }
148 if (isRtlBlock) {
149 // aFrame itself is also to the right of its left edge, so add its width.
150 pos += mVertical ? aFrame->GetSize().height : aFrame->GetSize().width;
151 // pos is now the distance from the left [top] edge of aFrame to the right [bottom] edge
152 // of the unbroken content. Change it to indicate the distance from the
153 // left [top] edge of the unbroken content to the left [top] edge of aFrame.
154 pos = mUnbrokenMeasure - pos;
155 }
156 } else {
157 pos = mContinuationPoint;
158 }
159
160 // Assume background-origin: border and return a rect with offsets
161 // relative to (0,0). If we have a different background-origin,
162 // then our rect should be deflated appropriately by our caller.
163 return mVertical
164 ? nsRect(0, -pos, mFrame->GetSize().width, mUnbrokenMeasure)
165 : nsRect(-pos, 0, mUnbrokenMeasure, mFrame->GetSize().height);
166 }
167
168 /**
169 * Return a continuous rect for (an inline) aFrame relative to the
170 * continuation that should draw the left[top]-border. This is used when painting
171 * borders and clipping backgrounds. This may NOT be the same continuous rect
172 * as for drawing backgrounds; the continuation with the left[top]-border might be
173 * somewhere in the middle of that rect (e.g. BIDI), in those cases we need
174 * the reverse background order starting at the left[top]-border continuation.
175 */
GetBorderContinuousRectInlineBackgroundData176 nsRect GetBorderContinuousRect(nsIFrame* aFrame, nsRect aBorderArea)
177 {
178 // Calling GetContinuousRect(aFrame) here may lead to Reset/Init which
179 // resets our mPIStartBorderData so we save it ...
180 PhysicalInlineStartBorderData saved(mPIStartBorderData);
181 nsRect joinedBorderArea = GetContinuousRect(aFrame);
182 if (!saved.mIsValid || saved.mFrame != mPIStartBorderData.mFrame) {
183 if (aFrame == mPIStartBorderData.mFrame) {
184 if (mVertical) {
185 mPIStartBorderData.SetCoord(joinedBorderArea.y);
186 } else {
187 mPIStartBorderData.SetCoord(joinedBorderArea.x);
188 }
189 } else if (mPIStartBorderData.mFrame) {
190 if (mVertical) {
191 mPIStartBorderData.SetCoord(GetContinuousRect(mPIStartBorderData.mFrame).y);
192 } else {
193 mPIStartBorderData.SetCoord(GetContinuousRect(mPIStartBorderData.mFrame).x);
194 }
195 }
196 } else {
197 // ... and restore it when possible.
198 mPIStartBorderData.mCoord = saved.mCoord;
199 }
200 if (mVertical) {
201 if (joinedBorderArea.y > mPIStartBorderData.mCoord) {
202 joinedBorderArea.y =
203 -(mUnbrokenMeasure + joinedBorderArea.y - aBorderArea.height);
204 } else {
205 joinedBorderArea.y -= mPIStartBorderData.mCoord;
206 }
207 } else {
208 if (joinedBorderArea.x > mPIStartBorderData.mCoord) {
209 joinedBorderArea.x =
210 -(mUnbrokenMeasure + joinedBorderArea.x - aBorderArea.width);
211 } else {
212 joinedBorderArea.x -= mPIStartBorderData.mCoord;
213 }
214 }
215 return joinedBorderArea;
216 }
217
GetBoundingRectInlineBackgroundData218 nsRect GetBoundingRect(nsIFrame* aFrame)
219 {
220 SetFrame(aFrame);
221
222 // Move the offsets relative to (0,0) which puts the bounding box into
223 // our coordinate system rather than our parent's. We do this by
224 // moving it the back distance from us to the bounding box.
225 // This also assumes background-origin: border, so our caller will
226 // need to deflate us if needed.
227 nsRect boundingBox(mBoundingBox);
228 nsPoint point = mFrame->GetPosition();
229 boundingBox.MoveBy(-point.x, -point.y);
230
231 return boundingBox;
232 }
233
234 protected:
235 // This is a coordinate on the inline axis, but is not a true logical inline-
236 // coord because it is always measured from left to right (if horizontal) or
237 // from top to bottom (if vertical), ignoring any bidi RTL directionality.
238 // We'll call this "physical inline start", or PIStart for short.
239 struct PhysicalInlineStartBorderData {
240 nsIFrame* mFrame; // the continuation that may have a left-border
241 nscoord mCoord; // cached GetContinuousRect(mFrame).x or .y
242 bool mIsValid; // true if mCoord is valid
ResetInlineBackgroundData::PhysicalInlineStartBorderData243 void Reset() { mFrame = nullptr; mIsValid = false; }
SetCoordInlineBackgroundData::PhysicalInlineStartBorderData244 void SetCoord(nscoord aCoord) { mCoord = aCoord; mIsValid = true; }
245 };
246
247 nsIFrame* mFrame;
248 nsIFrame* mLineContainer;
249 nsRect mBoundingBox;
250 nscoord mContinuationPoint;
251 nscoord mUnbrokenMeasure;
252 nscoord mLineContinuationPoint;
253 PhysicalInlineStartBorderData mPIStartBorderData;
254 bool mBidiEnabled;
255 bool mVertical;
256
SetFrameInlineBackgroundData257 void SetFrame(nsIFrame* aFrame)
258 {
259 NS_PRECONDITION(aFrame, "Need a frame");
260 NS_ASSERTION(gFrameTreeLockCount > 0,
261 "Can't call this when frame tree is not locked");
262
263 if (aFrame == mFrame) {
264 return;
265 }
266
267 nsIFrame *prevContinuation = GetPrevContinuation(aFrame);
268
269 if (!prevContinuation || mFrame != prevContinuation) {
270 // Ok, we've got the wrong frame. We have to start from scratch.
271 Reset();
272 Init(aFrame);
273 return;
274 }
275
276 // Get our last frame's size and add its width to our continuation
277 // point before we cache the new frame.
278 mContinuationPoint += mVertical ? mFrame->GetSize().height
279 : mFrame->GetSize().width;
280
281 // If this a new line, update mLineContinuationPoint.
282 if (mBidiEnabled &&
283 (aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) {
284 mLineContinuationPoint = mContinuationPoint;
285 }
286
287 mFrame = aFrame;
288 }
289
GetPrevContinuationInlineBackgroundData290 nsIFrame* GetPrevContinuation(nsIFrame* aFrame)
291 {
292 nsIFrame* prevCont = aFrame->GetPrevContinuation();
293 if (!prevCont &&
294 (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
295 nsIFrame* block =
296 aFrame->Properties().Get(nsIFrame::IBSplitPrevSibling());
297 if (block) {
298 // The {ib} properties are only stored on first continuations
299 NS_ASSERTION(!block->GetPrevContinuation(),
300 "Incorrect value for IBSplitPrevSibling");
301 prevCont =
302 block->Properties().Get(nsIFrame::IBSplitPrevSibling());
303 NS_ASSERTION(prevCont, "How did that happen?");
304 }
305 }
306 return prevCont;
307 }
308
GetNextContinuationInlineBackgroundData309 nsIFrame* GetNextContinuation(nsIFrame* aFrame)
310 {
311 nsIFrame* nextCont = aFrame->GetNextContinuation();
312 if (!nextCont &&
313 (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
314 // The {ib} properties are only stored on first continuations
315 aFrame = aFrame->FirstContinuation();
316 nsIFrame* block = aFrame->Properties().Get(nsIFrame::IBSplitSibling());
317 if (block) {
318 nextCont = block->Properties().Get(nsIFrame::IBSplitSibling());
319 NS_ASSERTION(nextCont, "How did that happen?");
320 }
321 }
322 return nextCont;
323 }
324
InitInlineBackgroundData325 void Init(nsIFrame* aFrame)
326 {
327 mPIStartBorderData.Reset();
328 mBidiEnabled = aFrame->PresContext()->BidiEnabled();
329 if (mBidiEnabled) {
330 // Find the line container frame
331 mLineContainer = aFrame;
332 while (mLineContainer &&
333 mLineContainer->IsFrameOfType(nsIFrame::eLineParticipant)) {
334 mLineContainer = mLineContainer->GetParent();
335 }
336
337 MOZ_ASSERT(mLineContainer, "Cannot find line containing frame.");
338 MOZ_ASSERT(mLineContainer != aFrame, "line container frame "
339 "should be an ancestor of the target frame.");
340 }
341
342 mVertical = aFrame->GetWritingMode().IsVertical();
343
344 // Start with the previous flow frame as our continuation point
345 // is the total of the widths of the previous frames.
346 nsIFrame* inlineFrame = GetPrevContinuation(aFrame);
347 while (inlineFrame) {
348 if (!mPIStartBorderData.mFrame &&
349 !(mVertical ? inlineFrame->GetSkipSides().Top()
350 : inlineFrame->GetSkipSides().Left())) {
351 mPIStartBorderData.mFrame = inlineFrame;
352 }
353 nsRect rect = inlineFrame->GetRect();
354 mContinuationPoint += mVertical ? rect.height : rect.width;
355 if (mBidiEnabled && !AreOnSameLine(aFrame, inlineFrame)) {
356 mLineContinuationPoint += mVertical ? rect.height : rect.width;
357 }
358 mUnbrokenMeasure += mVertical ? rect.height : rect.width;
359 mBoundingBox.UnionRect(mBoundingBox, rect);
360 inlineFrame = GetPrevContinuation(inlineFrame);
361 }
362
363 // Next add this frame and subsequent frames to the bounding box and
364 // unbroken width.
365 inlineFrame = aFrame;
366 while (inlineFrame) {
367 if (!mPIStartBorderData.mFrame &&
368 !(mVertical ? inlineFrame->GetSkipSides().Top()
369 : inlineFrame->GetSkipSides().Left())) {
370 mPIStartBorderData.mFrame = inlineFrame;
371 }
372 nsRect rect = inlineFrame->GetRect();
373 mUnbrokenMeasure += mVertical ? rect.height : rect.width;
374 mBoundingBox.UnionRect(mBoundingBox, rect);
375 inlineFrame = GetNextContinuation(inlineFrame);
376 }
377
378 mFrame = aFrame;
379 }
380
AreOnSameLineInlineBackgroundData381 bool AreOnSameLine(nsIFrame* aFrame1, nsIFrame* aFrame2) {
382 if (nsBlockFrame* blockFrame = do_QueryFrame(mLineContainer)) {
383 bool isValid1, isValid2;
384 nsBlockInFlowLineIterator it1(blockFrame, aFrame1, &isValid1);
385 nsBlockInFlowLineIterator it2(blockFrame, aFrame2, &isValid2);
386 return isValid1 && isValid2 &&
387 // Make sure aFrame1 and aFrame2 are in the same continuation of
388 // blockFrame.
389 it1.GetContainer() == it2.GetContainer() &&
390 // And on the same line in it
391 it1.GetLine() == it2.GetLine();
392 }
393 if (nsRubyTextContainerFrame* rtcFrame = do_QueryFrame(mLineContainer)) {
394 nsBlockFrame* block = nsLayoutUtils::FindNearestBlockAncestor(rtcFrame);
395 // Ruby text container can only hold one line of text, so if they
396 // are in the same continuation, they are in the same line. Since
397 // ruby text containers are bidi isolate, they are never split for
398 // bidi reordering, which means being in different continuation
399 // indicates being in different lines.
400 for (nsIFrame* frame = rtcFrame->FirstContinuation();
401 frame; frame = frame->GetNextContinuation()) {
402 bool isDescendant1 =
403 nsLayoutUtils::IsProperAncestorFrame(frame, aFrame1, block);
404 bool isDescendant2 =
405 nsLayoutUtils::IsProperAncestorFrame(frame, aFrame2, block);
406 if (isDescendant1 && isDescendant2) {
407 return true;
408 }
409 if (isDescendant1 || isDescendant2) {
410 return false;
411 }
412 }
413 MOZ_ASSERT_UNREACHABLE("None of the frames is a descendant of this rtc?");
414 }
415 MOZ_ASSERT_UNREACHABLE("Do we have any other type of line container?");
416 return false;
417 }
418 };
419
420 // A resolved color stop, with a specific position along the gradient line and
421 // a color.
422 struct ColorStop {
ColorStopColorStop423 ColorStop(): mPosition(0), mIsMidpoint(false) {}
ColorStopColorStop424 ColorStop(double aPosition, bool aIsMidPoint, const Color& aColor) :
425 mPosition(aPosition), mIsMidpoint(aIsMidPoint), mColor(aColor) {}
426 double mPosition; // along the gradient line; 0=start, 1=end
427 bool mIsMidpoint;
428 Color mColor;
429 };
430
431 /* Local functions */
432 static DrawResult DrawBorderImage(nsPresContext* aPresContext,
433 nsRenderingContext& aRenderingContext,
434 nsIFrame* aForFrame,
435 const nsRect& aBorderArea,
436 const nsStyleBorder& aStyleBorder,
437 const nsRect& aDirtyRect,
438 Sides aSkipSides,
439 PaintBorderFlags aFlags);
440
441 static nscolor MakeBevelColor(mozilla::css::Side whichSide, uint8_t style,
442 nscolor aBackgroundColor,
443 nscolor aBorderColor);
444
445 static InlineBackgroundData* gInlineBGData = nullptr;
446
447 // Initialize any static variables used by nsCSSRendering.
Init()448 void nsCSSRendering::Init()
449 {
450 NS_ASSERTION(!gInlineBGData, "Init called twice");
451 gInlineBGData = new InlineBackgroundData();
452 }
453
454 // Clean up any global variables used by nsCSSRendering.
Shutdown()455 void nsCSSRendering::Shutdown()
456 {
457 delete gInlineBGData;
458 gInlineBGData = nullptr;
459 }
460
461 /**
462 * Make a bevel color
463 */
464 static nscolor
MakeBevelColor(mozilla::css::Side whichSide,uint8_t style,nscolor aBackgroundColor,nscolor aBorderColor)465 MakeBevelColor(mozilla::css::Side whichSide, uint8_t style,
466 nscolor aBackgroundColor, nscolor aBorderColor)
467 {
468
469 nscolor colors[2];
470 nscolor theColor;
471
472 // Given a background color and a border color
473 // calculate the color used for the shading
474 NS_GetSpecial3DColors(colors, aBackgroundColor, aBorderColor);
475
476 if ((style == NS_STYLE_BORDER_STYLE_OUTSET) ||
477 (style == NS_STYLE_BORDER_STYLE_RIDGE)) {
478 // Flip colors for these two border styles
479 switch (whichSide) {
480 case NS_SIDE_BOTTOM: whichSide = NS_SIDE_TOP; break;
481 case NS_SIDE_RIGHT: whichSide = NS_SIDE_LEFT; break;
482 case NS_SIDE_TOP: whichSide = NS_SIDE_BOTTOM; break;
483 case NS_SIDE_LEFT: whichSide = NS_SIDE_RIGHT; break;
484 }
485 }
486
487 switch (whichSide) {
488 case NS_SIDE_BOTTOM:
489 theColor = colors[1];
490 break;
491 case NS_SIDE_RIGHT:
492 theColor = colors[1];
493 break;
494 case NS_SIDE_TOP:
495 theColor = colors[0];
496 break;
497 case NS_SIDE_LEFT:
498 default:
499 theColor = colors[0];
500 break;
501 }
502 return theColor;
503 }
504
505 static bool
GetRadii(nsIFrame * aForFrame,const nsStyleBorder & aBorder,const nsRect & aOrigBorderArea,const nsRect & aBorderArea,nscoord aRadii[8])506 GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
507 const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
508 nscoord aRadii[8])
509 {
510 bool haveRoundedCorners;
511 nsSize sz = aBorderArea.Size();
512 nsSize frameSize = aForFrame->GetSize();
513 if (&aBorder == aForFrame->StyleBorder() &&
514 frameSize == aOrigBorderArea.Size()) {
515 haveRoundedCorners = aForFrame->GetBorderRadii(sz, sz, Sides(), aRadii);
516 } else {
517 haveRoundedCorners =
518 nsIFrame::ComputeBorderRadii(aBorder.mBorderRadius, frameSize, sz, Sides(), aRadii);
519 }
520
521 return haveRoundedCorners;
522 }
523
524 static bool
GetRadii(nsIFrame * aForFrame,const nsStyleBorder & aBorder,const nsRect & aOrigBorderArea,const nsRect & aBorderArea,RectCornerRadii * aBgRadii)525 GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
526 const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
527 RectCornerRadii* aBgRadii)
528 {
529 nscoord radii[8];
530 bool haveRoundedCorners = GetRadii(aForFrame, aBorder, aOrigBorderArea, aBorderArea, radii);
531
532 if (haveRoundedCorners) {
533 auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
534 nsCSSRendering::ComputePixelRadii(radii, d2a, aBgRadii);
535 }
536 return haveRoundedCorners;
537 }
538
539 static nsRect
JoinBoxesForVerticalSlice(nsIFrame * aFrame,const nsRect & aBorderArea)540 JoinBoxesForVerticalSlice(nsIFrame* aFrame, const nsRect& aBorderArea)
541 {
542 // Inflate vertically as if our continuations were laid out vertically
543 // adjacent. Note that we don't touch the width.
544 nsRect borderArea = aBorderArea;
545 nscoord h = 0;
546 nsIFrame* f = aFrame->GetNextContinuation();
547 for (; f; f = f->GetNextContinuation()) {
548 MOZ_ASSERT(!(f->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT),
549 "anonymous ib-split block shouldn't have border/background");
550 h += f->GetRect().height;
551 }
552 borderArea.height += h;
553 h = 0;
554 f = aFrame->GetPrevContinuation();
555 for (; f; f = f->GetPrevContinuation()) {
556 MOZ_ASSERT(!(f->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT),
557 "anonymous ib-split block shouldn't have border/background");
558 h += f->GetRect().height;
559 }
560 borderArea.y -= h;
561 borderArea.height += h;
562 return borderArea;
563 }
564
565 /**
566 * Inflate aBorderArea which is relative to aFrame's origin to calculate
567 * a hypothetical non-split frame area for all the continuations.
568 * See "Joining Boxes for 'slice'" in
569 * http://dev.w3.org/csswg/css-break/#break-decoration
570 */
571 enum InlineBoxOrder { eForBorder, eForBackground };
572 static nsRect
JoinBoxesForSlice(nsIFrame * aFrame,const nsRect & aBorderArea,InlineBoxOrder aOrder)573 JoinBoxesForSlice(nsIFrame* aFrame, const nsRect& aBorderArea,
574 InlineBoxOrder aOrder)
575 {
576 if (static_cast<nsInlineFrame*>(do_QueryFrame(aFrame))) {
577 return (aOrder == eForBorder
578 ? gInlineBGData->GetBorderContinuousRect(aFrame, aBorderArea)
579 : gInlineBGData->GetContinuousRect(aFrame)) +
580 aBorderArea.TopLeft();
581 }
582 return JoinBoxesForVerticalSlice(aFrame, aBorderArea);
583 }
584
585 static bool
IsBoxDecorationSlice(const nsStyleBorder & aStyleBorder)586 IsBoxDecorationSlice(const nsStyleBorder& aStyleBorder)
587 {
588 return aStyleBorder.mBoxDecorationBreak == StyleBoxDecorationBreak::Slice;
589 }
590
591 static nsRect
BoxDecorationRectForBorder(nsIFrame * aFrame,const nsRect & aBorderArea,Sides aSkipSides,const nsStyleBorder * aStyleBorder=nullptr)592 BoxDecorationRectForBorder(nsIFrame* aFrame, const nsRect& aBorderArea,
593 Sides aSkipSides,
594 const nsStyleBorder* aStyleBorder = nullptr)
595 {
596 if (!aStyleBorder) {
597 aStyleBorder = aFrame->StyleBorder();
598 }
599 // If aSkipSides.IsEmpty() then there are no continuations, or it's
600 // a ::first-letter that wants all border sides on the first continuation.
601 return ::IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
602 ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBorder)
603 : aBorderArea;
604 }
605
606 static nsRect
BoxDecorationRectForBackground(nsIFrame * aFrame,const nsRect & aBorderArea,Sides aSkipSides,const nsStyleBorder * aStyleBorder=nullptr)607 BoxDecorationRectForBackground(nsIFrame* aFrame, const nsRect& aBorderArea,
608 Sides aSkipSides,
609 const nsStyleBorder* aStyleBorder = nullptr)
610 {
611 if (!aStyleBorder) {
612 aStyleBorder = aFrame->StyleBorder();
613 }
614 // If aSkipSides.IsEmpty() then there are no continuations, or it's
615 // a ::first-letter that wants all border sides on the first continuation.
616 return ::IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
617 ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBackground)
618 : aBorderArea;
619 }
620
621 //----------------------------------------------------------------------
622 // Thebes Border Rendering Code Start
623
624 /*
625 * Compute the float-pixel radii that should be used for drawing
626 * this border/outline, given the various input bits.
627 */
628 /* static */ void
ComputePixelRadii(const nscoord * aAppUnitsRadii,nscoord aAppUnitsPerPixel,RectCornerRadii * oBorderRadii)629 nsCSSRendering::ComputePixelRadii(const nscoord *aAppUnitsRadii,
630 nscoord aAppUnitsPerPixel,
631 RectCornerRadii *oBorderRadii)
632 {
633 Float radii[8];
634 NS_FOR_CSS_HALF_CORNERS(corner)
635 radii[corner] = Float(aAppUnitsRadii[corner]) / aAppUnitsPerPixel;
636
637 (*oBorderRadii)[C_TL] = Size(radii[NS_CORNER_TOP_LEFT_X],
638 radii[NS_CORNER_TOP_LEFT_Y]);
639 (*oBorderRadii)[C_TR] = Size(radii[NS_CORNER_TOP_RIGHT_X],
640 radii[NS_CORNER_TOP_RIGHT_Y]);
641 (*oBorderRadii)[C_BR] = Size(radii[NS_CORNER_BOTTOM_RIGHT_X],
642 radii[NS_CORNER_BOTTOM_RIGHT_Y]);
643 (*oBorderRadii)[C_BL] = Size(radii[NS_CORNER_BOTTOM_LEFT_X],
644 radii[NS_CORNER_BOTTOM_LEFT_Y]);
645 }
646
647 DrawResult
PaintBorder(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,nsStyleContext * aStyleContext,PaintBorderFlags aFlags,Sides aSkipSides)648 nsCSSRendering::PaintBorder(nsPresContext* aPresContext,
649 nsRenderingContext& aRenderingContext,
650 nsIFrame* aForFrame,
651 const nsRect& aDirtyRect,
652 const nsRect& aBorderArea,
653 nsStyleContext* aStyleContext,
654 PaintBorderFlags aFlags,
655 Sides aSkipSides)
656 {
657 PROFILER_LABEL("nsCSSRendering", "PaintBorder",
658 js::ProfileEntry::Category::GRAPHICS);
659
660 nsStyleContext *styleIfVisited = aStyleContext->GetStyleIfVisited();
661 const nsStyleBorder *styleBorder = aStyleContext->StyleBorder();
662 // Don't check RelevantLinkVisited here, since we want to take the
663 // same amount of time whether or not it's true.
664 if (!styleIfVisited) {
665 return PaintBorderWithStyleBorder(aPresContext, aRenderingContext, aForFrame,
666 aDirtyRect, aBorderArea, *styleBorder,
667 aStyleContext, aFlags, aSkipSides);
668 }
669
670 nsStyleBorder newStyleBorder(*styleBorder);
671
672 NS_FOR_CSS_SIDES(side) {
673 nscolor color = aStyleContext->GetVisitedDependentColor(
674 nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color)[side]);
675 newStyleBorder.mBorderColor[side] = StyleComplexColor::FromColor(color);
676 }
677 DrawResult result =
678 PaintBorderWithStyleBorder(aPresContext, aRenderingContext, aForFrame,
679 aDirtyRect, aBorderArea, newStyleBorder,
680 aStyleContext, aFlags, aSkipSides);
681
682 return result;
683 }
684
685 DrawResult
PaintBorderWithStyleBorder(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,const nsStyleBorder & aStyleBorder,nsStyleContext * aStyleContext,PaintBorderFlags aFlags,Sides aSkipSides)686 nsCSSRendering::PaintBorderWithStyleBorder(nsPresContext* aPresContext,
687 nsRenderingContext& aRenderingContext,
688 nsIFrame* aForFrame,
689 const nsRect& aDirtyRect,
690 const nsRect& aBorderArea,
691 const nsStyleBorder& aStyleBorder,
692 nsStyleContext* aStyleContext,
693 PaintBorderFlags aFlags,
694 Sides aSkipSides)
695 {
696 DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
697
698 PrintAsStringNewline("++ PaintBorder");
699
700 // Check to see if we have an appearance defined. If so, we let the theme
701 // renderer draw the border. DO not get the data from aForFrame, since the passed in style context
702 // may be different! Always use |aStyleContext|!
703 const nsStyleDisplay* displayData = aStyleContext->StyleDisplay();
704 if (displayData->mAppearance) {
705 nsITheme *theme = aPresContext->GetTheme();
706 if (theme &&
707 theme->ThemeSupportsWidget(aPresContext, aForFrame,
708 displayData->mAppearance)) {
709 return DrawResult::SUCCESS; // Let the theme handle it.
710 }
711 }
712
713 if (aStyleBorder.IsBorderImageLoaded()) {
714 return DrawBorderImage(aPresContext, aRenderingContext, aForFrame,
715 aBorderArea, aStyleBorder, aDirtyRect,
716 aSkipSides, aFlags);
717 }
718
719 DrawResult result = DrawResult::SUCCESS;
720
721 // If we had a border-image, but it wasn't loaded, then we should return
722 // DrawResult::NOT_READY; we'll want to try again if we do a paint with sync
723 // decoding enabled.
724 if (aStyleBorder.mBorderImageSource.GetType() != eStyleImageType_Null) {
725 result = DrawResult::NOT_READY;
726 }
727
728 // Get our style context's color struct.
729 const nsStyleColor* ourColor = aStyleContext->StyleColor();
730
731 // In NavQuirks mode we want to use the parent's context as a starting point
732 // for determining the background color.
733 bool quirks = aPresContext->CompatibilityMode() == eCompatibility_NavQuirks;
734 nsIFrame* bgFrame = FindNonTransparentBackgroundFrame(aForFrame, quirks);
735 nsStyleContext* bgContext = bgFrame->StyleContext();
736 nscolor bgColor =
737 bgContext->GetVisitedDependentColor(eCSSProperty_background_color);
738
739 nsMargin border = aStyleBorder.GetComputedBorder();
740 if (0 == border.left && 0 == border.right &&
741 0 == border.top && 0 == border.bottom) {
742 // Empty border area
743 return result;
744 }
745
746 // Compute the outermost boundary of the area that might be painted.
747 // Same coordinate space as aBorderArea & aBGClipRect.
748 nsRect joinedBorderArea =
749 ::BoxDecorationRectForBorder(aForFrame, aBorderArea, aSkipSides, &aStyleBorder);
750 RectCornerRadii bgRadii;
751 ::GetRadii(aForFrame, aStyleBorder, aBorderArea, joinedBorderArea, &bgRadii);
752
753 PrintAsFormatString(" joinedBorderArea: %d %d %d %d\n", joinedBorderArea.x, joinedBorderArea.y,
754 joinedBorderArea.width, joinedBorderArea.height);
755
756 // start drawing
757 bool needToPopClip = false;
758
759 if (::IsBoxDecorationSlice(aStyleBorder)) {
760 if (joinedBorderArea.IsEqualEdges(aBorderArea)) {
761 // No need for a clip, just skip the sides we don't want.
762 border.ApplySkipSides(aSkipSides);
763 } else {
764 // We're drawing borders around the joined continuation boxes so we need
765 // to clip that to the slice that we want for this frame.
766 aDrawTarget.PushClipRect(
767 NSRectToSnappedRect(aBorderArea,
768 aForFrame->PresContext()->AppUnitsPerDevPixel(),
769 aDrawTarget));
770 needToPopClip = true;
771 }
772 } else {
773 MOZ_ASSERT(joinedBorderArea.IsEqualEdges(aBorderArea),
774 "Should use aBorderArea for box-decoration-break:clone");
775 MOZ_ASSERT(aForFrame->GetSkipSides().IsEmpty() ||
776 IS_TRUE_OVERFLOW_CONTAINER(aForFrame),
777 "Should not skip sides for box-decoration-break:clone except "
778 "::first-letter/line continuations or other frame types that "
779 "don't have borders but those shouldn't reach this point. "
780 "Overflow containers do reach this point though.");
781 border.ApplySkipSides(aSkipSides);
782 }
783
784 // Convert to dev pixels.
785 nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
786 Rect joinedBorderAreaPx = NSRectToRect(joinedBorderArea, twipsPerPixel);
787 Float borderWidths[4] = { Float(border.top / twipsPerPixel),
788 Float(border.right / twipsPerPixel),
789 Float(border.bottom / twipsPerPixel),
790 Float(border.left / twipsPerPixel) };
791 Rect dirtyRect = NSRectToRect(aDirtyRect, twipsPerPixel);
792
793 uint8_t borderStyles[4];
794 nscolor borderColors[4];
795 nsBorderColors *compositeColors[4];
796
797 // pull out styles, colors, composite colors
798 NS_FOR_CSS_SIDES (i) {
799 borderStyles[i] = aStyleBorder.GetBorderStyle(i);
800 borderColors[i] = ourColor->CalcComplexColor(aStyleBorder.mBorderColor[i]);
801 aStyleBorder.GetCompositeColors(i, &compositeColors[i]);
802 }
803
804 PrintAsFormatString(" borderStyles: %d %d %d %d\n", borderStyles[0], borderStyles[1], borderStyles[2], borderStyles[3]);
805 //PrintAsFormatString ("bgRadii: %f %f %f %f\n", bgRadii[0], bgRadii[1], bgRadii[2], bgRadii[3]);
806
807 #if 0
808 // this will draw a transparent red backround underneath the border area
809 ColorPattern color(ToDeviceColor(Color(1.f, 0.f, 0.f, 0.5f)));
810 aDrawTarget.FillRect(joinedBorderAreaPx, color);
811 #endif
812
813 nsIDocument* document = nullptr;
814 nsIContent* content = aForFrame->GetContent();
815 if (content) {
816 document = content->OwnerDoc();
817 }
818
819 nsCSSBorderRenderer br(aPresContext,
820 document,
821 &aDrawTarget,
822 dirtyRect,
823 joinedBorderAreaPx,
824 borderStyles,
825 borderWidths,
826 bgRadii,
827 borderColors,
828 compositeColors,
829 bgColor);
830 br.DrawBorders();
831
832 if (needToPopClip) {
833 aDrawTarget.PopClip();
834 }
835
836 PrintAsStringNewline();
837
838 return result;
839 }
840
841 static nsRect
GetOutlineInnerRect(nsIFrame * aFrame)842 GetOutlineInnerRect(nsIFrame* aFrame)
843 {
844 nsRect* savedOutlineInnerRect =
845 aFrame->Properties().Get(nsIFrame::OutlineInnerRectProperty());
846 if (savedOutlineInnerRect)
847 return *savedOutlineInnerRect;
848 NS_NOTREACHED("we should have saved a frame property");
849 return nsRect(nsPoint(0, 0), aFrame->GetSize());
850 }
851
852 void
PaintOutline(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,nsStyleContext * aStyleContext)853 nsCSSRendering::PaintOutline(nsPresContext* aPresContext,
854 nsRenderingContext& aRenderingContext,
855 nsIFrame* aForFrame,
856 const nsRect& aDirtyRect,
857 const nsRect& aBorderArea,
858 nsStyleContext* aStyleContext)
859 {
860 nscoord twipsRadii[8];
861
862 // Get our style context's color struct.
863 const nsStyleOutline* ourOutline = aStyleContext->StyleOutline();
864 MOZ_ASSERT(ourOutline != NS_STYLE_BORDER_STYLE_NONE,
865 "shouldn't have created nsDisplayOutline item");
866
867 uint8_t outlineStyle = ourOutline->mOutlineStyle;
868 nscoord width = ourOutline->GetOutlineWidth();
869
870 if (width == 0 && outlineStyle != NS_STYLE_BORDER_STYLE_AUTO) {
871 // Empty outline
872 return;
873 }
874
875 nsIFrame* bgFrame = nsCSSRendering::FindNonTransparentBackgroundFrame
876 (aForFrame, false);
877 nsStyleContext* bgContext = bgFrame->StyleContext();
878 nscolor bgColor =
879 bgContext->GetVisitedDependentColor(eCSSProperty_background_color);
880
881 nsRect innerRect;
882 if (
883 #ifdef MOZ_XUL
884 aStyleContext->GetPseudoType() == CSSPseudoElementType::XULTree
885 #else
886 false
887 #endif
888 ) {
889 innerRect = aBorderArea;
890 } else {
891 innerRect = GetOutlineInnerRect(aForFrame) + aBorderArea.TopLeft();
892 }
893 nscoord offset = ourOutline->mOutlineOffset;
894 innerRect.Inflate(offset, offset);
895 // If the dirty rect is completely inside the border area (e.g., only the
896 // content is being painted), then we can skip out now
897 // XXX this isn't exactly true for rounded borders, where the inside curves may
898 // encroach into the content area. A safer calculation would be to
899 // shorten insideRect by the radius one each side before performing this test.
900 if (innerRect.Contains(aDirtyRect))
901 return;
902
903 nsRect outerRect = innerRect;
904 outerRect.Inflate(width, width);
905
906 // get the radius for our outline
907 nsIFrame::ComputeBorderRadii(ourOutline->mOutlineRadius, aBorderArea.Size(),
908 outerRect.Size(), Sides(), twipsRadii);
909
910 // Get our conversion values
911 nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
912
913 // get the outer rectangles
914 Rect oRect(NSRectToRect(outerRect, twipsPerPixel));
915
916 // convert the radii
917 nsMargin outlineMargin(width, width, width, width);
918 RectCornerRadii outlineRadii;
919 ComputePixelRadii(twipsRadii, twipsPerPixel, &outlineRadii);
920
921 if (outlineStyle == NS_STYLE_BORDER_STYLE_AUTO) {
922 if (nsLayoutUtils::IsOutlineStyleAutoEnabled()) {
923 nsITheme* theme = aPresContext->GetTheme();
924 if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame,
925 NS_THEME_FOCUS_OUTLINE)) {
926 theme->DrawWidgetBackground(&aRenderingContext, aForFrame,
927 NS_THEME_FOCUS_OUTLINE, innerRect,
928 aDirtyRect);
929 return;
930 }
931 }
932 if (width == 0) {
933 return; // empty outline
934 }
935 // http://dev.w3.org/csswg/css-ui/#outline
936 // "User agents may treat 'auto' as 'solid'."
937 outlineStyle = NS_STYLE_BORDER_STYLE_SOLID;
938 }
939
940 uint8_t outlineStyles[4] = { outlineStyle, outlineStyle,
941 outlineStyle, outlineStyle };
942
943 // This handles treating the initial color as 'currentColor'; if we
944 // ever want 'invert' back we'll need to do a bit of work here too.
945 nscolor outlineColor =
946 aStyleContext->GetVisitedDependentColor(eCSSProperty_outline_color);
947 nscolor outlineColors[4] = { outlineColor,
948 outlineColor,
949 outlineColor,
950 outlineColor };
951
952 // convert the border widths
953 Float outlineWidths[4] = { Float(width / twipsPerPixel),
954 Float(width / twipsPerPixel),
955 Float(width / twipsPerPixel),
956 Float(width / twipsPerPixel) };
957 Rect dirtyRect = NSRectToRect(aDirtyRect, twipsPerPixel);
958
959 nsIDocument* document = nullptr;
960 nsIContent* content = aForFrame->GetContent();
961 if (content) {
962 document = content->OwnerDoc();
963 }
964
965 // start drawing
966
967 nsCSSBorderRenderer br(aPresContext,
968 document,
969 aRenderingContext.GetDrawTarget(),
970 dirtyRect,
971 oRect,
972 outlineStyles,
973 outlineWidths,
974 outlineRadii,
975 outlineColors,
976 nullptr,
977 bgColor);
978 br.DrawBorders();
979
980 PrintAsStringNewline();
981 }
982
983 void
PaintFocus(nsPresContext * aPresContext,DrawTarget * aDrawTarget,const nsRect & aFocusRect,nscolor aColor)984 nsCSSRendering::PaintFocus(nsPresContext* aPresContext,
985 DrawTarget* aDrawTarget,
986 const nsRect& aFocusRect,
987 nscolor aColor)
988 {
989 nscoord oneCSSPixel = nsPresContext::CSSPixelsToAppUnits(1);
990 nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
991
992 Rect focusRect(NSRectToRect(aFocusRect, oneDevPixel));
993
994 RectCornerRadii focusRadii;
995 {
996 nscoord twipsRadii[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
997 ComputePixelRadii(twipsRadii, oneDevPixel, &focusRadii);
998 }
999 Float focusWidths[4] = { Float(oneCSSPixel / oneDevPixel),
1000 Float(oneCSSPixel / oneDevPixel),
1001 Float(oneCSSPixel / oneDevPixel),
1002 Float(oneCSSPixel / oneDevPixel) };
1003
1004 uint8_t focusStyles[4] = { NS_STYLE_BORDER_STYLE_DOTTED,
1005 NS_STYLE_BORDER_STYLE_DOTTED,
1006 NS_STYLE_BORDER_STYLE_DOTTED,
1007 NS_STYLE_BORDER_STYLE_DOTTED };
1008 nscolor focusColors[4] = { aColor, aColor, aColor, aColor };
1009
1010 // Because this renders a dotted border, the background color
1011 // should not be used. Therefore, we provide a value that will
1012 // be blatantly wrong if it ever does get used. (If this becomes
1013 // something that CSS can style, this function will then have access
1014 // to a style context and can use the same logic that PaintBorder
1015 // and PaintOutline do.)
1016 nsCSSBorderRenderer br(aPresContext,
1017 nullptr,
1018 aDrawTarget,
1019 focusRect,
1020 focusRect,
1021 focusStyles,
1022 focusWidths,
1023 focusRadii,
1024 focusColors,
1025 nullptr,
1026 NS_RGB(255, 0, 0));
1027 br.DrawBorders();
1028
1029 PrintAsStringNewline();
1030 }
1031
1032 // Thebes Border Rendering Code End
1033 //----------------------------------------------------------------------
1034
1035
1036 //----------------------------------------------------------------------
1037
1038 /**
1039 * Helper for ComputeObjectAnchorPoint; parameters are the same as for
1040 * that function, except they're for a single coordinate / a single size
1041 * dimension. (so, x/width vs. y/height)
1042 */
1043 static void
ComputeObjectAnchorCoord(const Position::Coord & aCoord,const nscoord aOriginBounds,const nscoord aImageSize,nscoord * aTopLeftCoord,nscoord * aAnchorPointCoord)1044 ComputeObjectAnchorCoord(const Position::Coord& aCoord,
1045 const nscoord aOriginBounds,
1046 const nscoord aImageSize,
1047 nscoord* aTopLeftCoord,
1048 nscoord* aAnchorPointCoord)
1049 {
1050 *aAnchorPointCoord = aCoord.mLength;
1051 *aTopLeftCoord = aCoord.mLength;
1052
1053 if (aCoord.mHasPercent) {
1054 // Adjust aTopLeftCoord by the specified % of the extra space.
1055 nscoord extraSpace = aOriginBounds - aImageSize;
1056 *aTopLeftCoord += NSToCoordRound(aCoord.mPercent * extraSpace);
1057
1058 // The anchor-point doesn't care about our image's size; just the size
1059 // of the region we're rendering into.
1060 *aAnchorPointCoord += NSToCoordRound(aCoord.mPercent * aOriginBounds);
1061 }
1062 }
1063
1064 void
ComputeObjectAnchorPoint(const Position & aPos,const nsSize & aOriginBounds,const nsSize & aImageSize,nsPoint * aTopLeft,nsPoint * aAnchorPoint)1065 nsImageRenderer::ComputeObjectAnchorPoint(
1066 const Position& aPos,
1067 const nsSize& aOriginBounds,
1068 const nsSize& aImageSize,
1069 nsPoint* aTopLeft,
1070 nsPoint* aAnchorPoint)
1071 {
1072 ComputeObjectAnchorCoord(aPos.mXPosition,
1073 aOriginBounds.width, aImageSize.width,
1074 &aTopLeft->x, &aAnchorPoint->x);
1075
1076 ComputeObjectAnchorCoord(aPos.mYPosition,
1077 aOriginBounds.height, aImageSize.height,
1078 &aTopLeft->y, &aAnchorPoint->y);
1079 }
1080
1081 nsIFrame*
FindNonTransparentBackgroundFrame(nsIFrame * aFrame,bool aStartAtParent)1082 nsCSSRendering::FindNonTransparentBackgroundFrame(nsIFrame* aFrame,
1083 bool aStartAtParent /*= false*/)
1084 {
1085 NS_ASSERTION(aFrame, "Cannot find NonTransparentBackgroundFrame in a null frame");
1086
1087 nsIFrame* frame = nullptr;
1088 if (aStartAtParent) {
1089 frame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
1090 }
1091 if (!frame) {
1092 frame = aFrame;
1093 }
1094
1095 while (frame) {
1096 // No need to call GetVisitedDependentColor because it always uses
1097 // this alpha component anyway.
1098 if (NS_GET_A(frame->StyleBackground()->mBackgroundColor) > 0)
1099 break;
1100
1101 if (frame->IsThemed())
1102 break;
1103
1104 nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(frame);
1105 if (!parent)
1106 break;
1107
1108 frame = parent;
1109 }
1110 return frame;
1111 }
1112
1113 // Returns true if aFrame is a canvas frame.
1114 // We need to treat the viewport as canvas because, even though
1115 // it does not actually paint a background, we need to get the right
1116 // background style so we correctly detect transparent documents.
1117 bool
IsCanvasFrame(nsIFrame * aFrame)1118 nsCSSRendering::IsCanvasFrame(nsIFrame* aFrame)
1119 {
1120 nsIAtom* frameType = aFrame->GetType();
1121 return frameType == nsGkAtoms::canvasFrame ||
1122 frameType == nsGkAtoms::rootFrame ||
1123 frameType == nsGkAtoms::pageContentFrame ||
1124 frameType == nsGkAtoms::viewportFrame;
1125 }
1126
1127 nsIFrame*
FindBackgroundStyleFrame(nsIFrame * aForFrame)1128 nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame)
1129 {
1130 const nsStyleBackground* result = aForFrame->StyleBackground();
1131
1132 // Check if we need to do propagation from BODY rather than HTML.
1133 if (!result->IsTransparent()) {
1134 return aForFrame;
1135 }
1136
1137 nsIContent* content = aForFrame->GetContent();
1138 // The root element content can't be null. We wouldn't know what
1139 // frame to create for aFrame.
1140 // Use |OwnerDoc| so it works during destruction.
1141 if (!content) {
1142 return aForFrame;
1143 }
1144
1145 nsIDocument* document = content->OwnerDoc();
1146
1147 dom::Element* bodyContent = document->GetBodyElement();
1148 // We need to null check the body node (bug 118829) since
1149 // there are cases, thanks to the fix for bug 5569, where we
1150 // will reflow a document with no body. In particular, if a
1151 // SCRIPT element in the head blocks the parser and then has a
1152 // SCRIPT that does "document.location.href = 'foo'", then
1153 // nsParser::Terminate will call |DidBuildModel| methods
1154 // through to the content sink, which will call |StartLayout|
1155 // and thus |Initialize| on the pres shell. See bug 119351
1156 // for the ugly details.
1157 if (!bodyContent) {
1158 return aForFrame;
1159 }
1160
1161 nsIFrame *bodyFrame = bodyContent->GetPrimaryFrame();
1162 if (!bodyFrame) {
1163 return aForFrame;
1164 }
1165
1166 return nsLayoutUtils::GetStyleFrame(bodyFrame);
1167 }
1168
1169 /**
1170 * |FindBackground| finds the correct style data to use to paint the
1171 * background. It is responsible for handling the following two
1172 * statements in section 14.2 of CSS2:
1173 *
1174 * The background of the box generated by the root element covers the
1175 * entire canvas.
1176 *
1177 * For HTML documents, however, we recommend that authors specify the
1178 * background for the BODY element rather than the HTML element. User
1179 * agents should observe the following precedence rules to fill in the
1180 * background: if the value of the 'background' property for the HTML
1181 * element is different from 'transparent' then use it, else use the
1182 * value of the 'background' property for the BODY element. If the
1183 * resulting value is 'transparent', the rendering is undefined.
1184 *
1185 * Thus, in our implementation, it is responsible for ensuring that:
1186 * + we paint the correct background on the |nsCanvasFrame|,
1187 * |nsRootBoxFrame|, or |nsPageFrame|,
1188 * + we don't paint the background on the root element, and
1189 * + we don't paint the background on the BODY element in *some* cases,
1190 * and for SGML-based HTML documents only.
1191 *
1192 * |FindBackground| returns true if a background should be painted, and
1193 * the resulting style context to use for the background information
1194 * will be filled in to |aBackground|.
1195 */
1196 nsStyleContext*
FindRootFrameBackground(nsIFrame * aForFrame)1197 nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame)
1198 {
1199 return FindBackgroundStyleFrame(aForFrame)->StyleContext();
1200 }
1201
1202 inline bool
FindElementBackground(nsIFrame * aForFrame,nsIFrame * aRootElementFrame,nsStyleContext ** aBackgroundSC)1203 FindElementBackground(nsIFrame* aForFrame, nsIFrame* aRootElementFrame,
1204 nsStyleContext** aBackgroundSC)
1205 {
1206 if (aForFrame == aRootElementFrame) {
1207 // We must have propagated our background to the viewport or canvas. Abort.
1208 return false;
1209 }
1210
1211 *aBackgroundSC = aForFrame->StyleContext();
1212
1213 // Return true unless the frame is for a BODY element whose background
1214 // was propagated to the viewport.
1215
1216 nsIContent* content = aForFrame->GetContent();
1217 if (!content || content->NodeInfo()->NameAtom() != nsGkAtoms::body)
1218 return true; // not frame for a "body" element
1219 // It could be a non-HTML "body" element but that's OK, we'd fail the
1220 // bodyContent check below
1221
1222 if (aForFrame->StyleContext()->GetPseudo())
1223 return true; // A pseudo-element frame.
1224
1225 // We should only look at the <html> background if we're in an HTML document
1226 nsIDocument* document = content->OwnerDoc();
1227
1228 dom::Element* bodyContent = document->GetBodyElement();
1229 if (bodyContent != content)
1230 return true; // this wasn't the background that was propagated
1231
1232 // This can be called even when there's no root element yet, during frame
1233 // construction, via nsLayoutUtils::FrameHasTransparency and
1234 // nsContainerFrame::SyncFrameViewProperties.
1235 if (!aRootElementFrame)
1236 return true;
1237
1238 const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground();
1239 return !htmlBG->IsTransparent();
1240 }
1241
1242 bool
FindBackground(nsIFrame * aForFrame,nsStyleContext ** aBackgroundSC)1243 nsCSSRendering::FindBackground(nsIFrame* aForFrame,
1244 nsStyleContext** aBackgroundSC)
1245 {
1246 nsIFrame* rootElementFrame =
1247 aForFrame->PresContext()->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
1248 if (IsCanvasFrame(aForFrame)) {
1249 *aBackgroundSC = FindCanvasBackground(aForFrame, rootElementFrame);
1250 return true;
1251 } else {
1252 return FindElementBackground(aForFrame, rootElementFrame, aBackgroundSC);
1253 }
1254 }
1255
1256 void
BeginFrameTreesLocked()1257 nsCSSRendering::BeginFrameTreesLocked()
1258 {
1259 ++gFrameTreeLockCount;
1260 }
1261
1262 void
EndFrameTreesLocked()1263 nsCSSRendering::EndFrameTreesLocked()
1264 {
1265 NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked");
1266 --gFrameTreeLockCount;
1267 if (gFrameTreeLockCount == 0) {
1268 gInlineBGData->Reset();
1269 }
1270 }
1271
1272 void
PaintBoxShadowOuter(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aFrameArea,const nsRect & aDirtyRect,float aOpacity)1273 nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext,
1274 nsRenderingContext& aRenderingContext,
1275 nsIFrame* aForFrame,
1276 const nsRect& aFrameArea,
1277 const nsRect& aDirtyRect,
1278 float aOpacity)
1279 {
1280 DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
1281 nsCSSShadowArray* shadows = aForFrame->StyleEffects()->mBoxShadow;
1282 if (!shadows)
1283 return;
1284
1285 bool hasBorderRadius;
1286 bool nativeTheme; // mutually exclusive with hasBorderRadius
1287 const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay();
1288 nsITheme::Transparency transparency;
1289 if (aForFrame->IsThemed(styleDisplay, &transparency)) {
1290 // We don't respect border-radius for native-themed widgets
1291 hasBorderRadius = false;
1292 // For opaque (rectangular) theme widgets we can take the generic
1293 // border-box path with border-radius disabled.
1294 nativeTheme = transparency != nsITheme::eOpaque;
1295 } else {
1296 nativeTheme = false;
1297 hasBorderRadius = true; // we'll update this below
1298 }
1299
1300 nsRect frameRect = nativeTheme ?
1301 aForFrame->GetVisualOverflowRectRelativeToSelf() + aFrameArea.TopLeft() :
1302 aFrameArea;
1303 Sides skipSides = aForFrame->GetSkipSides();
1304 frameRect = ::BoxDecorationRectForBorder(aForFrame, frameRect, skipSides);
1305
1306 // Get any border radius, since box-shadow must also have rounded corners if
1307 // the frame does.
1308 RectCornerRadii borderRadii;
1309 const nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
1310 if (hasBorderRadius) {
1311 nscoord twipsRadii[8];
1312 NS_ASSERTION(aFrameArea.Size() == aForFrame->VisualBorderRectRelativeToSelf().Size(),
1313 "unexpected size");
1314 nsSize sz = frameRect.Size();
1315 hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
1316 if (hasBorderRadius) {
1317 ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii);
1318 }
1319 }
1320
1321
1322 // We don't show anything that intersects with the frame we're blurring on. So tell the
1323 // blurrer not to do unnecessary work there.
1324 gfxRect skipGfxRect = ThebesRect(NSRectToRect(frameRect, twipsPerPixel));
1325 skipGfxRect.Round();
1326 bool useSkipGfxRect = true;
1327 if (nativeTheme) {
1328 // Optimize non-leaf native-themed frames by skipping computing pixels
1329 // in the padding-box. We assume the padding-box is going to be painted
1330 // opaquely for non-leaf frames.
1331 // XXX this may not be a safe assumption; we should make this go away
1332 // by optimizing box-shadow drawing more for the cases where we don't have a skip-rect.
1333 useSkipGfxRect = !aForFrame->IsLeaf();
1334 nsRect paddingRect =
1335 aForFrame->GetPaddingRect() - aForFrame->GetPosition() + aFrameArea.TopLeft();
1336 skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, twipsPerPixel);
1337 } else if (hasBorderRadius) {
1338 skipGfxRect.Deflate(gfxMargin(
1339 std::max(borderRadii[C_TL].height, borderRadii[C_TR].height), 0,
1340 std::max(borderRadii[C_BL].height, borderRadii[C_BR].height), 0));
1341 }
1342
1343 gfxContext* renderContext = aRenderingContext.ThebesContext();
1344
1345 for (uint32_t i = shadows->Length(); i > 0; --i) {
1346 nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1);
1347 if (shadowItem->mInset)
1348 continue;
1349
1350 nsRect shadowRect = frameRect;
1351 shadowRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset);
1352 if (!nativeTheme) {
1353 shadowRect.Inflate(shadowItem->mSpread, shadowItem->mSpread);
1354 }
1355
1356 // shadowRect won't include the blur, so make an extra rect here that includes the blur
1357 // for use in the even-odd rule below.
1358 nsRect shadowRectPlusBlur = shadowRect;
1359 nscoord blurRadius = shadowItem->mRadius;
1360 shadowRectPlusBlur.Inflate(
1361 nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, twipsPerPixel));
1362
1363 Rect shadowGfxRectPlusBlur =
1364 NSRectToRect(shadowRectPlusBlur, twipsPerPixel);
1365 shadowGfxRectPlusBlur.RoundOut();
1366 MaybeSnapToDevicePixels(shadowGfxRectPlusBlur, aDrawTarget, true);
1367
1368 // Set the shadow color; if not specified, use the foreground color
1369 nscolor shadowColor;
1370 if (shadowItem->mHasColor)
1371 shadowColor = shadowItem->mColor;
1372 else
1373 shadowColor = aForFrame->StyleColor()->mColor;
1374
1375 Color gfxShadowColor(Color::FromABGR(shadowColor));
1376 gfxShadowColor.a *= aOpacity;
1377
1378 if (nativeTheme) {
1379 nsContextBoxBlur blurringArea;
1380
1381 // When getting the widget shape from the native theme, we're going
1382 // to draw the widget into the shadow surface to create a mask.
1383 // We need to ensure that there actually *is* a shadow surface
1384 // and that we're not going to draw directly into renderContext.
1385 gfxContext* shadowContext =
1386 blurringArea.Init(shadowRect, shadowItem->mSpread,
1387 blurRadius, twipsPerPixel, renderContext, aDirtyRect,
1388 useSkipGfxRect ? &skipGfxRect : nullptr,
1389 nsContextBoxBlur::FORCE_MASK);
1390 if (!shadowContext)
1391 continue;
1392
1393 MOZ_ASSERT(shadowContext == blurringArea.GetContext());
1394
1395 renderContext->Save();
1396 renderContext->SetColor(gfxShadowColor);
1397
1398 // Draw the shape of the frame so it can be blurred. Recall how nsContextBoxBlur
1399 // doesn't make any temporary surfaces if blur is 0 and it just returns the original
1400 // surface? If we have no blur, we're painting this fill on the actual content surface
1401 // (renderContext == shadowContext) which is why we set up the color and clip
1402 // before doing this.
1403
1404 // We don't clip the border-box from the shadow, nor any other box.
1405 // We assume that the native theme is going to paint over the shadow.
1406
1407 // Draw the widget shape
1408 gfxContextMatrixAutoSaveRestore save(shadowContext);
1409 gfxPoint devPixelOffset =
1410 nsLayoutUtils::PointToGfxPoint(nsPoint(shadowItem->mXOffset,
1411 shadowItem->mYOffset),
1412 aPresContext->AppUnitsPerDevPixel());
1413 shadowContext->SetMatrix(
1414 shadowContext->CurrentMatrix().Translate(devPixelOffset));
1415
1416 nsRect nativeRect = aDirtyRect;
1417 nativeRect.MoveBy(-nsPoint(shadowItem->mXOffset, shadowItem->mYOffset));
1418 nativeRect.IntersectRect(frameRect, nativeRect);
1419 nsRenderingContext wrapperCtx(shadowContext);
1420 aPresContext->GetTheme()->DrawWidgetBackground(&wrapperCtx, aForFrame,
1421 styleDisplay->mAppearance, aFrameArea, nativeRect);
1422
1423 blurringArea.DoPaint();
1424 renderContext->Restore();
1425 } else {
1426 renderContext->Save();
1427
1428 {
1429 Rect innerClipRect = NSRectToRect(frameRect, twipsPerPixel);
1430 if (!MaybeSnapToDevicePixels(innerClipRect, aDrawTarget, true)) {
1431 innerClipRect.Round();
1432 }
1433
1434 // Clip out the interior of the frame's border edge so that the shadow
1435 // is only painted outside that area.
1436 RefPtr<PathBuilder> builder =
1437 aDrawTarget.CreatePathBuilder(FillRule::FILL_EVEN_ODD);
1438 AppendRectToPath(builder, shadowGfxRectPlusBlur);
1439 if (hasBorderRadius) {
1440 AppendRoundedRectToPath(builder, innerClipRect, borderRadii);
1441 } else {
1442 AppendRectToPath(builder, innerClipRect);
1443 }
1444 RefPtr<Path> path = builder->Finish();
1445 renderContext->Clip(path);
1446 }
1447
1448 // Clip the shadow so that we only get the part that applies to aForFrame.
1449 nsRect fragmentClip = shadowRectPlusBlur;
1450 if (!skipSides.IsEmpty()) {
1451 if (skipSides.Left()) {
1452 nscoord xmost = fragmentClip.XMost();
1453 fragmentClip.x = aFrameArea.x;
1454 fragmentClip.width = xmost - fragmentClip.x;
1455 }
1456 if (skipSides.Right()) {
1457 nscoord xmost = fragmentClip.XMost();
1458 nscoord overflow = xmost - aFrameArea.XMost();
1459 if (overflow > 0) {
1460 fragmentClip.width -= overflow;
1461 }
1462 }
1463 if (skipSides.Top()) {
1464 nscoord ymost = fragmentClip.YMost();
1465 fragmentClip.y = aFrameArea.y;
1466 fragmentClip.height = ymost - fragmentClip.y;
1467 }
1468 if (skipSides.Bottom()) {
1469 nscoord ymost = fragmentClip.YMost();
1470 nscoord overflow = ymost - aFrameArea.YMost();
1471 if (overflow > 0) {
1472 fragmentClip.height -= overflow;
1473 }
1474 }
1475 }
1476 fragmentClip = fragmentClip.Intersect(aDirtyRect);
1477 renderContext->
1478 Clip(NSRectToSnappedRect(fragmentClip,
1479 aForFrame->PresContext()->AppUnitsPerDevPixel(),
1480 aDrawTarget));
1481
1482 RectCornerRadii clipRectRadii;
1483 if (hasBorderRadius) {
1484 Float spreadDistance = shadowItem->mSpread / twipsPerPixel;
1485
1486 Float borderSizes[4];
1487
1488 borderSizes[NS_SIDE_LEFT] = spreadDistance;
1489 borderSizes[NS_SIDE_TOP] = spreadDistance;
1490 borderSizes[NS_SIDE_RIGHT] = spreadDistance;
1491 borderSizes[NS_SIDE_BOTTOM] = spreadDistance;
1492
1493 nsCSSBorderRenderer::ComputeOuterRadii(borderRadii, borderSizes,
1494 &clipRectRadii);
1495
1496 }
1497 nsContextBoxBlur::BlurRectangle(renderContext,
1498 shadowRect,
1499 twipsPerPixel,
1500 hasBorderRadius ? &clipRectRadii : nullptr,
1501 blurRadius,
1502 gfxShadowColor,
1503 aDirtyRect,
1504 skipGfxRect);
1505 renderContext->Restore();
1506 }
1507
1508 }
1509 }
1510
1511 void
PaintBoxShadowInner(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aFrameArea)1512 nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext,
1513 nsRenderingContext& aRenderingContext,
1514 nsIFrame* aForFrame,
1515 const nsRect& aFrameArea)
1516 {
1517 nsCSSShadowArray* shadows = aForFrame->StyleEffects()->mBoxShadow;
1518 if (!shadows)
1519 return;
1520 if (aForFrame->IsThemed() && aForFrame->GetContent() &&
1521 !nsContentUtils::IsChromeDoc(aForFrame->GetContent()->GetUncomposedDoc())) {
1522 // There's no way of getting hold of a shape corresponding to a
1523 // "padding-box" for native-themed widgets, so just don't draw
1524 // inner box-shadows for them. But we allow chrome to paint inner
1525 // box shadows since chrome can be aware of the platform theme.
1526 return;
1527 }
1528
1529 NS_ASSERTION(aForFrame->GetType() == nsGkAtoms::fieldSetFrame ||
1530 aFrameArea.Size() == aForFrame->GetSize(), "unexpected size");
1531
1532 Sides skipSides = aForFrame->GetSkipSides();
1533 nsRect frameRect =
1534 ::BoxDecorationRectForBorder(aForFrame, aFrameArea, skipSides);
1535 nsRect paddingRect = frameRect;
1536 nsMargin border = aForFrame->GetUsedBorder();
1537 paddingRect.Deflate(border);
1538
1539 // Get any border radius, since box-shadow must also have rounded corners
1540 // if the frame does.
1541 nscoord twipsRadii[8];
1542 nsSize sz = frameRect.Size();
1543 bool hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
1544 const nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
1545
1546 RectCornerRadii innerRadii;
1547 if (hasBorderRadius) {
1548 RectCornerRadii borderRadii;
1549
1550 ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii);
1551 Float borderSizes[4] = {
1552 Float(border.top / twipsPerPixel),
1553 Float(border.right / twipsPerPixel),
1554 Float(border.bottom / twipsPerPixel),
1555 Float(border.left / twipsPerPixel)
1556 };
1557 nsCSSBorderRenderer::ComputeInnerRadii(borderRadii, borderSizes,
1558 &innerRadii);
1559 }
1560
1561 for (uint32_t i = shadows->Length(); i > 0; --i) {
1562 nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1);
1563 if (!shadowItem->mInset)
1564 continue;
1565
1566 // shadowPaintRect: the area to paint on the temp surface
1567 // shadowClipRect: the area on the temporary surface within shadowPaintRect
1568 // that we will NOT paint in
1569 nscoord blurRadius = shadowItem->mRadius;
1570 nsMargin blurMargin =
1571 nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, twipsPerPixel);
1572 nsRect shadowPaintRect = paddingRect;
1573 shadowPaintRect.Inflate(blurMargin);
1574
1575 Rect shadowPaintGfxRect = NSRectToRect(shadowPaintRect, twipsPerPixel);
1576 shadowPaintGfxRect.RoundOut();
1577
1578 // Round the spread radius to device pixels (by truncation).
1579 // This mostly matches what we do for borders, except that we don't round
1580 // up values between zero and one device pixels to one device pixel.
1581 // This way of rounding is symmetric around zero, which makes sense for
1582 // the spread radius.
1583 int32_t spreadDistance = shadowItem->mSpread / twipsPerPixel;
1584 nscoord spreadDistanceAppUnits = aPresContext->DevPixelsToAppUnits(spreadDistance);
1585
1586 nsRect shadowClipRect = paddingRect;
1587 shadowClipRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset);
1588 shadowClipRect.Deflate(spreadDistanceAppUnits, spreadDistanceAppUnits);
1589
1590 Rect shadowClipGfxRect = NSRectToRect(shadowClipRect, twipsPerPixel);
1591 shadowClipGfxRect.Round();
1592
1593 RectCornerRadii clipRectRadii;
1594 if (hasBorderRadius) {
1595 // Calculate the radii the inner clipping rect will have
1596 Float borderSizes[4] = {0, 0, 0, 0};
1597
1598 // See PaintBoxShadowOuter and bug 514670
1599 if (innerRadii[C_TL].width > 0 || innerRadii[C_BL].width > 0) {
1600 borderSizes[NS_SIDE_LEFT] = spreadDistance;
1601 }
1602
1603 if (innerRadii[C_TL].height > 0 || innerRadii[C_TR].height > 0) {
1604 borderSizes[NS_SIDE_TOP] = spreadDistance;
1605 }
1606
1607 if (innerRadii[C_TR].width > 0 || innerRadii[C_BR].width > 0) {
1608 borderSizes[NS_SIDE_RIGHT] = spreadDistance;
1609 }
1610
1611 if (innerRadii[C_BL].height > 0 || innerRadii[C_BR].height > 0) {
1612 borderSizes[NS_SIDE_BOTTOM] = spreadDistance;
1613 }
1614
1615 nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes,
1616 &clipRectRadii);
1617 }
1618
1619 // Set the "skip rect" to the area within the frame that we don't paint in,
1620 // including after blurring.
1621 nsRect skipRect = shadowClipRect;
1622 skipRect.Deflate(blurMargin);
1623 gfxRect skipGfxRect = nsLayoutUtils::RectToGfxRect(skipRect, twipsPerPixel);
1624 if (hasBorderRadius) {
1625 skipGfxRect.Deflate(gfxMargin(
1626 std::max(clipRectRadii[C_TL].height, clipRectRadii[C_TR].height), 0,
1627 std::max(clipRectRadii[C_BL].height, clipRectRadii[C_BR].height), 0));
1628 }
1629
1630 // When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area
1631 // unchanged. And by construction the gfxSkipRect is not touched by the
1632 // rendered shadow (even after blurring), so those pixels must be completely
1633 // transparent in the shadow, so drawing them changes nothing.
1634 gfxContext* renderContext = aRenderingContext.ThebesContext();
1635 DrawTarget* drawTarget = renderContext->GetDrawTarget();
1636 nsContextBoxBlur blurringArea;
1637
1638 // Clip the context to the area of the frame's padding rect, so no part of the
1639 // shadow is painted outside. Also cut out anything beyond where the inset shadow
1640 // will be.
1641 Rect shadowGfxRect = NSRectToRect(paddingRect, twipsPerPixel);
1642 shadowGfxRect.Round();
1643
1644 // Set the shadow color; if not specified, use the foreground color
1645 Color shadowColor = Color::FromABGR(shadowItem->mHasColor ?
1646 shadowItem->mColor :
1647 aForFrame->StyleColor()->mColor);
1648
1649 renderContext->Save();
1650
1651 // This clips the outside border radius.
1652 // clipRectRadii is the border radius inside the inset shadow.
1653 if (hasBorderRadius) {
1654 RefPtr<Path> roundedRect =
1655 MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii);
1656 renderContext->Clip(roundedRect);
1657 } else {
1658 renderContext->Clip(shadowGfxRect);
1659 }
1660
1661 nsContextBoxBlur insetBoxBlur;
1662 gfxRect destRect = nsLayoutUtils::RectToGfxRect(shadowPaintRect, twipsPerPixel);
1663 Point shadowOffset(shadowItem->mXOffset / twipsPerPixel,
1664 shadowItem->mYOffset / twipsPerPixel);
1665
1666 insetBoxBlur.InsetBoxBlur(renderContext, ToRect(destRect),
1667 shadowClipGfxRect, shadowColor,
1668 blurRadius, spreadDistanceAppUnits,
1669 twipsPerPixel, hasBorderRadius,
1670 clipRectRadii, ToRect(skipGfxRect),
1671 shadowOffset);
1672 renderContext->Restore();
1673 }
1674 }
1675
1676 /* static */
1677 nsCSSRendering::PaintBGParams
ForAllLayers(nsPresContext & aPresCtx,nsRenderingContext & aRenderingCtx,const nsRect & aDirtyRect,const nsRect & aBorderArea,nsIFrame * aFrame,uint32_t aPaintFlags)1678 nsCSSRendering::PaintBGParams::ForAllLayers(nsPresContext& aPresCtx,
1679 nsRenderingContext& aRenderingCtx,
1680 const nsRect& aDirtyRect,
1681 const nsRect& aBorderArea,
1682 nsIFrame *aFrame,
1683 uint32_t aPaintFlags)
1684 {
1685 MOZ_ASSERT(aFrame);
1686
1687 PaintBGParams result(aPresCtx, aRenderingCtx, aDirtyRect, aBorderArea, aFrame,
1688 aPaintFlags, -1, CompositionOp::OP_OVER);
1689
1690 return result;
1691 }
1692
1693 /* static */
1694 nsCSSRendering::PaintBGParams
ForSingleLayer(nsPresContext & aPresCtx,nsRenderingContext & aRenderingCtx,const nsRect & aDirtyRect,const nsRect & aBorderArea,nsIFrame * aFrame,uint32_t aPaintFlags,int32_t aLayer,CompositionOp aCompositionOp)1695 nsCSSRendering::PaintBGParams::ForSingleLayer(nsPresContext& aPresCtx,
1696 nsRenderingContext& aRenderingCtx,
1697 const nsRect& aDirtyRect,
1698 const nsRect& aBorderArea,
1699 nsIFrame *aFrame,
1700 uint32_t aPaintFlags,
1701 int32_t aLayer,
1702 CompositionOp aCompositionOp)
1703 {
1704 MOZ_ASSERT(aFrame && (aLayer != -1));
1705
1706 PaintBGParams result(aPresCtx, aRenderingCtx, aDirtyRect, aBorderArea, aFrame,
1707 aPaintFlags, aLayer, aCompositionOp);
1708
1709 return result;
1710 }
1711
1712 DrawResult
PaintBackground(const PaintBGParams & aParams)1713 nsCSSRendering::PaintBackground(const PaintBGParams& aParams)
1714 {
1715 PROFILER_LABEL("nsCSSRendering", "PaintBackground",
1716 js::ProfileEntry::Category::GRAPHICS);
1717
1718 NS_PRECONDITION(aParams.frame,
1719 "Frame is expected to be provided to PaintBackground");
1720
1721 nsStyleContext *sc;
1722 if (!FindBackground(aParams.frame, &sc)) {
1723 // We don't want to bail out if moz-appearance is set on a root
1724 // node. If it has a parent content node, bail because it's not
1725 // a root, otherwise keep going in order to let the theme stuff
1726 // draw the background. The canvas really should be drawing the
1727 // bg, but there's no way to hook that up via css.
1728 if (!aParams.frame->StyleDisplay()->mAppearance) {
1729 return DrawResult::SUCCESS;
1730 }
1731
1732 nsIContent* content = aParams.frame->GetContent();
1733 if (!content || content->GetParent()) {
1734 return DrawResult::SUCCESS;
1735 }
1736
1737 sc = aParams.frame->StyleContext();
1738 }
1739
1740 return PaintBackgroundWithSC(aParams, sc, *aParams.frame->StyleBorder());
1741 }
1742
1743 static bool
IsOpaqueBorderEdge(const nsStyleBorder & aBorder,mozilla::css::Side aSide)1744 IsOpaqueBorderEdge(const nsStyleBorder& aBorder, mozilla::css::Side aSide)
1745 {
1746 if (aBorder.GetComputedBorder().Side(aSide) == 0)
1747 return true;
1748 switch (aBorder.GetBorderStyle(aSide)) {
1749 case NS_STYLE_BORDER_STYLE_SOLID:
1750 case NS_STYLE_BORDER_STYLE_GROOVE:
1751 case NS_STYLE_BORDER_STYLE_RIDGE:
1752 case NS_STYLE_BORDER_STYLE_INSET:
1753 case NS_STYLE_BORDER_STYLE_OUTSET:
1754 break;
1755 default:
1756 return false;
1757 }
1758
1759 // If we're using a border image, assume it's not fully opaque,
1760 // because we may not even have the image loaded at this point, and
1761 // even if we did, checking whether the relevant tile is fully
1762 // opaque would be too much work.
1763 if (aBorder.mBorderImageSource.GetType() != eStyleImageType_Null)
1764 return false;
1765
1766 StyleComplexColor color = aBorder.mBorderColor[aSide];
1767 // We don't know the foreground color here, so if it's being used
1768 // we must assume it might be transparent.
1769 if (!color.IsNumericColor()) {
1770 return false;
1771 }
1772 return NS_GET_A(color.mColor) == 255;
1773 }
1774
1775 /**
1776 * Returns true if all border edges are either missing or opaque.
1777 */
1778 static bool
IsOpaqueBorder(const nsStyleBorder & aBorder)1779 IsOpaqueBorder(const nsStyleBorder& aBorder)
1780 {
1781 if (aBorder.mBorderColors)
1782 return false;
1783 NS_FOR_CSS_SIDES(i) {
1784 if (!IsOpaqueBorderEdge(aBorder, i))
1785 return false;
1786 }
1787 return true;
1788 }
1789
1790 static inline void
SetupDirtyRects(const nsRect & aBGClipArea,const nsRect & aCallerDirtyRect,nscoord aAppUnitsPerPixel,nsRect * aDirtyRect,gfxRect * aDirtyRectGfx)1791 SetupDirtyRects(const nsRect& aBGClipArea, const nsRect& aCallerDirtyRect,
1792 nscoord aAppUnitsPerPixel,
1793 /* OUT: */
1794 nsRect* aDirtyRect, gfxRect* aDirtyRectGfx)
1795 {
1796 aDirtyRect->IntersectRect(aBGClipArea, aCallerDirtyRect);
1797
1798 // Compute the Thebes equivalent of the dirtyRect.
1799 *aDirtyRectGfx = nsLayoutUtils::RectToGfxRect(*aDirtyRect, aAppUnitsPerPixel);
1800 NS_WARNING_ASSERTION(aDirtyRect->IsEmpty() || !aDirtyRectGfx->IsEmpty(),
1801 "converted dirty rect should not be empty");
1802 MOZ_ASSERT(!aDirtyRect->IsEmpty() || aDirtyRectGfx->IsEmpty(),
1803 "second should be empty if first is");
1804 }
1805
1806 /* static */ void
GetImageLayerClip(const nsStyleImageLayers::Layer & aLayer,nsIFrame * aForFrame,const nsStyleBorder & aBorder,const nsRect & aBorderArea,const nsRect & aCallerDirtyRect,bool aWillPaintBorder,nscoord aAppUnitsPerPixel,ImageLayerClipState * aClipState)1807 nsCSSRendering::GetImageLayerClip(const nsStyleImageLayers::Layer& aLayer,
1808 nsIFrame* aForFrame, const nsStyleBorder& aBorder,
1809 const nsRect& aBorderArea, const nsRect& aCallerDirtyRect,
1810 bool aWillPaintBorder, nscoord aAppUnitsPerPixel,
1811 /* out */ ImageLayerClipState* aClipState)
1812 {
1813 // Compute the outermost boundary of the area that might be painted.
1814 // Same coordinate space as aBorderArea.
1815 Sides skipSides = aForFrame->GetSkipSides();
1816 nsRect clipBorderArea =
1817 ::BoxDecorationRectForBorder(aForFrame, aBorderArea, skipSides, &aBorder);
1818
1819 bool haveRoundedCorners = GetRadii(aForFrame, aBorder, aBorderArea,
1820 clipBorderArea, aClipState->mRadii);
1821
1822 uint8_t backgroundClip = aLayer.mClip;
1823
1824 bool isSolidBorder =
1825 aWillPaintBorder && IsOpaqueBorder(aBorder);
1826 if (isSolidBorder && backgroundClip == NS_STYLE_IMAGELAYER_CLIP_BORDER) {
1827 // If we have rounded corners, we need to inflate the background
1828 // drawing area a bit to avoid seams between the border and
1829 // background.
1830 backgroundClip = haveRoundedCorners ?
1831 NS_STYLE_IMAGELAYER_CLIP_MOZ_ALMOST_PADDING : NS_STYLE_IMAGELAYER_CLIP_PADDING;
1832 }
1833
1834 aClipState->mBGClipArea = clipBorderArea;
1835 aClipState->mHasAdditionalBGClipArea = false;
1836 aClipState->mCustomClip = false;
1837
1838 if (aForFrame->GetType() == nsGkAtoms::scrollFrame &&
1839 NS_STYLE_IMAGELAYER_ATTACHMENT_LOCAL == aLayer.mAttachment) {
1840 // As of this writing, this is still in discussion in the CSS Working Group
1841 // http://lists.w3.org/Archives/Public/www-style/2013Jul/0250.html
1842
1843 // The rectangle for 'background-clip' scrolls with the content,
1844 // but the background is also clipped at a non-scrolling 'padding-box'
1845 // like the content. (See below.)
1846 // Therefore, only 'content-box' makes a difference here.
1847 if (backgroundClip == NS_STYLE_IMAGELAYER_CLIP_CONTENT) {
1848 nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
1849 // Clip at a rectangle attached to the scrolled content.
1850 aClipState->mHasAdditionalBGClipArea = true;
1851 aClipState->mAdditionalBGClipArea = nsRect(
1852 aClipState->mBGClipArea.TopLeft()
1853 + scrollableFrame->GetScrolledFrame()->GetPosition()
1854 // For the dir=rtl case:
1855 + scrollableFrame->GetScrollRange().TopLeft(),
1856 scrollableFrame->GetScrolledRect().Size());
1857 nsMargin padding = aForFrame->GetUsedPadding();
1858 // padding-bottom is ignored on scrollable frames:
1859 // https://bugzilla.mozilla.org/show_bug.cgi?id=748518
1860 padding.bottom = 0;
1861 padding.ApplySkipSides(skipSides);
1862 aClipState->mAdditionalBGClipArea.Deflate(padding);
1863 }
1864
1865 // Also clip at a non-scrolling, rounded-corner 'padding-box',
1866 // same as the scrolled content because of the 'overflow' property.
1867 backgroundClip = NS_STYLE_IMAGELAYER_CLIP_PADDING;
1868 }
1869
1870 if (backgroundClip != NS_STYLE_IMAGELAYER_CLIP_BORDER &&
1871 backgroundClip != NS_STYLE_IMAGELAYER_CLIP_TEXT) {
1872 nsMargin border = aForFrame->GetUsedBorder();
1873 if (backgroundClip == NS_STYLE_IMAGELAYER_CLIP_MOZ_ALMOST_PADDING) {
1874 // Reduce |border| by 1px (device pixels) on all sides, if
1875 // possible, so that we don't get antialiasing seams between the
1876 // background and border.
1877 border.top = std::max(0, border.top - aAppUnitsPerPixel);
1878 border.right = std::max(0, border.right - aAppUnitsPerPixel);
1879 border.bottom = std::max(0, border.bottom - aAppUnitsPerPixel);
1880 border.left = std::max(0, border.left - aAppUnitsPerPixel);
1881 } else if (backgroundClip != NS_STYLE_IMAGELAYER_CLIP_PADDING) {
1882 NS_ASSERTION(backgroundClip == NS_STYLE_IMAGELAYER_CLIP_CONTENT,
1883 "unexpected background-clip");
1884 border += aForFrame->GetUsedPadding();
1885 }
1886 border.ApplySkipSides(skipSides);
1887 aClipState->mBGClipArea.Deflate(border);
1888
1889 if (haveRoundedCorners) {
1890 nsIFrame::InsetBorderRadii(aClipState->mRadii, border);
1891 }
1892 }
1893
1894 if (haveRoundedCorners) {
1895 auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
1896 nsCSSRendering::ComputePixelRadii(aClipState->mRadii, d2a, &aClipState->mClippedRadii);
1897 aClipState->mHasRoundedCorners = true;
1898 } else {
1899 aClipState->mHasRoundedCorners = false;
1900 }
1901
1902
1903 if (!haveRoundedCorners && aClipState->mHasAdditionalBGClipArea) {
1904 // Do the intersection here to account for the fast path(?) below.
1905 aClipState->mBGClipArea =
1906 aClipState->mBGClipArea.Intersect(aClipState->mAdditionalBGClipArea);
1907 aClipState->mHasAdditionalBGClipArea = false;
1908 }
1909
1910 SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, aAppUnitsPerPixel,
1911 &aClipState->mDirtyRect, &aClipState->mDirtyRectGfx);
1912 }
1913
1914 static void
SetupImageLayerClip(nsCSSRendering::ImageLayerClipState & aClipState,gfxContext * aCtx,nscoord aAppUnitsPerPixel,gfxContextAutoSaveRestore * aAutoSR)1915 SetupImageLayerClip(nsCSSRendering::ImageLayerClipState& aClipState,
1916 gfxContext *aCtx, nscoord aAppUnitsPerPixel,
1917 gfxContextAutoSaveRestore* aAutoSR)
1918 {
1919 if (aClipState.mDirtyRectGfx.IsEmpty()) {
1920 // Our caller won't draw anything under this condition, so no need
1921 // to set more up.
1922 return;
1923 }
1924
1925 if (aClipState.mCustomClip) {
1926 // We don't support custom clips and rounded corners, arguably a bug, but
1927 // table painting seems to depend on it.
1928 return;
1929 }
1930
1931 DrawTarget* drawTarget = aCtx->GetDrawTarget();
1932
1933 // If we have rounded corners, clip all subsequent drawing to the
1934 // rounded rectangle defined by bgArea and bgRadii (we don't know
1935 // whether the rounded corners intrude on the dirtyRect or not).
1936 // Do not do this if we have a caller-provided clip rect --
1937 // as above with bgArea, arguably a bug, but table painting seems
1938 // to depend on it.
1939
1940 if (aClipState.mHasAdditionalBGClipArea) {
1941 gfxRect bgAreaGfx = nsLayoutUtils::RectToGfxRect(
1942 aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
1943 bgAreaGfx.Round();
1944 bgAreaGfx.Condition();
1945
1946 aAutoSR->EnsureSaved(aCtx);
1947 aCtx->NewPath();
1948 aCtx->Rectangle(bgAreaGfx, true);
1949 aCtx->Clip();
1950 }
1951
1952 if (aClipState.mHasRoundedCorners) {
1953 Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
1954 bgAreaGfx.Round();
1955
1956 if (bgAreaGfx.IsEmpty()) {
1957 // I think it's become possible to hit this since
1958 // http://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
1959 NS_WARNING("converted background area should not be empty");
1960 // Make our caller not do anything.
1961 aClipState.mDirtyRectGfx.SizeTo(gfxSize(0.0, 0.0));
1962 return;
1963 }
1964
1965 aAutoSR->EnsureSaved(aCtx);
1966
1967 RefPtr<Path> roundedRect =
1968 MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii);
1969 aCtx->Clip(roundedRect);
1970 }
1971 }
1972
1973 static void
DrawBackgroundColor(nsCSSRendering::ImageLayerClipState & aClipState,gfxContext * aCtx,nscoord aAppUnitsPerPixel)1974 DrawBackgroundColor(nsCSSRendering::ImageLayerClipState& aClipState,
1975 gfxContext *aCtx, nscoord aAppUnitsPerPixel)
1976 {
1977 if (aClipState.mDirtyRectGfx.IsEmpty()) {
1978 // Our caller won't draw anything under this condition, so no need
1979 // to set more up.
1980 return;
1981 }
1982
1983 DrawTarget* drawTarget = aCtx->GetDrawTarget();
1984
1985 // We don't support custom clips and rounded corners, arguably a bug, but
1986 // table painting seems to depend on it.
1987 if (!aClipState.mHasRoundedCorners || aClipState.mCustomClip) {
1988 aCtx->NewPath();
1989 aCtx->Rectangle(aClipState.mDirtyRectGfx, true);
1990 aCtx->Fill();
1991 return;
1992 }
1993
1994 Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
1995 bgAreaGfx.Round();
1996
1997 if (bgAreaGfx.IsEmpty()) {
1998 // I think it's become possible to hit this since
1999 // http://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
2000 NS_WARNING("converted background area should not be empty");
2001 // Make our caller not do anything.
2002 aClipState.mDirtyRectGfx.SizeTo(gfxSize(0.0, 0.0));
2003 return;
2004 }
2005
2006 aCtx->Save();
2007 gfxRect dirty = ThebesRect(bgAreaGfx).Intersect(aClipState.mDirtyRectGfx);
2008
2009 aCtx->NewPath();
2010 aCtx->Rectangle(dirty, true);
2011 aCtx->Clip();
2012
2013 if (aClipState.mHasAdditionalBGClipArea) {
2014 gfxRect bgAdditionalAreaGfx = nsLayoutUtils::RectToGfxRect(
2015 aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
2016 bgAdditionalAreaGfx.Round();
2017 bgAdditionalAreaGfx.Condition();
2018 aCtx->NewPath();
2019 aCtx->Rectangle(bgAdditionalAreaGfx, true);
2020 aCtx->Clip();
2021 }
2022
2023 RefPtr<Path> roundedRect =
2024 MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii);
2025 aCtx->SetPath(roundedRect);
2026 aCtx->Fill();
2027 aCtx->Restore();
2028 }
2029
2030 nscolor
DetermineBackgroundColor(nsPresContext * aPresContext,nsStyleContext * aStyleContext,nsIFrame * aFrame,bool & aDrawBackgroundImage,bool & aDrawBackgroundColor)2031 nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext,
2032 nsStyleContext* aStyleContext,
2033 nsIFrame* aFrame,
2034 bool& aDrawBackgroundImage,
2035 bool& aDrawBackgroundColor)
2036 {
2037 aDrawBackgroundImage = true;
2038 aDrawBackgroundColor = true;
2039
2040 const nsStyleVisibility* visibility = aStyleContext->StyleVisibility();
2041
2042 if (visibility->mColorAdjust != NS_STYLE_COLOR_ADJUST_EXACT &&
2043 aFrame->HonorPrintBackgroundSettings()) {
2044 aDrawBackgroundImage = aPresContext->GetBackgroundImageDraw();
2045 aDrawBackgroundColor = aPresContext->GetBackgroundColorDraw();
2046 }
2047
2048 const nsStyleBackground *bg = aStyleContext->StyleBackground();
2049 nscolor bgColor;
2050 if (aDrawBackgroundColor) {
2051 bgColor =
2052 aStyleContext->GetVisitedDependentColor(eCSSProperty_background_color);
2053 if (NS_GET_A(bgColor) == 0) {
2054 aDrawBackgroundColor = false;
2055 }
2056 } else {
2057 // If GetBackgroundColorDraw() is false, we are still expected to
2058 // draw color in the background of any frame that's not completely
2059 // transparent, but we are expected to use white instead of whatever
2060 // color was specified.
2061 bgColor = NS_RGB(255, 255, 255);
2062 if (aDrawBackgroundImage || !bg->IsTransparent()) {
2063 aDrawBackgroundColor = true;
2064 } else {
2065 bgColor = NS_RGBA(0,0,0,0);
2066 }
2067 }
2068
2069 // We can skip painting the background color if a background image is opaque.
2070 nsStyleImageLayers::Repeat repeat = bg->BottomLayer().mRepeat;
2071 bool xFullRepeat = repeat.mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
2072 repeat.mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND;
2073 bool yFullRepeat = repeat.mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
2074 repeat.mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND;
2075 if (aDrawBackgroundColor &&
2076 xFullRepeat && yFullRepeat &&
2077 bg->BottomLayer().mImage.IsOpaque() &&
2078 bg->BottomLayer().mBlendMode == NS_STYLE_BLEND_NORMAL) {
2079 aDrawBackgroundColor = false;
2080 }
2081
2082 return bgColor;
2083 }
2084
2085 static gfxFloat
ConvertGradientValueToPixels(const nsStyleCoord & aCoord,gfxFloat aFillLength,int32_t aAppUnitsPerPixel)2086 ConvertGradientValueToPixels(const nsStyleCoord& aCoord,
2087 gfxFloat aFillLength,
2088 int32_t aAppUnitsPerPixel)
2089 {
2090 switch (aCoord.GetUnit()) {
2091 case eStyleUnit_Percent:
2092 return aCoord.GetPercentValue() * aFillLength;
2093 case eStyleUnit_Coord:
2094 return NSAppUnitsToFloatPixels(aCoord.GetCoordValue(), aAppUnitsPerPixel);
2095 case eStyleUnit_Calc: {
2096 const nsStyleCoord::Calc *calc = aCoord.GetCalcValue();
2097 return calc->mPercent * aFillLength +
2098 NSAppUnitsToFloatPixels(calc->mLength, aAppUnitsPerPixel);
2099 }
2100 default:
2101 NS_WARNING("Unexpected coord unit");
2102 return 0;
2103 }
2104 }
2105
2106 // Given a box with size aBoxSize and origin (0,0), and an angle aAngle,
2107 // and a starting point for the gradient line aStart, find the endpoint of
2108 // the gradient line --- the intersection of the gradient line with a line
2109 // perpendicular to aAngle that passes through the farthest corner in the
2110 // direction aAngle.
2111 static gfxPoint
ComputeGradientLineEndFromAngle(const gfxPoint & aStart,double aAngle,const gfxSize & aBoxSize)2112 ComputeGradientLineEndFromAngle(const gfxPoint& aStart,
2113 double aAngle,
2114 const gfxSize& aBoxSize)
2115 {
2116 double dx = cos(-aAngle);
2117 double dy = sin(-aAngle);
2118 gfxPoint farthestCorner(dx > 0 ? aBoxSize.width : 0,
2119 dy > 0 ? aBoxSize.height : 0);
2120 gfxPoint delta = farthestCorner - aStart;
2121 double u = delta.x*dy - delta.y*dx;
2122 return farthestCorner + gfxPoint(-u*dy, u*dx);
2123 }
2124
2125 // Compute the start and end points of the gradient line for a linear gradient.
2126 static void
ComputeLinearGradientLine(nsPresContext * aPresContext,nsStyleGradient * aGradient,const gfxSize & aBoxSize,gfxPoint * aLineStart,gfxPoint * aLineEnd)2127 ComputeLinearGradientLine(nsPresContext* aPresContext,
2128 nsStyleGradient* aGradient,
2129 const gfxSize& aBoxSize,
2130 gfxPoint* aLineStart,
2131 gfxPoint* aLineEnd)
2132 {
2133 if (aGradient->mBgPosX.GetUnit() == eStyleUnit_None) {
2134 double angle;
2135 if (aGradient->mAngle.IsAngleValue()) {
2136 angle = aGradient->mAngle.GetAngleValueInRadians();
2137 if (!aGradient->mLegacySyntax) {
2138 angle = M_PI_2 - angle;
2139 }
2140 } else {
2141 angle = -M_PI_2; // defaults to vertical gradient starting from top
2142 }
2143 gfxPoint center(aBoxSize.width/2, aBoxSize.height/2);
2144 *aLineEnd = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
2145 *aLineStart = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineEnd;
2146 } else if (!aGradient->mLegacySyntax) {
2147 float xSign = aGradient->mBgPosX.GetPercentValue() * 2 - 1;
2148 float ySign = 1 - aGradient->mBgPosY.GetPercentValue() * 2;
2149 double angle = atan2(ySign * aBoxSize.width, xSign * aBoxSize.height);
2150 gfxPoint center(aBoxSize.width/2, aBoxSize.height/2);
2151 *aLineEnd = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
2152 *aLineStart = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineEnd;
2153 } else {
2154 int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
2155 *aLineStart = gfxPoint(
2156 ConvertGradientValueToPixels(aGradient->mBgPosX, aBoxSize.width,
2157 appUnitsPerPixel),
2158 ConvertGradientValueToPixels(aGradient->mBgPosY, aBoxSize.height,
2159 appUnitsPerPixel));
2160 if (aGradient->mAngle.IsAngleValue()) {
2161 MOZ_ASSERT(aGradient->mLegacySyntax);
2162 double angle = aGradient->mAngle.GetAngleValueInRadians();
2163 *aLineEnd = ComputeGradientLineEndFromAngle(*aLineStart, angle, aBoxSize);
2164 } else {
2165 // No angle, the line end is just the reflection of the start point
2166 // through the center of the box
2167 *aLineEnd = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineStart;
2168 }
2169 }
2170 }
2171
2172 // Compute the start and end points of the gradient line for a radial gradient.
2173 // Also returns the horizontal and vertical radii defining the circle or
2174 // ellipse to use.
2175 static void
ComputeRadialGradientLine(nsPresContext * aPresContext,nsStyleGradient * aGradient,const gfxSize & aBoxSize,gfxPoint * aLineStart,gfxPoint * aLineEnd,double * aRadiusX,double * aRadiusY)2176 ComputeRadialGradientLine(nsPresContext* aPresContext,
2177 nsStyleGradient* aGradient,
2178 const gfxSize& aBoxSize,
2179 gfxPoint* aLineStart,
2180 gfxPoint* aLineEnd,
2181 double* aRadiusX,
2182 double* aRadiusY)
2183 {
2184 if (aGradient->mBgPosX.GetUnit() == eStyleUnit_None) {
2185 // Default line start point is the center of the box
2186 *aLineStart = gfxPoint(aBoxSize.width/2, aBoxSize.height/2);
2187 } else {
2188 int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
2189 *aLineStart = gfxPoint(
2190 ConvertGradientValueToPixels(aGradient->mBgPosX, aBoxSize.width,
2191 appUnitsPerPixel),
2192 ConvertGradientValueToPixels(aGradient->mBgPosY, aBoxSize.height,
2193 appUnitsPerPixel));
2194 }
2195
2196 // Compute gradient shape: the x and y radii of an ellipse.
2197 double radiusX, radiusY;
2198 double leftDistance = Abs(aLineStart->x);
2199 double rightDistance = Abs(aBoxSize.width - aLineStart->x);
2200 double topDistance = Abs(aLineStart->y);
2201 double bottomDistance = Abs(aBoxSize.height - aLineStart->y);
2202 switch (aGradient->mSize) {
2203 case NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE:
2204 radiusX = std::min(leftDistance, rightDistance);
2205 radiusY = std::min(topDistance, bottomDistance);
2206 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
2207 radiusX = radiusY = std::min(radiusX, radiusY);
2208 }
2209 break;
2210 case NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER: {
2211 // Compute x and y distances to nearest corner
2212 double offsetX = std::min(leftDistance, rightDistance);
2213 double offsetY = std::min(topDistance, bottomDistance);
2214 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
2215 radiusX = radiusY = NS_hypot(offsetX, offsetY);
2216 } else {
2217 // maintain aspect ratio
2218 radiusX = offsetX*M_SQRT2;
2219 radiusY = offsetY*M_SQRT2;
2220 }
2221 break;
2222 }
2223 case NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE:
2224 radiusX = std::max(leftDistance, rightDistance);
2225 radiusY = std::max(topDistance, bottomDistance);
2226 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
2227 radiusX = radiusY = std::max(radiusX, radiusY);
2228 }
2229 break;
2230 case NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER: {
2231 // Compute x and y distances to nearest corner
2232 double offsetX = std::max(leftDistance, rightDistance);
2233 double offsetY = std::max(topDistance, bottomDistance);
2234 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
2235 radiusX = radiusY = NS_hypot(offsetX, offsetY);
2236 } else {
2237 // maintain aspect ratio
2238 radiusX = offsetX*M_SQRT2;
2239 radiusY = offsetY*M_SQRT2;
2240 }
2241 break;
2242 }
2243 case NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE: {
2244 int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
2245 radiusX = ConvertGradientValueToPixels(aGradient->mRadiusX,
2246 aBoxSize.width, appUnitsPerPixel);
2247 radiusY = ConvertGradientValueToPixels(aGradient->mRadiusY,
2248 aBoxSize.height, appUnitsPerPixel);
2249 break;
2250 }
2251 default:
2252 radiusX = radiusY = 0;
2253 MOZ_ASSERT(false, "unknown radial gradient sizing method");
2254 }
2255 *aRadiusX = radiusX;
2256 *aRadiusY = radiusY;
2257
2258 double angle;
2259 if (aGradient->mAngle.IsAngleValue()) {
2260 angle = aGradient->mAngle.GetAngleValueInRadians();
2261 } else {
2262 // Default angle is 0deg
2263 angle = 0.0;
2264 }
2265
2266 // The gradient line end point is where the gradient line intersects
2267 // the ellipse.
2268 *aLineEnd = *aLineStart + gfxPoint(radiusX*cos(-angle), radiusY*sin(-angle));
2269 }
2270
2271
Interpolate(float aF1,float aF2,float aFrac)2272 static float Interpolate(float aF1, float aF2, float aFrac)
2273 {
2274 return aF1 + aFrac * (aF2 - aF1);
2275 }
2276
2277 // Returns aFrac*aC2 + (1 - aFrac)*C1. The interpolation is done
2278 // in unpremultiplied space, which is what SVG gradients and cairo
2279 // gradients expect.
2280 static Color
InterpolateColor(const Color & aC1,const Color & aC2,float aFrac)2281 InterpolateColor(const Color& aC1, const Color& aC2, float aFrac)
2282 {
2283 double other = 1 - aFrac;
2284 return Color(aC2.r*aFrac + aC1.r*other,
2285 aC2.g*aFrac + aC1.g*other,
2286 aC2.b*aFrac + aC1.b*other,
2287 aC2.a*aFrac + aC1.a*other);
2288 }
2289
2290 static nscoord
FindTileStart(nscoord aDirtyCoord,nscoord aTilePos,nscoord aTileDim)2291 FindTileStart(nscoord aDirtyCoord, nscoord aTilePos, nscoord aTileDim)
2292 {
2293 NS_ASSERTION(aTileDim > 0, "Non-positive tile dimension");
2294 double multiples = floor(double(aDirtyCoord - aTilePos)/aTileDim);
2295 return NSToCoordRound(multiples*aTileDim + aTilePos);
2296 }
2297
2298 static gfxFloat
LinearGradientStopPositionForPoint(const gfxPoint & aGradientStart,const gfxPoint & aGradientEnd,const gfxPoint & aPoint)2299 LinearGradientStopPositionForPoint(const gfxPoint& aGradientStart,
2300 const gfxPoint& aGradientEnd,
2301 const gfxPoint& aPoint)
2302 {
2303 gfxPoint d = aGradientEnd - aGradientStart;
2304 gfxPoint p = aPoint - aGradientStart;
2305 /**
2306 * Compute a parameter t such that a line perpendicular to the
2307 * d vector, passing through aGradientStart + d*t, also
2308 * passes through aPoint.
2309 *
2310 * t is given by
2311 * (p.x - d.x*t)*d.x + (p.y - d.y*t)*d.y = 0
2312 *
2313 * Solving for t we get
2314 * numerator = d.x*p.x + d.y*p.y
2315 * denominator = d.x^2 + d.y^2
2316 * t = numerator/denominator
2317 *
2318 * In nsCSSRendering::PaintGradient we know the length of d
2319 * is not zero.
2320 */
2321 double numerator = d.x * p.x + d.y * p.y;
2322 double denominator = d.x * d.x + d.y * d.y;
2323 return numerator / denominator;
2324 }
2325
2326 static bool
RectIsBeyondLinearGradientEdge(const gfxRect & aRect,const gfxMatrix & aPatternMatrix,const nsTArray<ColorStop> & aStops,const gfxPoint & aGradientStart,const gfxPoint & aGradientEnd,Color * aOutEdgeColor)2327 RectIsBeyondLinearGradientEdge(const gfxRect& aRect,
2328 const gfxMatrix& aPatternMatrix,
2329 const nsTArray<ColorStop>& aStops,
2330 const gfxPoint& aGradientStart,
2331 const gfxPoint& aGradientEnd,
2332 Color* aOutEdgeColor)
2333 {
2334 gfxFloat topLeft = LinearGradientStopPositionForPoint(
2335 aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.TopLeft()));
2336 gfxFloat topRight = LinearGradientStopPositionForPoint(
2337 aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.TopRight()));
2338 gfxFloat bottomLeft = LinearGradientStopPositionForPoint(
2339 aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.BottomLeft()));
2340 gfxFloat bottomRight = LinearGradientStopPositionForPoint(
2341 aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.BottomRight()));
2342
2343 const ColorStop& firstStop = aStops[0];
2344 if (topLeft < firstStop.mPosition && topRight < firstStop.mPosition &&
2345 bottomLeft < firstStop.mPosition && bottomRight < firstStop.mPosition) {
2346 *aOutEdgeColor = firstStop.mColor;
2347 return true;
2348 }
2349
2350 const ColorStop& lastStop = aStops.LastElement();
2351 if (topLeft >= lastStop.mPosition && topRight >= lastStop.mPosition &&
2352 bottomLeft >= lastStop.mPosition && bottomRight >= lastStop.mPosition) {
2353 *aOutEdgeColor = lastStop.mColor;
2354 return true;
2355 }
2356
2357 return false;
2358 }
2359
ResolveMidpoints(nsTArray<ColorStop> & stops)2360 static void ResolveMidpoints(nsTArray<ColorStop>& stops)
2361 {
2362 for (size_t x = 1; x < stops.Length() - 1;) {
2363 if (!stops[x].mIsMidpoint) {
2364 x++;
2365 continue;
2366 }
2367
2368 Color color1 = stops[x-1].mColor;
2369 Color color2 = stops[x+1].mColor;
2370 float offset1 = stops[x-1].mPosition;
2371 float offset2 = stops[x+1].mPosition;
2372 float offset = stops[x].mPosition;
2373 // check if everything coincides. If so, ignore the midpoint.
2374 if (offset - offset1 == offset2 - offset) {
2375 stops.RemoveElementAt(x);
2376 continue;
2377 }
2378
2379 // Check if we coincide with the left colorstop.
2380 if (offset1 == offset) {
2381 // Morph the midpoint to a regular stop with the color of the next
2382 // color stop.
2383 stops[x].mColor = color2;
2384 stops[x].mIsMidpoint = false;
2385 continue;
2386 }
2387
2388 // Check if we coincide with the right colorstop.
2389 if (offset2 == offset) {
2390 // Morph the midpoint to a regular stop with the color of the previous
2391 // color stop.
2392 stops[x].mColor = color1;
2393 stops[x].mIsMidpoint = false;
2394 continue;
2395 }
2396
2397 float midpoint = (offset - offset1) / (offset2 - offset1);
2398 ColorStop newStops[9];
2399 if (midpoint > .5f) {
2400 for (size_t y = 0; y < 7; y++) {
2401 newStops[y].mPosition = offset1 + (offset - offset1) * (7 + y) / 13;
2402 }
2403
2404 newStops[7].mPosition = offset + (offset2 - offset) / 3;
2405 newStops[8].mPosition = offset + (offset2 - offset) * 2 / 3;
2406 } else {
2407 newStops[0].mPosition = offset1 + (offset - offset1) / 3;
2408 newStops[1].mPosition = offset1 + (offset - offset1) * 2 / 3;
2409
2410 for (size_t y = 0; y < 7; y++) {
2411 newStops[y+2].mPosition = offset + (offset2 - offset) * y / 13;
2412 }
2413 }
2414 // calculate colors
2415
2416 for (size_t y = 0; y < 9; y++) {
2417 // Calculate the intermediate color stops per the formula of the CSS images
2418 // spec. http://dev.w3.org/csswg/css-images/#color-stop-syntax
2419 // 9 points were chosen since it is the minimum number of stops that always
2420 // give the smoothest appearace regardless of midpoint position and difference
2421 // in luminance of the end points.
2422 float relativeOffset = (newStops[y].mPosition - offset1) / (offset2 - offset1);
2423 float multiplier = powf(relativeOffset, logf(.5f) / logf(midpoint));
2424
2425 gfx::Float red = color1.r + multiplier * (color2.r - color1.r);
2426 gfx::Float green = color1.g + multiplier * (color2.g - color1.g);
2427 gfx::Float blue = color1.b + multiplier * (color2.b - color1.b);
2428 gfx::Float alpha = color1.a + multiplier * (color2.a - color1.a);
2429
2430 newStops[y].mColor = Color(red, green, blue, alpha);
2431 }
2432
2433 stops.ReplaceElementsAt(x, 1, newStops, 9);
2434 x += 9;
2435 }
2436 }
2437
2438 static Color
Premultiply(const Color & aColor)2439 Premultiply(const Color& aColor)
2440 {
2441 gfx::Float a = aColor.a;
2442 return Color(aColor.r * a, aColor.g * a, aColor.b * a, a);
2443 }
2444
2445 static Color
Unpremultiply(const Color & aColor)2446 Unpremultiply(const Color& aColor)
2447 {
2448 gfx::Float a = aColor.a;
2449 return (a > 0.f)
2450 ? Color(aColor.r / a, aColor.g / a, aColor.b / a, a)
2451 : aColor;
2452 }
2453
2454 static Color
TransparentColor(Color aColor)2455 TransparentColor(Color aColor) {
2456 aColor.a = 0;
2457 return aColor;
2458 }
2459
2460 // Adjusts and adds color stops in such a way that drawing the gradient with
2461 // unpremultiplied interpolation looks nearly the same as if it were drawn with
2462 // premultiplied interpolation.
2463 static const float kAlphaIncrementPerGradientStep = 0.1f;
2464 static void
ResolvePremultipliedAlpha(nsTArray<ColorStop> & aStops)2465 ResolvePremultipliedAlpha(nsTArray<ColorStop>& aStops)
2466 {
2467 for (size_t x = 1; x < aStops.Length(); x++) {
2468 const ColorStop leftStop = aStops[x - 1];
2469 const ColorStop rightStop = aStops[x];
2470
2471 // if the left and right stop have the same alpha value, we don't need
2472 // to do anything
2473 if (leftStop.mColor.a == rightStop.mColor.a) {
2474 continue;
2475 }
2476
2477 // Is the stop on the left 100% transparent? If so, have it adopt the color
2478 // of the right stop
2479 if (leftStop.mColor.a == 0) {
2480 aStops[x - 1].mColor = TransparentColor(rightStop.mColor);
2481 continue;
2482 }
2483
2484 // Is the stop on the right completely transparent?
2485 // If so, duplicate it and assign it the color on the left.
2486 if (rightStop.mColor.a == 0) {
2487 ColorStop newStop = rightStop;
2488 newStop.mColor = TransparentColor(leftStop.mColor);
2489 aStops.InsertElementAt(x, newStop);
2490 x++;
2491 continue;
2492 }
2493
2494 // Now handle cases where one or both of the stops are partially transparent.
2495 if (leftStop.mColor.a != 1.0f || rightStop.mColor.a != 1.0f) {
2496 Color premulLeftColor = Premultiply(leftStop.mColor);
2497 Color premulRightColor = Premultiply(rightStop.mColor);
2498 // Calculate how many extra steps. We do a step per 10% transparency.
2499 size_t stepCount = NSToIntFloor(fabsf(leftStop.mColor.a - rightStop.mColor.a) / kAlphaIncrementPerGradientStep);
2500 for (size_t y = 1; y < stepCount; y++) {
2501 float frac = static_cast<float>(y) / stepCount;
2502 ColorStop newStop(Interpolate(leftStop.mPosition, rightStop.mPosition, frac), false,
2503 Unpremultiply(InterpolateColor(premulLeftColor, premulRightColor, frac)));
2504 aStops.InsertElementAt(x, newStop);
2505 x++;
2506 }
2507 }
2508 }
2509 }
2510
2511 static ColorStop
InterpolateColorStop(const ColorStop & aFirst,const ColorStop & aSecond,double aPosition,const Color & aDefault)2512 InterpolateColorStop(const ColorStop& aFirst, const ColorStop& aSecond,
2513 double aPosition, const Color& aDefault)
2514 {
2515 MOZ_ASSERT(aFirst.mPosition <= aPosition);
2516 MOZ_ASSERT(aPosition <= aSecond.mPosition);
2517
2518 double delta = aSecond.mPosition - aFirst.mPosition;
2519
2520 if (delta < 1e-6) {
2521 return ColorStop(aPosition, false, aDefault);
2522 }
2523
2524 return ColorStop(aPosition, false,
2525 Unpremultiply(InterpolateColor(Premultiply(aFirst.mColor),
2526 Premultiply(aSecond.mColor),
2527 (aPosition - aFirst.mPosition) / delta)));
2528 }
2529
2530 // Clamp and extend the given ColorStop array in-place to fit exactly into the
2531 // range [0, 1].
2532 static void
ClampColorStops(nsTArray<ColorStop> & aStops)2533 ClampColorStops(nsTArray<ColorStop>& aStops)
2534 {
2535 MOZ_ASSERT(aStops.Length() > 0);
2536
2537 // If all stops are outside the range, then get rid of everything and replace
2538 // with a single colour.
2539 if (aStops.Length() < 2 || aStops[0].mPosition > 1 ||
2540 aStops.LastElement().mPosition < 0) {
2541 Color c = aStops[0].mPosition > 1 ? aStops[0].mColor : aStops.LastElement().mColor;
2542 aStops.Clear();
2543 aStops.AppendElement(ColorStop(0, false, c));
2544 return;
2545 }
2546
2547 // Create the 0 and 1 points if they fall in the range of |aStops|, and discard
2548 // all stops outside the range [0, 1].
2549 // XXX: If we have stops positioned at 0 or 1, we only keep the innermost of
2550 // those stops. This should be fine for the current user(s) of this function.
2551 for (size_t i = aStops.Length() - 1; i > 0; i--) {
2552 if (aStops[i - 1].mPosition < 1 && aStops[i].mPosition >= 1) {
2553 // Add a point to position 1.
2554 aStops[i] = InterpolateColorStop(aStops[i - 1], aStops[i],
2555 /* aPosition = */ 1,
2556 aStops[i - 1].mColor);
2557 // Remove all the elements whose position is greater than 1.
2558 aStops.RemoveElementsAt(i + 1, aStops.Length() - (i + 1));
2559 }
2560 if (aStops[i - 1].mPosition <= 0 && aStops[i].mPosition > 0) {
2561 // Add a point to position 0.
2562 aStops[i - 1] = InterpolateColorStop(aStops[i - 1], aStops[i],
2563 /* aPosition = */ 0,
2564 aStops[i].mColor);
2565 // Remove all of the preceding stops -- they are all negative.
2566 aStops.RemoveElementsAt(0, i - 1);
2567 break;
2568 }
2569 }
2570
2571 MOZ_ASSERT(aStops[0].mPosition >= -1e6);
2572 MOZ_ASSERT(aStops.LastElement().mPosition - 1 <= 1e6);
2573
2574 // The end points won't exist yet if they don't fall in the original range of
2575 // |aStops|. Create them if needed.
2576 if (aStops[0].mPosition > 0) {
2577 aStops.InsertElementAt(0, ColorStop(0, false, aStops[0].mColor));
2578 }
2579 if (aStops.LastElement().mPosition < 1) {
2580 aStops.AppendElement(ColorStop(1, false, aStops.LastElement().mColor));
2581 }
2582 }
2583
2584 void
PaintGradient(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,nsStyleGradient * aGradient,const nsRect & aDirtyRect,const nsRect & aDest,const nsRect & aFillArea,const nsSize & aRepeatSize,const CSSIntRect & aSrc,const nsSize & aIntrinsicSize)2585 nsCSSRendering::PaintGradient(nsPresContext* aPresContext,
2586 nsRenderingContext& aRenderingContext,
2587 nsStyleGradient* aGradient,
2588 const nsRect& aDirtyRect,
2589 const nsRect& aDest,
2590 const nsRect& aFillArea,
2591 const nsSize& aRepeatSize,
2592 const CSSIntRect& aSrc,
2593 const nsSize& aIntrinsicSize)
2594 {
2595 PROFILER_LABEL("nsCSSRendering", "PaintGradient",
2596 js::ProfileEntry::Category::GRAPHICS);
2597
2598 Telemetry::AutoTimer<Telemetry::GRADIENT_DURATION, Telemetry::Microsecond> gradientTimer;
2599 if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
2600 return;
2601 }
2602
2603 gfxContext *ctx = aRenderingContext.ThebesContext();
2604 nscoord appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
2605 gfxSize srcSize = gfxSize(gfxFloat(aIntrinsicSize.width)/appUnitsPerDevPixel,
2606 gfxFloat(aIntrinsicSize.height)/appUnitsPerDevPixel);
2607
2608 bool cellContainsFill = aDest.Contains(aFillArea);
2609
2610 // Compute "gradient line" start and end relative to the intrinsic size of
2611 // the gradient.
2612 gfxPoint lineStart, lineEnd;
2613 double radiusX = 0, radiusY = 0; // for radial gradients only
2614 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
2615 ComputeLinearGradientLine(aPresContext, aGradient, srcSize,
2616 &lineStart, &lineEnd);
2617 } else {
2618 ComputeRadialGradientLine(aPresContext, aGradient, srcSize,
2619 &lineStart, &lineEnd, &radiusX, &radiusY);
2620 }
2621 // Avoid sending Infs or Nans to downwind draw targets.
2622 if (!lineStart.IsFinite() || !lineEnd.IsFinite()) {
2623 lineStart = lineEnd = gfxPoint(0, 0);
2624 }
2625 gfxFloat lineLength = NS_hypot(lineEnd.x - lineStart.x,
2626 lineEnd.y - lineStart.y);
2627
2628 MOZ_ASSERT(aGradient->mStops.Length() >= 2,
2629 "The parser should reject gradients with less than two stops");
2630
2631 // Build color stop array and compute stop positions
2632 nsTArray<ColorStop> stops;
2633 // If there is a run of stops before stop i that did not have specified
2634 // positions, then this is the index of the first stop in that run, otherwise
2635 // it's -1.
2636 int32_t firstUnsetPosition = -1;
2637 for (uint32_t i = 0; i < aGradient->mStops.Length(); ++i) {
2638 const nsStyleGradientStop& stop = aGradient->mStops[i];
2639 double position;
2640 switch (stop.mLocation.GetUnit()) {
2641 case eStyleUnit_None:
2642 if (i == 0) {
2643 // First stop defaults to position 0.0
2644 position = 0.0;
2645 } else if (i == aGradient->mStops.Length() - 1) {
2646 // Last stop defaults to position 1.0
2647 position = 1.0;
2648 } else {
2649 // Other stops with no specified position get their position assigned
2650 // later by interpolation, see below.
2651 // Remeber where the run of stops with no specified position starts,
2652 // if it starts here.
2653 if (firstUnsetPosition < 0) {
2654 firstUnsetPosition = i;
2655 }
2656 stops.AppendElement(ColorStop(0, stop.mIsInterpolationHint,
2657 Color::FromABGR(stop.mColor)));
2658 continue;
2659 }
2660 break;
2661 case eStyleUnit_Percent:
2662 position = stop.mLocation.GetPercentValue();
2663 break;
2664 case eStyleUnit_Coord:
2665 position = lineLength < 1e-6 ? 0.0 :
2666 stop.mLocation.GetCoordValue() / appUnitsPerDevPixel / lineLength;
2667 break;
2668 case eStyleUnit_Calc:
2669 nsStyleCoord::Calc *calc;
2670 calc = stop.mLocation.GetCalcValue();
2671 position = calc->mPercent +
2672 ((lineLength < 1e-6) ? 0.0 :
2673 (NSAppUnitsToFloatPixels(calc->mLength, appUnitsPerDevPixel) / lineLength));
2674 break;
2675 default:
2676 MOZ_ASSERT(false, "Unknown stop position type");
2677 }
2678
2679 if (i > 0) {
2680 // Prevent decreasing stop positions by advancing this position
2681 // to the previous stop position, if necessary
2682 position = std::max(position, stops[i - 1].mPosition);
2683 }
2684 stops.AppendElement(ColorStop(position, stop.mIsInterpolationHint,
2685 Color::FromABGR(stop.mColor)));
2686 if (firstUnsetPosition > 0) {
2687 // Interpolate positions for all stops that didn't have a specified position
2688 double p = stops[firstUnsetPosition - 1].mPosition;
2689 double d = (stops[i].mPosition - p)/(i - firstUnsetPosition + 1);
2690 for (uint32_t j = firstUnsetPosition; j < i; ++j) {
2691 p += d;
2692 stops[j].mPosition = p;
2693 }
2694 firstUnsetPosition = -1;
2695 }
2696 }
2697
2698 // If a non-repeating linear gradient is axis-aligned and there are no gaps
2699 // between tiles, we can optimise away most of the work by converting to a
2700 // repeating linear gradient and filling the whole destination rect at once.
2701 bool forceRepeatToCoverTiles =
2702 aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR &&
2703 (lineStart.x == lineEnd.x) != (lineStart.y == lineEnd.y) &&
2704 aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height &&
2705 !aGradient->mRepeating && !aSrc.IsEmpty() && !cellContainsFill;
2706
2707 gfxMatrix matrix;
2708 if (forceRepeatToCoverTiles) {
2709 // Length of the source rectangle along the gradient axis.
2710 double rectLen;
2711 // The position of the start of the rectangle along the gradient.
2712 double offset;
2713
2714 // The gradient line is "backwards". Flip the line upside down to make
2715 // things easier, and then rotate the matrix to turn everything back the
2716 // right way up.
2717 if (lineStart.x > lineEnd.x || lineStart.y > lineEnd.y) {
2718 std::swap(lineStart, lineEnd);
2719 matrix.Scale(-1, -1);
2720 }
2721
2722 // Fit the gradient line exactly into the source rect.
2723 // aSrc is relative to aIntrinsincSize.
2724 // srcRectDev will be relative to srcSize, so in the same coordinate space
2725 // as lineStart / lineEnd.
2726 gfxRect srcRectDev = nsLayoutUtils::RectToGfxRect(
2727 CSSPixel::ToAppUnits(aSrc), appUnitsPerDevPixel);
2728 if (lineStart.x != lineEnd.x) {
2729 rectLen = srcRectDev.width;
2730 offset = (srcRectDev.x - lineStart.x) / lineLength;
2731 lineStart.x = srcRectDev.x;
2732 lineEnd.x = srcRectDev.XMost();
2733 } else {
2734 rectLen = srcRectDev.height;
2735 offset = (srcRectDev.y - lineStart.y) / lineLength;
2736 lineStart.y = srcRectDev.y;
2737 lineEnd.y = srcRectDev.YMost();
2738 }
2739
2740 // Adjust gradient stop positions for the new gradient line.
2741 double scale = lineLength / rectLen;
2742 for (size_t i = 0; i < stops.Length(); i++) {
2743 stops[i].mPosition = (stops[i].mPosition - offset) * fabs(scale);
2744 }
2745
2746 // Clamp or extrapolate gradient stops to exactly [0, 1].
2747 ClampColorStops(stops);
2748
2749 lineLength = rectLen;
2750 }
2751
2752 // Eliminate negative-position stops if the gradient is radial.
2753 double firstStop = stops[0].mPosition;
2754 if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && firstStop < 0.0) {
2755 if (aGradient->mRepeating) {
2756 // Choose an instance of the repeated pattern that gives us all positive
2757 // stop-offsets.
2758 double lastStop = stops[stops.Length() - 1].mPosition;
2759 double stopDelta = lastStop - firstStop;
2760 // If all the stops are in approximately the same place then logic below
2761 // will kick in that makes us draw just the last stop color, so don't
2762 // try to do anything in that case. We certainly need to avoid
2763 // dividing by zero.
2764 if (stopDelta >= 1e-6) {
2765 double instanceCount = ceil(-firstStop/stopDelta);
2766 // Advance stops by instanceCount multiples of the period of the
2767 // repeating gradient.
2768 double offset = instanceCount*stopDelta;
2769 for (uint32_t i = 0; i < stops.Length(); i++) {
2770 stops[i].mPosition += offset;
2771 }
2772 }
2773 } else {
2774 // Move negative-position stops to position 0.0. We may also need
2775 // to set the color of the stop to the color the gradient should have
2776 // at the center of the ellipse.
2777 for (uint32_t i = 0; i < stops.Length(); i++) {
2778 double pos = stops[i].mPosition;
2779 if (pos < 0.0) {
2780 stops[i].mPosition = 0.0;
2781 // If this is the last stop, we don't need to adjust the color,
2782 // it will fill the entire area.
2783 if (i < stops.Length() - 1) {
2784 double nextPos = stops[i + 1].mPosition;
2785 // If nextPos is approximately equal to pos, then we don't
2786 // need to adjust the color of this stop because it's
2787 // not going to be displayed.
2788 // If nextPos is negative, we don't need to adjust the color of
2789 // this stop since it's not going to be displayed because
2790 // nextPos will also be moved to 0.0.
2791 if (nextPos >= 0.0 && nextPos - pos >= 1e-6) {
2792 // Compute how far the new position 0.0 is along the interval
2793 // between pos and nextPos.
2794 // XXX Color interpolation (in cairo, too) should use the
2795 // CSS 'color-interpolation' property!
2796 float frac = float((0.0 - pos)/(nextPos - pos));
2797 stops[i].mColor =
2798 InterpolateColor(stops[i].mColor, stops[i + 1].mColor, frac);
2799 }
2800 }
2801 }
2802 }
2803 }
2804 firstStop = stops[0].mPosition;
2805 MOZ_ASSERT(firstStop >= 0.0, "Failed to fix stop offsets");
2806 }
2807
2808 if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && !aGradient->mRepeating) {
2809 // Direct2D can only handle a particular class of radial gradients because
2810 // of the way the it specifies gradients. Setting firstStop to 0, when we
2811 // can, will help us stay on the fast path. Currently we don't do this
2812 // for repeating gradients but we could by adjusting the stop collection
2813 // to start at 0
2814 firstStop = 0;
2815 }
2816
2817 double lastStop = stops[stops.Length() - 1].mPosition;
2818 // Cairo gradients must have stop positions in the range [0, 1]. So,
2819 // stop positions will be normalized below by subtracting firstStop and then
2820 // multiplying by stopScale.
2821 double stopScale;
2822 double stopOrigin = firstStop;
2823 double stopEnd = lastStop;
2824 double stopDelta = lastStop - firstStop;
2825 bool zeroRadius = aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR &&
2826 (radiusX < 1e-6 || radiusY < 1e-6);
2827 if (stopDelta < 1e-6 || lineLength < 1e-6 || zeroRadius) {
2828 // Stops are all at the same place. Map all stops to 0.0.
2829 // For repeating radial gradients, or for any radial gradients with
2830 // a zero radius, we need to fill with the last stop color, so just set
2831 // both radii to 0.
2832 if (aGradient->mRepeating || zeroRadius) {
2833 radiusX = radiusY = 0.0;
2834 }
2835 stopDelta = 0.0;
2836 lastStop = firstStop;
2837 }
2838
2839 // Don't normalize non-repeating or degenerate gradients below 0..1
2840 // This keeps the gradient line as large as the box and doesn't
2841 // lets us avoiding having to get padding correct for stops
2842 // at 0 and 1
2843 if (!aGradient->mRepeating || stopDelta == 0.0) {
2844 stopOrigin = std::min(stopOrigin, 0.0);
2845 stopEnd = std::max(stopEnd, 1.0);
2846 }
2847 stopScale = 1.0/(stopEnd - stopOrigin);
2848
2849 // Create the gradient pattern.
2850 RefPtr<gfxPattern> gradientPattern;
2851 gfxPoint gradientStart;
2852 gfxPoint gradientEnd;
2853 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
2854 // Compute the actual gradient line ends we need to pass to cairo after
2855 // stops have been normalized.
2856 gradientStart = lineStart + (lineEnd - lineStart)*stopOrigin;
2857 gradientEnd = lineStart + (lineEnd - lineStart)*stopEnd;
2858 gfxPoint gradientStopStart = lineStart + (lineEnd - lineStart)*firstStop;
2859 gfxPoint gradientStopEnd = lineStart + (lineEnd - lineStart)*lastStop;
2860
2861 if (stopDelta == 0.0) {
2862 // Stops are all at the same place. For repeating gradients, this will
2863 // just paint the last stop color. We don't need to do anything.
2864 // For non-repeating gradients, this should render as two colors, one
2865 // on each "side" of the gradient line segment, which is a point. All
2866 // our stops will be at 0.0; we just need to set the direction vector
2867 // correctly.
2868 gradientEnd = gradientStart + (lineEnd - lineStart);
2869 gradientStopEnd = gradientStopStart + (lineEnd - lineStart);
2870 }
2871
2872 gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y,
2873 gradientEnd.x, gradientEnd.y);
2874 } else {
2875 NS_ASSERTION(firstStop >= 0.0,
2876 "Negative stops not allowed for radial gradients");
2877
2878 // To form an ellipse, we'll stretch a circle vertically, if necessary.
2879 // So our radii are based on radiusX.
2880 double innerRadius = radiusX*stopOrigin;
2881 double outerRadius = radiusX*stopEnd;
2882 if (stopDelta == 0.0) {
2883 // Stops are all at the same place. See above (except we now have
2884 // the inside vs. outside of an ellipse).
2885 outerRadius = innerRadius + 1;
2886 }
2887 gradientPattern = new gfxPattern(lineStart.x, lineStart.y, innerRadius,
2888 lineStart.x, lineStart.y, outerRadius);
2889 if (radiusX != radiusY) {
2890 // Stretch the circles into ellipses vertically by setting a transform
2891 // in the pattern.
2892 // Recall that this is the transform from user space to pattern space.
2893 // So to stretch the ellipse by factor of P vertically, we scale
2894 // user coordinates by 1/P.
2895 matrix.Translate(lineStart);
2896 matrix.Scale(1.0, radiusX/radiusY);
2897 matrix.Translate(-lineStart);
2898 }
2899 }
2900 // Use a pattern transform to take account of source and dest rects
2901 matrix.Translate(gfxPoint(aPresContext->CSSPixelsToDevPixels(aSrc.x),
2902 aPresContext->CSSPixelsToDevPixels(aSrc.y)));
2903 matrix.Scale(gfxFloat(aPresContext->CSSPixelsToAppUnits(aSrc.width))/aDest.width,
2904 gfxFloat(aPresContext->CSSPixelsToAppUnits(aSrc.height))/aDest.height);
2905 gradientPattern->SetMatrix(matrix);
2906
2907 if (gradientPattern->CairoStatus())
2908 return;
2909
2910 if (stopDelta == 0.0) {
2911 // Non-repeating gradient with all stops in same place -> just add
2912 // first stop and last stop, both at position 0.
2913 // Repeating gradient with all stops in the same place, or radial
2914 // gradient with radius of 0 -> just paint the last stop color.
2915 // We use firstStop offset to keep |stops| with same units (will later normalize to 0).
2916 Color firstColor(stops[0].mColor);
2917 Color lastColor(stops.LastElement().mColor);
2918 stops.Clear();
2919
2920 if (!aGradient->mRepeating && !zeroRadius) {
2921 stops.AppendElement(ColorStop(firstStop, false, firstColor));
2922 }
2923 stops.AppendElement(ColorStop(firstStop, false, lastColor));
2924 }
2925
2926 ResolveMidpoints(stops);
2927 ResolvePremultipliedAlpha(stops);
2928
2929 bool isRepeat = aGradient->mRepeating || forceRepeatToCoverTiles;
2930
2931 // Now set normalized color stops in pattern.
2932 // Offscreen gradient surface cache (not a tile):
2933 // On some backends (e.g. D2D), the GradientStops object holds an offscreen surface
2934 // which is a lookup table used to evaluate the gradient. This surface can use
2935 // much memory (ram and/or GPU ram) and can be expensive to create. So we cache it.
2936 // The cache key correlates 1:1 with the arguments for CreateGradientStops (also the implied backend type)
2937 // Note that GradientStop is a simple struct with a stop value (while GradientStops has the surface).
2938 nsTArray<gfx::GradientStop> rawStops(stops.Length());
2939 rawStops.SetLength(stops.Length());
2940 for(uint32_t i = 0; i < stops.Length(); i++) {
2941 rawStops[i].color = stops[i].mColor;
2942 rawStops[i].offset = stopScale * (stops[i].mPosition - stopOrigin);
2943 }
2944 RefPtr<mozilla::gfx::GradientStops> gs =
2945 gfxGradientCache::GetOrCreateGradientStops(ctx->GetDrawTarget(),
2946 rawStops,
2947 isRepeat ? gfx::ExtendMode::REPEAT : gfx::ExtendMode::CLAMP);
2948 gradientPattern->SetColorStops(gs);
2949
2950 // Paint gradient tiles. This isn't terribly efficient, but doing it this
2951 // way is simple and sure to get pixel-snapping right. We could speed things
2952 // up by drawing tiles into temporary surfaces and copying those to the
2953 // destination, but after pixel-snapping tiles may not all be the same size.
2954 nsRect dirty;
2955 if (!dirty.IntersectRect(aDirtyRect, aFillArea))
2956 return;
2957
2958 gfxRect areaToFill =
2959 nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel);
2960 gfxRect dirtyAreaToFill = nsLayoutUtils::RectToGfxRect(dirty, appUnitsPerDevPixel);
2961 dirtyAreaToFill.RoundOut();
2962
2963 gfxMatrix ctm = ctx->CurrentMatrix();
2964 bool isCTMPreservingAxisAlignedRectangles = ctm.PreservesAxisAlignedRectangles();
2965
2966 // xStart/yStart are the top-left corner of the top-left tile.
2967 nscoord xStart = FindTileStart(dirty.x, aDest.x, aRepeatSize.width);
2968 nscoord yStart = FindTileStart(dirty.y, aDest.y, aRepeatSize.height);
2969 nscoord xEnd = forceRepeatToCoverTiles ? xStart + aDest.width : dirty.XMost();
2970 nscoord yEnd = forceRepeatToCoverTiles ? yStart + aDest.height : dirty.YMost();
2971
2972 // x and y are the top-left corner of the tile to draw
2973 for (nscoord y = yStart; y < yEnd; y += aRepeatSize.height) {
2974 for (nscoord x = xStart; x < xEnd; x += aRepeatSize.width) {
2975 // The coordinates of the tile
2976 gfxRect tileRect = nsLayoutUtils::RectToGfxRect(
2977 nsRect(x, y, aDest.width, aDest.height),
2978 appUnitsPerDevPixel);
2979 // The actual area to fill with this tile is the intersection of this
2980 // tile with the overall area we're supposed to be filling
2981 gfxRect fillRect =
2982 forceRepeatToCoverTiles ? areaToFill : tileRect.Intersect(areaToFill);
2983 // Try snapping the fill rect. Snap its top-left and bottom-right
2984 // independently to preserve the orientation.
2985 gfxPoint snappedFillRectTopLeft = fillRect.TopLeft();
2986 gfxPoint snappedFillRectTopRight = fillRect.TopRight();
2987 gfxPoint snappedFillRectBottomRight = fillRect.BottomRight();
2988 // Snap three points instead of just two to ensure we choose the
2989 // correct orientation if there's a reflection.
2990 if (isCTMPreservingAxisAlignedRectangles &&
2991 ctx->UserToDevicePixelSnapped(snappedFillRectTopLeft, true) &&
2992 ctx->UserToDevicePixelSnapped(snappedFillRectBottomRight, true) &&
2993 ctx->UserToDevicePixelSnapped(snappedFillRectTopRight, true)) {
2994 if (snappedFillRectTopLeft.x == snappedFillRectBottomRight.x ||
2995 snappedFillRectTopLeft.y == snappedFillRectBottomRight.y) {
2996 // Nothing to draw; avoid scaling by zero and other weirdness that
2997 // could put the context in an error state.
2998 continue;
2999 }
3000 // Set the context's transform to the transform that maps fillRect to
3001 // snappedFillRect. The part of the gradient that was going to
3002 // exactly fill fillRect will fill snappedFillRect instead.
3003 gfxMatrix transform = gfxUtils::TransformRectToRect(fillRect,
3004 snappedFillRectTopLeft, snappedFillRectTopRight,
3005 snappedFillRectBottomRight);
3006 ctx->SetMatrix(transform);
3007 }
3008 ctx->NewPath();
3009 ctx->Rectangle(fillRect);
3010
3011 gfxRect dirtyFillRect = fillRect.Intersect(dirtyAreaToFill);
3012 gfxRect fillRectRelativeToTile = dirtyFillRect - tileRect.TopLeft();
3013 Color edgeColor;
3014 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR && !isRepeat &&
3015 RectIsBeyondLinearGradientEdge(fillRectRelativeToTile, matrix, stops,
3016 gradientStart, gradientEnd, &edgeColor)) {
3017 ctx->SetColor(edgeColor);
3018 } else {
3019 ctx->SetMatrix(
3020 ctx->CurrentMatrix().Copy().Translate(tileRect.TopLeft()));
3021 ctx->SetPattern(gradientPattern);
3022 }
3023 ctx->Fill();
3024 ctx->SetMatrix(ctm);
3025 }
3026 }
3027 }
3028
3029 static CompositionOp
DetermineCompositionOp(const nsCSSRendering::PaintBGParams & aParams,const nsStyleImageLayers & aLayers,uint32_t aLayerIndex)3030 DetermineCompositionOp(const nsCSSRendering::PaintBGParams& aParams,
3031 const nsStyleImageLayers& aLayers,
3032 uint32_t aLayerIndex)
3033 {
3034 if (aParams.layer >= 0) {
3035 // When drawing a single layer, use the specified composition op.
3036 return aParams.compositionOp;
3037 }
3038
3039 const nsStyleImageLayers::Layer& layer = aLayers.mLayers[aLayerIndex];
3040 // When drawing all layers, get the compositon op from each image layer.
3041 if (aParams.paintFlags & nsCSSRendering::PAINTBG_MASK_IMAGE) {
3042 // Always using OP_OVER mode while drawing the bottom mask layer.
3043 if (aLayerIndex == (aLayers.mImageCount - 1)) {
3044 return CompositionOp::OP_OVER;
3045 }
3046
3047 return nsCSSRendering::GetGFXCompositeMode(layer.mComposite);
3048 }
3049
3050 return nsCSSRendering::GetGFXBlendMode(layer.mBlendMode);
3051 }
3052
3053 DrawResult
PaintBackgroundWithSC(const PaintBGParams & aParams,nsStyleContext * aBackgroundSC,const nsStyleBorder & aBorder)3054 nsCSSRendering::PaintBackgroundWithSC(const PaintBGParams& aParams,
3055 nsStyleContext *aBackgroundSC,
3056 const nsStyleBorder& aBorder)
3057 {
3058 NS_PRECONDITION(aParams.frame,
3059 "Frame is expected to be provided to PaintBackground");
3060
3061 // If we're drawing all layers, aCompositonOp is ignored, so make sure that
3062 // it was left at its default value.
3063 MOZ_ASSERT_IF(aParams.layer == -1,
3064 aParams.compositionOp == CompositionOp::OP_OVER);
3065
3066 DrawResult result = DrawResult::SUCCESS;
3067
3068 // Check to see if we have an appearance defined. If so, we let the theme
3069 // renderer draw the background and bail out.
3070 // XXXzw this ignores aParams.bgClipRect.
3071 const nsStyleDisplay* displayData = aParams.frame->StyleDisplay();
3072 if (displayData->mAppearance) {
3073 nsITheme *theme = aParams.presCtx.GetTheme();
3074 if (theme && theme->ThemeSupportsWidget(&aParams.presCtx,
3075 aParams.frame,
3076 displayData->mAppearance)) {
3077 nsRect drawing(aParams.borderArea);
3078 theme->GetWidgetOverflow(aParams.presCtx.DeviceContext(),
3079 aParams.frame, displayData->mAppearance,
3080 &drawing);
3081 drawing.IntersectRect(drawing, aParams.dirtyRect);
3082 theme->DrawWidgetBackground(&aParams.renderingCtx, aParams.frame,
3083 displayData->mAppearance, aParams.borderArea,
3084 drawing);
3085 return DrawResult::SUCCESS;
3086 }
3087 }
3088
3089 // For canvas frames (in the CSS sense) we draw the background color using
3090 // a solid color item that gets added in nsLayoutUtils::PaintFrame,
3091 // or nsSubDocumentFrame::BuildDisplayList (bug 488242). (The solid
3092 // color may be moved into nsDisplayCanvasBackground by
3093 // nsPresShell::AddCanvasBackgroundColorItem, and painted by
3094 // nsDisplayCanvasBackground directly.) Either way we don't need to
3095 // paint the background color here.
3096 bool isCanvasFrame = IsCanvasFrame(aParams.frame);
3097
3098 // Determine whether we are drawing background images and/or
3099 // background colors.
3100 bool drawBackgroundImage;
3101 bool drawBackgroundColor;
3102
3103 nscolor bgColor = DetermineBackgroundColor(&aParams.presCtx,
3104 aBackgroundSC,
3105 aParams.frame,
3106 drawBackgroundImage,
3107 drawBackgroundColor);
3108
3109 bool paintMask = (aParams.paintFlags & PAINTBG_MASK_IMAGE);
3110 const nsStyleImageLayers& layers = paintMask ?
3111 aBackgroundSC->StyleSVGReset()->mMask :
3112 aBackgroundSC->StyleBackground()->mImage;
3113 // If we're drawing a specific layer, we don't want to draw the
3114 // background color.
3115 if ((drawBackgroundColor && aParams.layer >= 0) || paintMask) {
3116 drawBackgroundColor = false;
3117 }
3118
3119 // At this point, drawBackgroundImage and drawBackgroundColor are
3120 // true if and only if we are actually supposed to paint an image or
3121 // color into aDirtyRect, respectively.
3122 if (!drawBackgroundImage && !drawBackgroundColor)
3123 return DrawResult::SUCCESS;
3124
3125 // Compute the outermost boundary of the area that might be painted.
3126 // Same coordinate space as aParams.borderArea & aParams.bgClipRect.
3127 Sides skipSides = aParams.frame->GetSkipSides();
3128 nsRect paintBorderArea =
3129 ::BoxDecorationRectForBackground(aParams.frame, aParams.borderArea,
3130 skipSides, &aBorder);
3131 nsRect clipBorderArea =
3132 ::BoxDecorationRectForBorder(aParams.frame, aParams.borderArea,
3133 skipSides, &aBorder);
3134
3135 // The 'bgClipArea' (used only by the image tiling logic, far below)
3136 // is the caller-provided aParams.bgClipRect if any, or else the area
3137 // determined by the value of 'background-clip' in
3138 // SetupCurrentBackgroundClip. (Arguably it should be the
3139 // intersection, but that breaks the table painter -- in particular,
3140 // taking the intersection breaks reftests/bugs/403249-1[ab].)
3141 gfxContext* ctx = aParams.renderingCtx.ThebesContext();
3142 nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel();
3143 ImageLayerClipState clipState;
3144 if (aParams.bgClipRect) {
3145 clipState.mBGClipArea = *aParams.bgClipRect;
3146 clipState.mCustomClip = true;
3147 clipState.mHasRoundedCorners = false;
3148 SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel,
3149 &clipState.mDirtyRect, &clipState.mDirtyRectGfx);
3150 } else {
3151 GetImageLayerClip(layers.BottomLayer(),
3152 aParams.frame, aBorder, aParams.borderArea,
3153 aParams.dirtyRect,
3154 (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
3155 appUnitsPerPixel,
3156 &clipState);
3157 }
3158
3159 // If we might be using a background color, go ahead and set it now.
3160 if (drawBackgroundColor && !isCanvasFrame)
3161 ctx->SetColor(Color::FromABGR(bgColor));
3162
3163 // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved(ctx)
3164 // in the cases we need it.
3165 gfxContextAutoSaveRestore autoSR;
3166
3167 // If there is no background image, draw a color. (If there is
3168 // neither a background image nor a color, we wouldn't have gotten
3169 // this far.)
3170 if (!drawBackgroundImage) {
3171 if (!isCanvasFrame) {
3172 DrawBackgroundColor(clipState, ctx, appUnitsPerPixel);
3173 }
3174 return DrawResult::SUCCESS;
3175 }
3176
3177 if (layers.mImageCount < 1) {
3178 // Return if there are no background layers, all work from this point
3179 // onwards happens iteratively on these.
3180 return DrawResult::SUCCESS;
3181 }
3182
3183 // Validate the layer range before we start iterating.
3184 int32_t startLayer = aParams.layer;
3185 int32_t nLayers = 1;
3186 if (startLayer < 0) {
3187 startLayer = (int32_t)layers.mImageCount - 1;
3188 nLayers = layers.mImageCount;
3189 }
3190
3191 // Ensure we get invalidated for loads of the image. We need to do
3192 // this here because this might be the only code that knows about the
3193 // association of the style data with the frame.
3194 if (aBackgroundSC != aParams.frame->StyleContext()) {
3195 NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE(i, layers, startLayer, nLayers) {
3196 aParams.frame->AssociateImage(layers.mLayers[i].mImage,
3197 &aParams.presCtx);
3198 }
3199 }
3200
3201 // The background color is rendered over the entire dirty area,
3202 // even if the image isn't.
3203 if (drawBackgroundColor && !isCanvasFrame) {
3204 DrawBackgroundColor(clipState, ctx, appUnitsPerPixel);
3205 }
3206
3207 if (drawBackgroundImage) {
3208 bool clipSet = false;
3209 uint8_t currentBackgroundClip = NS_STYLE_IMAGELAYER_CLIP_BORDER;
3210 NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE(i, layers, layers.mImageCount - 1,
3211 nLayers + (layers.mImageCount -
3212 startLayer - 1)) {
3213 const nsStyleImageLayers::Layer& layer = layers.mLayers[i];
3214 if (!aParams.bgClipRect) {
3215 if (currentBackgroundClip != layer.mClip || !clipSet) {
3216 currentBackgroundClip = layer.mClip;
3217 // If clipSet is false that means this is the bottom layer and we
3218 // already called GetImageLayerClip above and it stored its results
3219 // in clipState.
3220 if (clipSet) {
3221 autoSR.Restore(); // reset the previous one
3222 GetImageLayerClip(layer, aParams.frame,
3223 aBorder, aParams.borderArea, aParams.dirtyRect,
3224 (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
3225 appUnitsPerPixel, &clipState);
3226 }
3227 SetupImageLayerClip(clipState, ctx, appUnitsPerPixel, &autoSR);
3228 clipSet = true;
3229 if (!clipBorderArea.IsEqualEdges(aParams.borderArea)) {
3230 // We're drawing the background for the joined continuation boxes
3231 // so we need to clip that to the slice that we want for this
3232 // frame.
3233 gfxRect clip =
3234 nsLayoutUtils::RectToGfxRect(aParams.borderArea, appUnitsPerPixel);
3235 autoSR.EnsureSaved(ctx);
3236 ctx->NewPath();
3237 ctx->SnappedRectangle(clip);
3238 ctx->Clip();
3239 }
3240 }
3241 }
3242 if ((aParams.layer < 0 || i == (uint32_t)startLayer) &&
3243 !clipState.mDirtyRectGfx.IsEmpty()) {
3244 CompositionOp co = DetermineCompositionOp(aParams, layers, i);
3245 nsBackgroundLayerState state =
3246 PrepareImageLayer(&aParams.presCtx, aParams.frame,
3247 aParams.paintFlags, paintBorderArea, clipState.mBGClipArea,
3248 layer, nullptr);
3249 result &= state.mImageRenderer.PrepareResult();
3250 if (!state.mFillArea.IsEmpty()) {
3251 if (co != CompositionOp::OP_OVER) {
3252 NS_ASSERTION(ctx->CurrentOp() == CompositionOp::OP_OVER,
3253 "It is assumed the initial op is OP_OVER, when it is "
3254 "restored later");
3255 ctx->SetOp(co);
3256 }
3257
3258 result &=
3259 state.mImageRenderer.DrawBackground(&aParams.presCtx,
3260 aParams.renderingCtx,
3261 state.mDestArea, state.mFillArea,
3262 state.mAnchor + paintBorderArea.TopLeft(),
3263 clipState.mDirtyRect,
3264 state.mRepeatSize);
3265
3266 if (co != CompositionOp::OP_OVER) {
3267 ctx->SetOp(CompositionOp::OP_OVER);
3268 }
3269 }
3270 }
3271 }
3272 }
3273
3274 return result;
3275 }
3276
3277 nsRect
ComputeImageLayerPositioningArea(nsPresContext * aPresContext,nsIFrame * aForFrame,const nsRect & aBorderArea,const nsStyleImageLayers::Layer & aLayer,nsIFrame ** aAttachedToFrame,bool * aOutIsTransformedFixed)3278 nsCSSRendering::ComputeImageLayerPositioningArea(nsPresContext* aPresContext,
3279 nsIFrame* aForFrame,
3280 const nsRect& aBorderArea,
3281 const nsStyleImageLayers::Layer& aLayer,
3282 nsIFrame** aAttachedToFrame,
3283 bool* aOutIsTransformedFixed)
3284 {
3285 // Compute background origin area relative to aBorderArea now as we may need
3286 // it to compute the effective image size for a CSS gradient.
3287 nsRect bgPositioningArea;
3288
3289 nsIAtom* frameType = aForFrame->GetType();
3290 nsIFrame* geometryFrame = aForFrame;
3291 if (MOZ_UNLIKELY(frameType == nsGkAtoms::scrollFrame &&
3292 NS_STYLE_IMAGELAYER_ATTACHMENT_LOCAL == aLayer.mAttachment)) {
3293 nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
3294 bgPositioningArea = nsRect(
3295 scrollableFrame->GetScrolledFrame()->GetPosition()
3296 // For the dir=rtl case:
3297 + scrollableFrame->GetScrollRange().TopLeft(),
3298 scrollableFrame->GetScrolledRect().Size());
3299 // The ScrolledRect’s size does not include the borders or scrollbars,
3300 // reverse the handling of background-origin
3301 // compared to the common case below.
3302 if (aLayer.mOrigin == NS_STYLE_IMAGELAYER_ORIGIN_BORDER) {
3303 nsMargin border = geometryFrame->GetUsedBorder();
3304 border.ApplySkipSides(geometryFrame->GetSkipSides());
3305 bgPositioningArea.Inflate(border);
3306 bgPositioningArea.Inflate(scrollableFrame->GetActualScrollbarSizes());
3307 } else if (aLayer.mOrigin != NS_STYLE_IMAGELAYER_ORIGIN_PADDING) {
3308 nsMargin padding = geometryFrame->GetUsedPadding();
3309 padding.ApplySkipSides(geometryFrame->GetSkipSides());
3310 bgPositioningArea.Deflate(padding);
3311 NS_ASSERTION(aLayer.mOrigin == NS_STYLE_IMAGELAYER_ORIGIN_CONTENT,
3312 "unknown background-origin value");
3313 }
3314 *aAttachedToFrame = aForFrame;
3315 return bgPositioningArea;
3316 }
3317
3318 if (MOZ_UNLIKELY(frameType == nsGkAtoms::canvasFrame)) {
3319 geometryFrame = aForFrame->PrincipalChildList().FirstChild();
3320 // geometryFrame might be null if this canvas is a page created
3321 // as an overflow container (e.g. the in-flow content has already
3322 // finished and this page only displays the continuations of
3323 // absolutely positioned content).
3324 if (geometryFrame) {
3325 bgPositioningArea = geometryFrame->GetRect();
3326 }
3327 } else {
3328 bgPositioningArea = nsRect(nsPoint(0,0), aBorderArea.Size());
3329 }
3330
3331 // Background images are tiled over the 'background-clip' area
3332 // but the origin of the tiling is based on the 'background-origin' area
3333 // XXX: Bug 1303623 will bring in new origin value, we should iterate from
3334 // NS_STYLE_IMAGELAYER_ORIGIN_MARGIN instead of
3335 // NS_STYLE_IMAGELAYER_ORIGIN_BORDER.
3336 if (aLayer.mOrigin != NS_STYLE_IMAGELAYER_ORIGIN_BORDER && geometryFrame) {
3337 nsMargin border = geometryFrame->GetUsedBorder();
3338 if (aLayer.mOrigin != NS_STYLE_IMAGELAYER_ORIGIN_PADDING) {
3339 border += geometryFrame->GetUsedPadding();
3340 NS_ASSERTION(aLayer.mOrigin == NS_STYLE_IMAGELAYER_ORIGIN_CONTENT,
3341 "unknown background-origin value");
3342 }
3343 bgPositioningArea.Deflate(border);
3344 }
3345
3346 nsIFrame* attachedToFrame = aForFrame;
3347 if (NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED == aLayer.mAttachment) {
3348 // If it's a fixed background attachment, then the image is placed
3349 // relative to the viewport, which is the area of the root frame
3350 // in a screen context or the page content frame in a print context.
3351 attachedToFrame = aPresContext->PresShell()->FrameManager()->GetRootFrame();
3352 NS_ASSERTION(attachedToFrame, "no root frame");
3353 nsIFrame* pageContentFrame = nullptr;
3354 if (aPresContext->IsPaginated()) {
3355 pageContentFrame =
3356 nsLayoutUtils::GetClosestFrameOfType(aForFrame, nsGkAtoms::pageContentFrame);
3357 if (pageContentFrame) {
3358 attachedToFrame = pageContentFrame;
3359 }
3360 // else this is an embedded shell and its root frame is what we want
3361 }
3362
3363 // If the background is affected by a transform, treat is as if it
3364 // wasn't fixed.
3365 if (nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame)) {
3366 attachedToFrame = aForFrame;
3367 *aOutIsTransformedFixed = true;
3368 } else {
3369 // Set the background positioning area to the viewport's area
3370 // (relative to aForFrame)
3371 bgPositioningArea =
3372 nsRect(-aForFrame->GetOffsetTo(attachedToFrame), attachedToFrame->GetSize());
3373
3374 if (!pageContentFrame) {
3375 // Subtract the size of scrollbars.
3376 nsIScrollableFrame* scrollableFrame =
3377 aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
3378 if (scrollableFrame) {
3379 nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes();
3380 bgPositioningArea.Deflate(scrollbars);
3381 }
3382 }
3383 }
3384 }
3385 *aAttachedToFrame = attachedToFrame;
3386
3387 return bgPositioningArea;
3388 }
3389
3390 // Implementation of the formula for computation of background-repeat round
3391 // See http://dev.w3.org/csswg/css3-background/#the-background-size
3392 // This function returns the adjusted size of the background image.
3393 static nscoord
ComputeRoundedSize(nscoord aCurrentSize,nscoord aPositioningSize)3394 ComputeRoundedSize(nscoord aCurrentSize, nscoord aPositioningSize)
3395 {
3396 float repeatCount = NS_roundf(float(aPositioningSize) / float(aCurrentSize));
3397 if (repeatCount < 1.0f) {
3398 return aPositioningSize;
3399 }
3400 return nscoord(NS_lround(float(aPositioningSize) / repeatCount));
3401 }
3402
3403 // Apply the CSS image sizing algorithm as it applies to background images.
3404 // See http://www.w3.org/TR/css3-background/#the-background-size .
3405 // aIntrinsicSize is the size that the background image 'would like to be'.
3406 // It can be found by calling nsImageRenderer::ComputeIntrinsicSize.
3407 static nsSize
ComputeDrawnSizeForBackground(const CSSSizeOrRatio & aIntrinsicSize,const nsSize & aBgPositioningArea,const nsStyleImageLayers::Size & aLayerSize,uint8_t aXRepeat,uint8_t aYRepeat)3408 ComputeDrawnSizeForBackground(const CSSSizeOrRatio& aIntrinsicSize,
3409 const nsSize& aBgPositioningArea,
3410 const nsStyleImageLayers::Size& aLayerSize,
3411 uint8_t aXRepeat, uint8_t aYRepeat)
3412 {
3413 nsSize imageSize;
3414
3415 // Size is dictated by cover or contain rules.
3416 if (aLayerSize.mWidthType == nsStyleImageLayers::Size::eContain ||
3417 aLayerSize.mWidthType == nsStyleImageLayers::Size::eCover) {
3418 nsImageRenderer::FitType fitType =
3419 aLayerSize.mWidthType == nsStyleImageLayers::Size::eCover
3420 ? nsImageRenderer::COVER
3421 : nsImageRenderer::CONTAIN;
3422 imageSize = nsImageRenderer::ComputeConstrainedSize(aBgPositioningArea,
3423 aIntrinsicSize.mRatio,
3424 fitType);
3425 } else {
3426 // No cover/contain constraint, use default algorithm.
3427 CSSSizeOrRatio specifiedSize;
3428 if (aLayerSize.mWidthType == nsStyleImageLayers::Size::eLengthPercentage) {
3429 specifiedSize.SetWidth(
3430 aLayerSize.ResolveWidthLengthPercentage(aBgPositioningArea));
3431 }
3432 if (aLayerSize.mHeightType == nsStyleImageLayers::Size::eLengthPercentage) {
3433 specifiedSize.SetHeight(
3434 aLayerSize.ResolveHeightLengthPercentage(aBgPositioningArea));
3435 }
3436
3437 imageSize = nsImageRenderer::ComputeConcreteSize(specifiedSize,
3438 aIntrinsicSize,
3439 aBgPositioningArea);
3440 }
3441
3442 // See https://www.w3.org/TR/css3-background/#background-size .
3443 // "If 'background-repeat' is 'round' for one (or both) dimensions, there is a second
3444 // step. The UA must scale the image in that dimension (or both dimensions) so that
3445 // it fits a whole number of times in the background positioning area."
3446 // "If 'background-repeat' is 'round' for one dimension only and if 'background-size'
3447 // is 'auto' for the other dimension, then there is a third step: that other dimension
3448 // is scaled so that the original aspect ratio is restored."
3449 bool isRepeatRoundInBothDimensions = aXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND &&
3450 aYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND;
3451
3452 // Calculate the rounded size only if the background-size computation
3453 // returned a correct size for the image.
3454 if (imageSize.width && aXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND) {
3455 imageSize.width = ComputeRoundedSize(imageSize.width, aBgPositioningArea.width);
3456 if (!isRepeatRoundInBothDimensions &&
3457 aLayerSize.mHeightType == nsStyleImageLayers::Size::DimensionType::eAuto) {
3458 // Restore intrinsic rato
3459 if (aIntrinsicSize.mRatio.width) {
3460 float scale = float(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width;
3461 imageSize.height = NSCoordSaturatingNonnegativeMultiply(imageSize.width, scale);
3462 }
3463 }
3464 }
3465
3466 // Calculate the rounded size only if the background-size computation
3467 // returned a correct size for the image.
3468 if (imageSize.height && aYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND) {
3469 imageSize.height = ComputeRoundedSize(imageSize.height, aBgPositioningArea.height);
3470 if (!isRepeatRoundInBothDimensions &&
3471 aLayerSize.mWidthType == nsStyleImageLayers::Size::DimensionType::eAuto) {
3472 // Restore intrinsic rato
3473 if (aIntrinsicSize.mRatio.height) {
3474 float scale = float(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height;
3475 imageSize.width = NSCoordSaturatingNonnegativeMultiply(imageSize.height, scale);
3476 }
3477 }
3478 }
3479
3480 return imageSize;
3481 }
3482
3483 /* ComputeSpacedRepeatSize
3484 * aImageDimension: the image width/height
3485 * aAvailableSpace: the background positioning area width/height
3486 * aRepeat: determine whether the image is repeated
3487 * Returns the image size plus gap size of app units for use as spacing
3488 */
3489 static nscoord
ComputeSpacedRepeatSize(nscoord aImageDimension,nscoord aAvailableSpace,bool & aRepeat)3490 ComputeSpacedRepeatSize(nscoord aImageDimension,
3491 nscoord aAvailableSpace,
3492 bool& aRepeat) {
3493 float ratio = static_cast<float>(aAvailableSpace) / aImageDimension;
3494
3495 if (ratio < 2.0f) { // If you can't repeat at least twice, then don't repeat.
3496 aRepeat = false;
3497 return aImageDimension;
3498 } else {
3499 aRepeat = true;
3500 return (aAvailableSpace - aImageDimension) / (NSToIntFloor(ratio) - 1);
3501 }
3502 }
3503
3504 /* ComputeBorderSpacedRepeatSize
3505 * aImageDimension: the image width/height
3506 * aAvailableSpace: the background positioning area width/height
3507 * aSpace: the space between each image
3508 * Returns the image size plus gap size of app units for use as spacing
3509 */
3510 static nscoord
ComputeBorderSpacedRepeatSize(nscoord aImageDimension,nscoord aAvailableSpace,nscoord & aSpace)3511 ComputeBorderSpacedRepeatSize(nscoord aImageDimension,
3512 nscoord aAvailableSpace,
3513 nscoord& aSpace)
3514 {
3515 int32_t count = aAvailableSpace / aImageDimension;
3516 aSpace = (aAvailableSpace - aImageDimension * count) / (count + 1);
3517 return aSpace + aImageDimension;
3518 }
3519
3520 nsBackgroundLayerState
PrepareImageLayer(nsPresContext * aPresContext,nsIFrame * aForFrame,uint32_t aFlags,const nsRect & aBorderArea,const nsRect & aBGClipRect,const nsStyleImageLayers::Layer & aLayer,bool * aOutIsTransformedFixed)3521 nsCSSRendering::PrepareImageLayer(nsPresContext* aPresContext,
3522 nsIFrame* aForFrame,
3523 uint32_t aFlags,
3524 const nsRect& aBorderArea,
3525 const nsRect& aBGClipRect,
3526 const nsStyleImageLayers::Layer& aLayer,
3527 bool* aOutIsTransformedFixed)
3528 {
3529 /*
3530 * The properties we need to keep in mind when drawing style image
3531 * layers are:
3532 *
3533 * background-image/ mask-image
3534 * background-repeat/ mask-repeat
3535 * background-attachment
3536 * background-position/ mask-position
3537 * background-clip/ mask-clip
3538 * background-origin/ mask-origin
3539 * background-size/ mask-size
3540 * background-blend-mode
3541 * box-decoration-break
3542 * mask-mode
3543 * mask-composite
3544 *
3545 * (background-color applies to the entire element and not to individual
3546 * layers, so it is irrelevant to this method.)
3547 *
3548 * These properties have the following dependencies upon each other when
3549 * determining rendering:
3550 *
3551 * background-image/ mask-image
3552 * no dependencies
3553 * background-repeat/ mask-repeat
3554 * no dependencies
3555 * background-attachment
3556 * no dependencies
3557 * background-position/ mask-position
3558 * depends upon background-size/mask-size (for the image's scaled size)
3559 * and background-break (for the background positioning area)
3560 * background-clip/ mask-clip
3561 * no dependencies
3562 * background-origin/ mask-origin
3563 * depends upon background-attachment (only in the case where that value
3564 * is 'fixed')
3565 * background-size/ mask-size
3566 * depends upon box-decoration-break (for the background positioning area
3567 * for resolving percentages), background-image (for the image's intrinsic
3568 * size), background-repeat (if that value is 'round'), and
3569 * background-origin (for the background painting area, when
3570 * background-repeat is 'round')
3571 * background-blend-mode
3572 * no dependencies
3573 * mask-mode
3574 * no dependencies
3575 * mask-composite
3576 * no dependencies
3577 * box-decoration-break
3578 * no dependencies
3579 *
3580 * As a result of only-if dependencies we don't strictly do a topological
3581 * sort of the above properties when processing, but it's pretty close to one:
3582 *
3583 * background-clip/mask-clip (by caller)
3584 * background-image/ mask-image
3585 * box-decoration-break, background-origin/ mask origin
3586 * background-attachment (postfix for background-origin if 'fixed')
3587 * background-size/ mask-size
3588 * background-position/ mask-position
3589 * background-repeat/ mask-repeat
3590 */
3591
3592 uint32_t irFlags = 0;
3593 if (aFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) {
3594 irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
3595 }
3596 if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) {
3597 irFlags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
3598 }
3599
3600 nsBackgroundLayerState state(aForFrame, &aLayer.mImage, irFlags);
3601 if (!state.mImageRenderer.PrepareImage()) {
3602 // There's no image or it's not ready to be painted.
3603 if (aOutIsTransformedFixed) {
3604 *aOutIsTransformedFixed = false;
3605 }
3606 return state;
3607 }
3608
3609 // The frame to which the background is attached
3610 nsIFrame* attachedToFrame = aForFrame;
3611 // Is the background marked 'fixed', but affected by a transform?
3612 bool transformedFixed = false;
3613 // Compute background origin area relative to aBorderArea now as we may need
3614 // it to compute the effective image size for a CSS gradient.
3615 nsRect bgPositioningArea =
3616 ComputeImageLayerPositioningArea(aPresContext, aForFrame, aBorderArea,
3617 aLayer, &attachedToFrame, &transformedFixed);
3618 if (aOutIsTransformedFixed) {
3619 *aOutIsTransformedFixed = transformedFixed;
3620 }
3621
3622 // For background-attachment:fixed backgrounds, we'll limit the area
3623 // where the background can be drawn to the viewport.
3624 nsRect bgClipRect = aBGClipRect;
3625
3626 // Compute the anchor point.
3627 //
3628 // relative to aBorderArea.TopLeft() (which is where the top-left
3629 // of aForFrame's border-box will be rendered)
3630 nsPoint imageTopLeft;
3631 if (NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED == aLayer.mAttachment && !transformedFixed) {
3632 if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) {
3633 // Clip background-attachment:fixed backgrounds to the viewport, if we're
3634 // painting to the screen and not transformed. This avoids triggering
3635 // tiling in common cases, without affecting output since drawing is
3636 // always clipped to the viewport when we draw to the screen. (But it's
3637 // not a pure optimization since it can affect the values of pixels at the
3638 // edge of the viewport --- whether they're sampled from a putative "next
3639 // tile" or not.)
3640 bgClipRect.IntersectRect(bgClipRect, bgPositioningArea + aBorderArea.TopLeft());
3641 }
3642 }
3643
3644 int repeatX = aLayer.mRepeat.mXRepeat;
3645 int repeatY = aLayer.mRepeat.mYRepeat;
3646
3647 // Scale the image as specified for background-size and background-repeat.
3648 // Also as required for proper background positioning when background-position
3649 // is defined with percentages.
3650 CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize();
3651 nsSize bgPositionSize = bgPositioningArea.Size();
3652 nsSize imageSize = ComputeDrawnSizeForBackground(intrinsicSize,
3653 bgPositionSize,
3654 aLayer.mSize,
3655 repeatX,
3656 repeatY);
3657
3658 if (imageSize.width <= 0 || imageSize.height <= 0)
3659 return state;
3660
3661 state.mImageRenderer.SetPreferredSize(intrinsicSize,
3662 imageSize);
3663
3664 // Compute the position of the background now that the background's size is
3665 // determined.
3666 nsImageRenderer::ComputeObjectAnchorPoint(aLayer.mPosition,
3667 bgPositionSize, imageSize,
3668 &imageTopLeft, &state.mAnchor);
3669 state.mRepeatSize = imageSize;
3670 if (repeatX == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
3671 bool isRepeat;
3672 state.mRepeatSize.width = ComputeSpacedRepeatSize(imageSize.width,
3673 bgPositionSize.width,
3674 isRepeat);
3675 if (isRepeat) {
3676 imageTopLeft.x = 0;
3677 state.mAnchor.x = 0;
3678 } else {
3679 repeatX = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
3680 }
3681 }
3682
3683 if (repeatY == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
3684 bool isRepeat;
3685 state.mRepeatSize.height = ComputeSpacedRepeatSize(imageSize.height,
3686 bgPositionSize.height,
3687 isRepeat);
3688 if (isRepeat) {
3689 imageTopLeft.y = 0;
3690 state.mAnchor.y = 0;
3691 } else {
3692 repeatY = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
3693 }
3694 }
3695
3696 imageTopLeft += bgPositioningArea.TopLeft();
3697 state.mAnchor += bgPositioningArea.TopLeft();
3698 state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize);
3699 state.mFillArea = state.mDestArea;
3700
3701 ExtendMode repeatMode = ExtendMode::CLAMP;
3702 if (repeatX == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
3703 repeatX == NS_STYLE_IMAGELAYER_REPEAT_ROUND ||
3704 repeatX == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
3705 state.mFillArea.x = bgClipRect.x;
3706 state.mFillArea.width = bgClipRect.width;
3707 repeatMode = ExtendMode::REPEAT_X;
3708 }
3709 if (repeatY == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
3710 repeatY == NS_STYLE_IMAGELAYER_REPEAT_ROUND ||
3711 repeatY == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
3712 state.mFillArea.y = bgClipRect.y;
3713 state.mFillArea.height = bgClipRect.height;
3714
3715 /***
3716 * We're repeating on the X axis already,
3717 * so if we have to repeat in the Y axis,
3718 * we really need to repeat in both directions.
3719 */
3720 if (repeatMode == ExtendMode::REPEAT_X) {
3721 repeatMode = ExtendMode::REPEAT;
3722 } else {
3723 repeatMode = ExtendMode::REPEAT_Y;
3724 }
3725 }
3726 state.mImageRenderer.SetExtendMode(repeatMode);
3727 state.mImageRenderer.SetMaskOp(aLayer.mMaskMode);
3728
3729 state.mFillArea.IntersectRect(state.mFillArea, bgClipRect);
3730
3731 return state;
3732 }
3733
3734 nsRect
GetBackgroundLayerRect(nsPresContext * aPresContext,nsIFrame * aForFrame,const nsRect & aBorderArea,const nsRect & aClipRect,const nsStyleImageLayers::Layer & aLayer,uint32_t aFlags)3735 nsCSSRendering::GetBackgroundLayerRect(nsPresContext* aPresContext,
3736 nsIFrame* aForFrame,
3737 const nsRect& aBorderArea,
3738 const nsRect& aClipRect,
3739 const nsStyleImageLayers::Layer& aLayer,
3740 uint32_t aFlags)
3741 {
3742 Sides skipSides = aForFrame->GetSkipSides();
3743 nsRect borderArea =
3744 ::BoxDecorationRectForBackground(aForFrame, aBorderArea, skipSides);
3745 nsBackgroundLayerState state =
3746 PrepareImageLayer(aPresContext, aForFrame, aFlags, borderArea,
3747 aClipRect, aLayer);
3748 return state.mFillArea;
3749 }
3750
3751 static DrawResult
DrawBorderImage(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aBorderArea,const nsStyleBorder & aStyleBorder,const nsRect & aDirtyRect,Sides aSkipSides,PaintBorderFlags aFlags)3752 DrawBorderImage(nsPresContext* aPresContext,
3753 nsRenderingContext& aRenderingContext,
3754 nsIFrame* aForFrame,
3755 const nsRect& aBorderArea,
3756 const nsStyleBorder& aStyleBorder,
3757 const nsRect& aDirtyRect,
3758 Sides aSkipSides,
3759 PaintBorderFlags aFlags)
3760 {
3761 NS_PRECONDITION(aStyleBorder.IsBorderImageLoaded(),
3762 "drawing border image that isn't successfully loaded");
3763
3764 if (aDirtyRect.IsEmpty()) {
3765 return DrawResult::SUCCESS;
3766 }
3767
3768 uint32_t irFlags = 0;
3769 if (aFlags & PaintBorderFlags::SYNC_DECODE_IMAGES) {
3770 irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
3771 }
3772 nsImageRenderer renderer(aForFrame, &aStyleBorder.mBorderImageSource, irFlags);
3773
3774 // Ensure we get invalidated for loads and animations of the image.
3775 // We need to do this here because this might be the only code that
3776 // knows about the association of the style data with the frame.
3777 // XXX We shouldn't really... since if anybody is passing in a
3778 // different style, they'll potentially have the wrong size for the
3779 // border too.
3780 aForFrame->AssociateImage(aStyleBorder.mBorderImageSource, aPresContext);
3781
3782 if (!renderer.PrepareImage()) {
3783 return renderer.PrepareResult();
3784 }
3785
3786 // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved()
3787 // in case we need it.
3788 gfxContextAutoSaveRestore autoSR;
3789
3790 // Determine the border image area, which by default corresponds to the
3791 // border box but can be modified by 'border-image-outset'.
3792 // Note that 'border-radius' do not apply to 'border-image' borders per
3793 // <http://dev.w3.org/csswg/css-backgrounds/#corner-clipping>.
3794 nsRect borderImgArea;
3795 nsMargin borderWidths(aStyleBorder.GetComputedBorder());
3796 nsMargin imageOutset(aStyleBorder.GetImageOutset());
3797 if (::IsBoxDecorationSlice(aStyleBorder) && !aSkipSides.IsEmpty()) {
3798 borderImgArea = ::BoxDecorationRectForBorder(aForFrame, aBorderArea,
3799 aSkipSides, &aStyleBorder);
3800 if (borderImgArea.IsEqualEdges(aBorderArea)) {
3801 // No need for a clip, just skip the sides we don't want.
3802 borderWidths.ApplySkipSides(aSkipSides);
3803 imageOutset.ApplySkipSides(aSkipSides);
3804 borderImgArea.Inflate(imageOutset);
3805 } else {
3806 // We're drawing borders around the joined continuation boxes so we need
3807 // to clip that to the slice that we want for this frame.
3808 borderImgArea.Inflate(imageOutset);
3809 imageOutset.ApplySkipSides(aSkipSides);
3810 nsRect clip = aBorderArea;
3811 clip.Inflate(imageOutset);
3812 autoSR.EnsureSaved(aRenderingContext.ThebesContext());
3813 aRenderingContext.ThebesContext()->
3814 Clip(NSRectToSnappedRect(clip,
3815 aForFrame->PresContext()->AppUnitsPerDevPixel(),
3816 *aRenderingContext.GetDrawTarget()));
3817 }
3818 } else {
3819 borderImgArea = aBorderArea;
3820 borderImgArea.Inflate(imageOutset);
3821 }
3822
3823 // Calculate the image size used to compute slice points.
3824 CSSSizeOrRatio intrinsicSize = renderer.ComputeIntrinsicSize();
3825 nsSize imageSize = nsImageRenderer::ComputeConcreteSize(CSSSizeOrRatio(),
3826 intrinsicSize,
3827 borderImgArea.Size());
3828 renderer.SetPreferredSize(intrinsicSize, imageSize);
3829
3830 // Compute the used values of 'border-image-slice' and 'border-image-width';
3831 // we do them together because the latter can depend on the former.
3832 nsMargin slice;
3833 nsMargin border;
3834 NS_FOR_CSS_SIDES(s) {
3835 nsStyleCoord coord = aStyleBorder.mBorderImageSlice.Get(s);
3836 int32_t imgDimension = NS_SIDE_IS_VERTICAL(s)
3837 ? imageSize.width : imageSize.height;
3838 nscoord borderDimension = NS_SIDE_IS_VERTICAL(s)
3839 ? borderImgArea.width : borderImgArea.height;
3840 double value;
3841 switch (coord.GetUnit()) {
3842 case eStyleUnit_Percent:
3843 value = coord.GetPercentValue() * imgDimension;
3844 break;
3845 case eStyleUnit_Factor:
3846 value = nsPresContext::CSSPixelsToAppUnits(
3847 NS_lround(coord.GetFactorValue()));
3848 break;
3849 default:
3850 NS_NOTREACHED("unexpected CSS unit for image slice");
3851 value = 0;
3852 break;
3853 }
3854 if (value < 0)
3855 value = 0;
3856 if (value > imgDimension)
3857 value = imgDimension;
3858 slice.Side(s) = value;
3859
3860 coord = aStyleBorder.mBorderImageWidth.Get(s);
3861 switch (coord.GetUnit()) {
3862 case eStyleUnit_Coord: // absolute dimension
3863 value = coord.GetCoordValue();
3864 break;
3865 case eStyleUnit_Percent:
3866 value = coord.GetPercentValue() * borderDimension;
3867 break;
3868 case eStyleUnit_Factor:
3869 value = coord.GetFactorValue() * borderWidths.Side(s);
3870 break;
3871 case eStyleUnit_Auto: // same as the slice value, in CSS pixels
3872 value = slice.Side(s);
3873 break;
3874 default:
3875 NS_NOTREACHED("unexpected CSS unit for border image area division");
3876 value = 0;
3877 break;
3878 }
3879 // NSToCoordRoundWithClamp rounds towards infinity, but that's OK
3880 // because we expect value to be non-negative.
3881 MOZ_ASSERT(value >= 0);
3882 border.Side(s) = NSToCoordRoundWithClamp(value);
3883 MOZ_ASSERT(border.Side(s) >= 0);
3884 }
3885
3886 // "If two opposite border-image-width offsets are large enough that they
3887 // overlap, their used values are proportionately reduced until they no
3888 // longer overlap."
3889 uint32_t combinedBorderWidth = uint32_t(border.left) +
3890 uint32_t(border.right);
3891 double scaleX = combinedBorderWidth > uint32_t(borderImgArea.width)
3892 ? borderImgArea.width / double(combinedBorderWidth)
3893 : 1.0;
3894 uint32_t combinedBorderHeight = uint32_t(border.top) +
3895 uint32_t(border.bottom);
3896 double scaleY = combinedBorderHeight > uint32_t(borderImgArea.height)
3897 ? borderImgArea.height / double(combinedBorderHeight)
3898 : 1.0;
3899 double scale = std::min(scaleX, scaleY);
3900 if (scale < 1.0) {
3901 border.left *= scale;
3902 border.right *= scale;
3903 border.top *= scale;
3904 border.bottom *= scale;
3905 NS_ASSERTION(border.left + border.right <= borderImgArea.width &&
3906 border.top + border.bottom <= borderImgArea.height,
3907 "rounding error in width reduction???");
3908 }
3909
3910 // These helper tables recharacterize the 'slice' and 'width' margins
3911 // in a more convenient form: they are the x/y/width/height coords
3912 // required for various bands of the border, and they have been transformed
3913 // to be relative to the innerRect (for 'slice') or the page (for 'border').
3914 enum {
3915 LEFT, MIDDLE, RIGHT,
3916 TOP = LEFT, BOTTOM = RIGHT
3917 };
3918 const nscoord borderX[3] = {
3919 borderImgArea.x + 0,
3920 borderImgArea.x + border.left,
3921 borderImgArea.x + borderImgArea.width - border.right,
3922 };
3923 const nscoord borderY[3] = {
3924 borderImgArea.y + 0,
3925 borderImgArea.y + border.top,
3926 borderImgArea.y + borderImgArea.height - border.bottom,
3927 };
3928 const nscoord borderWidth[3] = {
3929 border.left,
3930 borderImgArea.width - border.left - border.right,
3931 border.right,
3932 };
3933 const nscoord borderHeight[3] = {
3934 border.top,
3935 borderImgArea.height - border.top - border.bottom,
3936 border.bottom,
3937 };
3938 const int32_t sliceX[3] = {
3939 0,
3940 slice.left,
3941 imageSize.width - slice.right,
3942 };
3943 const int32_t sliceY[3] = {
3944 0,
3945 slice.top,
3946 imageSize.height - slice.bottom,
3947 };
3948 const int32_t sliceWidth[3] = {
3949 slice.left,
3950 std::max(imageSize.width - slice.left - slice.right, 0),
3951 slice.right,
3952 };
3953 const int32_t sliceHeight[3] = {
3954 slice.top,
3955 std::max(imageSize.height - slice.top - slice.bottom, 0),
3956 slice.bottom,
3957 };
3958
3959 DrawResult result = DrawResult::SUCCESS;
3960
3961 // intrinsicSize.CanComputeConcreteSize() return false means we can not
3962 // read intrinsic size from aStyleBorder.mBorderImageSource.
3963 // In this condition, we pass imageSize(a resolved size comes from
3964 // default sizing algorithm) to renderer as the viewport size.
3965 Maybe<nsSize> svgViewportSize = intrinsicSize.CanComputeConcreteSize() ?
3966 Nothing() : Some(imageSize);
3967 bool hasIntrinsicRatio = intrinsicSize.HasRatio();
3968 renderer.PurgeCacheForViewportChange(svgViewportSize, hasIntrinsicRatio);
3969
3970 for (int i = LEFT; i <= RIGHT; i++) {
3971 for (int j = TOP; j <= BOTTOM; j++) {
3972 uint8_t fillStyleH, fillStyleV;
3973 nsSize unitSize;
3974
3975 if (i == MIDDLE && j == MIDDLE) {
3976 // Discard the middle portion unless set to fill.
3977 if (NS_STYLE_BORDER_IMAGE_SLICE_NOFILL ==
3978 aStyleBorder.mBorderImageFill) {
3979 continue;
3980 }
3981
3982 NS_ASSERTION(NS_STYLE_BORDER_IMAGE_SLICE_FILL ==
3983 aStyleBorder.mBorderImageFill,
3984 "Unexpected border image fill");
3985
3986 // css-background:
3987 // The middle image's width is scaled by the same factor as the
3988 // top image unless that factor is zero or infinity, in which
3989 // case the scaling factor of the bottom is substituted, and
3990 // failing that, the width is not scaled. The height of the
3991 // middle image is scaled by the same factor as the left image
3992 // unless that factor is zero or infinity, in which case the
3993 // scaling factor of the right image is substituted, and failing
3994 // that, the height is not scaled.
3995 gfxFloat hFactor, vFactor;
3996
3997 if (0 < border.left && 0 < slice.left)
3998 vFactor = gfxFloat(border.left)/slice.left;
3999 else if (0 < border.right && 0 < slice.right)
4000 vFactor = gfxFloat(border.right)/slice.right;
4001 else
4002 vFactor = 1;
4003
4004 if (0 < border.top && 0 < slice.top)
4005 hFactor = gfxFloat(border.top)/slice.top;
4006 else if (0 < border.bottom && 0 < slice.bottom)
4007 hFactor = gfxFloat(border.bottom)/slice.bottom;
4008 else
4009 hFactor = 1;
4010
4011 unitSize.width = sliceWidth[i]*hFactor;
4012 unitSize.height = sliceHeight[j]*vFactor;
4013 fillStyleH = aStyleBorder.mBorderImageRepeatH;
4014 fillStyleV = aStyleBorder.mBorderImageRepeatV;
4015
4016 } else if (i == MIDDLE) { // top, bottom
4017 // Sides are always stretched to the thickness of their border,
4018 // and stretched proportionately on the other axis.
4019 gfxFloat factor;
4020 if (0 < borderHeight[j] && 0 < sliceHeight[j])
4021 factor = gfxFloat(borderHeight[j])/sliceHeight[j];
4022 else
4023 factor = 1;
4024
4025 unitSize.width = sliceWidth[i]*factor;
4026 unitSize.height = borderHeight[j];
4027 fillStyleH = aStyleBorder.mBorderImageRepeatH;
4028 fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
4029
4030 } else if (j == MIDDLE) { // left, right
4031 gfxFloat factor;
4032 if (0 < borderWidth[i] && 0 < sliceWidth[i])
4033 factor = gfxFloat(borderWidth[i])/sliceWidth[i];
4034 else
4035 factor = 1;
4036
4037 unitSize.width = borderWidth[i];
4038 unitSize.height = sliceHeight[j]*factor;
4039 fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
4040 fillStyleV = aStyleBorder.mBorderImageRepeatV;
4041
4042 } else {
4043 // Corners are always stretched to fit the corner.
4044 unitSize.width = borderWidth[i];
4045 unitSize.height = borderHeight[j];
4046 fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
4047 fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
4048 }
4049
4050 nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]);
4051 nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]);
4052 if (subArea.IsEmpty())
4053 continue;
4054
4055 nsIntRect intSubArea = subArea.ToOutsidePixels(nsPresContext::AppUnitsPerCSSPixel());
4056 result &=
4057 renderer.DrawBorderImageComponent(aPresContext,
4058 aRenderingContext, aDirtyRect,
4059 destArea, CSSIntRect(intSubArea.x,
4060 intSubArea.y,
4061 intSubArea.width,
4062 intSubArea.height),
4063 fillStyleH, fillStyleV,
4064 unitSize, j * (RIGHT + 1) + i,
4065 svgViewportSize, hasIntrinsicRatio);
4066 }
4067 }
4068
4069 return result;
4070 }
4071
4072 // Begin table border-collapsing section
4073 // These functions were written to not disrupt the normal ones and yet satisfy some additional requirements
4074 // At some point, all functions should be unified to include the additional functionality that these provide
4075
4076 static nscoord
RoundIntToPixel(nscoord aValue,nscoord aTwipsPerPixel,bool aRoundDown=false)4077 RoundIntToPixel(nscoord aValue,
4078 nscoord aTwipsPerPixel,
4079 bool aRoundDown = false)
4080 {
4081 if (aTwipsPerPixel <= 0)
4082 // We must be rendering to a device that has a resolution greater than Twips!
4083 // In that case, aValue is as accurate as it's going to get.
4084 return aValue;
4085
4086 nscoord halfPixel = NSToCoordRound(aTwipsPerPixel / 2.0f);
4087 nscoord extra = aValue % aTwipsPerPixel;
4088 nscoord finalValue = (!aRoundDown && (extra >= halfPixel)) ? aValue + (aTwipsPerPixel - extra) : aValue - extra;
4089 return finalValue;
4090 }
4091
4092 static nscoord
RoundFloatToPixel(float aValue,nscoord aTwipsPerPixel,bool aRoundDown=false)4093 RoundFloatToPixel(float aValue,
4094 nscoord aTwipsPerPixel,
4095 bool aRoundDown = false)
4096 {
4097 return RoundIntToPixel(NSToCoordRound(aValue), aTwipsPerPixel, aRoundDown);
4098 }
4099
SetPoly(const Rect & aRect,Point * poly)4100 static void SetPoly(const Rect& aRect, Point* poly)
4101 {
4102 poly[0].x = aRect.x;
4103 poly[0].y = aRect.y;
4104 poly[1].x = aRect.x + aRect.width;
4105 poly[1].y = aRect.y;
4106 poly[2].x = aRect.x + aRect.width;
4107 poly[2].y = aRect.y + aRect.height;
4108 poly[3].x = aRect.x;
4109 poly[3].y = aRect.y + aRect.height;
4110 }
4111
4112 static void
DrawDashedSegment(DrawTarget & aDrawTarget,nsRect aRect,nscoord aDashLength,nscolor aColor,int32_t aAppUnitsPerDevPixel,nscoord aTwipsPerPixel,bool aHorizontal)4113 DrawDashedSegment(DrawTarget& aDrawTarget,
4114 nsRect aRect,
4115 nscoord aDashLength,
4116 nscolor aColor,
4117 int32_t aAppUnitsPerDevPixel,
4118 nscoord aTwipsPerPixel,
4119 bool aHorizontal)
4120 {
4121 ColorPattern color(ToDeviceColor(aColor));
4122 DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
4123 StrokeOptions strokeOptions;
4124
4125 Float dash[2];
4126 dash[0] = Float(aDashLength) / aAppUnitsPerDevPixel;
4127 dash[1] = dash[0];
4128
4129 strokeOptions.mDashPattern = dash;
4130 strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
4131
4132 if (aHorizontal) {
4133 nsPoint left = (aRect.TopLeft() + aRect.BottomLeft()) / 2;
4134 nsPoint right = (aRect.TopRight() + aRect.BottomRight()) / 2;
4135 strokeOptions.mLineWidth = Float(aRect.height) / aAppUnitsPerDevPixel;
4136 StrokeLineWithSnapping(left, right,
4137 aAppUnitsPerDevPixel, aDrawTarget,
4138 color, strokeOptions, drawOptions);
4139 } else {
4140 nsPoint top = (aRect.TopLeft() + aRect.TopRight()) / 2;
4141 nsPoint bottom = (aRect.BottomLeft() + aRect.BottomRight()) / 2;
4142 strokeOptions.mLineWidth = Float(aRect.width) / aAppUnitsPerDevPixel;
4143 StrokeLineWithSnapping(top, bottom,
4144 aAppUnitsPerDevPixel, aDrawTarget,
4145 color, strokeOptions, drawOptions);
4146 }
4147 }
4148
4149 static void
DrawSolidBorderSegment(DrawTarget & aDrawTarget,nsRect aRect,nscolor aColor,int32_t aAppUnitsPerDevPixel,nscoord aTwipsPerPixel,uint8_t aStartBevelSide=0,nscoord aStartBevelOffset=0,uint8_t aEndBevelSide=0,nscoord aEndBevelOffset=0)4150 DrawSolidBorderSegment(DrawTarget& aDrawTarget,
4151 nsRect aRect,
4152 nscolor aColor,
4153 int32_t aAppUnitsPerDevPixel,
4154 nscoord aTwipsPerPixel,
4155 uint8_t aStartBevelSide = 0,
4156 nscoord aStartBevelOffset = 0,
4157 uint8_t aEndBevelSide = 0,
4158 nscoord aEndBevelOffset = 0)
4159 {
4160 ColorPattern color(ToDeviceColor(aColor));
4161 DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
4162
4163 // We don't need to bevel single pixel borders
4164 if ((aRect.width == aTwipsPerPixel) || (aRect.height == aTwipsPerPixel) ||
4165 ((0 == aStartBevelOffset) && (0 == aEndBevelOffset))) {
4166 // simple rectangle
4167 aDrawTarget.FillRect(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel,
4168 aDrawTarget),
4169 color, drawOptions);
4170 }
4171 else {
4172 // polygon with beveling
4173 Point poly[4];
4174 SetPoly(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget),
4175 poly);
4176
4177 Float startBevelOffset =
4178 NSAppUnitsToFloatPixels(aStartBevelOffset, aAppUnitsPerDevPixel);
4179 switch(aStartBevelSide) {
4180 case NS_SIDE_TOP:
4181 poly[0].x += startBevelOffset;
4182 break;
4183 case NS_SIDE_BOTTOM:
4184 poly[3].x += startBevelOffset;
4185 break;
4186 case NS_SIDE_RIGHT:
4187 poly[1].y += startBevelOffset;
4188 break;
4189 case NS_SIDE_LEFT:
4190 poly[0].y += startBevelOffset;
4191 }
4192
4193 Float endBevelOffset =
4194 NSAppUnitsToFloatPixels(aEndBevelOffset, aAppUnitsPerDevPixel);
4195 switch(aEndBevelSide) {
4196 case NS_SIDE_TOP:
4197 poly[1].x -= endBevelOffset;
4198 break;
4199 case NS_SIDE_BOTTOM:
4200 poly[2].x -= endBevelOffset;
4201 break;
4202 case NS_SIDE_RIGHT:
4203 poly[2].y -= endBevelOffset;
4204 break;
4205 case NS_SIDE_LEFT:
4206 poly[3].y -= endBevelOffset;
4207 }
4208
4209 RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
4210 builder->MoveTo(poly[0]);
4211 builder->LineTo(poly[1]);
4212 builder->LineTo(poly[2]);
4213 builder->LineTo(poly[3]);
4214 builder->Close();
4215 RefPtr<Path> path = builder->Finish();
4216 aDrawTarget.Fill(path, color, drawOptions);
4217 }
4218 }
4219
4220 static void
GetDashInfo(nscoord aBorderLength,nscoord aDashLength,nscoord aTwipsPerPixel,int32_t & aNumDashSpaces,nscoord & aStartDashLength,nscoord & aEndDashLength)4221 GetDashInfo(nscoord aBorderLength,
4222 nscoord aDashLength,
4223 nscoord aTwipsPerPixel,
4224 int32_t& aNumDashSpaces,
4225 nscoord& aStartDashLength,
4226 nscoord& aEndDashLength)
4227 {
4228 aNumDashSpaces = 0;
4229 if (aStartDashLength + aDashLength + aEndDashLength >= aBorderLength) {
4230 aStartDashLength = aBorderLength;
4231 aEndDashLength = 0;
4232 }
4233 else {
4234 aNumDashSpaces = (aBorderLength - aDashLength)/ (2 * aDashLength); // round down
4235 nscoord extra = aBorderLength - aStartDashLength - aEndDashLength - (((2 * aNumDashSpaces) - 1) * aDashLength);
4236 if (extra > 0) {
4237 nscoord half = RoundIntToPixel(extra / 2, aTwipsPerPixel);
4238 aStartDashLength += half;
4239 aEndDashLength += (extra - half);
4240 }
4241 }
4242 }
4243
4244 void
DrawTableBorderSegment(DrawTarget & aDrawTarget,uint8_t aBorderStyle,nscolor aBorderColor,const nsStyleBackground * aBGColor,const nsRect & aBorder,int32_t aAppUnitsPerDevPixel,int32_t aAppUnitsPerCSSPixel,uint8_t aStartBevelSide,nscoord aStartBevelOffset,uint8_t aEndBevelSide,nscoord aEndBevelOffset)4245 nsCSSRendering::DrawTableBorderSegment(DrawTarget& aDrawTarget,
4246 uint8_t aBorderStyle,
4247 nscolor aBorderColor,
4248 const nsStyleBackground* aBGColor,
4249 const nsRect& aBorder,
4250 int32_t aAppUnitsPerDevPixel,
4251 int32_t aAppUnitsPerCSSPixel,
4252 uint8_t aStartBevelSide,
4253 nscoord aStartBevelOffset,
4254 uint8_t aEndBevelSide,
4255 nscoord aEndBevelOffset)
4256 {
4257 bool horizontal = ((NS_SIDE_TOP == aStartBevelSide) || (NS_SIDE_BOTTOM == aStartBevelSide));
4258 nscoord twipsPerPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerCSSPixel);
4259 uint8_t ridgeGroove = NS_STYLE_BORDER_STYLE_RIDGE;
4260
4261 if ((twipsPerPixel >= aBorder.width) || (twipsPerPixel >= aBorder.height) ||
4262 (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle) || (NS_STYLE_BORDER_STYLE_DOTTED == aBorderStyle)) {
4263 // no beveling for 1 pixel border, dash or dot
4264 aStartBevelOffset = 0;
4265 aEndBevelOffset = 0;
4266 }
4267
4268 switch (aBorderStyle) {
4269 case NS_STYLE_BORDER_STYLE_NONE:
4270 case NS_STYLE_BORDER_STYLE_HIDDEN:
4271 //NS_ASSERTION(false, "style of none or hidden");
4272 break;
4273 case NS_STYLE_BORDER_STYLE_DOTTED:
4274 case NS_STYLE_BORDER_STYLE_DASHED:
4275 {
4276 nscoord dashLength = (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle) ? DASH_LENGTH : DOT_LENGTH;
4277 // make the dash length proportional to the border thickness
4278 dashLength *= (horizontal) ? aBorder.height : aBorder.width;
4279 // make the min dash length for the ends 1/2 the dash length
4280 nscoord minDashLength = (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle)
4281 ? RoundFloatToPixel(((float)dashLength) / 2.0f, twipsPerPixel) : dashLength;
4282 minDashLength = std::max(minDashLength, twipsPerPixel);
4283 nscoord numDashSpaces = 0;
4284 nscoord startDashLength = minDashLength;
4285 nscoord endDashLength = minDashLength;
4286 if (horizontal) {
4287 GetDashInfo(aBorder.width, dashLength, twipsPerPixel, numDashSpaces,
4288 startDashLength, endDashLength);
4289 nsRect rect(aBorder.x, aBorder.y, startDashLength, aBorder.height);
4290 DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
4291 aAppUnitsPerDevPixel, twipsPerPixel);
4292
4293 rect.x += startDashLength + dashLength;
4294 rect.width = aBorder.width
4295 - (startDashLength + endDashLength + dashLength);
4296 DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor,
4297 aAppUnitsPerDevPixel, twipsPerPixel, horizontal);
4298
4299 rect.x += rect.width;
4300 rect.width = endDashLength;
4301 DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
4302 aAppUnitsPerDevPixel, twipsPerPixel);
4303 }
4304 else {
4305 GetDashInfo(aBorder.height, dashLength, twipsPerPixel, numDashSpaces,
4306 startDashLength, endDashLength);
4307 nsRect rect(aBorder.x, aBorder.y, aBorder.width, startDashLength);
4308 DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
4309 aAppUnitsPerDevPixel, twipsPerPixel);
4310
4311 rect.y += rect.height + dashLength;
4312 rect.height = aBorder.height
4313 - (startDashLength + endDashLength + dashLength);
4314 DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor,
4315 aAppUnitsPerDevPixel, twipsPerPixel, horizontal);
4316
4317 rect.y += rect.height;
4318 rect.height = endDashLength;
4319 DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
4320 aAppUnitsPerDevPixel, twipsPerPixel);
4321 }
4322 }
4323 break;
4324 case NS_STYLE_BORDER_STYLE_GROOVE:
4325 ridgeGroove = NS_STYLE_BORDER_STYLE_GROOVE; // and fall through to ridge
4326 MOZ_FALLTHROUGH;
4327 case NS_STYLE_BORDER_STYLE_RIDGE:
4328 if ((horizontal && (twipsPerPixel >= aBorder.height)) ||
4329 (!horizontal && (twipsPerPixel >= aBorder.width))) {
4330 // a one pixel border
4331 DrawSolidBorderSegment(aDrawTarget, aBorder, aBorderColor,
4332 aAppUnitsPerDevPixel, twipsPerPixel,
4333 aStartBevelSide, aStartBevelOffset,
4334 aEndBevelSide, aEndBevelOffset);
4335 }
4336 else {
4337 nscoord startBevel = (aStartBevelOffset > 0)
4338 ? RoundFloatToPixel(0.5f * (float)aStartBevelOffset, twipsPerPixel, true) : 0;
4339 nscoord endBevel = (aEndBevelOffset > 0)
4340 ? RoundFloatToPixel(0.5f * (float)aEndBevelOffset, twipsPerPixel, true) : 0;
4341 mozilla::css::Side ridgeGrooveSide = (horizontal) ? NS_SIDE_TOP : NS_SIDE_LEFT;
4342 // FIXME: In theory, this should use the visited-dependent
4343 // background color, but I don't care.
4344 nscolor bevelColor = MakeBevelColor(ridgeGrooveSide, ridgeGroove,
4345 aBGColor->mBackgroundColor,
4346 aBorderColor);
4347 nsRect rect(aBorder);
4348 nscoord half;
4349 if (horizontal) { // top, bottom
4350 half = RoundFloatToPixel(0.5f * (float)aBorder.height, twipsPerPixel);
4351 rect.height = half;
4352 if (NS_SIDE_TOP == aStartBevelSide) {
4353 rect.x += startBevel;
4354 rect.width -= startBevel;
4355 }
4356 if (NS_SIDE_TOP == aEndBevelSide) {
4357 rect.width -= endBevel;
4358 }
4359 DrawSolidBorderSegment(aDrawTarget, rect, bevelColor,
4360 aAppUnitsPerDevPixel, twipsPerPixel,
4361 aStartBevelSide, startBevel, aEndBevelSide,
4362 endBevel);
4363 }
4364 else { // left, right
4365 half = RoundFloatToPixel(0.5f * (float)aBorder.width, twipsPerPixel);
4366 rect.width = half;
4367 if (NS_SIDE_LEFT == aStartBevelSide) {
4368 rect.y += startBevel;
4369 rect.height -= startBevel;
4370 }
4371 if (NS_SIDE_LEFT == aEndBevelSide) {
4372 rect.height -= endBevel;
4373 }
4374 DrawSolidBorderSegment(aDrawTarget, rect, bevelColor,
4375 aAppUnitsPerDevPixel, twipsPerPixel,
4376 aStartBevelSide, startBevel, aEndBevelSide,
4377 endBevel);
4378 }
4379
4380 rect = aBorder;
4381 ridgeGrooveSide = (NS_SIDE_TOP == ridgeGrooveSide) ? NS_SIDE_BOTTOM : NS_SIDE_RIGHT;
4382 // FIXME: In theory, this should use the visited-dependent
4383 // background color, but I don't care.
4384 bevelColor = MakeBevelColor(ridgeGrooveSide, ridgeGroove,
4385 aBGColor->mBackgroundColor, aBorderColor);
4386 if (horizontal) {
4387 rect.y = rect.y + half;
4388 rect.height = aBorder.height - half;
4389 if (NS_SIDE_BOTTOM == aStartBevelSide) {
4390 rect.x += startBevel;
4391 rect.width -= startBevel;
4392 }
4393 if (NS_SIDE_BOTTOM == aEndBevelSide) {
4394 rect.width -= endBevel;
4395 }
4396 DrawSolidBorderSegment(aDrawTarget, rect, bevelColor,
4397 aAppUnitsPerDevPixel, twipsPerPixel,
4398 aStartBevelSide, startBevel, aEndBevelSide,
4399 endBevel);
4400 }
4401 else {
4402 rect.x = rect.x + half;
4403 rect.width = aBorder.width - half;
4404 if (NS_SIDE_RIGHT == aStartBevelSide) {
4405 rect.y += aStartBevelOffset - startBevel;
4406 rect.height -= startBevel;
4407 }
4408 if (NS_SIDE_RIGHT == aEndBevelSide) {
4409 rect.height -= endBevel;
4410 }
4411 DrawSolidBorderSegment(aDrawTarget, rect, bevelColor,
4412 aAppUnitsPerDevPixel, twipsPerPixel,
4413 aStartBevelSide, startBevel, aEndBevelSide,
4414 endBevel);
4415 }
4416 }
4417 break;
4418 case NS_STYLE_BORDER_STYLE_DOUBLE:
4419 // We can only do "double" borders if the thickness of the border
4420 // is more than 2px. Otherwise, we fall through to painting a
4421 // solid border.
4422 if ((aBorder.width > 2*twipsPerPixel || horizontal) &&
4423 (aBorder.height > 2*twipsPerPixel || !horizontal)) {
4424 nscoord startBevel = (aStartBevelOffset > 0)
4425 ? RoundFloatToPixel(0.333333f * (float)aStartBevelOffset, twipsPerPixel) : 0;
4426 nscoord endBevel = (aEndBevelOffset > 0)
4427 ? RoundFloatToPixel(0.333333f * (float)aEndBevelOffset, twipsPerPixel) : 0;
4428 if (horizontal) { // top, bottom
4429 nscoord thirdHeight = RoundFloatToPixel(0.333333f * (float)aBorder.height, twipsPerPixel);
4430
4431 // draw the top line or rect
4432 nsRect topRect(aBorder.x, aBorder.y, aBorder.width, thirdHeight);
4433 if (NS_SIDE_TOP == aStartBevelSide) {
4434 topRect.x += aStartBevelOffset - startBevel;
4435 topRect.width -= aStartBevelOffset - startBevel;
4436 }
4437 if (NS_SIDE_TOP == aEndBevelSide) {
4438 topRect.width -= aEndBevelOffset - endBevel;
4439 }
4440 DrawSolidBorderSegment(aDrawTarget, topRect, aBorderColor,
4441 aAppUnitsPerDevPixel, twipsPerPixel,
4442 aStartBevelSide, startBevel, aEndBevelSide,
4443 endBevel);
4444
4445 // draw the botom line or rect
4446 nscoord heightOffset = aBorder.height - thirdHeight;
4447 nsRect bottomRect(aBorder.x, aBorder.y + heightOffset, aBorder.width, aBorder.height - heightOffset);
4448 if (NS_SIDE_BOTTOM == aStartBevelSide) {
4449 bottomRect.x += aStartBevelOffset - startBevel;
4450 bottomRect.width -= aStartBevelOffset - startBevel;
4451 }
4452 if (NS_SIDE_BOTTOM == aEndBevelSide) {
4453 bottomRect.width -= aEndBevelOffset - endBevel;
4454 }
4455 DrawSolidBorderSegment(aDrawTarget, bottomRect, aBorderColor,
4456 aAppUnitsPerDevPixel, twipsPerPixel,
4457 aStartBevelSide, startBevel, aEndBevelSide,
4458 endBevel);
4459 }
4460 else { // left, right
4461 nscoord thirdWidth = RoundFloatToPixel(0.333333f * (float)aBorder.width, twipsPerPixel);
4462
4463 nsRect leftRect(aBorder.x, aBorder.y, thirdWidth, aBorder.height);
4464 if (NS_SIDE_LEFT == aStartBevelSide) {
4465 leftRect.y += aStartBevelOffset - startBevel;
4466 leftRect.height -= aStartBevelOffset - startBevel;
4467 }
4468 if (NS_SIDE_LEFT == aEndBevelSide) {
4469 leftRect.height -= aEndBevelOffset - endBevel;
4470 }
4471 DrawSolidBorderSegment(aDrawTarget, leftRect, aBorderColor,
4472 aAppUnitsPerDevPixel, twipsPerPixel,
4473 aStartBevelSide, startBevel, aEndBevelSide,
4474 endBevel);
4475
4476 nscoord widthOffset = aBorder.width - thirdWidth;
4477 nsRect rightRect(aBorder.x + widthOffset, aBorder.y, aBorder.width - widthOffset, aBorder.height);
4478 if (NS_SIDE_RIGHT == aStartBevelSide) {
4479 rightRect.y += aStartBevelOffset - startBevel;
4480 rightRect.height -= aStartBevelOffset - startBevel;
4481 }
4482 if (NS_SIDE_RIGHT == aEndBevelSide) {
4483 rightRect.height -= aEndBevelOffset - endBevel;
4484 }
4485 DrawSolidBorderSegment(aDrawTarget, rightRect, aBorderColor,
4486 aAppUnitsPerDevPixel, twipsPerPixel,
4487 aStartBevelSide, startBevel, aEndBevelSide,
4488 endBevel);
4489 }
4490 break;
4491 }
4492 // else fall through to solid
4493 MOZ_FALLTHROUGH;
4494 case NS_STYLE_BORDER_STYLE_SOLID:
4495 DrawSolidBorderSegment(aDrawTarget, aBorder, aBorderColor,
4496 aAppUnitsPerDevPixel, twipsPerPixel, aStartBevelSide,
4497 aStartBevelOffset, aEndBevelSide, aEndBevelOffset);
4498 break;
4499 case NS_STYLE_BORDER_STYLE_OUTSET:
4500 case NS_STYLE_BORDER_STYLE_INSET:
4501 NS_ASSERTION(false, "inset, outset should have been converted to groove, ridge");
4502 break;
4503 case NS_STYLE_BORDER_STYLE_AUTO:
4504 NS_ASSERTION(false, "Unexpected 'auto' table border");
4505 break;
4506 }
4507 }
4508
4509 // End table border-collapsing section
4510
4511 Rect
ExpandPaintingRectForDecorationLine(nsIFrame * aFrame,const uint8_t aStyle,const Rect & aClippedRect,const Float aICoordInFrame,const Float aCycleLength,bool aVertical)4512 nsCSSRendering::ExpandPaintingRectForDecorationLine(
4513 nsIFrame* aFrame,
4514 const uint8_t aStyle,
4515 const Rect& aClippedRect,
4516 const Float aICoordInFrame,
4517 const Float aCycleLength,
4518 bool aVertical)
4519 {
4520 switch (aStyle) {
4521 case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
4522 case NS_STYLE_TEXT_DECORATION_STYLE_DASHED:
4523 case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
4524 break;
4525 default:
4526 NS_ERROR("Invalid style was specified");
4527 return aClippedRect;
4528 }
4529
4530 nsBlockFrame* block = nullptr;
4531 // Note that when we paint the decoration lines in relative positioned
4532 // box, we should paint them like all of the boxes are positioned as static.
4533 nscoord framePosInBlockAppUnits = 0;
4534 for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
4535 block = do_QueryFrame(f);
4536 if (block) {
4537 break;
4538 }
4539 framePosInBlockAppUnits += aVertical ?
4540 f->GetNormalPosition().y : f->GetNormalPosition().x;
4541 }
4542
4543 NS_ENSURE_TRUE(block, aClippedRect);
4544
4545 nsPresContext *pc = aFrame->PresContext();
4546 Float framePosInBlock = Float(pc->AppUnitsToGfxUnits(framePosInBlockAppUnits));
4547 int32_t rectPosInBlock =
4548 int32_t(NS_round(framePosInBlock + aICoordInFrame));
4549 int32_t extraStartEdge =
4550 rectPosInBlock - (rectPosInBlock / int32_t(aCycleLength) * aCycleLength);
4551 Rect rect(aClippedRect);
4552 if (aVertical) {
4553 rect.y -= extraStartEdge;
4554 rect.height += extraStartEdge;
4555 } else {
4556 rect.x -= extraStartEdge;
4557 rect.width += extraStartEdge;
4558 }
4559 return rect;
4560 }
4561
4562 void
PaintDecorationLine(nsIFrame * aFrame,DrawTarget & aDrawTarget,const PaintDecorationLineParams & aParams)4563 nsCSSRendering::PaintDecorationLine(nsIFrame* aFrame, DrawTarget& aDrawTarget,
4564 const PaintDecorationLineParams& aParams)
4565 {
4566 NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
4567 "aStyle is none");
4568
4569 Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams));
4570 if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) {
4571 return;
4572 }
4573
4574 if (aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE &&
4575 aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_OVERLINE &&
4576 aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
4577 NS_ERROR("Invalid decoration value!");
4578 return;
4579 }
4580
4581 Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
4582
4583 ColorPattern color(ToDeviceColor(aParams.color));
4584 StrokeOptions strokeOptions(lineThickness);
4585 DrawOptions drawOptions;
4586
4587 Float dash[2];
4588
4589 AutoPopClips autoPopClips(&aDrawTarget);
4590
4591 switch (aParams.style) {
4592 case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
4593 case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE:
4594 break;
4595 case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
4596 autoPopClips.PushClipRect(rect);
4597 Float dashWidth = lineThickness * DOT_LENGTH * DASH_LENGTH;
4598 dash[0] = dashWidth;
4599 dash[1] = dashWidth;
4600 strokeOptions.mDashPattern = dash;
4601 strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
4602 strokeOptions.mLineCap = CapStyle::BUTT;
4603 rect = ExpandPaintingRectForDecorationLine(aFrame, aParams.style,
4604 rect, aParams.icoordInFrame,
4605 dashWidth * 2,
4606 aParams.vertical);
4607 // We should continue to draw the last dash even if it is not in the rect.
4608 rect.width += dashWidth;
4609 break;
4610 }
4611 case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED: {
4612 autoPopClips.PushClipRect(rect);
4613 Float dashWidth = lineThickness * DOT_LENGTH;
4614 if (lineThickness > 2.0) {
4615 dash[0] = 0.f;
4616 dash[1] = dashWidth * 2.f;
4617 strokeOptions.mLineCap = CapStyle::ROUND;
4618 } else {
4619 dash[0] = dashWidth;
4620 dash[1] = dashWidth;
4621 }
4622 strokeOptions.mDashPattern = dash;
4623 strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
4624 rect = ExpandPaintingRectForDecorationLine(aFrame, aParams.style,
4625 rect, aParams.icoordInFrame,
4626 dashWidth * 2,
4627 aParams.vertical);
4628 // We should continue to draw the last dot even if it is not in the rect.
4629 rect.width += dashWidth;
4630 break;
4631 }
4632 case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
4633 autoPopClips.PushClipRect(rect);
4634 if (lineThickness > 2.0) {
4635 drawOptions.mAntialiasMode = AntialiasMode::SUBPIXEL;
4636 } else {
4637 // Don't use anti-aliasing here. Because looks like lighter color wavy
4638 // line at this case. And probably, users don't think the
4639 // non-anti-aliased wavy line is not pretty.
4640 drawOptions.mAntialiasMode = AntialiasMode::NONE;
4641 }
4642 break;
4643 default:
4644 NS_ERROR("Invalid style value!");
4645 return;
4646 }
4647
4648 // The block-direction position should be set to the middle of the line.
4649 if (aParams.vertical) {
4650 rect.x += lineThickness / 2;
4651 } else {
4652 rect.y += lineThickness / 2;
4653 }
4654
4655 switch (aParams.style) {
4656 case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
4657 case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
4658 case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
4659 Point p1 = rect.TopLeft();
4660 Point p2 = aParams.vertical ? rect.BottomLeft() : rect.TopRight();
4661 aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
4662 return;
4663 }
4664 case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE: {
4665 /**
4666 * We are drawing double line as:
4667 *
4668 * +-------------------------------------------+
4669 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4670 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4671 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4672 * | |
4673 * | |
4674 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4675 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4676 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4677 * +-------------------------------------------+
4678 */
4679 Point p1 = rect.TopLeft();
4680 Point p2 = aParams.vertical ? rect.BottomLeft() : rect.TopRight();
4681 aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
4682
4683 if (aParams.vertical) {
4684 rect.width -= lineThickness;
4685 } else {
4686 rect.height -= lineThickness;
4687 }
4688
4689 p1 = aParams.vertical ? rect.TopRight() : rect.BottomLeft();
4690 p2 = rect.BottomRight();
4691 aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
4692 return;
4693 }
4694 case NS_STYLE_TEXT_DECORATION_STYLE_WAVY: {
4695 /**
4696 * We are drawing wavy line as:
4697 *
4698 * P: Path, X: Painted pixel
4699 *
4700 * +---------------------------------------+
4701 * XX|X XXXXXX XXXXXX |
4702 * PP|PX XPPPPPPX XPPPPPPX | ^
4703 * XX|XPX XPXXXXXXPX XPXXXXXXPX| |
4704 * | XPX XPX XPX XPX XP|X |adv
4705 * | XPXXXXXXPX XPXXXXXXPX X|PX |
4706 * | XPPPPPPX XPPPPPPX |XPX v
4707 * | XXXXXX XXXXXX | XX
4708 * +---------------------------------------+
4709 * <---><---> ^
4710 * adv flatLengthAtVertex rightMost
4711 *
4712 * 1. Always starts from top-left of the drawing area, however, we need
4713 * to draw the line from outside of the rect. Because the start
4714 * point of the line is not good style if we draw from inside it.
4715 * 2. First, draw horizontal line from outside the rect to top-left of
4716 * the rect;
4717 * 3. Goes down to bottom of the area at 45 degrees.
4718 * 4. Slides to right horizontaly, see |flatLengthAtVertex|.
4719 * 5. Goes up to top of the area at 45 degrees.
4720 * 6. Slides to right horizontaly.
4721 * 7. Repeat from 2 until reached to right-most edge of the area.
4722 *
4723 * In the vertical case, swap horizontal and vertical coordinates and
4724 * directions in the above description.
4725 */
4726
4727 Float& rectICoord = aParams.vertical ? rect.y : rect.x;
4728 Float& rectISize = aParams.vertical ? rect.height : rect.width;
4729 const Float rectBSize = aParams.vertical ? rect.width : rect.height;
4730
4731 const Float adv = rectBSize - lineThickness;
4732 const Float flatLengthAtVertex =
4733 std::max((lineThickness - 1.0) * 2.0, 1.0);
4734
4735 // Align the start of wavy lines to the nearest ancestor block.
4736 const Float cycleLength = 2 * (adv + flatLengthAtVertex);
4737 rect = ExpandPaintingRectForDecorationLine(aFrame, aParams.style, rect,
4738 aParams.icoordInFrame,
4739 cycleLength, aParams.vertical);
4740 // figure out if we can trim whole cycles from the left and right edges
4741 // of the line, to try and avoid creating an unnecessarily long and
4742 // complex path
4743 const Float dirtyRectICoord = aParams.vertical ? aParams.dirtyRect.y
4744 : aParams.dirtyRect.x;
4745 int32_t skipCycles = floor((dirtyRectICoord - rectICoord) / cycleLength);
4746 if (skipCycles > 0) {
4747 rectICoord += skipCycles * cycleLength;
4748 rectISize -= skipCycles * cycleLength;
4749 }
4750
4751 rectICoord += lineThickness / 2.0;
4752 Point pt(rect.TopLeft());
4753 Float& ptICoord = aParams.vertical ? pt.y : pt.x;
4754 Float& ptBCoord = aParams.vertical ? pt.x : pt.y;
4755 if (aParams.vertical) {
4756 ptBCoord += adv + lineThickness / 2.0;
4757 }
4758 Float iCoordLimit = ptICoord + rectISize + lineThickness;
4759
4760 const Float dirtyRectIMost = aParams.vertical ?
4761 aParams.dirtyRect.YMost() : aParams.dirtyRect.XMost();
4762 skipCycles = floor((iCoordLimit - dirtyRectIMost) / cycleLength);
4763 if (skipCycles > 0) {
4764 iCoordLimit -= skipCycles * cycleLength;
4765 }
4766
4767 RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
4768 RefPtr<Path> path;
4769
4770 ptICoord -= lineThickness;
4771 builder->MoveTo(pt); // 1
4772
4773 ptICoord = rectICoord;
4774 builder->LineTo(pt); // 2
4775
4776 // In vertical mode, to go "down" relative to the text we need to
4777 // decrease the block coordinate, whereas in horizontal we increase
4778 // it. So the sense of this flag is effectively inverted.
4779 bool goDown = aParams.vertical ? false : true;
4780 uint32_t iter = 0;
4781 while (ptICoord < iCoordLimit) {
4782 if (++iter > 1000) {
4783 // stroke the current path and start again, to avoid pathological
4784 // behavior in cairo with huge numbers of path segments
4785 path = builder->Finish();
4786 aDrawTarget.Stroke(path, color, strokeOptions, drawOptions);
4787 builder = aDrawTarget.CreatePathBuilder();
4788 builder->MoveTo(pt);
4789 iter = 0;
4790 }
4791 ptICoord += adv;
4792 ptBCoord += goDown ? adv : -adv;
4793
4794 builder->LineTo(pt); // 3 and 5
4795
4796 ptICoord += flatLengthAtVertex;
4797 builder->LineTo(pt); // 4 and 6
4798
4799 goDown = !goDown;
4800 }
4801 path = builder->Finish();
4802 aDrawTarget.Stroke(path, color, strokeOptions, drawOptions);
4803 return;
4804 }
4805 default:
4806 NS_ERROR("Invalid style value!");
4807 }
4808 }
4809
4810 Rect
DecorationLineToPath(const PaintDecorationLineParams & aParams)4811 nsCSSRendering::DecorationLineToPath(const PaintDecorationLineParams& aParams)
4812 {
4813 NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
4814 "aStyle is none");
4815
4816 Rect path; // To benefit from RVO, we return this from all return points
4817
4818 Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams));
4819 if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) {
4820 return path;
4821 }
4822
4823 if (aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE &&
4824 aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_OVERLINE &&
4825 aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
4826 NS_ERROR("Invalid decoration value!");
4827 return path;
4828 }
4829
4830 if (aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_SOLID) {
4831 // For the moment, we support only solid text decorations.
4832 return path;
4833 }
4834
4835 Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
4836
4837 // The block-direction position should be set to the middle of the line.
4838 if (aParams.vertical) {
4839 rect.x += lineThickness / 2;
4840 path = Rect(rect.TopLeft() - Point(lineThickness / 2, 0.0),
4841 Size(lineThickness, rect.Height()));
4842 } else {
4843 rect.y += lineThickness / 2;
4844 path = Rect(rect.TopLeft() - Point(0.0, lineThickness / 2),
4845 Size(rect.Width(), lineThickness));
4846 }
4847
4848 return path;
4849 }
4850
4851 nsRect
GetTextDecorationRect(nsPresContext * aPresContext,const DecorationRectParams & aParams)4852 nsCSSRendering::GetTextDecorationRect(nsPresContext* aPresContext,
4853 const DecorationRectParams& aParams)
4854 {
4855 NS_ASSERTION(aPresContext, "aPresContext is null");
4856 NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
4857 "aStyle is none");
4858
4859 gfxRect rect = GetTextDecorationRectInternal(Point(0, 0), aParams);
4860 // The rect values are already rounded to nearest device pixels.
4861 nsRect r;
4862 r.x = aPresContext->GfxUnitsToAppUnits(rect.X());
4863 r.y = aPresContext->GfxUnitsToAppUnits(rect.Y());
4864 r.width = aPresContext->GfxUnitsToAppUnits(rect.Width());
4865 r.height = aPresContext->GfxUnitsToAppUnits(rect.Height());
4866 return r;
4867 }
4868
4869 gfxRect
GetTextDecorationRectInternal(const Point & aPt,const DecorationRectParams & aParams)4870 nsCSSRendering::GetTextDecorationRectInternal(const Point& aPt,
4871 const DecorationRectParams& aParams)
4872 {
4873 NS_ASSERTION(aParams.style <= NS_STYLE_TEXT_DECORATION_STYLE_WAVY,
4874 "Invalid aStyle value");
4875
4876 if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_NONE)
4877 return gfxRect(0, 0, 0, 0);
4878
4879 bool canLiftUnderline = aParams.descentLimit >= 0.0;
4880
4881 gfxFloat iCoord = aParams.vertical ? aPt.y : aPt.x;
4882 gfxFloat bCoord = aParams.vertical ? aPt.x : aPt.y;
4883
4884 // 'left' and 'right' are relative to the line, so for vertical writing modes
4885 // they will actually become top and bottom of the rendered line.
4886 // Similarly, aLineSize.width and .height are actually length and thickness
4887 // of the line, which runs horizontally or vertically according to aVertical.
4888 const gfxFloat left = floor(iCoord + 0.5),
4889 right = floor(iCoord + aParams.lineSize.width + 0.5);
4890
4891 // We compute |r| as if for a horizontal text run, and then swap vertical
4892 // and horizontal coordinates at the end if vertical was requested.
4893 gfxRect r(left, 0, right - left, 0);
4894
4895 gfxFloat lineThickness = NS_round(aParams.lineSize.height);
4896 lineThickness = std::max(lineThickness, 1.0);
4897
4898 gfxFloat ascent = NS_round(aParams.ascent);
4899 gfxFloat descentLimit = floor(aParams.descentLimit);
4900
4901 gfxFloat suggestedMaxRectHeight = std::max(std::min(ascent, descentLimit), 1.0);
4902 r.height = lineThickness;
4903 if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE) {
4904 /**
4905 * We will draw double line as:
4906 *
4907 * +-------------------------------------------+
4908 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4909 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4910 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4911 * | | ^
4912 * | | | gap
4913 * | | v
4914 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4915 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4916 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4917 * +-------------------------------------------+
4918 */
4919 gfxFloat gap = NS_round(lineThickness / 2.0);
4920 gap = std::max(gap, 1.0);
4921 r.height = lineThickness * 2.0 + gap;
4922 if (canLiftUnderline) {
4923 if (r.Height() > suggestedMaxRectHeight) {
4924 // Don't shrink the line height, because the thickness has some meaning.
4925 // We can just shrink the gap at this time.
4926 r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0 + 1.0);
4927 }
4928 }
4929 } else if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_WAVY) {
4930 /**
4931 * We will draw wavy line as:
4932 *
4933 * +-------------------------------------------+
4934 * |XXXXX XXXXXX XXXXXX | ^
4935 * |XXXXXX XXXXXXXX XXXXXXXX | | lineThickness
4936 * |XXXXXXX XXXXXXXXXX XXXXXXXXXX| v
4937 * | XXX XXX XXX XXX XX|
4938 * | XXXXXXXXXX XXXXXXXXXX X|
4939 * | XXXXXXXX XXXXXXXX |
4940 * | XXXXXX XXXXXX |
4941 * +-------------------------------------------+
4942 */
4943 r.height = lineThickness > 2.0 ? lineThickness * 4.0 : lineThickness * 3.0;
4944 if (canLiftUnderline) {
4945 if (r.Height() > suggestedMaxRectHeight) {
4946 // Don't shrink the line height even if there is not enough space,
4947 // because the thickness has some meaning. E.g., the 1px wavy line and
4948 // 2px wavy line can be used for different meaning in IME selections
4949 // at same time.
4950 r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0);
4951 }
4952 }
4953 }
4954
4955 gfxFloat baseline = floor(bCoord + aParams.ascent + 0.5);
4956 gfxFloat offset = 0.0;
4957 switch (aParams.decoration) {
4958 case NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE:
4959 offset = aParams.offset;
4960 if (canLiftUnderline) {
4961 if (descentLimit < -offset + r.Height()) {
4962 // If we can ignore the offset and the decoration line is overflowing,
4963 // we should align the bottom edge of the decoration line rect if it's
4964 // possible. Otherwise, we should lift up the top edge of the rect as
4965 // far as possible.
4966 gfxFloat offsetBottomAligned = -descentLimit + r.Height();
4967 gfxFloat offsetTopAligned = 0.0;
4968 offset = std::min(offsetBottomAligned, offsetTopAligned);
4969 }
4970 }
4971 break;
4972 case NS_STYLE_TEXT_DECORATION_LINE_OVERLINE:
4973 offset = aParams.offset - lineThickness + r.Height();
4974 break;
4975 case NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH: {
4976 gfxFloat extra = floor(r.Height() / 2.0 + 0.5);
4977 extra = std::max(extra, lineThickness);
4978 offset = aParams.offset - lineThickness + extra;
4979 break;
4980 }
4981 default:
4982 NS_ERROR("Invalid decoration value!");
4983 }
4984
4985 if (aParams.vertical) {
4986 r.y = baseline + floor(offset + 0.5);
4987 Swap(r.x, r.y);
4988 Swap(r.width, r.height);
4989 } else {
4990 r.y = baseline - floor(offset + 0.5);
4991 }
4992
4993 return r;
4994 }
4995
4996 // ------------------
4997 // ImageRenderer
4998 // ------------------
nsImageRenderer(nsIFrame * aForFrame,const nsStyleImage * aImage,uint32_t aFlags)4999 nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame,
5000 const nsStyleImage* aImage,
5001 uint32_t aFlags)
5002 : mForFrame(aForFrame)
5003 , mImage(aImage)
5004 , mType(aImage->GetType())
5005 , mImageContainer(nullptr)
5006 , mGradientData(nullptr)
5007 , mPaintServerFrame(nullptr)
5008 , mPrepareResult(DrawResult::NOT_READY)
5009 , mSize(0, 0)
5010 , mFlags(aFlags)
5011 , mExtendMode(ExtendMode::CLAMP)
5012 , mMaskOp(NS_STYLE_MASK_MODE_MATCH_SOURCE)
5013 {
5014 }
5015
~nsImageRenderer()5016 nsImageRenderer::~nsImageRenderer()
5017 {
5018 }
5019
5020 static bool
ShouldTreatAsCompleteDueToSyncDecode(const nsStyleImage * aImage,uint32_t aFlags)5021 ShouldTreatAsCompleteDueToSyncDecode(const nsStyleImage* aImage,
5022 uint32_t aFlags)
5023 {
5024 if (!(aFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES)) {
5025 return false;
5026 }
5027
5028 if (aImage->GetType() != eStyleImageType_Image) {
5029 return false;
5030 }
5031
5032 imgRequestProxy* req = aImage->GetImageData();
5033 if (!req) {
5034 return false;
5035 }
5036
5037 uint32_t status = 0;
5038 if (NS_FAILED(req->GetImageStatus(&status))) {
5039 return false;
5040 }
5041
5042 if (status & imgIRequest::STATUS_ERROR) {
5043 // The image is "complete" since it's a corrupt image. If we created an
5044 // imgIContainer at all, return true.
5045 nsCOMPtr<imgIContainer> image;
5046 req->GetImage(getter_AddRefs(image));
5047 return bool(image);
5048 }
5049
5050 if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
5051 // We must have loaded all of the image's data and the size must be
5052 // available, or else sync decoding won't be able to decode the image.
5053 return false;
5054 }
5055
5056 return true;
5057 }
5058
5059 bool
PrepareImage()5060 nsImageRenderer::PrepareImage()
5061 {
5062 if (mImage->IsEmpty()) {
5063 mPrepareResult = DrawResult::BAD_IMAGE;
5064 return false;
5065 }
5066
5067 if (!mImage->IsComplete()) {
5068 // Make sure the image is actually decoding.
5069 mImage->StartDecoding();
5070
5071 // Check again to see if we finished.
5072 // We cannot prepare the image for rendering if it is not fully loaded.
5073 // Special case: If we requested a sync decode and the image has loaded, push
5074 // on through because the Draw() will do a sync decode then.
5075 if (!mImage->IsComplete() &&
5076 !ShouldTreatAsCompleteDueToSyncDecode(mImage, mFlags)) {
5077 mPrepareResult = DrawResult::NOT_READY;
5078 return false;
5079 }
5080 }
5081
5082 switch (mType) {
5083 case eStyleImageType_Image: {
5084 MOZ_ASSERT(mImage->GetImageData(),
5085 "must have image data, since we checked IsEmpty above");
5086 nsCOMPtr<imgIContainer> srcImage;
5087 DebugOnly<nsresult> rv =
5088 mImage->GetImageData()->GetImage(getter_AddRefs(srcImage));
5089 MOZ_ASSERT(NS_SUCCEEDED(rv) && srcImage,
5090 "If GetImage() is failing, mImage->IsComplete() "
5091 "should have returned false");
5092
5093 if (!mImage->GetCropRect()) {
5094 mImageContainer.swap(srcImage);
5095 } else {
5096 nsIntRect actualCropRect;
5097 bool isEntireImage;
5098 bool success =
5099 mImage->ComputeActualCropRect(actualCropRect, &isEntireImage);
5100 NS_ASSERTION(success, "ComputeActualCropRect() should not fail here");
5101 if (!success || actualCropRect.IsEmpty()) {
5102 // The cropped image has zero size
5103 mPrepareResult = DrawResult::BAD_IMAGE;
5104 return false;
5105 }
5106 if (isEntireImage) {
5107 // The cropped image is identical to the source image
5108 mImageContainer.swap(srcImage);
5109 } else {
5110 nsCOMPtr<imgIContainer> subImage = ImageOps::Clip(srcImage,
5111 actualCropRect,
5112 Nothing());
5113 mImageContainer.swap(subImage);
5114 }
5115 }
5116 mPrepareResult = DrawResult::SUCCESS;
5117 break;
5118 }
5119 case eStyleImageType_Gradient:
5120 mGradientData = mImage->GetGradientData();
5121 mPrepareResult = DrawResult::SUCCESS;
5122 break;
5123 case eStyleImageType_Element:
5124 {
5125 nsAutoString elementId =
5126 NS_LITERAL_STRING("#") + nsDependentString(mImage->GetElementId());
5127 nsCOMPtr<nsIURI> targetURI;
5128 nsCOMPtr<nsIURI> base = mForFrame->GetContent()->GetBaseURI();
5129 nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), elementId,
5130 mForFrame->GetContent()->GetUncomposedDoc(), base);
5131 nsSVGPaintingProperty* property = nsSVGEffects::GetPaintingPropertyForURI(
5132 targetURI, mForFrame->FirstContinuation(),
5133 nsSVGEffects::BackgroundImageProperty());
5134 if (!property) {
5135 mPrepareResult = DrawResult::BAD_IMAGE;
5136 return false;
5137 }
5138
5139 // If the referenced element is an <img>, <canvas>, or <video> element,
5140 // prefer SurfaceFromElement as it's more reliable.
5141 mImageElementSurface =
5142 nsLayoutUtils::SurfaceFromElement(property->GetReferencedElement());
5143 if (!mImageElementSurface.GetSourceSurface()) {
5144 nsIFrame* paintServerFrame = property->GetReferencedFrame();
5145 // If there's no referenced frame, or the referenced frame is
5146 // non-displayable SVG, then we have nothing valid to paint.
5147 if (!paintServerFrame ||
5148 (paintServerFrame->IsFrameOfType(nsIFrame::eSVG) &&
5149 !paintServerFrame->IsFrameOfType(nsIFrame::eSVGPaintServer) &&
5150 !static_cast<nsISVGChildFrame*>(do_QueryFrame(paintServerFrame)))) {
5151 mPrepareResult = DrawResult::BAD_IMAGE;
5152 return false;
5153 }
5154 mPaintServerFrame = paintServerFrame;
5155 }
5156
5157 mPrepareResult = DrawResult::SUCCESS;
5158 break;
5159 }
5160 case eStyleImageType_Null:
5161 default:
5162 break;
5163 }
5164
5165 return IsReady();
5166 }
5167
5168 nsSize
ComputeConcreteSize() const5169 CSSSizeOrRatio::ComputeConcreteSize() const
5170 {
5171 NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute");
5172 if (mHasWidth && mHasHeight) {
5173 return nsSize(mWidth, mHeight);
5174 }
5175 if (mHasWidth) {
5176 nscoord height = NSCoordSaturatingNonnegativeMultiply(
5177 mWidth,
5178 double(mRatio.height) / mRatio.width);
5179 return nsSize(mWidth, height);
5180 }
5181
5182 MOZ_ASSERT(mHasHeight);
5183 nscoord width = NSCoordSaturatingNonnegativeMultiply(
5184 mHeight,
5185 double(mRatio.width) / mRatio.height);
5186 return nsSize(width, mHeight);
5187 }
5188
5189 CSSSizeOrRatio
ComputeIntrinsicSize()5190 nsImageRenderer::ComputeIntrinsicSize()
5191 {
5192 NS_ASSERTION(IsReady(), "Ensure PrepareImage() has returned true "
5193 "before calling me");
5194
5195 CSSSizeOrRatio result;
5196 switch (mType) {
5197 case eStyleImageType_Image:
5198 {
5199 bool haveWidth, haveHeight;
5200 CSSIntSize imageIntSize;
5201 nsLayoutUtils::ComputeSizeForDrawing(mImageContainer, imageIntSize,
5202 result.mRatio, haveWidth, haveHeight);
5203 if (haveWidth) {
5204 result.SetWidth(nsPresContext::CSSPixelsToAppUnits(imageIntSize.width));
5205 }
5206 if (haveHeight) {
5207 result.SetHeight(nsPresContext::CSSPixelsToAppUnits(imageIntSize.height));
5208 }
5209 break;
5210 }
5211 case eStyleImageType_Element:
5212 {
5213 // XXX element() should have the width/height of the referenced element,
5214 // and that element's ratio, if it matches. If it doesn't match, it
5215 // should have no width/height or ratio. See element() in CSS images:
5216 // <http://dev.w3.org/csswg/css-images-4/#element-notation>.
5217 // Make sure to change nsStyleImageLayers::Size::DependsOnFrameSize
5218 // when fixing this!
5219 if (mPaintServerFrame) {
5220 // SVG images have no intrinsic size
5221 if (!mPaintServerFrame->IsFrameOfType(nsIFrame::eSVG)) {
5222 // The intrinsic image size for a generic nsIFrame paint server is
5223 // the union of the border-box rects of all of its continuations,
5224 // rounded to device pixels.
5225 int32_t appUnitsPerDevPixel =
5226 mForFrame->PresContext()->AppUnitsPerDevPixel();
5227 result.SetSize(
5228 IntSizeToAppUnits(
5229 nsSVGIntegrationUtils::GetContinuationUnionSize(mPaintServerFrame).
5230 ToNearestPixels(appUnitsPerDevPixel),
5231 appUnitsPerDevPixel));
5232 }
5233 } else {
5234 NS_ASSERTION(mImageElementSurface.GetSourceSurface(),
5235 "Surface should be ready.");
5236 IntSize surfaceSize = mImageElementSurface.mSize;
5237 result.SetSize(
5238 nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width),
5239 nsPresContext::CSSPixelsToAppUnits(surfaceSize.height)));
5240 }
5241 break;
5242 }
5243 case eStyleImageType_Gradient:
5244 // Per <http://dev.w3.org/csswg/css3-images/#gradients>, gradients have no
5245 // intrinsic dimensions.
5246 case eStyleImageType_Null:
5247 default:
5248 break;
5249 }
5250
5251 return result;
5252 }
5253
5254 /* static */ nsSize
ComputeConcreteSize(const CSSSizeOrRatio & aSpecifiedSize,const CSSSizeOrRatio & aIntrinsicSize,const nsSize & aDefaultSize)5255 nsImageRenderer::ComputeConcreteSize(const CSSSizeOrRatio& aSpecifiedSize,
5256 const CSSSizeOrRatio& aIntrinsicSize,
5257 const nsSize& aDefaultSize)
5258 {
5259 // The specified size is fully specified, just use that
5260 if (aSpecifiedSize.IsConcrete()) {
5261 return aSpecifiedSize.ComputeConcreteSize();
5262 }
5263
5264 MOZ_ASSERT(!aSpecifiedSize.mHasWidth || !aSpecifiedSize.mHasHeight);
5265
5266 if (!aSpecifiedSize.mHasWidth && !aSpecifiedSize.mHasHeight) {
5267 // no specified size, try using the intrinsic size
5268 if (aIntrinsicSize.CanComputeConcreteSize()) {
5269 return aIntrinsicSize.ComputeConcreteSize();
5270 }
5271
5272 if (aIntrinsicSize.mHasWidth) {
5273 return nsSize(aIntrinsicSize.mWidth, aDefaultSize.height);
5274 }
5275 if (aIntrinsicSize.mHasHeight) {
5276 return nsSize(aDefaultSize.width, aIntrinsicSize.mHeight);
5277 }
5278
5279 // couldn't use the intrinsic size either, revert to using the default size
5280 return ComputeConstrainedSize(aDefaultSize,
5281 aIntrinsicSize.mRatio,
5282 CONTAIN);
5283 }
5284
5285 MOZ_ASSERT(aSpecifiedSize.mHasWidth || aSpecifiedSize.mHasHeight);
5286
5287 // The specified height is partial, try to compute the missing part.
5288 if (aSpecifiedSize.mHasWidth) {
5289 nscoord height;
5290 if (aIntrinsicSize.HasRatio()) {
5291 height = NSCoordSaturatingNonnegativeMultiply(
5292 aSpecifiedSize.mWidth,
5293 double(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width);
5294 } else if (aIntrinsicSize.mHasHeight) {
5295 height = aIntrinsicSize.mHeight;
5296 } else {
5297 height = aDefaultSize.height;
5298 }
5299 return nsSize(aSpecifiedSize.mWidth, height);
5300 }
5301
5302 MOZ_ASSERT(aSpecifiedSize.mHasHeight);
5303 nscoord width;
5304 if (aIntrinsicSize.HasRatio()) {
5305 width = NSCoordSaturatingNonnegativeMultiply(
5306 aSpecifiedSize.mHeight,
5307 double(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height);
5308 } else if (aIntrinsicSize.mHasWidth) {
5309 width = aIntrinsicSize.mWidth;
5310 } else {
5311 width = aDefaultSize.width;
5312 }
5313 return nsSize(width, aSpecifiedSize.mHeight);
5314 }
5315
5316 /* static */ nsSize
ComputeConstrainedSize(const nsSize & aConstrainingSize,const nsSize & aIntrinsicRatio,FitType aFitType)5317 nsImageRenderer::ComputeConstrainedSize(const nsSize& aConstrainingSize,
5318 const nsSize& aIntrinsicRatio,
5319 FitType aFitType)
5320 {
5321 if (aIntrinsicRatio.width <= 0 && aIntrinsicRatio.height <= 0) {
5322 return aConstrainingSize;
5323 }
5324
5325 float scaleX = double(aConstrainingSize.width) / aIntrinsicRatio.width;
5326 float scaleY = double(aConstrainingSize.height) / aIntrinsicRatio.height;
5327 nsSize size;
5328 if ((aFitType == CONTAIN) == (scaleX < scaleY)) {
5329 size.width = aConstrainingSize.width;
5330 size.height = NSCoordSaturatingNonnegativeMultiply(
5331 aIntrinsicRatio.height, scaleX);
5332 // If we're reducing the size by less than one css pixel, then just use the
5333 // constraining size.
5334 if (aFitType == CONTAIN && aConstrainingSize.height - size.height < nsPresContext::AppUnitsPerCSSPixel()) {
5335 size.height = aConstrainingSize.height;
5336 }
5337 } else {
5338 size.width = NSCoordSaturatingNonnegativeMultiply(
5339 aIntrinsicRatio.width, scaleY);
5340 if (aFitType == CONTAIN && aConstrainingSize.width - size.width < nsPresContext::AppUnitsPerCSSPixel()) {
5341 size.width = aConstrainingSize.width;
5342 }
5343 size.height = aConstrainingSize.height;
5344 }
5345 return size;
5346 }
5347
5348 /**
5349 * mSize is the image's "preferred" size for this particular rendering, while
5350 * the drawn (aka concrete) size is the actual rendered size after accounting
5351 * for background-size etc.. The preferred size is most often the image's
5352 * intrinsic dimensions. But for images with incomplete intrinsic dimensions,
5353 * the preferred size varies, depending on the specified and default sizes, see
5354 * nsImageRenderer::Compute*Size.
5355 *
5356 * This distinction is necessary because the components of a vector image are
5357 * specified with respect to its preferred size for a rendering situation, not
5358 * to its actual rendered size. For example, consider a 4px wide background
5359 * vector image with no height which contains a left-aligned
5360 * 2px wide black rectangle with height 100%. If the background-size width is
5361 * auto (or 4px), the vector image will render 4px wide, and the black rectangle
5362 * will be 2px wide. If the background-size width is 8px, the vector image will
5363 * render 8px wide, and the black rectangle will be 4px wide -- *not* 2px wide.
5364 * In both cases mSize.width will be 4px; but in the first case the returned
5365 * width will be 4px, while in the second case the returned width will be 8px.
5366 */
5367 void
SetPreferredSize(const CSSSizeOrRatio & aIntrinsicSize,const nsSize & aDefaultSize)5368 nsImageRenderer::SetPreferredSize(const CSSSizeOrRatio& aIntrinsicSize,
5369 const nsSize& aDefaultSize)
5370 {
5371 mSize.width = aIntrinsicSize.mHasWidth
5372 ? aIntrinsicSize.mWidth
5373 : aDefaultSize.width;
5374 mSize.height = aIntrinsicSize.mHasHeight
5375 ? aIntrinsicSize.mHeight
5376 : aDefaultSize.height;
5377 }
5378
5379 // Convert from nsImageRenderer flags to the flags we want to use for drawing in
5380 // the imgIContainer namespace.
5381 static uint32_t
ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags)5382 ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags)
5383 {
5384 uint32_t drawFlags = imgIContainer::FLAG_NONE;
5385 if (aImageRendererFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
5386 drawFlags |= imgIContainer::FLAG_SYNC_DECODE;
5387 }
5388 if (aImageRendererFlags & nsImageRenderer::FLAG_PAINTING_TO_WINDOW) {
5389 drawFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
5390 }
5391 return drawFlags;
5392 }
5393
5394 /*
5395 * SVG11: A luminanceToAlpha operation is equivalent to the following matrix operation: |
5396 * | R' | | 0 0 0 0 0 | | R |
5397 * | G' | | 0 0 0 0 0 | | G |
5398 * | B' | = | 0 0 0 0 0 | * | B |
5399 * | A' | | 0.2125 0.7154 0.0721 0 0 | | A |
5400 * | 1 | | 0 0 0 0 1 | | 1 |
5401 */
5402 static void
RGBALuminanceOperation(uint8_t * aData,int32_t aStride,const IntSize & aSize)5403 RGBALuminanceOperation(uint8_t *aData,
5404 int32_t aStride,
5405 const IntSize &aSize)
5406 {
5407 int32_t redFactor = 55; // 256 * 0.2125
5408 int32_t greenFactor = 183; // 256 * 0.7154
5409 int32_t blueFactor = 18; // 256 * 0.0721
5410
5411 for (int32_t y = 0; y < aSize.height; y++) {
5412 uint32_t *pixel = (uint32_t*)(aData + aStride * y);
5413 for (int32_t x = 0; x < aSize.width; x++) {
5414 *pixel = (((((*pixel & 0x00FF0000) >> 16) * redFactor) +
5415 (((*pixel & 0x0000FF00) >> 8) * greenFactor) +
5416 ((*pixel & 0x000000FF) * blueFactor)) >> 8) << 24;
5417 pixel++;
5418 }
5419 }
5420 }
5421
5422
5423 DrawResult
Draw(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,const nsRect & aDirtyRect,const nsRect & aDest,const nsRect & aFill,const nsPoint & aAnchor,const nsSize & aRepeatSize,const CSSIntRect & aSrc)5424 nsImageRenderer::Draw(nsPresContext* aPresContext,
5425 nsRenderingContext& aRenderingContext,
5426 const nsRect& aDirtyRect,
5427 const nsRect& aDest,
5428 const nsRect& aFill,
5429 const nsPoint& aAnchor,
5430 const nsSize& aRepeatSize,
5431 const CSSIntRect& aSrc)
5432 {
5433 if (!IsReady()) {
5434 NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
5435 return DrawResult::TEMPORARY_ERROR;
5436 }
5437 if (aDest.IsEmpty() || aFill.IsEmpty() ||
5438 mSize.width <= 0 || mSize.height <= 0) {
5439 return DrawResult::SUCCESS;
5440 }
5441
5442 SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
5443 DrawResult result = DrawResult::SUCCESS;
5444 RefPtr<gfxContext> ctx = aRenderingContext.ThebesContext();
5445 IntRect tmpDTRect;
5446
5447 if (ctx->CurrentOp() != CompositionOp::OP_OVER || mMaskOp == NS_STYLE_MASK_MODE_LUMINANCE) {
5448 gfxRect clipRect = ctx->GetClipExtents();
5449 tmpDTRect = RoundedOut(ToRect(clipRect));
5450 if (tmpDTRect.IsEmpty()) {
5451 return DrawResult::SUCCESS;
5452 }
5453 RefPtr<DrawTarget> tempDT =
5454 gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget(ctx->GetDrawTarget(),
5455 tmpDTRect.Size(),
5456 SurfaceFormat::B8G8R8A8);
5457 if (!tempDT || !tempDT->IsValid()) {
5458 gfxDevCrash(LogReason::InvalidContext) << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
5459 return DrawResult::TEMPORARY_ERROR;
5460 }
5461 tempDT->SetTransform(Matrix::Translation(-tmpDTRect.TopLeft()));
5462 ctx = gfxContext::CreatePreservingTransformOrNull(tempDT);
5463 if (!ctx) {
5464 gfxDevCrash(LogReason::InvalidContext) << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
5465 return DrawResult::TEMPORARY_ERROR;
5466 }
5467 }
5468
5469 switch (mType) {
5470 case eStyleImageType_Image:
5471 {
5472 CSSIntSize imageSize(nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
5473 nsPresContext::AppUnitsToIntCSSPixels(mSize.height));
5474 result =
5475 nsLayoutUtils::DrawBackgroundImage(*ctx,
5476 aPresContext,
5477 mImageContainer, imageSize,
5478 samplingFilter,
5479 aDest, aFill, aRepeatSize,
5480 aAnchor, aDirtyRect,
5481 ConvertImageRendererToDrawFlags(mFlags),
5482 mExtendMode);
5483 break;
5484 }
5485 case eStyleImageType_Gradient:
5486 {
5487 nsCSSRendering::PaintGradient(aPresContext, aRenderingContext,
5488 mGradientData, aDirtyRect,
5489 aDest, aFill, aRepeatSize, aSrc, mSize);
5490 break;
5491 }
5492 case eStyleImageType_Element:
5493 {
5494 RefPtr<gfxDrawable> drawable = DrawableForElement(aDest,
5495 aRenderingContext);
5496 if (!drawable) {
5497 NS_WARNING("Could not create drawable for element");
5498 return DrawResult::TEMPORARY_ERROR;
5499 }
5500
5501 nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
5502 result =
5503 nsLayoutUtils::DrawImage(*ctx,
5504 aPresContext, image,
5505 samplingFilter, aDest, aFill, aAnchor, aDirtyRect,
5506 ConvertImageRendererToDrawFlags(mFlags));
5507 break;
5508 }
5509 case eStyleImageType_Null:
5510 default:
5511 break;
5512 }
5513
5514 if (!tmpDTRect.IsEmpty()) {
5515 RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
5516 if (mMaskOp == NS_STYLE_MASK_MODE_LUMINANCE) {
5517 RefPtr<DataSourceSurface> maskData = surf->GetDataSurface();
5518 DataSourceSurface::MappedSurface map;
5519 if (!maskData->Map(DataSourceSurface::MapType::WRITE, &map)) {
5520 return result;
5521 }
5522
5523 RGBALuminanceOperation(map.mData, map.mStride, maskData->GetSize());
5524 maskData->Unmap();
5525 surf = maskData;
5526 }
5527
5528 DrawTarget* dt = aRenderingContext.ThebesContext()->GetDrawTarget();
5529 dt->DrawSurface(surf, Rect(tmpDTRect.x, tmpDTRect.y, tmpDTRect.width, tmpDTRect.height),
5530 Rect(0, 0, tmpDTRect.width, tmpDTRect.height),
5531 DrawSurfaceOptions(SamplingFilter::POINT),
5532 DrawOptions(1.0f, aRenderingContext.ThebesContext()->CurrentOp()));
5533 }
5534
5535 return result;
5536 }
5537
5538 already_AddRefed<gfxDrawable>
DrawableForElement(const nsRect & aImageRect,nsRenderingContext & aRenderingContext)5539 nsImageRenderer::DrawableForElement(const nsRect& aImageRect,
5540 nsRenderingContext& aRenderingContext)
5541 {
5542 NS_ASSERTION(mType == eStyleImageType_Element,
5543 "DrawableForElement only makes sense if backed by an element");
5544 if (mPaintServerFrame) {
5545 // XXX(seth): In order to not pass FLAG_SYNC_DECODE_IMAGES here,
5546 // DrawableFromPaintServer would have to return a DrawResult indicating
5547 // whether any images could not be painted because they weren't fully
5548 // decoded. Even always passing FLAG_SYNC_DECODE_IMAGES won't eliminate all
5549 // problems, as it won't help if there are image which haven't finished
5550 // loading, but it's better than nothing.
5551 int32_t appUnitsPerDevPixel = mForFrame->PresContext()->AppUnitsPerDevPixel();
5552 nsRect destRect = aImageRect - aImageRect.TopLeft();
5553 nsIntSize roundedOut = destRect.ToOutsidePixels(appUnitsPerDevPixel).Size();
5554 IntSize imageSize(roundedOut.width, roundedOut.height);
5555 RefPtr<gfxDrawable> drawable =
5556 nsSVGIntegrationUtils::DrawableFromPaintServer(
5557 mPaintServerFrame, mForFrame, mSize, imageSize,
5558 aRenderingContext.GetDrawTarget(),
5559 aRenderingContext.ThebesContext()->CurrentMatrix(),
5560 nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES);
5561
5562 return drawable.forget();
5563 }
5564 NS_ASSERTION(mImageElementSurface.GetSourceSurface(), "Surface should be ready.");
5565 RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(
5566 mImageElementSurface.GetSourceSurface().get(),
5567 mImageElementSurface.mSize);
5568 return drawable.forget();
5569 }
5570
5571 DrawResult
DrawBackground(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,const nsRect & aDest,const nsRect & aFill,const nsPoint & aAnchor,const nsRect & aDirty,const nsSize & aRepeatSize)5572 nsImageRenderer::DrawBackground(nsPresContext* aPresContext,
5573 nsRenderingContext& aRenderingContext,
5574 const nsRect& aDest,
5575 const nsRect& aFill,
5576 const nsPoint& aAnchor,
5577 const nsRect& aDirty,
5578 const nsSize& aRepeatSize)
5579 {
5580 if (!IsReady()) {
5581 NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
5582 return DrawResult::TEMPORARY_ERROR;
5583 }
5584 if (aDest.IsEmpty() || aFill.IsEmpty() ||
5585 mSize.width <= 0 || mSize.height <= 0) {
5586 return DrawResult::SUCCESS;
5587 }
5588
5589 return Draw(aPresContext, aRenderingContext,
5590 aDirty, aDest, aFill, aAnchor, aRepeatSize,
5591 CSSIntRect(0, 0,
5592 nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
5593 nsPresContext::AppUnitsToIntCSSPixels(mSize.height)));
5594 }
5595
5596 /**
5597 * Compute the size and position of the master copy of the image. I.e., a single
5598 * tile used to fill the dest rect.
5599 * aFill The destination rect to be filled
5600 * aHFill and aVFill are the repeat patterns for the component -
5601 * NS_STYLE_BORDER_IMAGE_REPEAT_* - i.e., how a tiling unit is used to fill aFill
5602 * aUnitSize The size of the source rect in dest coords.
5603 */
5604 static nsRect
ComputeTile(nsRect & aFill,uint8_t aHFill,uint8_t aVFill,const nsSize & aUnitSize,nsSize & aRepeatSize)5605 ComputeTile(nsRect& aFill,
5606 uint8_t aHFill,
5607 uint8_t aVFill,
5608 const nsSize& aUnitSize,
5609 nsSize& aRepeatSize)
5610 {
5611 nsRect tile;
5612 switch (aHFill) {
5613 case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
5614 tile.x = aFill.x;
5615 tile.width = aFill.width;
5616 aRepeatSize.width = tile.width;
5617 break;
5618 case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
5619 tile.x = aFill.x + aFill.width/2 - aUnitSize.width/2;
5620 tile.width = aUnitSize.width;
5621 aRepeatSize.width = tile.width;
5622 break;
5623 case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
5624 tile.x = aFill.x;
5625 tile.width = ComputeRoundedSize(aUnitSize.width, aFill.width);
5626 aRepeatSize.width = tile.width;
5627 break;
5628 case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
5629 {
5630 nscoord space;
5631 aRepeatSize.width =
5632 ComputeBorderSpacedRepeatSize(aUnitSize.width, aFill.width, space);
5633 tile.x = aFill.x + space;
5634 tile.width = aUnitSize.width;
5635 aFill.x = tile.x;
5636 aFill.width = aFill.width - space * 2;
5637 }
5638 break;
5639 default:
5640 NS_NOTREACHED("unrecognized border-image fill style");
5641 }
5642
5643 switch (aVFill) {
5644 case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
5645 tile.y = aFill.y;
5646 tile.height = aFill.height;
5647 aRepeatSize.height = tile.height;
5648 break;
5649 case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
5650 tile.y = aFill.y + aFill.height/2 - aUnitSize.height/2;
5651 tile.height = aUnitSize.height;
5652 aRepeatSize.height = tile.height;
5653 break;
5654 case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
5655 tile.y = aFill.y;
5656 tile.height = ComputeRoundedSize(aUnitSize.height, aFill.height);
5657 aRepeatSize.height = tile.height;
5658 break;
5659 case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
5660 {
5661 nscoord space;
5662 aRepeatSize.height =
5663 ComputeBorderSpacedRepeatSize(aUnitSize.height, aFill.height, space);
5664 tile.y = aFill.y + space;
5665 tile.height = aUnitSize.height;
5666 aFill.y = tile.y;
5667 aFill.height = aFill.height - space * 2;
5668 }
5669 break;
5670 default:
5671 NS_NOTREACHED("unrecognized border-image fill style");
5672 }
5673
5674 return tile;
5675 }
5676
5677 /**
5678 * Returns true if the given set of arguments will require the tiles which fill
5679 * the dest rect to be scaled from the source tile. See comment on ComputeTile
5680 * for argument descriptions.
5681 */
5682 static bool
RequiresScaling(const nsRect & aFill,uint8_t aHFill,uint8_t aVFill,const nsSize & aUnitSize)5683 RequiresScaling(const nsRect& aFill,
5684 uint8_t aHFill,
5685 uint8_t aVFill,
5686 const nsSize& aUnitSize)
5687 {
5688 // If we have no tiling in either direction, we can skip the intermediate
5689 // scaling step.
5690 return (aHFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH ||
5691 aVFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH) &&
5692 (aUnitSize.width != aFill.width ||
5693 aUnitSize.height != aFill.height);
5694 }
5695
5696 DrawResult
DrawBorderImageComponent(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,const nsRect & aDirtyRect,const nsRect & aFill,const CSSIntRect & aSrc,uint8_t aHFill,uint8_t aVFill,const nsSize & aUnitSize,uint8_t aIndex,const Maybe<nsSize> & aSVGViewportSize,const bool aHasIntrinsicRatio)5697 nsImageRenderer::DrawBorderImageComponent(nsPresContext* aPresContext,
5698 nsRenderingContext& aRenderingContext,
5699 const nsRect& aDirtyRect,
5700 const nsRect& aFill,
5701 const CSSIntRect& aSrc,
5702 uint8_t aHFill,
5703 uint8_t aVFill,
5704 const nsSize& aUnitSize,
5705 uint8_t aIndex,
5706 const Maybe<nsSize>& aSVGViewportSize,
5707 const bool aHasIntrinsicRatio)
5708 {
5709 if (!IsReady()) {
5710 NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
5711 return DrawResult::BAD_ARGS;
5712 }
5713 if (aFill.IsEmpty() || aSrc.IsEmpty()) {
5714 return DrawResult::SUCCESS;
5715 }
5716
5717 if (mType == eStyleImageType_Image || mType == eStyleImageType_Element) {
5718 nsCOMPtr<imgIContainer> subImage;
5719
5720 // To draw one portion of an image into a border component, we stretch that
5721 // portion to match the size of that border component and then draw onto.
5722 // However, preserveAspectRatio attribute of a SVG image may break this rule.
5723 // To get correct rendering result, we add
5724 // FLAG_FORCE_PRESERVEASPECTRATIO_NONE flag here, to tell mImage to ignore
5725 // preserveAspectRatio attribute, and always do non-uniform stretch.
5726 uint32_t drawFlags = ConvertImageRendererToDrawFlags(mFlags) |
5727 imgIContainer::FLAG_FORCE_PRESERVEASPECTRATIO_NONE;
5728 // For those SVG image sources which don't have fixed aspect ratio (i.e.
5729 // without viewport size and viewBox), we should scale the source uniformly
5730 // after the viewport size is decided by "Default Sizing Algorithm".
5731 if (!aHasIntrinsicRatio) {
5732 drawFlags = drawFlags | imgIContainer::FLAG_FORCE_UNIFORM_SCALING;
5733 }
5734 // Retrieve or create the subimage we'll draw.
5735 nsIntRect srcRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height);
5736 if (mType == eStyleImageType_Image) {
5737 if ((subImage = mImage->GetSubImage(aIndex)) == nullptr) {
5738 subImage = ImageOps::Clip(mImageContainer, srcRect, aSVGViewportSize);
5739 mImage->SetSubImage(aIndex, subImage);
5740 }
5741 } else {
5742 // This path, for eStyleImageType_Element, is currently slower than it
5743 // needs to be because we don't cache anything. (In particular, if we have
5744 // to draw to a temporary surface inside ClippedImage, we don't cache that
5745 // temporary surface since we immediately throw the ClippedImage we create
5746 // here away.) However, if we did cache, we'd need to know when to
5747 // invalidate that cache, and it's not clear that it's worth the trouble
5748 // since using border-image with -moz-element is rare.
5749
5750 RefPtr<gfxDrawable> drawable = DrawableForElement(nsRect(nsPoint(), mSize),
5751 aRenderingContext);
5752 if (!drawable) {
5753 NS_WARNING("Could not create drawable for element");
5754 return DrawResult::TEMPORARY_ERROR;
5755 }
5756
5757 nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
5758 subImage = ImageOps::Clip(image, srcRect, aSVGViewportSize);
5759 }
5760
5761 MOZ_ASSERT_IF(aSVGViewportSize,
5762 subImage->GetType() == imgIContainer::TYPE_VECTOR);
5763
5764 SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
5765
5766 if (!RequiresScaling(aFill, aHFill, aVFill, aUnitSize)) {
5767 return nsLayoutUtils::DrawSingleImage(*aRenderingContext.ThebesContext(),
5768 aPresContext,
5769 subImage,
5770 samplingFilter,
5771 aFill, aDirtyRect,
5772 nullptr,
5773 drawFlags);
5774 }
5775
5776 nsSize repeatSize;
5777 nsRect fillRect(aFill);
5778 nsRect tile = ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize);
5779 CSSIntSize imageSize(srcRect.width, srcRect.height);
5780 return nsLayoutUtils::DrawBackgroundImage(*aRenderingContext.ThebesContext(),
5781 aPresContext,
5782 subImage, imageSize, samplingFilter,
5783 tile, fillRect, repeatSize,
5784 tile.TopLeft(), aDirtyRect,
5785 drawFlags,
5786 ExtendMode::CLAMP);
5787 }
5788
5789 nsSize repeatSize(aFill.Size());
5790 nsRect fillRect(aFill);
5791 nsRect destTile = RequiresScaling(fillRect, aHFill, aVFill, aUnitSize)
5792 ? ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize)
5793 : fillRect;
5794 return Draw(aPresContext, aRenderingContext, aDirtyRect, destTile,
5795 fillRect, destTile.TopLeft(), repeatSize, aSrc);
5796 }
5797
5798 bool
IsRasterImage()5799 nsImageRenderer::IsRasterImage()
5800 {
5801 if (mType != eStyleImageType_Image || !mImageContainer)
5802 return false;
5803 return mImageContainer->GetType() == imgIContainer::TYPE_RASTER;
5804 }
5805
5806 bool
IsAnimatedImage()5807 nsImageRenderer::IsAnimatedImage()
5808 {
5809 if (mType != eStyleImageType_Image || !mImageContainer)
5810 return false;
5811 bool animated = false;
5812 if (NS_SUCCEEDED(mImageContainer->GetAnimated(&animated)) && animated)
5813 return true;
5814
5815 return false;
5816 }
5817
5818 already_AddRefed<imgIContainer>
GetImage()5819 nsImageRenderer::GetImage()
5820 {
5821 if (mType != eStyleImageType_Image || !mImageContainer) {
5822 return nullptr;
5823 }
5824
5825 nsCOMPtr<imgIContainer> image = mImageContainer;
5826 return image.forget();
5827 }
5828
5829 void
PurgeCacheForViewportChange(const Maybe<nsSize> & aSVGViewportSize,const bool aHasIntrinsicRatio)5830 nsImageRenderer::PurgeCacheForViewportChange(
5831 const Maybe<nsSize>& aSVGViewportSize, const bool aHasIntrinsicRatio)
5832 {
5833 // Check if we should flush the cached data - only vector images need to do
5834 // the check since they might not have fixed ratio.
5835 if (mImageContainer &&
5836 mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
5837 mImage->PurgeCacheForViewportChange(aSVGViewportSize, aHasIntrinsicRatio);
5838 }
5839 }
5840
5841 #define MAX_BLUR_RADIUS 300
5842 #define MAX_SPREAD_RADIUS 50
5843
ComputeBlurStdDev(nscoord aBlurRadius,int32_t aAppUnitsPerDevPixel,gfxFloat aScaleX,gfxFloat aScaleY)5844 static inline gfxPoint ComputeBlurStdDev(nscoord aBlurRadius,
5845 int32_t aAppUnitsPerDevPixel,
5846 gfxFloat aScaleX,
5847 gfxFloat aScaleY)
5848 {
5849 // http://dev.w3.org/csswg/css3-background/#box-shadow says that the
5850 // standard deviation of the blur should be half the given blur value.
5851 gfxFloat blurStdDev = gfxFloat(aBlurRadius) / gfxFloat(aAppUnitsPerDevPixel);
5852
5853 return gfxPoint(std::min((blurStdDev * aScaleX),
5854 gfxFloat(MAX_BLUR_RADIUS)) / 2.0,
5855 std::min((blurStdDev * aScaleY),
5856 gfxFloat(MAX_BLUR_RADIUS)) / 2.0);
5857 }
5858
5859 static inline IntSize
ComputeBlurRadius(nscoord aBlurRadius,int32_t aAppUnitsPerDevPixel,gfxFloat aScaleX=1.0,gfxFloat aScaleY=1.0)5860 ComputeBlurRadius(nscoord aBlurRadius,
5861 int32_t aAppUnitsPerDevPixel,
5862 gfxFloat aScaleX = 1.0,
5863 gfxFloat aScaleY = 1.0)
5864 {
5865 gfxPoint scaledBlurStdDev = ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel,
5866 aScaleX, aScaleY);
5867 return
5868 gfxAlphaBoxBlur::CalculateBlurRadius(scaledBlurStdDev);
5869 }
5870
5871 // -----
5872 // nsContextBoxBlur
5873 // -----
5874 gfxContext*
Init(const nsRect & aRect,nscoord aSpreadRadius,nscoord aBlurRadius,int32_t aAppUnitsPerDevPixel,gfxContext * aDestinationCtx,const nsRect & aDirtyRect,const gfxRect * aSkipRect,uint32_t aFlags)5875 nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius,
5876 nscoord aBlurRadius,
5877 int32_t aAppUnitsPerDevPixel,
5878 gfxContext* aDestinationCtx,
5879 const nsRect& aDirtyRect,
5880 const gfxRect* aSkipRect,
5881 uint32_t aFlags)
5882 {
5883 if (aRect.IsEmpty()) {
5884 mContext = nullptr;
5885 return nullptr;
5886 }
5887
5888 IntSize blurRadius;
5889 IntSize spreadRadius;
5890 GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
5891 aBlurRadius, aSpreadRadius,
5892 blurRadius, spreadRadius);
5893
5894 mDestinationCtx = aDestinationCtx;
5895
5896 // If not blurring, draw directly onto the destination device
5897 if (blurRadius.width <= 0 && blurRadius.height <= 0 &&
5898 spreadRadius.width <= 0 && spreadRadius.height <= 0 &&
5899 !(aFlags & FORCE_MASK)) {
5900 mContext = aDestinationCtx;
5901 return mContext;
5902 }
5903
5904 // Convert from app units to device pixels
5905 gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel);
5906
5907 gfxRect dirtyRect =
5908 nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
5909 dirtyRect.RoundOut();
5910
5911 gfxMatrix transform = aDestinationCtx->CurrentMatrix();
5912 rect = transform.TransformBounds(rect);
5913
5914 mPreTransformed = !transform.IsIdentity();
5915
5916 // Create the temporary surface for blurring
5917 dirtyRect = transform.TransformBounds(dirtyRect);
5918 if (aSkipRect) {
5919 gfxRect skipRect = transform.TransformBounds(*aSkipRect);
5920 mContext = mAlphaBoxBlur.Init(rect, spreadRadius,
5921 blurRadius, &dirtyRect, &skipRect);
5922 } else {
5923 mContext = mAlphaBoxBlur.Init(rect, spreadRadius,
5924 blurRadius, &dirtyRect, nullptr);
5925 }
5926
5927 if (mContext) {
5928 // we don't need to blur if skipRect is equal to rect
5929 // and mContext will be nullptr
5930 mContext->Multiply(transform);
5931 }
5932 return mContext;
5933 }
5934
5935 void
DoPaint()5936 nsContextBoxBlur::DoPaint()
5937 {
5938 if (mContext == mDestinationCtx) {
5939 return;
5940 }
5941
5942 gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx);
5943
5944 if (mPreTransformed) {
5945 mDestinationCtx->SetMatrix(gfxMatrix());
5946 }
5947
5948 mAlphaBoxBlur.Paint(mDestinationCtx);
5949 }
5950
5951 gfxContext*
GetContext()5952 nsContextBoxBlur::GetContext()
5953 {
5954 return mContext;
5955 }
5956
5957 /* static */ nsMargin
GetBlurRadiusMargin(nscoord aBlurRadius,int32_t aAppUnitsPerDevPixel)5958 nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius,
5959 int32_t aAppUnitsPerDevPixel)
5960 {
5961 IntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel);
5962
5963 nsMargin result;
5964 result.top = result.bottom = blurRadius.height * aAppUnitsPerDevPixel;
5965 result.left = result.right = blurRadius.width * aAppUnitsPerDevPixel;
5966 return result;
5967 }
5968
5969 /* static */ void
BlurRectangle(gfxContext * aDestinationCtx,const nsRect & aRect,int32_t aAppUnitsPerDevPixel,RectCornerRadii * aCornerRadii,nscoord aBlurRadius,const Color & aShadowColor,const nsRect & aDirtyRect,const gfxRect & aSkipRect)5970 nsContextBoxBlur::BlurRectangle(gfxContext* aDestinationCtx,
5971 const nsRect& aRect,
5972 int32_t aAppUnitsPerDevPixel,
5973 RectCornerRadii* aCornerRadii,
5974 nscoord aBlurRadius,
5975 const Color& aShadowColor,
5976 const nsRect& aDirtyRect,
5977 const gfxRect& aSkipRect)
5978 {
5979 DrawTarget& aDestDrawTarget = *aDestinationCtx->GetDrawTarget();
5980
5981 if (aRect.IsEmpty()) {
5982 return;
5983 }
5984
5985 Rect shadowGfxRect = NSRectToRect(aRect, aAppUnitsPerDevPixel);
5986
5987 if (aBlurRadius <= 0) {
5988 ColorPattern color(ToDeviceColor(aShadowColor));
5989 if (aCornerRadii) {
5990 RefPtr<Path> roundedRect = MakePathForRoundedRect(aDestDrawTarget,
5991 shadowGfxRect,
5992 *aCornerRadii);
5993 aDestDrawTarget.Fill(roundedRect, color);
5994 } else {
5995 aDestDrawTarget.FillRect(shadowGfxRect, color);
5996 }
5997 return;
5998 }
5999
6000 gfxFloat scaleX = 1;
6001 gfxFloat scaleY = 1;
6002
6003 // Do blurs in device space when possible.
6004 // Chrome/Skia always does the blurs in device space
6005 // and will sometimes get incorrect results (e.g. rotated blurs)
6006 gfxMatrix transform = aDestinationCtx->CurrentMatrix();
6007 // XXX: we could probably handle negative scales but for now it's easier just to fallback
6008 if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && transform._22 > 0.0) {
6009 scaleX = transform._11;
6010 scaleY = transform._22;
6011 aDestinationCtx->SetMatrix(gfxMatrix());
6012 } else {
6013 transform = gfxMatrix();
6014 }
6015
6016 gfxPoint blurStdDev = ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
6017
6018 gfxRect dirtyRect =
6019 nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
6020 dirtyRect.RoundOut();
6021
6022 gfxRect shadowThebesRect = transform.TransformBounds(ThebesRect(shadowGfxRect));
6023 dirtyRect = transform.TransformBounds(dirtyRect);
6024 gfxRect skipRect = transform.TransformBounds(aSkipRect);
6025
6026 if (aCornerRadii) {
6027 aCornerRadii->Scale(scaleX, scaleY);
6028 }
6029
6030 gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx,
6031 shadowThebesRect,
6032 aCornerRadii,
6033 blurStdDev,
6034 aShadowColor,
6035 dirtyRect,
6036 skipRect);
6037 }
6038
6039 /* static */ void
GetBlurAndSpreadRadius(DrawTarget * aDestDrawTarget,int32_t aAppUnitsPerDevPixel,nscoord aBlurRadius,nscoord aSpreadRadius,IntSize & aOutBlurRadius,IntSize & aOutSpreadRadius,bool aConstrainSpreadRadius)6040 nsContextBoxBlur::GetBlurAndSpreadRadius(DrawTarget* aDestDrawTarget,
6041 int32_t aAppUnitsPerDevPixel,
6042 nscoord aBlurRadius,
6043 nscoord aSpreadRadius,
6044 IntSize& aOutBlurRadius,
6045 IntSize& aOutSpreadRadius,
6046 bool aConstrainSpreadRadius)
6047 {
6048 // Do blurs in device space when possible.
6049 // Chrome/Skia always does the blurs in device space
6050 // and will sometimes get incorrect results (e.g. rotated blurs)
6051 Matrix transform = aDestDrawTarget->GetTransform();
6052 // XXX: we could probably handle negative scales but for now it's easier just to fallback
6053 gfxFloat scaleX, scaleY;
6054 if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 || transform._22 <= 0.0) {
6055 scaleX = 1;
6056 scaleY = 1;
6057 } else {
6058 scaleX = transform._11;
6059 scaleY = transform._22;
6060 }
6061
6062 // compute a large or smaller blur radius
6063 aOutBlurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
6064 aOutSpreadRadius =
6065 IntSize(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel),
6066 int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel));
6067
6068
6069 if (aConstrainSpreadRadius) {
6070 aOutSpreadRadius.width = std::min(aOutSpreadRadius.width, int32_t(MAX_SPREAD_RADIUS));
6071 aOutSpreadRadius.height = std::min(aOutSpreadRadius.height, int32_t(MAX_SPREAD_RADIUS));
6072 }
6073 }
6074
6075 /* static */ bool
InsetBoxBlur(gfxContext * aDestinationCtx,Rect aDestinationRect,Rect aShadowClipRect,Color & aShadowColor,nscoord aBlurRadiusAppUnits,nscoord aSpreadDistanceAppUnits,int32_t aAppUnitsPerDevPixel,bool aHasBorderRadius,RectCornerRadii & aInnerClipRectRadii,Rect aSkipRect,Point aShadowOffset)6076 nsContextBoxBlur::InsetBoxBlur(gfxContext* aDestinationCtx,
6077 Rect aDestinationRect,
6078 Rect aShadowClipRect,
6079 Color& aShadowColor,
6080 nscoord aBlurRadiusAppUnits,
6081 nscoord aSpreadDistanceAppUnits,
6082 int32_t aAppUnitsPerDevPixel,
6083 bool aHasBorderRadius,
6084 RectCornerRadii& aInnerClipRectRadii,
6085 Rect aSkipRect, Point aShadowOffset)
6086 {
6087 if (aDestinationRect.IsEmpty()) {
6088 mContext = nullptr;
6089 return false;
6090 }
6091
6092 gfxContextAutoSaveRestore autoRestore(aDestinationCtx);
6093
6094 IntSize blurRadius;
6095 IntSize spreadRadius;
6096 // Convert the blur and spread radius to device pixels
6097 bool constrainSpreadRadius = false;
6098 GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
6099 aBlurRadiusAppUnits, aSpreadDistanceAppUnits,
6100 blurRadius, spreadRadius, constrainSpreadRadius);
6101
6102 // The blur and spread radius are scaled already, so scale all
6103 // input data to the blur. This way, we don't have to scale the min
6104 // inset blur to the invert of the dest context, then rescale it back
6105 // when we draw to the destination surface.
6106 gfxSize scale = aDestinationCtx->CurrentMatrix().ScaleFactors(true);
6107 Matrix transform = ToMatrix(aDestinationCtx->CurrentMatrix());
6108
6109 // XXX: we could probably handle negative scales but for now it's easier just to fallback
6110 if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && transform._22 > 0.0) {
6111 // If we don't have a rotation, we're pre-transforming all the rects.
6112 aDestinationCtx->SetMatrix(gfxMatrix());
6113 } else {
6114 // Don't touch anything, we have a rotation.
6115 transform = Matrix();
6116 }
6117
6118 Rect transformedDestRect = transform.TransformBounds(aDestinationRect);
6119 Rect transformedShadowClipRect = transform.TransformBounds(aShadowClipRect);
6120 Rect transformedSkipRect = transform.TransformBounds(aSkipRect);
6121
6122 transformedDestRect.Round();
6123 transformedShadowClipRect.Round();
6124 transformedSkipRect.RoundIn();
6125
6126 for (size_t i = 0; i < 4; i++) {
6127 aInnerClipRectRadii[i].width = std::floor(scale.width * aInnerClipRectRadii[i].width);
6128 aInnerClipRectRadii[i].height = std::floor(scale.height * aInnerClipRectRadii[i].height);
6129 }
6130
6131 mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect,
6132 transformedShadowClipRect,
6133 blurRadius, spreadRadius,
6134 aShadowColor, aHasBorderRadius,
6135 aInnerClipRectRadii, transformedSkipRect,
6136 aShadowOffset);
6137 return true;
6138 }
6139