Bumping manifests a=b2g-bump
[gecko.git] / layout / base / nsCSSRendering.cpp
blobd424ff967e6761b6f07ee40cb53f417db29ed9e7
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/. */
7 /* utility functions for drawing borders and backgrounds */
9 #include <ctime>
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"
20 #include "nsStyleConsts.h"
21 #include "nsPresContext.h"
22 #include "nsIFrame.h"
23 #include "nsPoint.h"
24 #include "nsRect.h"
25 #include "nsIPresShell.h"
26 #include "nsFrameManager.h"
27 #include "nsStyleContext.h"
28 #include "nsGkAtoms.h"
29 #include "nsCSSAnonBoxes.h"
30 #include "nsIContent.h"
31 #include "nsIDocumentInlines.h"
32 #include "nsIScrollableFrame.h"
33 #include "imgIRequest.h"
34 #include "imgIContainer.h"
35 #include "ImageOps.h"
36 #include "nsCSSRendering.h"
37 #include "nsCSSColorUtils.h"
38 #include "nsITheme.h"
39 #include "nsThemeConstants.h"
40 #include "nsLayoutUtils.h"
41 #include "nsBlockFrame.h"
42 #include "gfxContext.h"
43 #include "nsRenderingContext.h"
44 #include "nsStyleStructInlines.h"
45 #include "nsCSSFrameConstructor.h"
46 #include "nsCSSProps.h"
47 #include "nsContentUtils.h"
48 #include "nsSVGEffects.h"
49 #include "nsSVGIntegrationUtils.h"
50 #include "gfxDrawable.h"
51 #include "GeckoProfiler.h"
52 #include "nsCSSRenderingBorders.h"
53 #include "mozilla/css/ImageLoader.h"
54 #include "ImageContainer.h"
55 #include "mozilla/Telemetry.h"
56 #include "gfxUtils.h"
57 #include "gfxColor.h"
58 #include "gfxGradientCache.h"
59 #include "GraphicsFilter.h"
60 #include <algorithm>
62 using namespace mozilla;
63 using namespace mozilla::css;
64 using namespace mozilla::gfx;
65 using namespace mozilla::image;
66 using mozilla::CSSSizeOrRatio;
68 static int gFrameTreeLockCount = 0;
70 // To avoid storing this data on nsInlineFrame (bloat) and to avoid
71 // recalculating this for each frame in a continuation (perf), hold
72 // a cache of various coordinate information that we need in order
73 // to paint inline backgrounds.
74 struct InlineBackgroundData
76 InlineBackgroundData()
77 : mFrame(nullptr), mBlockFrame(nullptr)
81 ~InlineBackgroundData()
85 void Reset()
87 mBoundingBox.SetRect(0,0,0,0);
88 mContinuationPoint = mLineContinuationPoint = mUnbrokenMeasure = 0;
89 mFrame = mBlockFrame = nullptr;
90 mPIStartBorderData.Reset();
93 /**
94 * Return a continuous rect for (an inline) aFrame relative to the
95 * continuation that draws the left-most part of the background.
96 * This is used when painting backgrounds.
98 nsRect GetContinuousRect(nsIFrame* aFrame)
100 MOZ_ASSERT(aFrame->GetType() == nsGkAtoms::inlineFrame);
102 SetFrame(aFrame);
104 nscoord pos; // an x coordinate if writing-mode is horizontal;
105 // y coordinate if vertical
106 if (mBidiEnabled) {
107 pos = mLineContinuationPoint;
109 // Scan continuations on the same line as aFrame and accumulate the widths
110 // of frames that are to the left (if this is an LTR block) or right
111 // (if it's RTL) of the current one.
112 bool isRtlBlock = (mBlockFrame->StyleVisibility()->mDirection ==
113 NS_STYLE_DIRECTION_RTL);
114 nscoord curOffset = mVertical ? aFrame->GetOffsetTo(mBlockFrame).y
115 : aFrame->GetOffsetTo(mBlockFrame).x;
117 // If the continuation is fluid we know inlineFrame is not on the same line.
118 // If it's not fluid, we need to test further to be sure.
119 nsIFrame* inlineFrame = aFrame->GetPrevContinuation();
120 while (inlineFrame && !inlineFrame->GetNextInFlow() &&
121 AreOnSameLine(aFrame, inlineFrame)) {
122 nscoord frameOffset = mVertical
123 ? inlineFrame->GetOffsetTo(mBlockFrame).y
124 : inlineFrame->GetOffsetTo(mBlockFrame).x;
125 if (isRtlBlock == (frameOffset >= curOffset)) {
126 pos += mVertical
127 ? inlineFrame->GetSize().height
128 : inlineFrame->GetSize().width;
130 inlineFrame = inlineFrame->GetPrevContinuation();
133 inlineFrame = aFrame->GetNextContinuation();
134 while (inlineFrame && !inlineFrame->GetPrevInFlow() &&
135 AreOnSameLine(aFrame, inlineFrame)) {
136 nscoord frameOffset = mVertical
137 ? inlineFrame->GetOffsetTo(mBlockFrame).y
138 : inlineFrame->GetOffsetTo(mBlockFrame).x;
139 if (isRtlBlock == (frameOffset >= curOffset)) {
140 pos += mVertical
141 ? inlineFrame->GetSize().height
142 : inlineFrame->GetSize().width;
144 inlineFrame = inlineFrame->GetNextContinuation();
146 if (isRtlBlock) {
147 // aFrame itself is also to the right of its left edge, so add its width.
148 pos += mVertical ? aFrame->GetSize().height : aFrame->GetSize().width;
149 // pos is now the distance from the left [top] edge of aFrame to the right [bottom] edge
150 // of the unbroken content. Change it to indicate the distance from the
151 // left [top] edge of the unbroken content to the left [top] edge of aFrame.
152 pos = mUnbrokenMeasure - pos;
154 } else {
155 pos = mContinuationPoint;
158 // Assume background-origin: border and return a rect with offsets
159 // relative to (0,0). If we have a different background-origin,
160 // then our rect should be deflated appropriately by our caller.
161 return mVertical
162 ? nsRect(0, -pos, mFrame->GetSize().width, mUnbrokenMeasure)
163 : nsRect(-pos, 0, mUnbrokenMeasure, mFrame->GetSize().height);
167 * Return a continuous rect for (an inline) aFrame relative to the
168 * continuation that should draw the left[top]-border. This is used when painting
169 * borders and clipping backgrounds. This may NOT be the same continuous rect
170 * as for drawing backgrounds; the continuation with the left[top]-border might be
171 * somewhere in the middle of that rect (e.g. BIDI), in those cases we need
172 * the reverse background order starting at the left[top]-border continuation.
174 nsRect GetBorderContinuousRect(nsIFrame* aFrame, nsRect aBorderArea)
176 // Calling GetContinuousRect(aFrame) here may lead to Reset/Init which
177 // resets our mPIStartBorderData so we save it ...
178 PhysicalInlineStartBorderData saved(mPIStartBorderData);
179 nsRect joinedBorderArea = GetContinuousRect(aFrame);
180 if (!saved.mIsValid || saved.mFrame != mPIStartBorderData.mFrame) {
181 if (aFrame == mPIStartBorderData.mFrame) {
182 if (mVertical) {
183 mPIStartBorderData.SetCoord(joinedBorderArea.y);
184 } else {
185 mPIStartBorderData.SetCoord(joinedBorderArea.x);
187 } else if (mPIStartBorderData.mFrame) {
188 if (mVertical) {
189 mPIStartBorderData.SetCoord(GetContinuousRect(mPIStartBorderData.mFrame).y);
190 } else {
191 mPIStartBorderData.SetCoord(GetContinuousRect(mPIStartBorderData.mFrame).x);
194 } else {
195 // ... and restore it when possible.
196 mPIStartBorderData.mCoord = saved.mCoord;
198 if (mVertical) {
199 if (joinedBorderArea.y > mPIStartBorderData.mCoord) {
200 joinedBorderArea.y =
201 -(mUnbrokenMeasure + joinedBorderArea.y - aBorderArea.height);
202 } else {
203 joinedBorderArea.y -= mPIStartBorderData.mCoord;
205 } else {
206 if (joinedBorderArea.x > mPIStartBorderData.mCoord) {
207 joinedBorderArea.x =
208 -(mUnbrokenMeasure + joinedBorderArea.x - aBorderArea.width);
209 } else {
210 joinedBorderArea.x -= mPIStartBorderData.mCoord;
213 return joinedBorderArea;
216 nsRect GetBoundingRect(nsIFrame* aFrame)
218 SetFrame(aFrame);
220 // Move the offsets relative to (0,0) which puts the bounding box into
221 // our coordinate system rather than our parent's. We do this by
222 // moving it the back distance from us to the bounding box.
223 // This also assumes background-origin: border, so our caller will
224 // need to deflate us if needed.
225 nsRect boundingBox(mBoundingBox);
226 nsPoint point = mFrame->GetPosition();
227 boundingBox.MoveBy(-point.x, -point.y);
229 return boundingBox;
232 protected:
233 // This is a coordinate on the inline axis, but is not a true logical inline-
234 // coord because it is always measured from left to right (if horizontal) or
235 // from top to bottom (if vertical), ignoring any bidi RTL directionality.
236 // We'll call this "physical inline start", or PIStart for short.
237 struct PhysicalInlineStartBorderData {
238 nsIFrame* mFrame; // the continuation that may have a left-border
239 nscoord mCoord; // cached GetContinuousRect(mFrame).x or .y
240 bool mIsValid; // true if mCoord is valid
241 void Reset() { mFrame = nullptr; mIsValid = false; }
242 void SetCoord(nscoord aCoord) { mCoord = aCoord; mIsValid = true; }
245 nsIFrame* mFrame;
246 nsBlockFrame* mBlockFrame;
247 nsRect mBoundingBox;
248 nscoord mContinuationPoint;
249 nscoord mUnbrokenMeasure;
250 nscoord mLineContinuationPoint;
251 PhysicalInlineStartBorderData mPIStartBorderData;
252 bool mBidiEnabled;
253 bool mVertical;
255 void SetFrame(nsIFrame* aFrame)
257 NS_PRECONDITION(aFrame, "Need a frame");
258 NS_ASSERTION(gFrameTreeLockCount > 0,
259 "Can't call this when frame tree is not locked");
261 if (aFrame == mFrame) {
262 return;
265 nsIFrame *prevContinuation = GetPrevContinuation(aFrame);
267 if (!prevContinuation || mFrame != prevContinuation) {
268 // Ok, we've got the wrong frame. We have to start from scratch.
269 Reset();
270 Init(aFrame);
271 return;
274 // Get our last frame's size and add its width to our continuation
275 // point before we cache the new frame.
276 mContinuationPoint += mVertical ? mFrame->GetSize().height
277 : mFrame->GetSize().width;
279 // If this a new line, update mLineContinuationPoint.
280 if (mBidiEnabled &&
281 (aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) {
282 mLineContinuationPoint = mContinuationPoint;
285 mFrame = aFrame;
288 nsIFrame* GetPrevContinuation(nsIFrame* aFrame)
290 nsIFrame* prevCont = aFrame->GetPrevContinuation();
291 if (!prevCont &&
292 (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
293 nsIFrame* block = static_cast<nsIFrame*>
294 (aFrame->Properties().Get(nsIFrame::IBSplitPrevSibling()));
295 if (block) {
296 // The {ib} properties are only stored on first continuations
297 NS_ASSERTION(!block->GetPrevContinuation(),
298 "Incorrect value for IBSplitPrevSibling");
299 prevCont = static_cast<nsIFrame*>
300 (block->Properties().Get(nsIFrame::IBSplitPrevSibling()));
301 NS_ASSERTION(prevCont, "How did that happen?");
304 return prevCont;
307 nsIFrame* GetNextContinuation(nsIFrame* aFrame)
309 nsIFrame* nextCont = aFrame->GetNextContinuation();
310 if (!nextCont &&
311 (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
312 // The {ib} properties are only stored on first continuations
313 aFrame = aFrame->FirstContinuation();
314 nsIFrame* block = static_cast<nsIFrame*>
315 (aFrame->Properties().Get(nsIFrame::IBSplitSibling()));
316 if (block) {
317 nextCont = static_cast<nsIFrame*>
318 (block->Properties().Get(nsIFrame::IBSplitSibling()));
319 NS_ASSERTION(nextCont, "How did that happen?");
322 return nextCont;
325 void Init(nsIFrame* aFrame)
327 mPIStartBorderData.Reset();
328 mBidiEnabled = aFrame->PresContext()->BidiEnabled();
329 if (mBidiEnabled) {
330 // Find the containing block frame
331 nsIFrame* frame = aFrame;
332 do {
333 frame = frame->GetParent();
334 mBlockFrame = do_QueryFrame(frame);
336 while (frame && frame->IsFrameOfType(nsIFrame::eLineParticipant));
338 NS_ASSERTION(mBlockFrame, "Cannot find containing block.");
341 mVertical = aFrame->GetWritingMode().IsVertical();
343 // Start with the previous flow frame as our continuation point
344 // is the total of the widths of the previous frames.
345 nsIFrame* inlineFrame = GetPrevContinuation(aFrame);
346 while (inlineFrame) {
347 if (!mPIStartBorderData.mFrame &&
348 !(mVertical ? inlineFrame->GetSkipSides().Top()
349 : inlineFrame->GetSkipSides().Left())) {
350 mPIStartBorderData.mFrame = inlineFrame;
352 nsRect rect = inlineFrame->GetRect();
353 mContinuationPoint += mVertical ? rect.height : rect.width;
354 if (mBidiEnabled && !AreOnSameLine(aFrame, inlineFrame)) {
355 mLineContinuationPoint += mVertical ? rect.height : rect.width;
357 mUnbrokenMeasure += mVertical ? rect.height : rect.width;
358 mBoundingBox.UnionRect(mBoundingBox, rect);
359 inlineFrame = GetPrevContinuation(inlineFrame);
362 // Next add this frame and subsequent frames to the bounding box and
363 // unbroken width.
364 inlineFrame = aFrame;
365 while (inlineFrame) {
366 if (!mPIStartBorderData.mFrame &&
367 !(mVertical ? inlineFrame->GetSkipSides().Top()
368 : inlineFrame->GetSkipSides().Left())) {
369 mPIStartBorderData.mFrame = inlineFrame;
371 nsRect rect = inlineFrame->GetRect();
372 mUnbrokenMeasure += mVertical ? rect.height : rect.width;
373 mBoundingBox.UnionRect(mBoundingBox, rect);
374 inlineFrame = GetNextContinuation(inlineFrame);
377 mFrame = aFrame;
380 bool AreOnSameLine(nsIFrame* aFrame1, nsIFrame* aFrame2) {
381 bool isValid1, isValid2;
382 nsBlockInFlowLineIterator it1(mBlockFrame, aFrame1, &isValid1);
383 nsBlockInFlowLineIterator it2(mBlockFrame, aFrame2, &isValid2);
384 return isValid1 && isValid2 &&
385 // Make sure aFrame1 and aFrame2 are in the same continuation of
386 // mBlockFrame.
387 it1.GetContainer() == it2.GetContainer() &&
388 // And on the same line in it
389 it1.GetLine() == it2.GetLine();
393 // A resolved color stop --- with a specific position along the gradient line,
394 // and a Thebes color
395 struct ColorStop {
396 ColorStop(): mPosition(0), mIsMidpoint(false) {}
397 ColorStop(double aPosition, bool aIsMidPoint, gfxRGBA aColor) :
398 mPosition(aPosition), mIsMidpoint(aIsMidPoint), mColor(aColor) {}
399 double mPosition; // along the gradient line; 0=start, 1=end
400 bool mIsMidpoint;
401 gfxRGBA mColor;
404 /* Local functions */
405 static void DrawBorderImage(nsPresContext* aPresContext,
406 nsRenderingContext& aRenderingContext,
407 nsIFrame* aForFrame,
408 const nsRect& aBorderArea,
409 const nsStyleBorder& aStyleBorder,
410 const nsRect& aDirtyRect,
411 Sides aSkipSides);
413 static nscolor MakeBevelColor(mozilla::css::Side whichSide, uint8_t style,
414 nscolor aBackgroundColor,
415 nscolor aBorderColor);
417 static InlineBackgroundData* gInlineBGData = nullptr;
419 // Initialize any static variables used by nsCSSRendering.
420 void nsCSSRendering::Init()
422 NS_ASSERTION(!gInlineBGData, "Init called twice");
423 gInlineBGData = new InlineBackgroundData();
426 // Clean up any global variables used by nsCSSRendering.
427 void nsCSSRendering::Shutdown()
429 delete gInlineBGData;
430 gInlineBGData = nullptr;
434 * Make a bevel color
436 static nscolor
437 MakeBevelColor(mozilla::css::Side whichSide, uint8_t style,
438 nscolor aBackgroundColor, nscolor aBorderColor)
441 nscolor colors[2];
442 nscolor theColor;
444 // Given a background color and a border color
445 // calculate the color used for the shading
446 NS_GetSpecial3DColors(colors, aBackgroundColor, aBorderColor);
448 if ((style == NS_STYLE_BORDER_STYLE_OUTSET) ||
449 (style == NS_STYLE_BORDER_STYLE_RIDGE)) {
450 // Flip colors for these two border styles
451 switch (whichSide) {
452 case NS_SIDE_BOTTOM: whichSide = NS_SIDE_TOP; break;
453 case NS_SIDE_RIGHT: whichSide = NS_SIDE_LEFT; break;
454 case NS_SIDE_TOP: whichSide = NS_SIDE_BOTTOM; break;
455 case NS_SIDE_LEFT: whichSide = NS_SIDE_RIGHT; break;
459 switch (whichSide) {
460 case NS_SIDE_BOTTOM:
461 theColor = colors[1];
462 break;
463 case NS_SIDE_RIGHT:
464 theColor = colors[1];
465 break;
466 case NS_SIDE_TOP:
467 theColor = colors[0];
468 break;
469 case NS_SIDE_LEFT:
470 default:
471 theColor = colors[0];
472 break;
474 return theColor;
477 static bool
478 GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
479 const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
480 nscoord aRadii[8])
482 bool haveRoundedCorners;
483 nsSize sz = aBorderArea.Size();
484 nsSize frameSize = aForFrame->GetSize();
485 if (&aBorder == aForFrame->StyleBorder() &&
486 frameSize == aOrigBorderArea.Size()) {
487 haveRoundedCorners = aForFrame->GetBorderRadii(sz, sz, Sides(), aRadii);
488 } else {
489 haveRoundedCorners =
490 nsIFrame::ComputeBorderRadii(aBorder.mBorderRadius, frameSize, sz, Sides(), aRadii);
493 return haveRoundedCorners;
496 static bool
497 GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
498 const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
499 RectCornerRadii* aBgRadii)
501 nscoord radii[8];
502 bool haveRoundedCorners = GetRadii(aForFrame, aBorder, aOrigBorderArea, aBorderArea, radii);
504 if (haveRoundedCorners) {
505 auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
506 nsCSSRendering::ComputePixelRadii(radii, d2a, aBgRadii);
508 return haveRoundedCorners;
511 static nsRect
512 JoinBoxesForVerticalSlice(nsIFrame* aFrame, const nsRect& aBorderArea)
514 // Inflate vertically as if our continuations were laid out vertically
515 // adjacent. Note that we don't touch the width.
516 nsRect borderArea = aBorderArea;
517 nscoord h = 0;
518 nsIFrame* f = aFrame->GetNextContinuation();
519 for (; f; f = f->GetNextContinuation()) {
520 MOZ_ASSERT(!(f->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT),
521 "anonymous ib-split block shouldn't have border/background");
522 h += f->GetRect().height;
524 borderArea.height += h;
525 h = 0;
526 f = aFrame->GetPrevContinuation();
527 for (; f; f = f->GetPrevContinuation()) {
528 MOZ_ASSERT(!(f->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT),
529 "anonymous ib-split block shouldn't have border/background");
530 h += f->GetRect().height;
532 borderArea.y -= h;
533 borderArea.height += h;
534 return borderArea;
538 * Inflate aBorderArea which is relative to aFrame's origin to calculate
539 * a hypothetical non-split frame area for all the continuations.
540 * See "Joining Boxes for 'slice'" in
541 * http://dev.w3.org/csswg/css-break/#break-decoration
543 enum InlineBoxOrder { eForBorder, eForBackground };
544 static nsRect
545 JoinBoxesForSlice(nsIFrame* aFrame, const nsRect& aBorderArea,
546 InlineBoxOrder aOrder)
548 if (aFrame->GetType() == nsGkAtoms::inlineFrame) {
549 return (aOrder == eForBorder
550 ? gInlineBGData->GetBorderContinuousRect(aFrame, aBorderArea)
551 : gInlineBGData->GetContinuousRect(aFrame)) +
552 aBorderArea.TopLeft();
554 return JoinBoxesForVerticalSlice(aFrame, aBorderArea);
557 static bool
558 IsBoxDecorationSlice(const nsStyleBorder& aStyleBorder)
560 return aStyleBorder.mBoxDecorationBreak ==
561 NS_STYLE_BOX_DECORATION_BREAK_SLICE;
564 static nsRect
565 BoxDecorationRectForBorder(nsIFrame* aFrame, const nsRect& aBorderArea,
566 Sides aSkipSides,
567 const nsStyleBorder* aStyleBorder = nullptr)
569 if (!aStyleBorder) {
570 aStyleBorder = aFrame->StyleBorder();
572 // If aSkipSides.IsEmpty() then there are no continuations, or it's
573 // a ::first-letter that wants all border sides on the first continuation.
574 return ::IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
575 ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBorder)
576 : aBorderArea;
579 static nsRect
580 BoxDecorationRectForBackground(nsIFrame* aFrame, const nsRect& aBorderArea,
581 Sides aSkipSides,
582 const nsStyleBorder* aStyleBorder = nullptr)
584 if (!aStyleBorder) {
585 aStyleBorder = aFrame->StyleBorder();
587 // If aSkipSides.IsEmpty() then there are no continuations, or it's
588 // a ::first-letter that wants all border sides on the first continuation.
589 return ::IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
590 ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBackground)
591 : aBorderArea;
594 //----------------------------------------------------------------------
595 // Thebes Border Rendering Code Start
598 * Compute the float-pixel radii that should be used for drawing
599 * this border/outline, given the various input bits.
601 /* static */ void
602 nsCSSRendering::ComputePixelRadii(const nscoord *aAppUnitsRadii,
603 nscoord aAppUnitsPerPixel,
604 RectCornerRadii *oBorderRadii)
606 Float radii[8];
607 NS_FOR_CSS_HALF_CORNERS(corner)
608 radii[corner] = Float(aAppUnitsRadii[corner]) / aAppUnitsPerPixel;
610 (*oBorderRadii)[C_TL] = Size(radii[NS_CORNER_TOP_LEFT_X],
611 radii[NS_CORNER_TOP_LEFT_Y]);
612 (*oBorderRadii)[C_TR] = Size(radii[NS_CORNER_TOP_RIGHT_X],
613 radii[NS_CORNER_TOP_RIGHT_Y]);
614 (*oBorderRadii)[C_BR] = Size(radii[NS_CORNER_BOTTOM_RIGHT_X],
615 radii[NS_CORNER_BOTTOM_RIGHT_Y]);
616 (*oBorderRadii)[C_BL] = Size(radii[NS_CORNER_BOTTOM_LEFT_X],
617 radii[NS_CORNER_BOTTOM_LEFT_Y]);
620 void
621 nsCSSRendering::PaintBorder(nsPresContext* aPresContext,
622 nsRenderingContext& aRenderingContext,
623 nsIFrame* aForFrame,
624 const nsRect& aDirtyRect,
625 const nsRect& aBorderArea,
626 nsStyleContext* aStyleContext,
627 Sides aSkipSides)
629 PROFILER_LABEL("nsCSSRendering", "PaintBorder",
630 js::ProfileEntry::Category::GRAPHICS);
632 nsStyleContext *styleIfVisited = aStyleContext->GetStyleIfVisited();
633 const nsStyleBorder *styleBorder = aStyleContext->StyleBorder();
634 // Don't check RelevantLinkVisited here, since we want to take the
635 // same amount of time whether or not it's true.
636 if (!styleIfVisited) {
637 PaintBorderWithStyleBorder(aPresContext, aRenderingContext, aForFrame,
638 aDirtyRect, aBorderArea, *styleBorder,
639 aStyleContext, aSkipSides);
640 return;
643 nsStyleBorder newStyleBorder(*styleBorder);
644 // We could do something fancy to avoid the TrackImage/UntrackImage
645 // work, but it doesn't seem worth it. (We need to call TrackImage
646 // since we're not going through nsRuleNode::ComputeBorderData.)
647 newStyleBorder.TrackImage(aPresContext);
649 NS_FOR_CSS_SIDES(side) {
650 newStyleBorder.SetBorderColor(side,
651 aStyleContext->GetVisitedDependentColor(
652 nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color)[side]));
654 PaintBorderWithStyleBorder(aPresContext, aRenderingContext, aForFrame,
655 aDirtyRect, aBorderArea, newStyleBorder,
656 aStyleContext, aSkipSides);
658 // We could do something fancy to avoid the TrackImage/UntrackImage
659 // work, but it doesn't seem worth it. (We need to call UntrackImage
660 // since we're not going through nsStyleBorder::Destroy.)
661 newStyleBorder.UntrackImage(aPresContext);
664 void
665 nsCSSRendering::PaintBorderWithStyleBorder(nsPresContext* aPresContext,
666 nsRenderingContext& aRenderingContext,
667 nsIFrame* aForFrame,
668 const nsRect& aDirtyRect,
669 const nsRect& aBorderArea,
670 const nsStyleBorder& aStyleBorder,
671 nsStyleContext* aStyleContext,
672 Sides aSkipSides)
674 DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
676 PrintAsStringNewline("++ PaintBorder");
678 // Check to see if we have an appearance defined. If so, we let the theme
679 // renderer draw the border. DO not get the data from aForFrame, since the passed in style context
680 // may be different! Always use |aStyleContext|!
681 const nsStyleDisplay* displayData = aStyleContext->StyleDisplay();
682 if (displayData->mAppearance) {
683 nsITheme *theme = aPresContext->GetTheme();
684 if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame, displayData->mAppearance))
685 return; // Let the theme handle it.
688 if (aStyleBorder.IsBorderImageLoaded()) {
689 DrawBorderImage(aPresContext, aRenderingContext, aForFrame,
690 aBorderArea, aStyleBorder, aDirtyRect, aSkipSides);
691 return;
694 // Get our style context's color struct.
695 const nsStyleColor* ourColor = aStyleContext->StyleColor();
697 // In NavQuirks mode we want to use the parent's context as a starting point
698 // for determining the background color.
699 bool quirks = aPresContext->CompatibilityMode() == eCompatibility_NavQuirks;
700 nsIFrame* bgFrame = FindNonTransparentBackgroundFrame(aForFrame, quirks);
701 nsStyleContext* bgContext = bgFrame->StyleContext();
702 nscolor bgColor =
703 bgContext->GetVisitedDependentColor(eCSSProperty_background_color);
705 nsMargin border = aStyleBorder.GetComputedBorder();
706 if (0 == border.left && 0 == border.right &&
707 0 == border.top && 0 == border.bottom) {
708 // Empty border area
709 return;
712 // Compute the outermost boundary of the area that might be painted.
713 // Same coordinate space as aBorderArea & aBGClipRect.
714 nsRect joinedBorderArea =
715 ::BoxDecorationRectForBorder(aForFrame, aBorderArea, aSkipSides, &aStyleBorder);
716 RectCornerRadii bgRadii;
717 ::GetRadii(aForFrame, aStyleBorder, aBorderArea, joinedBorderArea, &bgRadii);
719 PrintAsFormatString(" joinedBorderArea: %d %d %d %d\n", joinedBorderArea.x, joinedBorderArea.y,
720 joinedBorderArea.width, joinedBorderArea.height);
722 // start drawing
723 gfxContext* ctx = aRenderingContext.ThebesContext();
724 ctx->Save();
726 if (::IsBoxDecorationSlice(aStyleBorder)) {
727 if (joinedBorderArea.IsEqualEdges(aBorderArea)) {
728 // No need for a clip, just skip the sides we don't want.
729 border.ApplySkipSides(aSkipSides);
730 } else {
731 // We're drawing borders around the joined continuation boxes so we need
732 // to clip that to the slice that we want for this frame.
733 aRenderingContext.ThebesContext()->
734 Clip(NSRectToSnappedRect(aBorderArea,
735 aForFrame->PresContext()->AppUnitsPerDevPixel(),
736 aDrawTarget));
738 } else {
739 MOZ_ASSERT(joinedBorderArea.IsEqualEdges(aBorderArea),
740 "Should use aBorderArea for box-decoration-break:clone");
741 MOZ_ASSERT(aForFrame->GetSkipSides().IsEmpty(),
742 "Should not skip sides for box-decoration-break:clone except "
743 "::first-letter/line continuations or other frame types that "
744 "don't have borders but those shouldn't reach this point.");
747 // Convert to dev pixels.
748 nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
749 Rect joinedBorderAreaPx = NSRectToRect(joinedBorderArea, twipsPerPixel);
750 Float borderWidths[4] = { Float(border.top / twipsPerPixel),
751 Float(border.right / twipsPerPixel),
752 Float(border.bottom / twipsPerPixel),
753 Float(border.left / twipsPerPixel) };
755 uint8_t borderStyles[4];
756 nscolor borderColors[4];
757 nsBorderColors *compositeColors[4];
759 // pull out styles, colors, composite colors
760 NS_FOR_CSS_SIDES (i) {
761 bool foreground;
762 borderStyles[i] = aStyleBorder.GetBorderStyle(i);
763 aStyleBorder.GetBorderColor(i, borderColors[i], foreground);
764 aStyleBorder.GetCompositeColors(i, &compositeColors[i]);
766 if (foreground)
767 borderColors[i] = ourColor->mColor;
770 PrintAsFormatString(" borderStyles: %d %d %d %d\n", borderStyles[0], borderStyles[1], borderStyles[2], borderStyles[3]);
771 //PrintAsFormatString ("bgRadii: %f %f %f %f\n", bgRadii[0], bgRadii[1], bgRadii[2], bgRadii[3]);
773 #if 0
774 // this will draw a transparent red backround underneath the border area
775 ColorPattern color(ToDeviceColor(Color(1.f, 0.f, 0.f, 0.5f)));
776 aDrawTarget.FillRect(joinedBorderAreaPx, color);
777 #endif
779 nsCSSBorderRenderer br(&aDrawTarget,
780 joinedBorderAreaPx,
781 borderStyles,
782 borderWidths,
783 bgRadii,
784 borderColors,
785 compositeColors,
786 bgColor);
787 br.DrawBorders();
789 ctx->Restore();
791 PrintAsStringNewline();
794 static nsRect
795 GetOutlineInnerRect(nsIFrame* aFrame)
797 nsRect* savedOutlineInnerRect = static_cast<nsRect*>
798 (aFrame->Properties().Get(nsIFrame::OutlineInnerRectProperty()));
799 if (savedOutlineInnerRect)
800 return *savedOutlineInnerRect;
801 NS_NOTREACHED("we should have saved a frame property");
802 return nsRect(nsPoint(0, 0), aFrame->GetSize());
805 void
806 nsCSSRendering::PaintOutline(nsPresContext* aPresContext,
807 nsRenderingContext& aRenderingContext,
808 nsIFrame* aForFrame,
809 const nsRect& aDirtyRect,
810 const nsRect& aBorderArea,
811 nsStyleContext* aStyleContext)
813 nscoord twipsRadii[8];
815 // Get our style context's color struct.
816 const nsStyleOutline* ourOutline = aStyleContext->StyleOutline();
817 MOZ_ASSERT(ourOutline != NS_STYLE_BORDER_STYLE_NONE,
818 "shouldn't have created nsDisplayOutline item");
820 uint8_t outlineStyle = ourOutline->GetOutlineStyle();
821 nscoord width;
822 ourOutline->GetOutlineWidth(width);
824 if (width == 0 && outlineStyle != NS_STYLE_BORDER_STYLE_AUTO) {
825 // Empty outline
826 return;
829 nsIFrame* bgFrame = nsCSSRendering::FindNonTransparentBackgroundFrame
830 (aForFrame, false);
831 nsStyleContext* bgContext = bgFrame->StyleContext();
832 nscolor bgColor =
833 bgContext->GetVisitedDependentColor(eCSSProperty_background_color);
835 nsRect innerRect;
836 if (
837 #ifdef MOZ_XUL
838 aStyleContext->GetPseudoType() == nsCSSPseudoElements::ePseudo_XULTree
839 #else
840 false
841 #endif
843 innerRect = aBorderArea;
844 } else {
845 innerRect = GetOutlineInnerRect(aForFrame) + aBorderArea.TopLeft();
847 nscoord offset = ourOutline->mOutlineOffset;
848 innerRect.Inflate(offset, offset);
849 // If the dirty rect is completely inside the border area (e.g., only the
850 // content is being painted), then we can skip out now
851 // XXX this isn't exactly true for rounded borders, where the inside curves may
852 // encroach into the content area. A safer calculation would be to
853 // shorten insideRect by the radius one each side before performing this test.
854 if (innerRect.Contains(aDirtyRect))
855 return;
857 nsRect outerRect = innerRect;
858 outerRect.Inflate(width, width);
860 // get the radius for our outline
861 nsIFrame::ComputeBorderRadii(ourOutline->mOutlineRadius, aBorderArea.Size(),
862 outerRect.Size(), Sides(), twipsRadii);
864 // Get our conversion values
865 nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
867 // get the outer rectangles
868 Rect oRect(NSRectToRect(outerRect, twipsPerPixel));
870 // convert the radii
871 nsMargin outlineMargin(width, width, width, width);
872 RectCornerRadii outlineRadii;
873 ComputePixelRadii(twipsRadii, twipsPerPixel, &outlineRadii);
875 if (nsLayoutUtils::IsOutlineStyleAutoEnabled()) {
876 if (outlineStyle == NS_STYLE_BORDER_STYLE_AUTO) {
877 nsITheme* theme = aPresContext->GetTheme();
878 if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame,
879 NS_THEME_FOCUS_OUTLINE)) {
880 theme->DrawWidgetBackground(&aRenderingContext, aForFrame,
881 NS_THEME_FOCUS_OUTLINE, innerRect,
882 aDirtyRect);
883 return;
884 } else if (width == 0) {
885 return; // empty outline
887 // http://dev.w3.org/csswg/css-ui/#outline
888 // "User agents may treat 'auto' as 'solid'."
889 outlineStyle = NS_STYLE_BORDER_STYLE_SOLID;
893 uint8_t outlineStyles[4] = { outlineStyle, outlineStyle,
894 outlineStyle, outlineStyle };
896 // This handles treating the initial color as 'currentColor'; if we
897 // ever want 'invert' back we'll need to do a bit of work here too.
898 nscolor outlineColor =
899 aStyleContext->GetVisitedDependentColor(eCSSProperty_outline_color);
900 nscolor outlineColors[4] = { outlineColor,
901 outlineColor,
902 outlineColor,
903 outlineColor };
905 // convert the border widths
906 Float outlineWidths[4] = { Float(width / twipsPerPixel),
907 Float(width / twipsPerPixel),
908 Float(width / twipsPerPixel),
909 Float(width / twipsPerPixel) };
911 // start drawing
912 gfxContext *ctx = aRenderingContext.ThebesContext();
914 nsCSSBorderRenderer br(ctx->GetDrawTarget(),
915 oRect,
916 outlineStyles,
917 outlineWidths,
918 outlineRadii,
919 outlineColors,
920 nullptr,
921 bgColor);
922 br.DrawBorders();
924 PrintAsStringNewline();
927 void
928 nsCSSRendering::PaintFocus(nsPresContext* aPresContext,
929 nsRenderingContext& aRenderingContext,
930 const nsRect& aFocusRect,
931 nscolor aColor)
933 nscoord oneCSSPixel = nsPresContext::CSSPixelsToAppUnits(1);
934 nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
936 Rect focusRect(NSRectToRect(aFocusRect, oneDevPixel));
938 RectCornerRadii focusRadii;
940 nscoord twipsRadii[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
941 ComputePixelRadii(twipsRadii, oneDevPixel, &focusRadii);
943 Float focusWidths[4] = { Float(oneCSSPixel / oneDevPixel),
944 Float(oneCSSPixel / oneDevPixel),
945 Float(oneCSSPixel / oneDevPixel),
946 Float(oneCSSPixel / oneDevPixel) };
948 uint8_t focusStyles[4] = { NS_STYLE_BORDER_STYLE_DOTTED,
949 NS_STYLE_BORDER_STYLE_DOTTED,
950 NS_STYLE_BORDER_STYLE_DOTTED,
951 NS_STYLE_BORDER_STYLE_DOTTED };
952 nscolor focusColors[4] = { aColor, aColor, aColor, aColor };
954 gfxContext *ctx = aRenderingContext.ThebesContext();
956 // Because this renders a dotted border, the background color
957 // should not be used. Therefore, we provide a value that will
958 // be blatantly wrong if it ever does get used. (If this becomes
959 // something that CSS can style, this function will then have access
960 // to a style context and can use the same logic that PaintBorder
961 // and PaintOutline do.)
962 nsCSSBorderRenderer br(ctx->GetDrawTarget(),
963 focusRect,
964 focusStyles,
965 focusWidths,
966 focusRadii,
967 focusColors,
968 nullptr,
969 NS_RGB(255, 0, 0));
970 br.DrawBorders();
972 PrintAsStringNewline();
975 // Thebes Border Rendering Code End
976 //----------------------------------------------------------------------
979 //----------------------------------------------------------------------
982 * Helper for ComputeObjectAnchorPoint; parameters are the same as for
983 * that function, except they're for a single coordinate / a single size
984 * dimension. (so, x/width vs. y/height)
986 typedef nsStyleBackground::Position::PositionCoord PositionCoord;
987 static void
988 ComputeObjectAnchorCoord(const PositionCoord& aCoord,
989 const nscoord aOriginBounds,
990 const nscoord aImageSize,
991 nscoord* aTopLeftCoord,
992 nscoord* aAnchorPointCoord)
994 *aAnchorPointCoord = aCoord.mLength;
995 *aTopLeftCoord = aCoord.mLength;
997 if (aCoord.mHasPercent) {
998 // Adjust aTopLeftCoord by the specified % of the extra space.
999 nscoord extraSpace = aOriginBounds - aImageSize;
1000 *aTopLeftCoord += NSToCoordRound(aCoord.mPercent * extraSpace);
1002 // The anchor-point doesn't care about our image's size; just the size
1003 // of the region we're rendering into.
1004 *aAnchorPointCoord += NSToCoordRound(aCoord.mPercent * aOriginBounds);
1008 void
1009 nsImageRenderer::ComputeObjectAnchorPoint(
1010 const nsStyleBackground::Position& aPos,
1011 const nsSize& aOriginBounds,
1012 const nsSize& aImageSize,
1013 nsPoint* aTopLeft,
1014 nsPoint* aAnchorPoint)
1016 ComputeObjectAnchorCoord(aPos.mXPosition,
1017 aOriginBounds.width, aImageSize.width,
1018 &aTopLeft->x, &aAnchorPoint->x);
1020 ComputeObjectAnchorCoord(aPos.mYPosition,
1021 aOriginBounds.height, aImageSize.height,
1022 &aTopLeft->y, &aAnchorPoint->y);
1025 nsIFrame*
1026 nsCSSRendering::FindNonTransparentBackgroundFrame(nsIFrame* aFrame,
1027 bool aStartAtParent /*= false*/)
1029 NS_ASSERTION(aFrame, "Cannot find NonTransparentBackgroundFrame in a null frame");
1031 nsIFrame* frame = nullptr;
1032 if (aStartAtParent) {
1033 frame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
1035 if (!frame) {
1036 frame = aFrame;
1039 while (frame) {
1040 // No need to call GetVisitedDependentColor because it always uses
1041 // this alpha component anyway.
1042 if (NS_GET_A(frame->StyleBackground()->mBackgroundColor) > 0)
1043 break;
1045 if (frame->IsThemed())
1046 break;
1048 nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(frame);
1049 if (!parent)
1050 break;
1052 frame = parent;
1054 return frame;
1057 // Returns true if aFrame is a canvas frame.
1058 // We need to treat the viewport as canvas because, even though
1059 // it does not actually paint a background, we need to get the right
1060 // background style so we correctly detect transparent documents.
1061 bool
1062 nsCSSRendering::IsCanvasFrame(nsIFrame* aFrame)
1064 nsIAtom* frameType = aFrame->GetType();
1065 return frameType == nsGkAtoms::canvasFrame ||
1066 frameType == nsGkAtoms::rootFrame ||
1067 frameType == nsGkAtoms::pageContentFrame ||
1068 frameType == nsGkAtoms::viewportFrame;
1071 nsIFrame*
1072 nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame)
1074 const nsStyleBackground* result = aForFrame->StyleBackground();
1076 // Check if we need to do propagation from BODY rather than HTML.
1077 if (!result->IsTransparent()) {
1078 return aForFrame;
1081 nsIContent* content = aForFrame->GetContent();
1082 // The root element content can't be null. We wouldn't know what
1083 // frame to create for aFrame.
1084 // Use |OwnerDoc| so it works during destruction.
1085 if (!content) {
1086 return aForFrame;
1089 nsIDocument* document = content->OwnerDoc();
1091 dom::Element* bodyContent = document->GetBodyElement();
1092 // We need to null check the body node (bug 118829) since
1093 // there are cases, thanks to the fix for bug 5569, where we
1094 // will reflow a document with no body. In particular, if a
1095 // SCRIPT element in the head blocks the parser and then has a
1096 // SCRIPT that does "document.location.href = 'foo'", then
1097 // nsParser::Terminate will call |DidBuildModel| methods
1098 // through to the content sink, which will call |StartLayout|
1099 // and thus |Initialize| on the pres shell. See bug 119351
1100 // for the ugly details.
1101 if (!bodyContent) {
1102 return aForFrame;
1105 nsIFrame *bodyFrame = bodyContent->GetPrimaryFrame();
1106 if (!bodyFrame) {
1107 return aForFrame;
1110 return nsLayoutUtils::GetStyleFrame(bodyFrame);
1114 * |FindBackground| finds the correct style data to use to paint the
1115 * background. It is responsible for handling the following two
1116 * statements in section 14.2 of CSS2:
1118 * The background of the box generated by the root element covers the
1119 * entire canvas.
1121 * For HTML documents, however, we recommend that authors specify the
1122 * background for the BODY element rather than the HTML element. User
1123 * agents should observe the following precedence rules to fill in the
1124 * background: if the value of the 'background' property for the HTML
1125 * element is different from 'transparent' then use it, else use the
1126 * value of the 'background' property for the BODY element. If the
1127 * resulting value is 'transparent', the rendering is undefined.
1129 * Thus, in our implementation, it is responsible for ensuring that:
1130 * + we paint the correct background on the |nsCanvasFrame|,
1131 * |nsRootBoxFrame|, or |nsPageFrame|,
1132 * + we don't paint the background on the root element, and
1133 * + we don't paint the background on the BODY element in *some* cases,
1134 * and for SGML-based HTML documents only.
1136 * |FindBackground| returns true if a background should be painted, and
1137 * the resulting style context to use for the background information
1138 * will be filled in to |aBackground|.
1140 nsStyleContext*
1141 nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame)
1143 return FindBackgroundStyleFrame(aForFrame)->StyleContext();
1146 inline bool
1147 FindElementBackground(nsIFrame* aForFrame, nsIFrame* aRootElementFrame,
1148 nsStyleContext** aBackgroundSC)
1150 if (aForFrame == aRootElementFrame) {
1151 // We must have propagated our background to the viewport or canvas. Abort.
1152 return false;
1155 *aBackgroundSC = aForFrame->StyleContext();
1157 // Return true unless the frame is for a BODY element whose background
1158 // was propagated to the viewport.
1160 nsIContent* content = aForFrame->GetContent();
1161 if (!content || content->Tag() != nsGkAtoms::body)
1162 return true; // not frame for a "body" element
1163 // It could be a non-HTML "body" element but that's OK, we'd fail the
1164 // bodyContent check below
1166 if (aForFrame->StyleContext()->GetPseudo())
1167 return true; // A pseudo-element frame.
1169 // We should only look at the <html> background if we're in an HTML document
1170 nsIDocument* document = content->OwnerDoc();
1172 dom::Element* bodyContent = document->GetBodyElement();
1173 if (bodyContent != content)
1174 return true; // this wasn't the background that was propagated
1176 // This can be called even when there's no root element yet, during frame
1177 // construction, via nsLayoutUtils::FrameHasTransparency and
1178 // nsContainerFrame::SyncFrameViewProperties.
1179 if (!aRootElementFrame)
1180 return true;
1182 const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground();
1183 return !htmlBG->IsTransparent();
1186 bool
1187 nsCSSRendering::FindBackground(nsIFrame* aForFrame,
1188 nsStyleContext** aBackgroundSC)
1190 nsIFrame* rootElementFrame =
1191 aForFrame->PresContext()->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
1192 if (IsCanvasFrame(aForFrame)) {
1193 *aBackgroundSC = FindCanvasBackground(aForFrame, rootElementFrame);
1194 return true;
1195 } else {
1196 return FindElementBackground(aForFrame, rootElementFrame, aBackgroundSC);
1200 void
1201 nsCSSRendering::BeginFrameTreesLocked()
1203 ++gFrameTreeLockCount;
1206 void
1207 nsCSSRendering::EndFrameTreesLocked()
1209 NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked");
1210 --gFrameTreeLockCount;
1211 if (gFrameTreeLockCount == 0) {
1212 gInlineBGData->Reset();
1216 void
1217 nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext,
1218 nsRenderingContext& aRenderingContext,
1219 nsIFrame* aForFrame,
1220 const nsRect& aFrameArea,
1221 const nsRect& aDirtyRect,
1222 float aOpacity)
1224 DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
1225 const nsStyleBorder* styleBorder = aForFrame->StyleBorder();
1226 nsCSSShadowArray* shadows = styleBorder->mBoxShadow;
1227 if (!shadows)
1228 return;
1230 gfxContextAutoSaveRestore gfxStateRestorer;
1231 bool hasBorderRadius;
1232 bool nativeTheme; // mutually exclusive with hasBorderRadius
1233 const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay();
1234 nsITheme::Transparency transparency;
1235 if (aForFrame->IsThemed(styleDisplay, &transparency)) {
1236 // We don't respect border-radius for native-themed widgets
1237 hasBorderRadius = false;
1238 // For opaque (rectangular) theme widgets we can take the generic
1239 // border-box path with border-radius disabled.
1240 nativeTheme = transparency != nsITheme::eOpaque;
1241 } else {
1242 nativeTheme = false;
1243 hasBorderRadius = true; // we'll update this below
1246 nsRect frameRect = nativeTheme ?
1247 aForFrame->GetVisualOverflowRectRelativeToSelf() + aFrameArea.TopLeft() :
1248 aFrameArea;
1249 Sides skipSides = aForFrame->GetSkipSides();
1250 frameRect = ::BoxDecorationRectForBorder(aForFrame, frameRect, skipSides);
1252 // Get any border radius, since box-shadow must also have rounded corners if
1253 // the frame does.
1254 RectCornerRadii borderRadii;
1255 const nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
1256 if (hasBorderRadius) {
1257 nscoord twipsRadii[8];
1258 NS_ASSERTION(aFrameArea.Size() == aForFrame->VisualBorderRectRelativeToSelf().Size(),
1259 "unexpected size");
1260 nsSize sz = frameRect.Size();
1261 hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
1262 if (hasBorderRadius) {
1263 ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii);
1267 Rect frameGfxRect = NSRectToRect(frameRect, twipsPerPixel);
1268 frameGfxRect.Round();
1270 // We don't show anything that intersects with the frame we're blurring on. So tell the
1271 // blurrer not to do unnecessary work there.
1272 gfxRect skipGfxRect = ThebesRect(frameGfxRect);
1273 bool useSkipGfxRect = true;
1274 if (nativeTheme) {
1275 // Optimize non-leaf native-themed frames by skipping computing pixels
1276 // in the padding-box. We assume the padding-box is going to be painted
1277 // opaquely for non-leaf frames.
1278 // XXX this may not be a safe assumption; we should make this go away
1279 // by optimizing box-shadow drawing more for the cases where we don't have a skip-rect.
1280 useSkipGfxRect = !aForFrame->IsLeaf();
1281 nsRect paddingRect =
1282 aForFrame->GetPaddingRect() - aForFrame->GetPosition() + aFrameArea.TopLeft();
1283 skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, twipsPerPixel);
1284 } else if (hasBorderRadius) {
1285 skipGfxRect.Deflate(gfxMargin(
1286 std::max(borderRadii[C_TL].height, borderRadii[C_TR].height), 0,
1287 std::max(borderRadii[C_BL].height, borderRadii[C_BR].height), 0));
1290 gfxContext* renderContext = aRenderingContext.ThebesContext();
1292 for (uint32_t i = shadows->Length(); i > 0; --i) {
1293 nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1);
1294 if (shadowItem->mInset)
1295 continue;
1297 nsRect shadowRect = frameRect;
1298 shadowRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset);
1299 if (!nativeTheme) {
1300 shadowRect.Inflate(shadowItem->mSpread, shadowItem->mSpread);
1303 // shadowRect won't include the blur, so make an extra rect here that includes the blur
1304 // for use in the even-odd rule below.
1305 nsRect shadowRectPlusBlur = shadowRect;
1306 nscoord blurRadius = shadowItem->mRadius;
1307 shadowRectPlusBlur.Inflate(
1308 nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, twipsPerPixel));
1310 Rect shadowGfxRectPlusBlur =
1311 NSRectToRect(shadowRectPlusBlur, twipsPerPixel);
1312 shadowGfxRectPlusBlur.RoundOut();
1313 MaybeSnapToDevicePixels(shadowGfxRectPlusBlur, aDrawTarget, true);
1315 // Set the shadow color; if not specified, use the foreground color
1316 nscolor shadowColor;
1317 if (shadowItem->mHasColor)
1318 shadowColor = shadowItem->mColor;
1319 else
1320 shadowColor = aForFrame->StyleColor()->mColor;
1322 gfxRGBA gfxShadowColor(shadowColor);
1323 gfxShadowColor.a *= aOpacity;
1325 if (nativeTheme) {
1326 nsContextBoxBlur blurringArea;
1328 // When getting the widget shape from the native theme, we're going
1329 // to draw the widget into the shadow surface to create a mask.
1330 // We need to ensure that there actually *is* a shadow surface
1331 // and that we're not going to draw directly into renderContext.
1332 gfxContext* shadowContext =
1333 blurringArea.Init(shadowRect, shadowItem->mSpread,
1334 blurRadius, twipsPerPixel, renderContext, aDirtyRect,
1335 useSkipGfxRect ? &skipGfxRect : nullptr,
1336 nsContextBoxBlur::FORCE_MASK);
1337 if (!shadowContext)
1338 continue;
1340 MOZ_ASSERT(shadowContext == blurringArea.GetContext());
1342 renderContext->Save();
1343 renderContext->SetColor(gfxShadowColor);
1345 // Draw the shape of the frame so it can be blurred. Recall how nsContextBoxBlur
1346 // doesn't make any temporary surfaces if blur is 0 and it just returns the original
1347 // surface? If we have no blur, we're painting this fill on the actual content surface
1348 // (renderContext == shadowContext) which is why we set up the color and clip
1349 // before doing this.
1351 // We don't clip the border-box from the shadow, nor any other box.
1352 // We assume that the native theme is going to paint over the shadow.
1354 // Draw the widget shape
1355 gfxContextMatrixAutoSaveRestore save(shadowContext);
1356 gfxPoint devPixelOffset =
1357 nsLayoutUtils::PointToGfxPoint(nsPoint(shadowItem->mXOffset,
1358 shadowItem->mYOffset),
1359 aPresContext->AppUnitsPerDevPixel());
1360 shadowContext->SetMatrix(
1361 shadowContext->CurrentMatrix().Translate(devPixelOffset));
1363 nsRect nativeRect;
1364 nativeRect.IntersectRect(frameRect, aDirtyRect);
1365 nsRenderingContext wrapperCtx(shadowContext);
1366 aPresContext->GetTheme()->DrawWidgetBackground(&wrapperCtx, aForFrame,
1367 styleDisplay->mAppearance, aFrameArea, nativeRect);
1369 blurringArea.DoPaint();
1370 renderContext->Restore();
1371 } else {
1372 renderContext->Save();
1375 // Clip out the interior of the frame's border edge so that the shadow
1376 // is only painted outside that area.
1377 RefPtr<PathBuilder> builder =
1378 aDrawTarget.CreatePathBuilder(FillRule::FILL_EVEN_ODD);
1379 AppendRectToPath(builder, shadowGfxRectPlusBlur);
1380 if (hasBorderRadius) {
1381 AppendRoundedRectToPath(builder, frameGfxRect, borderRadii);
1382 } else {
1383 AppendRectToPath(builder, frameGfxRect);
1385 RefPtr<Path> path = builder->Finish();
1386 renderContext->Clip(path);
1389 // Clip the shadow so that we only get the part that applies to aForFrame.
1390 nsRect fragmentClip = shadowRectPlusBlur;
1391 if (!skipSides.IsEmpty()) {
1392 if (skipSides.Left()) {
1393 nscoord xmost = fragmentClip.XMost();
1394 fragmentClip.x = aFrameArea.x;
1395 fragmentClip.width = xmost - fragmentClip.x;
1397 if (skipSides.Right()) {
1398 nscoord xmost = fragmentClip.XMost();
1399 nscoord overflow = xmost - aFrameArea.XMost();
1400 if (overflow > 0) {
1401 fragmentClip.width -= overflow;
1404 if (skipSides.Top()) {
1405 nscoord ymost = fragmentClip.YMost();
1406 fragmentClip.y = aFrameArea.y;
1407 fragmentClip.height = ymost - fragmentClip.y;
1409 if (skipSides.Bottom()) {
1410 nscoord ymost = fragmentClip.YMost();
1411 nscoord overflow = ymost - aFrameArea.YMost();
1412 if (overflow > 0) {
1413 fragmentClip.height -= overflow;
1417 renderContext->
1418 Clip(NSRectToSnappedRect(fragmentClip,
1419 aForFrame->PresContext()->AppUnitsPerDevPixel(),
1420 aDrawTarget));
1422 RectCornerRadii clipRectRadii;
1423 if (hasBorderRadius) {
1424 Float spreadDistance = shadowItem->mSpread / twipsPerPixel;
1426 Float borderSizes[4];
1428 borderSizes[NS_SIDE_LEFT] = spreadDistance;
1429 borderSizes[NS_SIDE_TOP] = spreadDistance;
1430 borderSizes[NS_SIDE_RIGHT] = spreadDistance;
1431 borderSizes[NS_SIDE_BOTTOM] = spreadDistance;
1433 nsCSSBorderRenderer::ComputeOuterRadii(borderRadii, borderSizes,
1434 &clipRectRadii);
1437 nsContextBoxBlur::BlurRectangle(renderContext,
1438 shadowRect,
1439 twipsPerPixel,
1440 hasBorderRadius ? &clipRectRadii : nullptr,
1441 blurRadius,
1442 gfxShadowColor,
1443 aDirtyRect,
1444 skipGfxRect);
1445 renderContext->Restore();
1451 void
1452 nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext,
1453 nsRenderingContext& aRenderingContext,
1454 nsIFrame* aForFrame,
1455 const nsRect& aFrameArea,
1456 const nsRect& aDirtyRect)
1458 const nsStyleBorder* styleBorder = aForFrame->StyleBorder();
1459 nsCSSShadowArray* shadows = styleBorder->mBoxShadow;
1460 if (!shadows)
1461 return;
1462 if (aForFrame->IsThemed() && aForFrame->GetContent() &&
1463 !nsContentUtils::IsChromeDoc(aForFrame->GetContent()->GetCurrentDoc())) {
1464 // There's no way of getting hold of a shape corresponding to a
1465 // "padding-box" for native-themed widgets, so just don't draw
1466 // inner box-shadows for them. But we allow chrome to paint inner
1467 // box shadows since chrome can be aware of the platform theme.
1468 return;
1471 NS_ASSERTION(aForFrame->GetType() == nsGkAtoms::fieldSetFrame ||
1472 aFrameArea.Size() == aForFrame->GetSize(), "unexpected size");
1474 Sides skipSides = aForFrame->GetSkipSides();
1475 nsRect frameRect =
1476 ::BoxDecorationRectForBorder(aForFrame, aFrameArea, skipSides);
1477 nsRect paddingRect = frameRect;
1478 nsMargin border = aForFrame->GetUsedBorder();
1479 paddingRect.Deflate(border);
1481 // Get any border radius, since box-shadow must also have rounded corners
1482 // if the frame does.
1483 nscoord twipsRadii[8];
1484 nsSize sz = frameRect.Size();
1485 bool hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
1486 const nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
1488 RectCornerRadii innerRadii;
1489 if (hasBorderRadius) {
1490 RectCornerRadii borderRadii;
1492 ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii);
1493 Float borderSizes[4] = {
1494 Float(border.top / twipsPerPixel),
1495 Float(border.right / twipsPerPixel),
1496 Float(border.bottom / twipsPerPixel),
1497 Float(border.left / twipsPerPixel)
1499 nsCSSBorderRenderer::ComputeInnerRadii(borderRadii, borderSizes,
1500 &innerRadii);
1503 for (uint32_t i = shadows->Length(); i > 0; --i) {
1504 nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1);
1505 if (!shadowItem->mInset)
1506 continue;
1508 // shadowPaintRect: the area to paint on the temp surface
1509 // shadowClipRect: the area on the temporary surface within shadowPaintRect
1510 // that we will NOT paint in
1511 nscoord blurRadius = shadowItem->mRadius;
1512 nsMargin blurMargin =
1513 nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, twipsPerPixel);
1514 nsRect shadowPaintRect = paddingRect;
1515 shadowPaintRect.Inflate(blurMargin);
1517 nsRect shadowClipRect = paddingRect;
1518 shadowClipRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset);
1519 shadowClipRect.Deflate(shadowItem->mSpread, shadowItem->mSpread);
1521 RectCornerRadii clipRectRadii;
1522 if (hasBorderRadius) {
1523 // Calculate the radii the inner clipping rect will have
1524 Float spreadDistance = shadowItem->mSpread / twipsPerPixel;
1525 Float borderSizes[4] = {0, 0, 0, 0};
1527 // See PaintBoxShadowOuter and bug 514670
1528 if (innerRadii[C_TL].width > 0 || innerRadii[C_BL].width > 0) {
1529 borderSizes[NS_SIDE_LEFT] = spreadDistance;
1532 if (innerRadii[C_TL].height > 0 || innerRadii[C_TR].height > 0) {
1533 borderSizes[NS_SIDE_TOP] = spreadDistance;
1536 if (innerRadii[C_TR].width > 0 || innerRadii[C_BR].width > 0) {
1537 borderSizes[NS_SIDE_RIGHT] = spreadDistance;
1540 if (innerRadii[C_BL].height > 0 || innerRadii[C_BR].height > 0) {
1541 borderSizes[NS_SIDE_BOTTOM] = spreadDistance;
1544 nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes,
1545 &clipRectRadii);
1548 // Set the "skip rect" to the area within the frame that we don't paint in,
1549 // including after blurring.
1550 nsRect skipRect = shadowClipRect;
1551 skipRect.Deflate(blurMargin);
1552 gfxRect skipGfxRect = nsLayoutUtils::RectToGfxRect(skipRect, twipsPerPixel);
1553 if (hasBorderRadius) {
1554 skipGfxRect.Deflate(gfxMargin(
1555 std::max(clipRectRadii[C_TL].height, clipRectRadii[C_TR].height), 0,
1556 std::max(clipRectRadii[C_BL].height, clipRectRadii[C_BR].height), 0));
1559 // When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area
1560 // unchanged. And by construction the gfxSkipRect is not touched by the
1561 // rendered shadow (even after blurring), so those pixels must be completely
1562 // transparent in the shadow, so drawing them changes nothing.
1563 gfxContext* renderContext = aRenderingContext.ThebesContext();
1564 DrawTarget* drawTarget = renderContext->GetDrawTarget();
1565 nsContextBoxBlur blurringArea;
1566 gfxContext* shadowContext =
1567 blurringArea.Init(shadowPaintRect, 0, blurRadius, twipsPerPixel,
1568 renderContext, aDirtyRect, &skipGfxRect);
1569 if (!shadowContext)
1570 continue;
1571 DrawTarget* shadowDT = shadowContext->GetDrawTarget();
1573 // shadowContext is owned by either blurringArea or aRenderingContext.
1574 MOZ_ASSERT(shadowContext == renderContext ||
1575 shadowContext == blurringArea.GetContext());
1577 // Set the shadow color; if not specified, use the foreground color
1578 Color shadowColor = Color::FromABGR(shadowItem->mHasColor ?
1579 shadowItem->mColor :
1580 aForFrame->StyleColor()->mColor);
1581 renderContext->Save();
1582 renderContext->SetColor(ThebesColor(shadowColor));
1584 // Clip the context to the area of the frame's padding rect, so no part of the
1585 // shadow is painted outside. Also cut out anything beyond where the inset shadow
1586 // will be.
1587 Rect shadowGfxRect = NSRectToRect(paddingRect, twipsPerPixel);
1588 shadowGfxRect.Round();
1589 if (hasBorderRadius) {
1590 RefPtr<Path> roundedRect =
1591 MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii);
1592 renderContext->Clip(roundedRect);
1593 } else {
1594 renderContext->Clip(shadowGfxRect);
1597 // Fill the surface minus the area within the frame that we should
1598 // not paint in, and blur and apply it.
1599 Rect shadowPaintGfxRect = NSRectToRect(shadowPaintRect, twipsPerPixel);
1600 shadowPaintGfxRect.RoundOut();
1601 Rect shadowClipGfxRect = NSRectToRect(shadowClipRect, twipsPerPixel);
1602 shadowClipGfxRect.Round();
1603 RefPtr<PathBuilder> builder =
1604 shadowDT->CreatePathBuilder(FillRule::FILL_EVEN_ODD);
1605 AppendRectToPath(builder, shadowPaintGfxRect, true);
1606 if (hasBorderRadius) {
1607 AppendRoundedRectToPath(builder, shadowClipGfxRect, clipRectRadii, false);
1608 } else {
1609 AppendRectToPath(builder, shadowClipGfxRect, false);
1611 RefPtr<Path> path = builder->Finish();
1612 shadowContext->SetPath(path);
1613 shadowContext->Fill();
1614 shadowContext->NewPath();
1616 blurringArea.DoPaint();
1617 renderContext->Restore();
1621 DrawResult
1622 nsCSSRendering::PaintBackground(nsPresContext* aPresContext,
1623 nsRenderingContext& aRenderingContext,
1624 nsIFrame* aForFrame,
1625 const nsRect& aDirtyRect,
1626 const nsRect& aBorderArea,
1627 uint32_t aFlags,
1628 nsRect* aBGClipRect,
1629 int32_t aLayer)
1631 PROFILER_LABEL("nsCSSRendering", "PaintBackground",
1632 js::ProfileEntry::Category::GRAPHICS);
1634 NS_PRECONDITION(aForFrame,
1635 "Frame is expected to be provided to PaintBackground");
1637 nsStyleContext *sc;
1638 if (!FindBackground(aForFrame, &sc)) {
1639 // We don't want to bail out if moz-appearance is set on a root
1640 // node. If it has a parent content node, bail because it's not
1641 // a root, otherwise keep going in order to let the theme stuff
1642 // draw the background. The canvas really should be drawing the
1643 // bg, but there's no way to hook that up via css.
1644 if (!aForFrame->StyleDisplay()->mAppearance) {
1645 return DrawResult::SUCCESS;
1648 nsIContent* content = aForFrame->GetContent();
1649 if (!content || content->GetParent()) {
1650 return DrawResult::SUCCESS;
1653 sc = aForFrame->StyleContext();
1656 return PaintBackgroundWithSC(aPresContext, aRenderingContext, aForFrame,
1657 aDirtyRect, aBorderArea, sc,
1658 *aForFrame->StyleBorder(), aFlags,
1659 aBGClipRect, aLayer);
1662 static bool
1663 IsOpaqueBorderEdge(const nsStyleBorder& aBorder, mozilla::css::Side aSide)
1665 if (aBorder.GetComputedBorder().Side(aSide) == 0)
1666 return true;
1667 switch (aBorder.GetBorderStyle(aSide)) {
1668 case NS_STYLE_BORDER_STYLE_SOLID:
1669 case NS_STYLE_BORDER_STYLE_GROOVE:
1670 case NS_STYLE_BORDER_STYLE_RIDGE:
1671 case NS_STYLE_BORDER_STYLE_INSET:
1672 case NS_STYLE_BORDER_STYLE_OUTSET:
1673 break;
1674 default:
1675 return false;
1678 // If we're using a border image, assume it's not fully opaque,
1679 // because we may not even have the image loaded at this point, and
1680 // even if we did, checking whether the relevant tile is fully
1681 // opaque would be too much work.
1682 if (aBorder.mBorderImageSource.GetType() != eStyleImageType_Null)
1683 return false;
1685 nscolor color;
1686 bool isForeground;
1687 aBorder.GetBorderColor(aSide, color, isForeground);
1689 // We don't know the foreground color here, so if it's being used
1690 // we must assume it might be transparent.
1691 if (isForeground)
1692 return false;
1694 return NS_GET_A(color) == 255;
1698 * Returns true if all border edges are either missing or opaque.
1700 static bool
1701 IsOpaqueBorder(const nsStyleBorder& aBorder)
1703 if (aBorder.mBorderColors)
1704 return false;
1705 NS_FOR_CSS_SIDES(i) {
1706 if (!IsOpaqueBorderEdge(aBorder, i))
1707 return false;
1709 return true;
1712 static inline void
1713 SetupDirtyRects(const nsRect& aBGClipArea, const nsRect& aCallerDirtyRect,
1714 nscoord aAppUnitsPerPixel,
1715 /* OUT: */
1716 nsRect* aDirtyRect, gfxRect* aDirtyRectGfx)
1718 aDirtyRect->IntersectRect(aBGClipArea, aCallerDirtyRect);
1720 // Compute the Thebes equivalent of the dirtyRect.
1721 *aDirtyRectGfx = nsLayoutUtils::RectToGfxRect(*aDirtyRect, aAppUnitsPerPixel);
1722 NS_WARN_IF_FALSE(aDirtyRect->IsEmpty() || !aDirtyRectGfx->IsEmpty(),
1723 "converted dirty rect should not be empty");
1724 NS_ABORT_IF_FALSE(!aDirtyRect->IsEmpty() || aDirtyRectGfx->IsEmpty(),
1725 "second should be empty if first is");
1728 /* static */ void
1729 nsCSSRendering::GetBackgroundClip(const nsStyleBackground::Layer& aLayer,
1730 nsIFrame* aForFrame, const nsStyleBorder& aBorder,
1731 const nsRect& aBorderArea, const nsRect& aCallerDirtyRect,
1732 bool aWillPaintBorder, nscoord aAppUnitsPerPixel,
1733 /* out */ BackgroundClipState* aClipState)
1735 // Compute the outermost boundary of the area that might be painted.
1736 // Same coordinate space as aBorderArea.
1737 Sides skipSides = aForFrame->GetSkipSides();
1738 nsRect clipBorderArea =
1739 ::BoxDecorationRectForBorder(aForFrame, aBorderArea, skipSides, &aBorder);
1741 bool haveRoundedCorners = GetRadii(aForFrame, aBorder, aBorderArea,
1742 clipBorderArea, aClipState->mRadii);
1744 uint8_t backgroundClip = aLayer.mClip;
1746 bool isSolidBorder =
1747 aWillPaintBorder && IsOpaqueBorder(aBorder);
1748 if (isSolidBorder && backgroundClip == NS_STYLE_BG_CLIP_BORDER) {
1749 // If we have rounded corners, we need to inflate the background
1750 // drawing area a bit to avoid seams between the border and
1751 // background.
1752 backgroundClip = haveRoundedCorners ?
1753 NS_STYLE_BG_CLIP_MOZ_ALMOST_PADDING : NS_STYLE_BG_CLIP_PADDING;
1756 aClipState->mBGClipArea = clipBorderArea;
1757 aClipState->mHasAdditionalBGClipArea = false;
1758 aClipState->mCustomClip = false;
1760 if (aForFrame->GetType() == nsGkAtoms::scrollFrame &&
1761 NS_STYLE_BG_ATTACHMENT_LOCAL == aLayer.mAttachment) {
1762 // As of this writing, this is still in discussion in the CSS Working Group
1763 // http://lists.w3.org/Archives/Public/www-style/2013Jul/0250.html
1765 // The rectangle for 'background-clip' scrolls with the content,
1766 // but the background is also clipped at a non-scrolling 'padding-box'
1767 // like the content. (See below.)
1768 // Therefore, only 'content-box' makes a difference here.
1769 if (backgroundClip == NS_STYLE_BG_CLIP_CONTENT) {
1770 nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
1771 // Clip at a rectangle attached to the scrolled content.
1772 aClipState->mHasAdditionalBGClipArea = true;
1773 aClipState->mAdditionalBGClipArea = nsRect(
1774 aClipState->mBGClipArea.TopLeft()
1775 + scrollableFrame->GetScrolledFrame()->GetPosition()
1776 // For the dir=rtl case:
1777 + scrollableFrame->GetScrollRange().TopLeft(),
1778 scrollableFrame->GetScrolledRect().Size());
1779 nsMargin padding = aForFrame->GetUsedPadding();
1780 // padding-bottom is ignored on scrollable frames:
1781 // https://bugzilla.mozilla.org/show_bug.cgi?id=748518
1782 padding.bottom = 0;
1783 padding.ApplySkipSides(skipSides);
1784 aClipState->mAdditionalBGClipArea.Deflate(padding);
1787 // Also clip at a non-scrolling, rounded-corner 'padding-box',
1788 // same as the scrolled content because of the 'overflow' property.
1789 backgroundClip = NS_STYLE_BG_CLIP_PADDING;
1792 if (backgroundClip != NS_STYLE_BG_CLIP_BORDER) {
1793 nsMargin border = aForFrame->GetUsedBorder();
1794 if (backgroundClip == NS_STYLE_BG_CLIP_MOZ_ALMOST_PADDING) {
1795 // Reduce |border| by 1px (device pixels) on all sides, if
1796 // possible, so that we don't get antialiasing seams between the
1797 // background and border.
1798 border.top = std::max(0, border.top - aAppUnitsPerPixel);
1799 border.right = std::max(0, border.right - aAppUnitsPerPixel);
1800 border.bottom = std::max(0, border.bottom - aAppUnitsPerPixel);
1801 border.left = std::max(0, border.left - aAppUnitsPerPixel);
1802 } else if (backgroundClip != NS_STYLE_BG_CLIP_PADDING) {
1803 NS_ASSERTION(backgroundClip == NS_STYLE_BG_CLIP_CONTENT,
1804 "unexpected background-clip");
1805 border += aForFrame->GetUsedPadding();
1807 border.ApplySkipSides(skipSides);
1808 aClipState->mBGClipArea.Deflate(border);
1810 if (haveRoundedCorners) {
1811 nsIFrame::InsetBorderRadii(aClipState->mRadii, border);
1815 if (haveRoundedCorners) {
1816 auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
1817 nsCSSRendering::ComputePixelRadii(aClipState->mRadii, d2a, &aClipState->mClippedRadii);
1818 aClipState->mHasRoundedCorners = true;
1819 } else {
1820 aClipState->mHasRoundedCorners = false;
1824 if (!haveRoundedCorners && aClipState->mHasAdditionalBGClipArea) {
1825 // Do the intersection here to account for the fast path(?) below.
1826 aClipState->mBGClipArea =
1827 aClipState->mBGClipArea.Intersect(aClipState->mAdditionalBGClipArea);
1828 aClipState->mHasAdditionalBGClipArea = false;
1831 SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, aAppUnitsPerPixel,
1832 &aClipState->mDirtyRect, &aClipState->mDirtyRectGfx);
1835 static void
1836 SetupBackgroundClip(nsCSSRendering::BackgroundClipState& aClipState,
1837 gfxContext *aCtx, nscoord aAppUnitsPerPixel,
1838 gfxContextAutoSaveRestore* aAutoSR)
1840 if (aClipState.mDirtyRectGfx.IsEmpty()) {
1841 // Our caller won't draw anything under this condition, so no need
1842 // to set more up.
1843 return;
1846 if (aClipState.mCustomClip) {
1847 // We don't support custom clips and rounded corners, arguably a bug, but
1848 // table painting seems to depend on it.
1849 return;
1852 DrawTarget* drawTarget = aCtx->GetDrawTarget();
1854 // If we have rounded corners, clip all subsequent drawing to the
1855 // rounded rectangle defined by bgArea and bgRadii (we don't know
1856 // whether the rounded corners intrude on the dirtyRect or not).
1857 // Do not do this if we have a caller-provided clip rect --
1858 // as above with bgArea, arguably a bug, but table painting seems
1859 // to depend on it.
1861 if (aClipState.mHasAdditionalBGClipArea) {
1862 gfxRect bgAreaGfx = nsLayoutUtils::RectToGfxRect(
1863 aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
1864 bgAreaGfx.Round();
1865 bgAreaGfx.Condition();
1867 aAutoSR->EnsureSaved(aCtx);
1868 aCtx->NewPath();
1869 aCtx->Rectangle(bgAreaGfx, true);
1870 aCtx->Clip();
1873 if (aClipState.mHasRoundedCorners) {
1874 Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
1875 bgAreaGfx.Round();
1877 if (bgAreaGfx.IsEmpty()) {
1878 // I think it's become possible to hit this since
1879 // http://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
1880 NS_WARNING("converted background area should not be empty");
1881 // Make our caller not do anything.
1882 aClipState.mDirtyRectGfx.SizeTo(gfxSize(0.0, 0.0));
1883 return;
1886 aAutoSR->EnsureSaved(aCtx);
1888 RefPtr<Path> roundedRect =
1889 MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii);
1890 aCtx->Clip(roundedRect);
1894 static void
1895 DrawBackgroundColor(nsCSSRendering::BackgroundClipState& aClipState,
1896 gfxContext *aCtx, nscoord aAppUnitsPerPixel)
1898 if (aClipState.mDirtyRectGfx.IsEmpty()) {
1899 // Our caller won't draw anything under this condition, so no need
1900 // to set more up.
1901 return;
1904 DrawTarget* drawTarget = aCtx->GetDrawTarget();
1906 // We don't support custom clips and rounded corners, arguably a bug, but
1907 // table painting seems to depend on it.
1908 if (!aClipState.mHasRoundedCorners || aClipState.mCustomClip) {
1909 aCtx->NewPath();
1910 aCtx->Rectangle(aClipState.mDirtyRectGfx, true);
1911 aCtx->Fill();
1912 return;
1915 Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
1916 bgAreaGfx.Round();
1918 if (bgAreaGfx.IsEmpty()) {
1919 // I think it's become possible to hit this since
1920 // http://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
1921 NS_WARNING("converted background area should not be empty");
1922 // Make our caller not do anything.
1923 aClipState.mDirtyRectGfx.SizeTo(gfxSize(0.0, 0.0));
1924 return;
1927 aCtx->Save();
1928 gfxRect dirty = ThebesRect(bgAreaGfx).Intersect(aClipState.mDirtyRectGfx);
1930 aCtx->NewPath();
1931 aCtx->Rectangle(dirty, true);
1932 aCtx->Clip();
1934 if (aClipState.mHasAdditionalBGClipArea) {
1935 gfxRect bgAdditionalAreaGfx = nsLayoutUtils::RectToGfxRect(
1936 aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
1937 bgAdditionalAreaGfx.Round();
1938 bgAdditionalAreaGfx.Condition();
1939 aCtx->NewPath();
1940 aCtx->Rectangle(bgAdditionalAreaGfx, true);
1941 aCtx->Clip();
1944 RefPtr<Path> roundedRect =
1945 MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii);
1946 aCtx->SetPath(roundedRect);
1947 aCtx->Fill();
1948 aCtx->Restore();
1951 nscolor
1952 nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext,
1953 nsStyleContext* aStyleContext,
1954 nsIFrame* aFrame,
1955 bool& aDrawBackgroundImage,
1956 bool& aDrawBackgroundColor)
1958 aDrawBackgroundImage = true;
1959 aDrawBackgroundColor = true;
1961 if (aFrame->HonorPrintBackgroundSettings()) {
1962 aDrawBackgroundImage = aPresContext->GetBackgroundImageDraw();
1963 aDrawBackgroundColor = aPresContext->GetBackgroundColorDraw();
1966 const nsStyleBackground *bg = aStyleContext->StyleBackground();
1967 nscolor bgColor;
1968 if (aDrawBackgroundColor) {
1969 bgColor =
1970 aStyleContext->GetVisitedDependentColor(eCSSProperty_background_color);
1971 if (NS_GET_A(bgColor) == 0) {
1972 aDrawBackgroundColor = false;
1974 } else {
1975 // If GetBackgroundColorDraw() is false, we are still expected to
1976 // draw color in the background of any frame that's not completely
1977 // transparent, but we are expected to use white instead of whatever
1978 // color was specified.
1979 bgColor = NS_RGB(255, 255, 255);
1980 if (aDrawBackgroundImage || !bg->IsTransparent()) {
1981 aDrawBackgroundColor = true;
1982 } else {
1983 bgColor = NS_RGBA(0,0,0,0);
1987 // We can skip painting the background color if a background image is opaque.
1988 if (aDrawBackgroundColor &&
1989 bg->BottomLayer().mRepeat.mXRepeat == NS_STYLE_BG_REPEAT_REPEAT &&
1990 bg->BottomLayer().mRepeat.mYRepeat == NS_STYLE_BG_REPEAT_REPEAT &&
1991 bg->BottomLayer().mImage.IsOpaque() &&
1992 bg->BottomLayer().mBlendMode == NS_STYLE_BLEND_NORMAL) {
1993 aDrawBackgroundColor = false;
1996 return bgColor;
1999 static gfxFloat
2000 ConvertGradientValueToPixels(const nsStyleCoord& aCoord,
2001 gfxFloat aFillLength,
2002 int32_t aAppUnitsPerPixel)
2004 switch (aCoord.GetUnit()) {
2005 case eStyleUnit_Percent:
2006 return aCoord.GetPercentValue() * aFillLength;
2007 case eStyleUnit_Coord:
2008 return NSAppUnitsToFloatPixels(aCoord.GetCoordValue(), aAppUnitsPerPixel);
2009 case eStyleUnit_Calc: {
2010 const nsStyleCoord::Calc *calc = aCoord.GetCalcValue();
2011 return calc->mPercent * aFillLength +
2012 NSAppUnitsToFloatPixels(calc->mLength, aAppUnitsPerPixel);
2014 default:
2015 NS_WARNING("Unexpected coord unit");
2016 return 0;
2020 // Given a box with size aBoxSize and origin (0,0), and an angle aAngle,
2021 // and a starting point for the gradient line aStart, find the endpoint of
2022 // the gradient line --- the intersection of the gradient line with a line
2023 // perpendicular to aAngle that passes through the farthest corner in the
2024 // direction aAngle.
2025 static gfxPoint
2026 ComputeGradientLineEndFromAngle(const gfxPoint& aStart,
2027 double aAngle,
2028 const gfxSize& aBoxSize)
2030 double dx = cos(-aAngle);
2031 double dy = sin(-aAngle);
2032 gfxPoint farthestCorner(dx > 0 ? aBoxSize.width : 0,
2033 dy > 0 ? aBoxSize.height : 0);
2034 gfxPoint delta = farthestCorner - aStart;
2035 double u = delta.x*dy - delta.y*dx;
2036 return farthestCorner + gfxPoint(-u*dy, u*dx);
2039 // Compute the start and end points of the gradient line for a linear gradient.
2040 static void
2041 ComputeLinearGradientLine(nsPresContext* aPresContext,
2042 nsStyleGradient* aGradient,
2043 const gfxSize& aBoxSize,
2044 gfxPoint* aLineStart,
2045 gfxPoint* aLineEnd)
2047 if (aGradient->mBgPosX.GetUnit() == eStyleUnit_None) {
2048 double angle;
2049 if (aGradient->mAngle.IsAngleValue()) {
2050 angle = aGradient->mAngle.GetAngleValueInRadians();
2051 if (!aGradient->mLegacySyntax) {
2052 angle = M_PI_2 - angle;
2054 } else {
2055 angle = -M_PI_2; // defaults to vertical gradient starting from top
2057 gfxPoint center(aBoxSize.width/2, aBoxSize.height/2);
2058 *aLineEnd = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
2059 *aLineStart = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineEnd;
2060 } else if (!aGradient->mLegacySyntax) {
2061 float xSign = aGradient->mBgPosX.GetPercentValue() * 2 - 1;
2062 float ySign = 1 - aGradient->mBgPosY.GetPercentValue() * 2;
2063 double angle = atan2(ySign * aBoxSize.width, xSign * aBoxSize.height);
2064 gfxPoint center(aBoxSize.width/2, aBoxSize.height/2);
2065 *aLineEnd = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
2066 *aLineStart = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineEnd;
2067 } else {
2068 int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
2069 *aLineStart = gfxPoint(
2070 ConvertGradientValueToPixels(aGradient->mBgPosX, aBoxSize.width,
2071 appUnitsPerPixel),
2072 ConvertGradientValueToPixels(aGradient->mBgPosY, aBoxSize.height,
2073 appUnitsPerPixel));
2074 if (aGradient->mAngle.IsAngleValue()) {
2075 MOZ_ASSERT(aGradient->mLegacySyntax);
2076 double angle = aGradient->mAngle.GetAngleValueInRadians();
2077 *aLineEnd = ComputeGradientLineEndFromAngle(*aLineStart, angle, aBoxSize);
2078 } else {
2079 // No angle, the line end is just the reflection of the start point
2080 // through the center of the box
2081 *aLineEnd = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineStart;
2086 // Compute the start and end points of the gradient line for a radial gradient.
2087 // Also returns the horizontal and vertical radii defining the circle or
2088 // ellipse to use.
2089 static void
2090 ComputeRadialGradientLine(nsPresContext* aPresContext,
2091 nsStyleGradient* aGradient,
2092 const gfxSize& aBoxSize,
2093 gfxPoint* aLineStart,
2094 gfxPoint* aLineEnd,
2095 double* aRadiusX,
2096 double* aRadiusY)
2098 if (aGradient->mBgPosX.GetUnit() == eStyleUnit_None) {
2099 // Default line start point is the center of the box
2100 *aLineStart = gfxPoint(aBoxSize.width/2, aBoxSize.height/2);
2101 } else {
2102 int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
2103 *aLineStart = gfxPoint(
2104 ConvertGradientValueToPixels(aGradient->mBgPosX, aBoxSize.width,
2105 appUnitsPerPixel),
2106 ConvertGradientValueToPixels(aGradient->mBgPosY, aBoxSize.height,
2107 appUnitsPerPixel));
2110 // Compute gradient shape: the x and y radii of an ellipse.
2111 double radiusX, radiusY;
2112 double leftDistance = Abs(aLineStart->x);
2113 double rightDistance = Abs(aBoxSize.width - aLineStart->x);
2114 double topDistance = Abs(aLineStart->y);
2115 double bottomDistance = Abs(aBoxSize.height - aLineStart->y);
2116 switch (aGradient->mSize) {
2117 case NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE:
2118 radiusX = std::min(leftDistance, rightDistance);
2119 radiusY = std::min(topDistance, bottomDistance);
2120 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
2121 radiusX = radiusY = std::min(radiusX, radiusY);
2123 break;
2124 case NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER: {
2125 // Compute x and y distances to nearest corner
2126 double offsetX = std::min(leftDistance, rightDistance);
2127 double offsetY = std::min(topDistance, bottomDistance);
2128 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
2129 radiusX = radiusY = NS_hypot(offsetX, offsetY);
2130 } else {
2131 // maintain aspect ratio
2132 radiusX = offsetX*M_SQRT2;
2133 radiusY = offsetY*M_SQRT2;
2135 break;
2137 case NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE:
2138 radiusX = std::max(leftDistance, rightDistance);
2139 radiusY = std::max(topDistance, bottomDistance);
2140 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
2141 radiusX = radiusY = std::max(radiusX, radiusY);
2143 break;
2144 case NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER: {
2145 // Compute x and y distances to nearest corner
2146 double offsetX = std::max(leftDistance, rightDistance);
2147 double offsetY = std::max(topDistance, bottomDistance);
2148 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
2149 radiusX = radiusY = NS_hypot(offsetX, offsetY);
2150 } else {
2151 // maintain aspect ratio
2152 radiusX = offsetX*M_SQRT2;
2153 radiusY = offsetY*M_SQRT2;
2155 break;
2157 case NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE: {
2158 int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
2159 radiusX = ConvertGradientValueToPixels(aGradient->mRadiusX,
2160 aBoxSize.width, appUnitsPerPixel);
2161 radiusY = ConvertGradientValueToPixels(aGradient->mRadiusY,
2162 aBoxSize.height, appUnitsPerPixel);
2163 break;
2165 default:
2166 radiusX = radiusY = 0;
2167 NS_ABORT_IF_FALSE(false, "unknown radial gradient sizing method");
2169 *aRadiusX = radiusX;
2170 *aRadiusY = radiusY;
2172 double angle;
2173 if (aGradient->mAngle.IsAngleValue()) {
2174 angle = aGradient->mAngle.GetAngleValueInRadians();
2175 } else {
2176 // Default angle is 0deg
2177 angle = 0.0;
2180 // The gradient line end point is where the gradient line intersects
2181 // the ellipse.
2182 *aLineEnd = *aLineStart + gfxPoint(radiusX*cos(-angle), radiusY*sin(-angle));
2186 static float Interpolate(float aF1, float aF2, float aFrac)
2188 return aF1 + aFrac * (aF2 - aF1);
2191 // Returns aFrac*aC2 + (1 - aFrac)*C1. The interpolation is done
2192 // in unpremultiplied space, which is what SVG gradients and cairo
2193 // gradients expect.
2194 static gfxRGBA
2195 InterpolateColor(const gfxRGBA& aC1, const gfxRGBA& aC2, double aFrac)
2197 double other = 1 - aFrac;
2198 return gfxRGBA(aC2.r*aFrac + aC1.r*other,
2199 aC2.g*aFrac + aC1.g*other,
2200 aC2.b*aFrac + aC1.b*other,
2201 aC2.a*aFrac + aC1.a*other);
2204 static nscoord
2205 FindTileStart(nscoord aDirtyCoord, nscoord aTilePos, nscoord aTileDim)
2207 NS_ASSERTION(aTileDim > 0, "Non-positive tile dimension");
2208 double multiples = floor(double(aDirtyCoord - aTilePos)/aTileDim);
2209 return NSToCoordRound(multiples*aTileDim + aTilePos);
2212 static gfxFloat
2213 LinearGradientStopPositionForPoint(const gfxPoint& aGradientStart,
2214 const gfxPoint& aGradientEnd,
2215 const gfxPoint& aPoint)
2217 gfxPoint d = aGradientEnd - aGradientStart;
2218 gfxPoint p = aPoint - aGradientStart;
2220 * Compute a parameter t such that a line perpendicular to the
2221 * d vector, passing through aGradientStart + d*t, also
2222 * passes through aPoint.
2224 * t is given by
2225 * (p.x - d.x*t)*d.x + (p.y - d.y*t)*d.y = 0
2227 * Solving for t we get
2228 * numerator = d.x*p.x + d.y*p.y
2229 * denominator = d.x^2 + d.y^2
2230 * t = numerator/denominator
2232 * In nsCSSRendering::PaintGradient we know the length of d
2233 * is not zero.
2235 double numerator = d.x * p.x + d.y * p.y;
2236 double denominator = d.x * d.x + d.y * d.y;
2237 return numerator / denominator;
2240 static bool
2241 RectIsBeyondLinearGradientEdge(const gfxRect& aRect,
2242 const gfxMatrix& aPatternMatrix,
2243 const nsTArray<ColorStop>& aStops,
2244 const gfxPoint& aGradientStart,
2245 const gfxPoint& aGradientEnd,
2246 gfxRGBA* aOutEdgeColor)
2248 gfxFloat topLeft = LinearGradientStopPositionForPoint(
2249 aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.TopLeft()));
2250 gfxFloat topRight = LinearGradientStopPositionForPoint(
2251 aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.TopRight()));
2252 gfxFloat bottomLeft = LinearGradientStopPositionForPoint(
2253 aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.BottomLeft()));
2254 gfxFloat bottomRight = LinearGradientStopPositionForPoint(
2255 aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.BottomRight()));
2257 const ColorStop& firstStop = aStops[0];
2258 if (topLeft < firstStop.mPosition && topRight < firstStop.mPosition &&
2259 bottomLeft < firstStop.mPosition && bottomRight < firstStop.mPosition) {
2260 *aOutEdgeColor = firstStop.mColor;
2261 return true;
2264 const ColorStop& lastStop = aStops.LastElement();
2265 if (topLeft >= lastStop.mPosition && topRight >= lastStop.mPosition &&
2266 bottomLeft >= lastStop.mPosition && bottomRight >= lastStop.mPosition) {
2267 *aOutEdgeColor = lastStop.mColor;
2268 return true;
2271 return false;
2274 static void ResolveMidpoints(nsTArray<ColorStop>& stops)
2276 for (size_t x = 1; x < stops.Length() - 1;) {
2277 if (!stops[x].mIsMidpoint) {
2278 x++;
2279 continue;
2282 gfxRGBA color1 = stops[x-1].mColor;
2283 gfxRGBA color2 = stops[x+1].mColor;
2284 float offset1 = stops[x-1].mPosition;
2285 float offset2 = stops[x+1].mPosition;
2286 float offset = stops[x].mPosition;
2287 // check if everything coincides. If so, ignore the midpoint.
2288 if (offset - offset1 == offset2 - offset) {
2289 stops.RemoveElementAt(x);
2290 continue;
2293 // Check if we coincide with the left colorstop.
2294 if (offset1 == offset) {
2295 // Morph the midpoint to a regular stop with the color of the next
2296 // color stop.
2297 stops[x].mColor = color2;
2298 stops[x].mIsMidpoint = false;
2299 continue;
2302 // Check if we coincide with the right colorstop.
2303 if (offset2 == offset) {
2304 // Morph the midpoint to a regular stop with the color of the previous
2305 // color stop.
2306 stops[x].mColor = color1;
2307 stops[x].mIsMidpoint = false;
2308 continue;
2311 float midpoint = (offset - offset1) / (offset2 - offset1);
2312 ColorStop newStops[9];
2313 if (midpoint > .5f) {
2314 for (size_t y = 0; y < 7; y++) {
2315 newStops[y].mPosition = offset1 + (offset - offset1) * (7 + y) / 13;
2318 newStops[7].mPosition = offset + (offset2 - offset) / 3;
2319 newStops[8].mPosition = offset + (offset2 - offset) * 2 / 3;
2320 } else {
2321 newStops[0].mPosition = offset1 + (offset - offset1) / 3;
2322 newStops[1].mPosition = offset1 + (offset - offset1) * 2 / 3;
2324 for (size_t y = 0; y < 7; y++) {
2325 newStops[y+2].mPosition = offset + (offset2 - offset) * y / 13;
2328 // calculate colors
2330 for (size_t y = 0; y < 9; y++) {
2331 // Calculate the intermediate color stops per the formula of the CSS images
2332 // spec. http://dev.w3.org/csswg/css-images/#color-stop-syntax
2333 // 9 points were chosen since it is the minimum number of stops that always
2334 // give the smoothest appearace regardless of midpoint position and difference
2335 // in luminance of the end points.
2336 float relativeOffset = (newStops[y].mPosition - offset1) / (offset2 - offset1);
2337 float multiplier = powf(relativeOffset, logf(.5f) / logf(midpoint));
2339 gfxFloat red = color1.r + multiplier * (color2.r - color1.r);
2340 gfxFloat green = color1.g + multiplier * (color2.g - color1.g);
2341 gfxFloat blue = color1.b + multiplier * (color2.b - color1.b);
2342 gfxFloat alpha = color1.a + multiplier * (color2.a - color1.a);
2344 newStops[y].mColor = gfxRGBA(red, green, blue, alpha);
2347 stops.ReplaceElementsAt(x, 1, newStops, 9);
2348 x += 9;
2352 static gfxRGBA
2353 Premultiply(const gfxRGBA& aColor)
2355 gfxFloat a = aColor.a;
2356 return gfxRGBA(aColor.r * a, aColor.g * a, aColor.b * a, a);
2359 static gfxRGBA
2360 Unpremultiply(const gfxRGBA& aColor)
2362 gfxFloat a = aColor.a;
2363 return (a > 0.0) ? gfxRGBA(aColor.r / a, aColor.g / a, aColor.b / a, a) : aColor;
2366 static gfxRGBA
2367 TransparentColor(gfxRGBA aColor) {
2368 aColor.a = 0;
2369 return aColor;
2372 // Adjusts and adds color stops in such a way that drawing the gradient with
2373 // unpremultiplied interpolation looks nearly the same as if it were drawn with
2374 // premultiplied interpolation.
2375 static const float kAlphaIncrementPerGradientStep = 0.1f;
2376 static void
2377 ResolvePremultipliedAlpha(nsTArray<ColorStop>& aStops)
2379 for (size_t x = 1; x < aStops.Length(); x++) {
2380 const ColorStop leftStop = aStops[x - 1];
2381 const ColorStop rightStop = aStops[x];
2383 // if the left and right stop have the same alpha value, we don't need
2384 // to do anything
2385 if (leftStop.mColor.a == rightStop.mColor.a) {
2386 continue;
2389 // Is the stop on the left 100% transparent? If so, have it adopt the color
2390 // of the right stop
2391 if (leftStop.mColor.a == 0) {
2392 aStops[x - 1].mColor = TransparentColor(rightStop.mColor);
2393 continue;
2396 // Is the stop on the right completely transparent?
2397 // If so, duplicate it and assign it the color on the left.
2398 if (rightStop.mColor.a == 0) {
2399 ColorStop newStop = rightStop;
2400 newStop.mColor = TransparentColor(leftStop.mColor);
2401 aStops.InsertElementAt(x, newStop);
2402 x++;
2403 continue;
2406 // Now handle cases where one or both of the stops are partially transparent.
2407 if (leftStop.mColor.a != 1.0f || rightStop.mColor.a != 1.0f) {
2408 gfxRGBA premulLeftColor = Premultiply(leftStop.mColor);
2409 gfxRGBA premulRightColor = Premultiply(rightStop.mColor);
2410 // Calculate how many extra steps. We do a step per 10% transparency.
2411 size_t stepCount = NSToIntFloor(fabs(leftStop.mColor.a - rightStop.mColor.a) / kAlphaIncrementPerGradientStep);
2412 for (size_t y = 1; y < stepCount; y++) {
2413 float frac = static_cast<float>(y) / stepCount;
2414 ColorStop newStop(Interpolate(leftStop.mPosition, rightStop.mPosition, frac), false,
2415 Unpremultiply(InterpolateColor(premulLeftColor, premulRightColor, frac)));
2416 aStops.InsertElementAt(x, newStop);
2417 x++;
2423 void
2424 nsCSSRendering::PaintGradient(nsPresContext* aPresContext,
2425 nsRenderingContext& aRenderingContext,
2426 nsStyleGradient* aGradient,
2427 const nsRect& aDirtyRect,
2428 const nsRect& aDest,
2429 const nsRect& aFillArea,
2430 const CSSIntRect& aSrc,
2431 const nsSize& aIntrinsicSize)
2433 PROFILER_LABEL("nsCSSRendering", "PaintGradient",
2434 js::ProfileEntry::Category::GRAPHICS);
2436 Telemetry::AutoTimer<Telemetry::GRADIENT_DURATION, Telemetry::Microsecond> gradientTimer;
2437 if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
2438 return;
2441 gfxContext *ctx = aRenderingContext.ThebesContext();
2442 nscoord appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
2443 gfxSize srcSize = gfxSize(gfxFloat(aIntrinsicSize.width)/appUnitsPerDevPixel,
2444 gfxFloat(aIntrinsicSize.height)/appUnitsPerDevPixel);
2446 bool cellContainsFill = aDest.Contains(aFillArea);
2448 // Compute "gradient line" start and end relative to the intrinsic size of
2449 // the gradient.
2450 gfxPoint lineStart, lineEnd;
2451 double radiusX = 0, radiusY = 0; // for radial gradients only
2452 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
2453 ComputeLinearGradientLine(aPresContext, aGradient, srcSize,
2454 &lineStart, &lineEnd);
2455 } else {
2456 ComputeRadialGradientLine(aPresContext, aGradient, srcSize,
2457 &lineStart, &lineEnd, &radiusX, &radiusY);
2459 gfxFloat lineLength = NS_hypot(lineEnd.x - lineStart.x,
2460 lineEnd.y - lineStart.y);
2462 NS_ABORT_IF_FALSE(aGradient->mStops.Length() >= 2,
2463 "The parser should reject gradients with less than two stops");
2465 // Build color stop array and compute stop positions
2466 nsTArray<ColorStop> stops;
2467 // If there is a run of stops before stop i that did not have specified
2468 // positions, then this is the index of the first stop in that run, otherwise
2469 // it's -1.
2470 int32_t firstUnsetPosition = -1;
2471 for (uint32_t i = 0; i < aGradient->mStops.Length(); ++i) {
2472 const nsStyleGradientStop& stop = aGradient->mStops[i];
2473 double position;
2474 switch (stop.mLocation.GetUnit()) {
2475 case eStyleUnit_None:
2476 if (i == 0) {
2477 // First stop defaults to position 0.0
2478 position = 0.0;
2479 } else if (i == aGradient->mStops.Length() - 1) {
2480 // Last stop defaults to position 1.0
2481 position = 1.0;
2482 } else {
2483 // Other stops with no specified position get their position assigned
2484 // later by interpolation, see below.
2485 // Remeber where the run of stops with no specified position starts,
2486 // if it starts here.
2487 if (firstUnsetPosition < 0) {
2488 firstUnsetPosition = i;
2490 stops.AppendElement(ColorStop(0, stop.mIsInterpolationHint, stop.mColor));
2491 continue;
2493 break;
2494 case eStyleUnit_Percent:
2495 position = stop.mLocation.GetPercentValue();
2496 break;
2497 case eStyleUnit_Coord:
2498 position = lineLength < 1e-6 ? 0.0 :
2499 stop.mLocation.GetCoordValue() / appUnitsPerDevPixel / lineLength;
2500 break;
2501 case eStyleUnit_Calc:
2502 nsStyleCoord::Calc *calc;
2503 calc = stop.mLocation.GetCalcValue();
2504 position = calc->mPercent +
2505 ((lineLength < 1e-6) ? 0.0 :
2506 (NSAppUnitsToFloatPixels(calc->mLength, appUnitsPerDevPixel) / lineLength));
2507 break;
2508 default:
2509 NS_ABORT_IF_FALSE(false, "Unknown stop position type");
2512 if (i > 0) {
2513 // Prevent decreasing stop positions by advancing this position
2514 // to the previous stop position, if necessary
2515 position = std::max(position, stops[i - 1].mPosition);
2517 stops.AppendElement(ColorStop(position, stop.mIsInterpolationHint, stop.mColor));
2518 if (firstUnsetPosition > 0) {
2519 // Interpolate positions for all stops that didn't have a specified position
2520 double p = stops[firstUnsetPosition - 1].mPosition;
2521 double d = (stops[i].mPosition - p)/(i - firstUnsetPosition + 1);
2522 for (uint32_t j = firstUnsetPosition; j < i; ++j) {
2523 p += d;
2524 stops[j].mPosition = p;
2526 firstUnsetPosition = -1;
2530 // Eliminate negative-position stops if the gradient is radial.
2531 double firstStop = stops[0].mPosition;
2532 if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && firstStop < 0.0) {
2533 if (aGradient->mRepeating) {
2534 // Choose an instance of the repeated pattern that gives us all positive
2535 // stop-offsets.
2536 double lastStop = stops[stops.Length() - 1].mPosition;
2537 double stopDelta = lastStop - firstStop;
2538 // If all the stops are in approximately the same place then logic below
2539 // will kick in that makes us draw just the last stop color, so don't
2540 // try to do anything in that case. We certainly need to avoid
2541 // dividing by zero.
2542 if (stopDelta >= 1e-6) {
2543 double instanceCount = ceil(-firstStop/stopDelta);
2544 // Advance stops by instanceCount multiples of the period of the
2545 // repeating gradient.
2546 double offset = instanceCount*stopDelta;
2547 for (uint32_t i = 0; i < stops.Length(); i++) {
2548 stops[i].mPosition += offset;
2551 } else {
2552 // Move negative-position stops to position 0.0. We may also need
2553 // to set the color of the stop to the color the gradient should have
2554 // at the center of the ellipse.
2555 for (uint32_t i = 0; i < stops.Length(); i++) {
2556 double pos = stops[i].mPosition;
2557 if (pos < 0.0) {
2558 stops[i].mPosition = 0.0;
2559 // If this is the last stop, we don't need to adjust the color,
2560 // it will fill the entire area.
2561 if (i < stops.Length() - 1) {
2562 double nextPos = stops[i + 1].mPosition;
2563 // If nextPos is approximately equal to pos, then we don't
2564 // need to adjust the color of this stop because it's
2565 // not going to be displayed.
2566 // If nextPos is negative, we don't need to adjust the color of
2567 // this stop since it's not going to be displayed because
2568 // nextPos will also be moved to 0.0.
2569 if (nextPos >= 0.0 && nextPos - pos >= 1e-6) {
2570 // Compute how far the new position 0.0 is along the interval
2571 // between pos and nextPos.
2572 // XXX Color interpolation (in cairo, too) should use the
2573 // CSS 'color-interpolation' property!
2574 double frac = (0.0 - pos)/(nextPos - pos);
2575 stops[i].mColor =
2576 InterpolateColor(stops[i].mColor, stops[i + 1].mColor, frac);
2582 firstStop = stops[0].mPosition;
2583 NS_ABORT_IF_FALSE(firstStop >= 0.0, "Failed to fix stop offsets");
2586 if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && !aGradient->mRepeating) {
2587 // Direct2D can only handle a particular class of radial gradients because
2588 // of the way the it specifies gradients. Setting firstStop to 0, when we
2589 // can, will help us stay on the fast path. Currently we don't do this
2590 // for repeating gradients but we could by adjusting the stop collection
2591 // to start at 0
2592 firstStop = 0;
2595 double lastStop = stops[stops.Length() - 1].mPosition;
2596 // Cairo gradients must have stop positions in the range [0, 1]. So,
2597 // stop positions will be normalized below by subtracting firstStop and then
2598 // multiplying by stopScale.
2599 double stopScale;
2600 double stopOrigin = firstStop;
2601 double stopEnd = lastStop;
2602 double stopDelta = lastStop - firstStop;
2603 bool zeroRadius = aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR &&
2604 (radiusX < 1e-6 || radiusY < 1e-6);
2605 if (stopDelta < 1e-6 || lineLength < 1e-6 || zeroRadius) {
2606 // Stops are all at the same place. Map all stops to 0.0.
2607 // For repeating radial gradients, or for any radial gradients with
2608 // a zero radius, we need to fill with the last stop color, so just set
2609 // both radii to 0.
2610 if (aGradient->mRepeating || zeroRadius) {
2611 radiusX = radiusY = 0.0;
2613 stopDelta = 0.0;
2614 lastStop = firstStop;
2617 // Don't normalize non-repeating or degenerate gradients below 0..1
2618 // This keeps the gradient line as large as the box and doesn't
2619 // lets us avoiding having to get padding correct for stops
2620 // at 0 and 1
2621 if (!aGradient->mRepeating || stopDelta == 0.0) {
2622 stopOrigin = std::min(stopOrigin, 0.0);
2623 stopEnd = std::max(stopEnd, 1.0);
2625 stopScale = 1.0/(stopEnd - stopOrigin);
2627 // Create the gradient pattern.
2628 nsRefPtr<gfxPattern> gradientPattern;
2629 bool forceRepeatToCoverTiles = false;
2630 gfxMatrix matrix;
2631 gfxPoint gradientStart;
2632 gfxPoint gradientEnd;
2633 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
2634 // Compute the actual gradient line ends we need to pass to cairo after
2635 // stops have been normalized.
2636 gradientStart = lineStart + (lineEnd - lineStart)*stopOrigin;
2637 gradientEnd = lineStart + (lineEnd - lineStart)*stopEnd;
2638 gfxPoint gradientStopStart = lineStart + (lineEnd - lineStart)*firstStop;
2639 gfxPoint gradientStopEnd = lineStart + (lineEnd - lineStart)*lastStop;
2641 if (stopDelta == 0.0) {
2642 // Stops are all at the same place. For repeating gradients, this will
2643 // just paint the last stop color. We don't need to do anything.
2644 // For non-repeating gradients, this should render as two colors, one
2645 // on each "side" of the gradient line segment, which is a point. All
2646 // our stops will be at 0.0; we just need to set the direction vector
2647 // correctly.
2648 gradientEnd = gradientStart + (lineEnd - lineStart);
2649 gradientStopEnd = gradientStopStart + (lineEnd - lineStart);
2652 gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y,
2653 gradientEnd.x, gradientEnd.y);
2655 // When the gradient line is parallel to the x axis from the left edge
2656 // to the right edge of a tile, then we can repeat by just repeating the
2657 // gradient.
2658 if (!cellContainsFill &&
2659 ((gradientStopStart.y == gradientStopEnd.y && gradientStopStart.x == 0 &&
2660 gradientStopEnd.x == srcSize.width) ||
2661 (gradientStopStart.x == gradientStopEnd.x && gradientStopStart.y == 0 &&
2662 gradientStopEnd.y == srcSize.height))) {
2663 forceRepeatToCoverTiles = true;
2665 } else {
2666 NS_ASSERTION(firstStop >= 0.0,
2667 "Negative stops not allowed for radial gradients");
2669 // To form an ellipse, we'll stretch a circle vertically, if necessary.
2670 // So our radii are based on radiusX.
2671 double innerRadius = radiusX*stopOrigin;
2672 double outerRadius = radiusX*stopEnd;
2673 if (stopDelta == 0.0) {
2674 // Stops are all at the same place. See above (except we now have
2675 // the inside vs. outside of an ellipse).
2676 outerRadius = innerRadius + 1;
2678 gradientPattern = new gfxPattern(lineStart.x, lineStart.y, innerRadius,
2679 lineStart.x, lineStart.y, outerRadius);
2680 if (radiusX != radiusY) {
2681 // Stretch the circles into ellipses vertically by setting a transform
2682 // in the pattern.
2683 // Recall that this is the transform from user space to pattern space.
2684 // So to stretch the ellipse by factor of P vertically, we scale
2685 // user coordinates by 1/P.
2686 matrix.Translate(lineStart);
2687 matrix.Scale(1.0, radiusX/radiusY);
2688 matrix.Translate(-lineStart);
2691 // Use a pattern transform to take account of source and dest rects
2692 matrix.Translate(gfxPoint(aPresContext->CSSPixelsToDevPixels(aSrc.x),
2693 aPresContext->CSSPixelsToDevPixels(aSrc.y)));
2694 matrix.Scale(gfxFloat(aPresContext->CSSPixelsToAppUnits(aSrc.width))/aDest.width,
2695 gfxFloat(aPresContext->CSSPixelsToAppUnits(aSrc.height))/aDest.height);
2696 gradientPattern->SetMatrix(matrix);
2698 if (gradientPattern->CairoStatus())
2699 return;
2701 if (stopDelta == 0.0) {
2702 // Non-repeating gradient with all stops in same place -> just add
2703 // first stop and last stop, both at position 0.
2704 // Repeating gradient with all stops in the same place, or radial
2705 // gradient with radius of 0 -> just paint the last stop color.
2706 // We use firstStop offset to keep |stops| with same units (will later normalize to 0).
2707 gfxRGBA firstColor(stops[0].mColor);
2708 gfxRGBA lastColor(stops.LastElement().mColor);
2709 stops.Clear();
2711 if (!aGradient->mRepeating && !zeroRadius) {
2712 stops.AppendElement(ColorStop(firstStop, false, firstColor));
2714 stops.AppendElement(ColorStop(firstStop, false, lastColor));
2717 ResolveMidpoints(stops);
2718 ResolvePremultipliedAlpha(stops);
2720 bool isRepeat = aGradient->mRepeating || forceRepeatToCoverTiles;
2722 // Now set normalized color stops in pattern.
2723 // Offscreen gradient surface cache (not a tile):
2724 // On some backends (e.g. D2D), the GradientStops object holds an offscreen surface
2725 // which is a lookup table used to evaluate the gradient. This surface can use
2726 // much memory (ram and/or GPU ram) and can be expensive to create. So we cache it.
2727 // The cache key correlates 1:1 with the arguments for CreateGradientStops (also the implied backend type)
2728 // Note that GradientStop is a simple struct with a stop value (while GradientStops has the surface).
2729 nsTArray<gfx::GradientStop> rawStops(stops.Length());
2730 rawStops.SetLength(stops.Length());
2731 for(uint32_t i = 0; i < stops.Length(); i++) {
2732 rawStops[i].color = gfx::Color(stops[i].mColor.r, stops[i].mColor.g, stops[i].mColor.b, stops[i].mColor.a);
2733 rawStops[i].offset = stopScale * (stops[i].mPosition - stopOrigin);
2735 mozilla::RefPtr<mozilla::gfx::GradientStops> gs =
2736 gfxGradientCache::GetOrCreateGradientStops(ctx->GetDrawTarget(),
2737 rawStops,
2738 isRepeat ? gfx::ExtendMode::REPEAT : gfx::ExtendMode::CLAMP);
2739 gradientPattern->SetColorStops(gs);
2741 // Paint gradient tiles. This isn't terribly efficient, but doing it this
2742 // way is simple and sure to get pixel-snapping right. We could speed things
2743 // up by drawing tiles into temporary surfaces and copying those to the
2744 // destination, but after pixel-snapping tiles may not all be the same size.
2745 nsRect dirty;
2746 if (!dirty.IntersectRect(aDirtyRect, aFillArea))
2747 return;
2749 gfxRect areaToFill =
2750 nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel);
2751 gfxRect dirtyAreaToFill = nsLayoutUtils::RectToGfxRect(dirty, appUnitsPerDevPixel);
2752 dirtyAreaToFill.RoundOut();
2754 gfxMatrix ctm = ctx->CurrentMatrix();
2755 bool isCTMPreservingAxisAlignedRectangles = ctm.PreservesAxisAlignedRectangles();
2757 // xStart/yStart are the top-left corner of the top-left tile.
2758 nscoord xStart = FindTileStart(dirty.x, aDest.x, aDest.width);
2759 nscoord yStart = FindTileStart(dirty.y, aDest.y, aDest.height);
2760 nscoord xEnd = forceRepeatToCoverTiles ? xStart + aDest.width : dirty.XMost();
2761 nscoord yEnd = forceRepeatToCoverTiles ? yStart + aDest.height : dirty.YMost();
2763 // x and y are the top-left corner of the tile to draw
2764 for (nscoord y = yStart; y < yEnd; y += aDest.height) {
2765 for (nscoord x = xStart; x < xEnd; x += aDest.width) {
2766 // The coordinates of the tile
2767 gfxRect tileRect = nsLayoutUtils::RectToGfxRect(
2768 nsRect(x, y, aDest.width, aDest.height),
2769 appUnitsPerDevPixel);
2770 // The actual area to fill with this tile is the intersection of this
2771 // tile with the overall area we're supposed to be filling
2772 gfxRect fillRect =
2773 forceRepeatToCoverTiles ? areaToFill : tileRect.Intersect(areaToFill);
2774 // Try snapping the fill rect. Snap its top-left and bottom-right
2775 // independently to preserve the orientation.
2776 gfxPoint snappedFillRectTopLeft = fillRect.TopLeft();
2777 gfxPoint snappedFillRectTopRight = fillRect.TopRight();
2778 gfxPoint snappedFillRectBottomRight = fillRect.BottomRight();
2779 // Snap three points instead of just two to ensure we choose the
2780 // correct orientation if there's a reflection.
2781 if (isCTMPreservingAxisAlignedRectangles &&
2782 ctx->UserToDevicePixelSnapped(snappedFillRectTopLeft, true) &&
2783 ctx->UserToDevicePixelSnapped(snappedFillRectBottomRight, true) &&
2784 ctx->UserToDevicePixelSnapped(snappedFillRectTopRight, true)) {
2785 if (snappedFillRectTopLeft.x == snappedFillRectBottomRight.x ||
2786 snappedFillRectTopLeft.y == snappedFillRectBottomRight.y) {
2787 // Nothing to draw; avoid scaling by zero and other weirdness that
2788 // could put the context in an error state.
2789 continue;
2791 // Set the context's transform to the transform that maps fillRect to
2792 // snappedFillRect. The part of the gradient that was going to
2793 // exactly fill fillRect will fill snappedFillRect instead.
2794 gfxMatrix transform = gfxUtils::TransformRectToRect(fillRect,
2795 snappedFillRectTopLeft, snappedFillRectTopRight,
2796 snappedFillRectBottomRight);
2797 ctx->SetMatrix(transform);
2799 ctx->NewPath();
2800 ctx->Rectangle(fillRect);
2802 gfxRect dirtyFillRect = fillRect.Intersect(dirtyAreaToFill);
2803 gfxRect fillRectRelativeToTile = dirtyFillRect - tileRect.TopLeft();
2804 gfxRGBA edgeColor;
2805 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR && !isRepeat &&
2806 RectIsBeyondLinearGradientEdge(fillRectRelativeToTile, matrix, stops,
2807 gradientStart, gradientEnd, &edgeColor)) {
2808 ctx->SetColor(edgeColor);
2809 } else {
2810 ctx->SetMatrix(
2811 ctx->CurrentMatrix().Copy().Translate(tileRect.TopLeft()));
2812 ctx->SetPattern(gradientPattern);
2814 ctx->Fill();
2815 ctx->SetMatrix(ctm);
2820 DrawResult
2821 nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext,
2822 nsRenderingContext& aRenderingContext,
2823 nsIFrame* aForFrame,
2824 const nsRect& aDirtyRect,
2825 const nsRect& aBorderArea,
2826 nsStyleContext* aBackgroundSC,
2827 const nsStyleBorder& aBorder,
2828 uint32_t aFlags,
2829 nsRect* aBGClipRect,
2830 int32_t aLayer)
2832 NS_PRECONDITION(aForFrame,
2833 "Frame is expected to be provided to PaintBackground");
2835 // Initialize our result to success. We update it only if its value is
2836 // currently DrawResult::SUCCESS, which means that as soon as we hit our first
2837 // non-successful draw, we stop updating and will return that value.
2838 DrawResult result = DrawResult::SUCCESS;
2840 // Check to see if we have an appearance defined. If so, we let the theme
2841 // renderer draw the background and bail out.
2842 // XXXzw this ignores aBGClipRect.
2843 const nsStyleDisplay* displayData = aForFrame->StyleDisplay();
2844 if (displayData->mAppearance) {
2845 nsITheme *theme = aPresContext->GetTheme();
2846 if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame,
2847 displayData->mAppearance)) {
2848 nsRect drawing(aBorderArea);
2849 theme->GetWidgetOverflow(aPresContext->DeviceContext(),
2850 aForFrame, displayData->mAppearance, &drawing);
2851 drawing.IntersectRect(drawing, aDirtyRect);
2852 theme->DrawWidgetBackground(&aRenderingContext, aForFrame,
2853 displayData->mAppearance, aBorderArea,
2854 drawing);
2855 return DrawResult::SUCCESS;
2859 // For canvas frames (in the CSS sense) we draw the background color using
2860 // a solid color item that gets added in nsLayoutUtils::PaintFrame,
2861 // or nsSubDocumentFrame::BuildDisplayList (bug 488242). (The solid
2862 // color may be moved into nsDisplayCanvasBackground by
2863 // nsPresShell::AddCanvasBackgroundColorItem, and painted by
2864 // nsDisplayCanvasBackground directly.) Either way we don't need to
2865 // paint the background color here.
2866 bool isCanvasFrame = IsCanvasFrame(aForFrame);
2868 // Determine whether we are drawing background images and/or
2869 // background colors.
2870 bool drawBackgroundImage;
2871 bool drawBackgroundColor;
2873 nscolor bgColor = DetermineBackgroundColor(aPresContext,
2874 aBackgroundSC,
2875 aForFrame,
2876 drawBackgroundImage,
2877 drawBackgroundColor);
2879 // If we're drawing a specific layer, we don't want to draw the
2880 // background color.
2881 const nsStyleBackground *bg = aBackgroundSC->StyleBackground();
2882 if (drawBackgroundColor && aLayer >= 0) {
2883 drawBackgroundColor = false;
2886 // At this point, drawBackgroundImage and drawBackgroundColor are
2887 // true if and only if we are actually supposed to paint an image or
2888 // color into aDirtyRect, respectively.
2889 if (!drawBackgroundImage && !drawBackgroundColor)
2890 return DrawResult::SUCCESS;
2892 // Compute the outermost boundary of the area that might be painted.
2893 // Same coordinate space as aBorderArea & aBGClipRect.
2894 Sides skipSides = aForFrame->GetSkipSides();
2895 nsRect paintBorderArea =
2896 ::BoxDecorationRectForBackground(aForFrame, aBorderArea, skipSides, &aBorder);
2897 nsRect clipBorderArea =
2898 ::BoxDecorationRectForBorder(aForFrame, aBorderArea, skipSides, &aBorder);
2900 // The 'bgClipArea' (used only by the image tiling logic, far below)
2901 // is the caller-provided aBGClipRect if any, or else the area
2902 // determined by the value of 'background-clip' in
2903 // SetupCurrentBackgroundClip. (Arguably it should be the
2904 // intersection, but that breaks the table painter -- in particular,
2905 // taking the intersection breaks reftests/bugs/403249-1[ab].)
2906 gfxContext* ctx = aRenderingContext.ThebesContext();
2907 nscoord appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
2908 BackgroundClipState clipState;
2909 if (aBGClipRect) {
2910 clipState.mBGClipArea = *aBGClipRect;
2911 clipState.mCustomClip = true;
2912 clipState.mHasRoundedCorners = false;
2913 SetupDirtyRects(clipState.mBGClipArea, aDirtyRect, appUnitsPerPixel,
2914 &clipState.mDirtyRect, &clipState.mDirtyRectGfx);
2915 } else {
2916 GetBackgroundClip(bg->BottomLayer(),
2917 aForFrame, aBorder, aBorderArea,
2918 aDirtyRect, (aFlags & PAINTBG_WILL_PAINT_BORDER), appUnitsPerPixel,
2919 &clipState);
2922 // If we might be using a background color, go ahead and set it now.
2923 if (drawBackgroundColor && !isCanvasFrame)
2924 ctx->SetColor(gfxRGBA(bgColor));
2926 // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved(ctx)
2927 // in the cases we need it.
2928 gfxContextAutoSaveRestore autoSR;
2930 // If there is no background image, draw a color. (If there is
2931 // neither a background image nor a color, we wouldn't have gotten
2932 // this far.)
2933 if (!drawBackgroundImage) {
2934 if (!isCanvasFrame) {
2935 DrawBackgroundColor(clipState, ctx, appUnitsPerPixel);
2937 return DrawResult::SUCCESS;
2940 if (bg->mImageCount < 1) {
2941 // Return if there are no background layers, all work from this point
2942 // onwards happens iteratively on these.
2943 return DrawResult::SUCCESS;
2946 // Validate the layer range before we start iterating.
2947 int32_t startLayer = aLayer;
2948 int32_t nLayers = 1;
2949 if (startLayer < 0) {
2950 startLayer = (int32_t)bg->mImageCount - 1;
2951 nLayers = bg->mImageCount;
2954 // Ensure we get invalidated for loads of the image. We need to do
2955 // this here because this might be the only code that knows about the
2956 // association of the style data with the frame.
2957 if (aBackgroundSC != aForFrame->StyleContext()) {
2958 NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT_WITH_RANGE(i, bg, startLayer, nLayers) {
2959 aForFrame->AssociateImage(bg->mLayers[i].mImage, aPresContext);
2963 // The background color is rendered over the entire dirty area,
2964 // even if the image isn't.
2965 if (drawBackgroundColor && !isCanvasFrame) {
2966 DrawBackgroundColor(clipState, ctx, appUnitsPerPixel);
2969 if (drawBackgroundImage) {
2970 bool clipSet = false;
2971 uint8_t currentBackgroundClip = NS_STYLE_BG_CLIP_BORDER;
2972 NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT_WITH_RANGE(i, bg, bg->mImageCount - 1,
2973 nLayers + (bg->mImageCount -
2974 startLayer - 1)) {
2975 const nsStyleBackground::Layer &layer = bg->mLayers[i];
2976 if (!aBGClipRect) {
2977 if (currentBackgroundClip != layer.mClip || !clipSet) {
2978 currentBackgroundClip = layer.mClip;
2979 // If clipSet is false that means this is the bottom layer and we
2980 // already called GetBackgroundClip above and it stored its results
2981 // in clipState.
2982 if (clipSet) {
2983 autoSR.Restore(); // reset the previous one
2984 GetBackgroundClip(layer, aForFrame,
2985 aBorder, aBorderArea, aDirtyRect, (aFlags & PAINTBG_WILL_PAINT_BORDER),
2986 appUnitsPerPixel, &clipState);
2988 SetupBackgroundClip(clipState, ctx, appUnitsPerPixel, &autoSR);
2989 clipSet = true;
2990 if (!clipBorderArea.IsEqualEdges(aBorderArea)) {
2991 // We're drawing the background for the joined continuation boxes
2992 // so we need to clip that to the slice that we want for this frame.
2993 gfxRect clip =
2994 nsLayoutUtils::RectToGfxRect(aBorderArea, appUnitsPerPixel);
2995 autoSR.EnsureSaved(ctx);
2996 ctx->NewPath();
2997 ctx->SnappedRectangle(clip);
2998 ctx->Clip();
3002 if ((aLayer < 0 || i == (uint32_t)startLayer) &&
3003 !clipState.mDirtyRectGfx.IsEmpty()) {
3004 nsBackgroundLayerState state = PrepareBackgroundLayer(aPresContext, aForFrame,
3005 aFlags, paintBorderArea, clipState.mBGClipArea, layer);
3006 if (!state.mFillArea.IsEmpty()) {
3007 if (state.mCompositingOp != gfxContext::OPERATOR_OVER) {
3008 NS_ASSERTION(ctx->CurrentOperator() == gfxContext::OPERATOR_OVER,
3009 "It is assumed the initial operator is OPERATOR_OVER, when it is restored later");
3010 ctx->SetOperator(state.mCompositingOp);
3013 DrawResult resultForLayer =
3014 state.mImageRenderer.DrawBackground(aPresContext, aRenderingContext,
3015 state.mDestArea, state.mFillArea,
3016 state.mAnchor + paintBorderArea.TopLeft(),
3017 clipState.mDirtyRect);
3019 if (result == DrawResult::SUCCESS) {
3020 result = resultForLayer;
3023 if (state.mCompositingOp != gfxContext::OPERATOR_OVER) {
3024 ctx->SetOperator(gfxContext::OPERATOR_OVER);
3031 return result;
3034 static inline bool
3035 IsTransformed(nsIFrame* aForFrame, nsIFrame* aTopFrame)
3037 for (nsIFrame* f = aForFrame; f != aTopFrame; f = f->GetParent()) {
3038 if (f->IsTransformed()) {
3039 return true;
3042 return false;
3045 nsRect
3046 nsCSSRendering::ComputeBackgroundPositioningArea(nsPresContext* aPresContext,
3047 nsIFrame* aForFrame,
3048 const nsRect& aBorderArea,
3049 const nsStyleBackground::Layer& aLayer,
3050 nsIFrame** aAttachedToFrame)
3052 // Compute background origin area relative to aBorderArea now as we may need
3053 // it to compute the effective image size for a CSS gradient.
3054 nsRect bgPositioningArea;
3056 nsIAtom* frameType = aForFrame->GetType();
3057 nsIFrame* geometryFrame = aForFrame;
3058 if (MOZ_UNLIKELY(frameType == nsGkAtoms::scrollFrame &&
3059 NS_STYLE_BG_ATTACHMENT_LOCAL == aLayer.mAttachment)) {
3060 nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
3061 bgPositioningArea = nsRect(
3062 scrollableFrame->GetScrolledFrame()->GetPosition()
3063 // For the dir=rtl case:
3064 + scrollableFrame->GetScrollRange().TopLeft(),
3065 scrollableFrame->GetScrolledRect().Size());
3066 // The ScrolledRect’s size does not include the borders or scrollbars,
3067 // reverse the handling of background-origin
3068 // compared to the common case below.
3069 if (aLayer.mOrigin == NS_STYLE_BG_ORIGIN_BORDER) {
3070 nsMargin border = geometryFrame->GetUsedBorder();
3071 border.ApplySkipSides(geometryFrame->GetSkipSides());
3072 bgPositioningArea.Inflate(border);
3073 bgPositioningArea.Inflate(scrollableFrame->GetActualScrollbarSizes());
3074 } else if (aLayer.mOrigin != NS_STYLE_BG_ORIGIN_PADDING) {
3075 nsMargin padding = geometryFrame->GetUsedPadding();
3076 padding.ApplySkipSides(geometryFrame->GetSkipSides());
3077 bgPositioningArea.Deflate(padding);
3078 NS_ASSERTION(aLayer.mOrigin == NS_STYLE_BG_ORIGIN_CONTENT,
3079 "unknown background-origin value");
3081 *aAttachedToFrame = aForFrame;
3082 return bgPositioningArea;
3085 if (MOZ_UNLIKELY(frameType == nsGkAtoms::canvasFrame)) {
3086 geometryFrame = aForFrame->GetFirstPrincipalChild();
3087 // geometryFrame might be null if this canvas is a page created
3088 // as an overflow container (e.g. the in-flow content has already
3089 // finished and this page only displays the continuations of
3090 // absolutely positioned content).
3091 if (geometryFrame) {
3092 bgPositioningArea = geometryFrame->GetRect();
3094 } else {
3095 bgPositioningArea = nsRect(nsPoint(0,0), aBorderArea.Size());
3098 // Background images are tiled over the 'background-clip' area
3099 // but the origin of the tiling is based on the 'background-origin' area
3100 if (aLayer.mOrigin != NS_STYLE_BG_ORIGIN_BORDER && geometryFrame) {
3101 nsMargin border = geometryFrame->GetUsedBorder();
3102 if (aLayer.mOrigin != NS_STYLE_BG_ORIGIN_PADDING) {
3103 border += geometryFrame->GetUsedPadding();
3104 NS_ASSERTION(aLayer.mOrigin == NS_STYLE_BG_ORIGIN_CONTENT,
3105 "unknown background-origin value");
3107 bgPositioningArea.Deflate(border);
3110 nsIFrame* attachedToFrame = aForFrame;
3111 if (NS_STYLE_BG_ATTACHMENT_FIXED == aLayer.mAttachment) {
3112 // If it's a fixed background attachment, then the image is placed
3113 // relative to the viewport, which is the area of the root frame
3114 // in a screen context or the page content frame in a print context.
3115 attachedToFrame = aPresContext->PresShell()->FrameManager()->GetRootFrame();
3116 NS_ASSERTION(attachedToFrame, "no root frame");
3117 nsIFrame* pageContentFrame = nullptr;
3118 if (aPresContext->IsPaginated()) {
3119 pageContentFrame =
3120 nsLayoutUtils::GetClosestFrameOfType(aForFrame, nsGkAtoms::pageContentFrame);
3121 if (pageContentFrame) {
3122 attachedToFrame = pageContentFrame;
3124 // else this is an embedded shell and its root frame is what we want
3127 // Set the background positioning area to the viewport's area
3128 // (relative to aForFrame)
3129 bgPositioningArea =
3130 nsRect(-aForFrame->GetOffsetTo(attachedToFrame), attachedToFrame->GetSize());
3132 if (!pageContentFrame) {
3133 // Subtract the size of scrollbars.
3134 nsIScrollableFrame* scrollableFrame =
3135 aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
3136 if (scrollableFrame) {
3137 nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes();
3138 bgPositioningArea.Deflate(scrollbars);
3142 *aAttachedToFrame = attachedToFrame;
3144 return bgPositioningArea;
3147 // Apply the CSS image sizing algorithm as it applies to background images.
3148 // See http://www.w3.org/TR/css3-background/#the-background-size .
3149 // aIntrinsicSize is the size that the background image 'would like to be'.
3150 // It can be found by calling nsImageRenderer::ComputeIntrinsicSize.
3151 static nsSize
3152 ComputeDrawnSizeForBackground(const CSSSizeOrRatio& aIntrinsicSize,
3153 const nsSize& aBgPositioningArea,
3154 const nsStyleBackground::Size& aLayerSize)
3156 // Size is dictated by cover or contain rules.
3157 if (aLayerSize.mWidthType == nsStyleBackground::Size::eContain ||
3158 aLayerSize.mWidthType == nsStyleBackground::Size::eCover) {
3159 nsImageRenderer::FitType fitType =
3160 aLayerSize.mWidthType == nsStyleBackground::Size::eCover
3161 ? nsImageRenderer::COVER
3162 : nsImageRenderer::CONTAIN;
3163 return nsImageRenderer::ComputeConstrainedSize(aBgPositioningArea,
3164 aIntrinsicSize.mRatio,
3165 fitType);
3168 // No cover/contain constraint, use default algorithm.
3169 CSSSizeOrRatio specifiedSize;
3170 if (aLayerSize.mWidthType == nsStyleBackground::Size::eLengthPercentage) {
3171 specifiedSize.SetWidth(
3172 aLayerSize.ResolveWidthLengthPercentage(aBgPositioningArea));
3174 if (aLayerSize.mHeightType == nsStyleBackground::Size::eLengthPercentage) {
3175 specifiedSize.SetHeight(
3176 aLayerSize.ResolveHeightLengthPercentage(aBgPositioningArea));
3179 return nsImageRenderer::ComputeConcreteSize(specifiedSize,
3180 aIntrinsicSize,
3181 aBgPositioningArea);
3184 nsBackgroundLayerState
3185 nsCSSRendering::PrepareBackgroundLayer(nsPresContext* aPresContext,
3186 nsIFrame* aForFrame,
3187 uint32_t aFlags,
3188 const nsRect& aBorderArea,
3189 const nsRect& aBGClipRect,
3190 const nsStyleBackground::Layer& aLayer)
3193 * The properties we need to keep in mind when drawing background
3194 * layers are:
3196 * background-image
3197 * background-repeat
3198 * background-attachment
3199 * background-position
3200 * background-clip
3201 * background-origin
3202 * background-size
3203 * background-blend-mode
3204 * box-decoration-break
3206 * (background-color applies to the entire element and not to individual
3207 * layers, so it is irrelevant to this method.)
3209 * These properties have the following dependencies upon each other when
3210 * determining rendering:
3212 * background-image
3213 * no dependencies
3214 * background-repeat
3215 * no dependencies
3216 * background-attachment
3217 * no dependencies
3218 * background-position
3219 * depends upon background-size (for the image's scaled size) and
3220 * background-break (for the background positioning area)
3221 * background-clip
3222 * no dependencies
3223 * background-origin
3224 * depends upon background-attachment (only in the case where that value
3225 * is 'fixed')
3226 * background-size
3227 * depends upon box-decoration-break (for the background positioning area
3228 * for resolving percentages), background-image (for the image's intrinsic
3229 * size), background-repeat (if that value is 'round'), and
3230 * background-origin (for the background painting area, when
3231 * background-repeat is 'round')
3232 * box-decoration-break
3233 * no dependencies
3235 * As a result of only-if dependencies we don't strictly do a topological
3236 * sort of the above properties when processing, but it's pretty close to one:
3238 * background-clip (by caller)
3239 * background-image
3240 * box-decoration-break, background-origin
3241 * background-attachment (postfix for background-origin if 'fixed')
3242 * background-size
3243 * background-position
3244 * background-repeat
3247 uint32_t irFlags = 0;
3248 if (aFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) {
3249 irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
3251 if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) {
3252 irFlags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
3255 nsBackgroundLayerState state(aForFrame, &aLayer.mImage, irFlags);
3256 if (!state.mImageRenderer.PrepareImage()) {
3257 // There's no image or it's not ready to be painted.
3258 return state;
3261 // The frame to which the background is attached
3262 nsIFrame* attachedToFrame = aForFrame;
3263 // Compute background origin area relative to aBorderArea now as we may need
3264 // it to compute the effective image size for a CSS gradient.
3265 nsRect bgPositioningArea =
3266 ComputeBackgroundPositioningArea(aPresContext, aForFrame, aBorderArea,
3267 aLayer, &attachedToFrame);
3269 // For background-attachment:fixed backgrounds, we'll limit the area
3270 // where the background can be drawn to the viewport.
3271 nsRect bgClipRect = aBGClipRect;
3273 // Compute the anchor point.
3275 // relative to aBorderArea.TopLeft() (which is where the top-left
3276 // of aForFrame's border-box will be rendered)
3277 nsPoint imageTopLeft;
3278 if (NS_STYLE_BG_ATTACHMENT_FIXED == aLayer.mAttachment) {
3279 if ((aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) &&
3280 !IsTransformed(aForFrame, attachedToFrame)) {
3281 // Clip background-attachment:fixed backgrounds to the viewport, if we're
3282 // painting to the screen and not transformed. This avoids triggering
3283 // tiling in common cases, without affecting output since drawing is
3284 // always clipped to the viewport when we draw to the screen. (But it's
3285 // not a pure optimization since it can affect the values of pixels at the
3286 // edge of the viewport --- whether they're sampled from a putative "next
3287 // tile" or not.)
3288 bgClipRect.IntersectRect(bgClipRect, bgPositioningArea + aBorderArea.TopLeft());
3292 // Scale the image as specified for background-size and as required for
3293 // proper background positioning when background-position is defined with
3294 // percentages.
3295 CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize();
3296 nsSize bgPositionSize = bgPositioningArea.Size();
3297 nsSize imageSize = ComputeDrawnSizeForBackground(intrinsicSize,
3298 bgPositionSize,
3299 aLayer.mSize);
3300 if (imageSize.width <= 0 || imageSize.height <= 0)
3301 return state;
3303 state.mImageRenderer.SetPreferredSize(intrinsicSize,
3304 imageSize);
3306 // Compute the position of the background now that the background's size is
3307 // determined.
3308 nsImageRenderer::ComputeObjectAnchorPoint(aLayer.mPosition,
3309 bgPositionSize, imageSize,
3310 &imageTopLeft, &state.mAnchor);
3311 imageTopLeft += bgPositioningArea.TopLeft();
3312 state.mAnchor += bgPositioningArea.TopLeft();
3314 state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize);
3315 state.mFillArea = state.mDestArea;
3316 int repeatX = aLayer.mRepeat.mXRepeat;
3317 int repeatY = aLayer.mRepeat.mYRepeat;
3318 if (repeatX == NS_STYLE_BG_REPEAT_REPEAT) {
3319 state.mFillArea.x = bgClipRect.x;
3320 state.mFillArea.width = bgClipRect.width;
3322 if (repeatY == NS_STYLE_BG_REPEAT_REPEAT) {
3323 state.mFillArea.y = bgClipRect.y;
3324 state.mFillArea.height = bgClipRect.height;
3326 state.mFillArea.IntersectRect(state.mFillArea, bgClipRect);
3328 state.mCompositingOp = GetGFXBlendMode(aLayer.mBlendMode);
3330 return state;
3333 nsRect
3334 nsCSSRendering::GetBackgroundLayerRect(nsPresContext* aPresContext,
3335 nsIFrame* aForFrame,
3336 const nsRect& aBorderArea,
3337 const nsRect& aClipRect,
3338 const nsStyleBackground::Layer& aLayer,
3339 uint32_t aFlags)
3341 Sides skipSides = aForFrame->GetSkipSides();
3342 nsRect borderArea =
3343 ::BoxDecorationRectForBackground(aForFrame, aBorderArea, skipSides);
3344 nsBackgroundLayerState state =
3345 PrepareBackgroundLayer(aPresContext, aForFrame, aFlags, borderArea,
3346 aClipRect, aLayer);
3347 return state.mFillArea;
3350 static void
3351 DrawBorderImage(nsPresContext* aPresContext,
3352 nsRenderingContext& aRenderingContext,
3353 nsIFrame* aForFrame,
3354 const nsRect& aBorderArea,
3355 const nsStyleBorder& aStyleBorder,
3356 const nsRect& aDirtyRect,
3357 Sides aSkipSides)
3359 NS_PRECONDITION(aStyleBorder.IsBorderImageLoaded(),
3360 "drawing border image that isn't successfully loaded");
3362 if (aDirtyRect.IsEmpty())
3363 return;
3365 nsImageRenderer renderer(aForFrame, &aStyleBorder.mBorderImageSource, 0);
3367 // Ensure we get invalidated for loads and animations of the image.
3368 // We need to do this here because this might be the only code that
3369 // knows about the association of the style data with the frame.
3370 // XXX We shouldn't really... since if anybody is passing in a
3371 // different style, they'll potentially have the wrong size for the
3372 // border too.
3373 aForFrame->AssociateImage(aStyleBorder.mBorderImageSource, aPresContext);
3375 if (!renderer.PrepareImage()) {
3376 return;
3379 // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved()
3380 // in case we need it.
3381 gfxContextAutoSaveRestore autoSR;
3383 // Determine the border image area, which by default corresponds to the
3384 // border box but can be modified by 'border-image-outset'.
3385 // Note that 'border-radius' do not apply to 'border-image' borders per
3386 // <http://dev.w3.org/csswg/css-backgrounds/#corner-clipping>.
3387 nsRect borderImgArea;
3388 nsMargin borderWidths(aStyleBorder.GetComputedBorder());
3389 nsMargin imageOutset(aStyleBorder.GetImageOutset());
3390 if (::IsBoxDecorationSlice(aStyleBorder) && !aSkipSides.IsEmpty()) {
3391 borderImgArea = ::BoxDecorationRectForBorder(aForFrame, aBorderArea,
3392 aSkipSides, &aStyleBorder);
3393 if (borderImgArea.IsEqualEdges(aBorderArea)) {
3394 // No need for a clip, just skip the sides we don't want.
3395 borderWidths.ApplySkipSides(aSkipSides);
3396 imageOutset.ApplySkipSides(aSkipSides);
3397 borderImgArea.Inflate(imageOutset);
3398 } else {
3399 // We're drawing borders around the joined continuation boxes so we need
3400 // to clip that to the slice that we want for this frame.
3401 borderImgArea.Inflate(imageOutset);
3402 imageOutset.ApplySkipSides(aSkipSides);
3403 nsRect clip = aBorderArea;
3404 clip.Inflate(imageOutset);
3405 autoSR.EnsureSaved(aRenderingContext.ThebesContext());
3406 aRenderingContext.ThebesContext()->
3407 Clip(NSRectToSnappedRect(clip,
3408 aForFrame->PresContext()->AppUnitsPerDevPixel(),
3409 *aRenderingContext.GetDrawTarget()));
3411 } else {
3412 borderImgArea = aBorderArea;
3413 borderImgArea.Inflate(imageOutset);
3416 // Calculate the image size used to compute slice points.
3417 CSSSizeOrRatio intrinsicSize = renderer.ComputeIntrinsicSize();
3418 nsSize imageSize = nsImageRenderer::ComputeConcreteSize(CSSSizeOrRatio(),
3419 intrinsicSize,
3420 borderImgArea.Size());
3421 renderer.SetPreferredSize(intrinsicSize, imageSize);
3423 // Compute the used values of 'border-image-slice' and 'border-image-width';
3424 // we do them together because the latter can depend on the former.
3425 nsMargin slice;
3426 nsMargin border;
3427 NS_FOR_CSS_SIDES(s) {
3428 nsStyleCoord coord = aStyleBorder.mBorderImageSlice.Get(s);
3429 int32_t imgDimension = NS_SIDE_IS_VERTICAL(s)
3430 ? imageSize.width : imageSize.height;
3431 nscoord borderDimension = NS_SIDE_IS_VERTICAL(s)
3432 ? borderImgArea.width : borderImgArea.height;
3433 double value;
3434 switch (coord.GetUnit()) {
3435 case eStyleUnit_Percent:
3436 value = coord.GetPercentValue() * imgDimension;
3437 break;
3438 case eStyleUnit_Factor:
3439 value = nsPresContext::CSSPixelsToAppUnits(
3440 NS_lround(coord.GetFactorValue()));
3441 break;
3442 default:
3443 NS_NOTREACHED("unexpected CSS unit for image slice");
3444 value = 0;
3445 break;
3447 if (value < 0)
3448 value = 0;
3449 if (value > imgDimension)
3450 value = imgDimension;
3451 slice.Side(s) = value;
3453 coord = aStyleBorder.mBorderImageWidth.Get(s);
3454 switch (coord.GetUnit()) {
3455 case eStyleUnit_Coord: // absolute dimension
3456 value = coord.GetCoordValue();
3457 break;
3458 case eStyleUnit_Percent:
3459 value = coord.GetPercentValue() * borderDimension;
3460 break;
3461 case eStyleUnit_Factor:
3462 value = coord.GetFactorValue() * borderWidths.Side(s);
3463 break;
3464 case eStyleUnit_Auto: // same as the slice value, in CSS pixels
3465 value = slice.Side(s);
3466 break;
3467 default:
3468 NS_NOTREACHED("unexpected CSS unit for border image area division");
3469 value = 0;
3470 break;
3472 // NSToCoordRoundWithClamp rounds towards infinity, but that's OK
3473 // because we expect value to be non-negative.
3474 MOZ_ASSERT(value >= 0);
3475 border.Side(s) = NSToCoordRoundWithClamp(value);
3476 MOZ_ASSERT(border.Side(s) >= 0);
3479 // "If two opposite border-image-width offsets are large enough that they
3480 // overlap, their used values are proportionately reduced until they no
3481 // longer overlap."
3482 uint32_t combinedBorderWidth = uint32_t(border.left) +
3483 uint32_t(border.right);
3484 double scaleX = combinedBorderWidth > uint32_t(borderImgArea.width)
3485 ? borderImgArea.width / double(combinedBorderWidth)
3486 : 1.0;
3487 uint32_t combinedBorderHeight = uint32_t(border.top) +
3488 uint32_t(border.bottom);
3489 double scaleY = combinedBorderHeight > uint32_t(borderImgArea.height)
3490 ? borderImgArea.height / double(combinedBorderHeight)
3491 : 1.0;
3492 double scale = std::min(scaleX, scaleY);
3493 if (scale < 1.0) {
3494 border.left *= scale;
3495 border.right *= scale;
3496 border.top *= scale;
3497 border.bottom *= scale;
3498 NS_ASSERTION(border.left + border.right <= borderImgArea.width &&
3499 border.top + border.bottom <= borderImgArea.height,
3500 "rounding error in width reduction???");
3503 // These helper tables recharacterize the 'slice' and 'width' margins
3504 // in a more convenient form: they are the x/y/width/height coords
3505 // required for various bands of the border, and they have been transformed
3506 // to be relative to the innerRect (for 'slice') or the page (for 'border').
3507 enum {
3508 LEFT, MIDDLE, RIGHT,
3509 TOP = LEFT, BOTTOM = RIGHT
3511 const nscoord borderX[3] = {
3512 borderImgArea.x + 0,
3513 borderImgArea.x + border.left,
3514 borderImgArea.x + borderImgArea.width - border.right,
3516 const nscoord borderY[3] = {
3517 borderImgArea.y + 0,
3518 borderImgArea.y + border.top,
3519 borderImgArea.y + borderImgArea.height - border.bottom,
3521 const nscoord borderWidth[3] = {
3522 border.left,
3523 borderImgArea.width - border.left - border.right,
3524 border.right,
3526 const nscoord borderHeight[3] = {
3527 border.top,
3528 borderImgArea.height - border.top - border.bottom,
3529 border.bottom,
3531 const int32_t sliceX[3] = {
3533 slice.left,
3534 imageSize.width - slice.right,
3536 const int32_t sliceY[3] = {
3538 slice.top,
3539 imageSize.height - slice.bottom,
3541 const int32_t sliceWidth[3] = {
3542 slice.left,
3543 std::max(imageSize.width - slice.left - slice.right, 0),
3544 slice.right,
3546 const int32_t sliceHeight[3] = {
3547 slice.top,
3548 std::max(imageSize.height - slice.top - slice.bottom, 0),
3549 slice.bottom,
3552 for (int i = LEFT; i <= RIGHT; i++) {
3553 for (int j = TOP; j <= BOTTOM; j++) {
3554 uint8_t fillStyleH, fillStyleV;
3555 nsSize unitSize;
3557 if (i == MIDDLE && j == MIDDLE) {
3558 // Discard the middle portion unless set to fill.
3559 if (NS_STYLE_BORDER_IMAGE_SLICE_NOFILL ==
3560 aStyleBorder.mBorderImageFill) {
3561 continue;
3564 NS_ASSERTION(NS_STYLE_BORDER_IMAGE_SLICE_FILL ==
3565 aStyleBorder.mBorderImageFill,
3566 "Unexpected border image fill");
3568 // css-background:
3569 // The middle image's width is scaled by the same factor as the
3570 // top image unless that factor is zero or infinity, in which
3571 // case the scaling factor of the bottom is substituted, and
3572 // failing that, the width is not scaled. The height of the
3573 // middle image is scaled by the same factor as the left image
3574 // unless that factor is zero or infinity, in which case the
3575 // scaling factor of the right image is substituted, and failing
3576 // that, the height is not scaled.
3577 gfxFloat hFactor, vFactor;
3579 if (0 < border.left && 0 < slice.left)
3580 vFactor = gfxFloat(border.left)/slice.left;
3581 else if (0 < border.right && 0 < slice.right)
3582 vFactor = gfxFloat(border.right)/slice.right;
3583 else
3584 vFactor = 1;
3586 if (0 < border.top && 0 < slice.top)
3587 hFactor = gfxFloat(border.top)/slice.top;
3588 else if (0 < border.bottom && 0 < slice.bottom)
3589 hFactor = gfxFloat(border.bottom)/slice.bottom;
3590 else
3591 hFactor = 1;
3593 unitSize.width = sliceWidth[i]*hFactor;
3594 unitSize.height = sliceHeight[j]*vFactor;
3595 fillStyleH = aStyleBorder.mBorderImageRepeatH;
3596 fillStyleV = aStyleBorder.mBorderImageRepeatV;
3598 } else if (i == MIDDLE) { // top, bottom
3599 // Sides are always stretched to the thickness of their border,
3600 // and stretched proportionately on the other axis.
3601 gfxFloat factor;
3602 if (0 < borderHeight[j] && 0 < sliceHeight[j])
3603 factor = gfxFloat(borderHeight[j])/sliceHeight[j];
3604 else
3605 factor = 1;
3607 unitSize.width = sliceWidth[i]*factor;
3608 unitSize.height = borderHeight[j];
3609 fillStyleH = aStyleBorder.mBorderImageRepeatH;
3610 fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
3612 } else if (j == MIDDLE) { // left, right
3613 gfxFloat factor;
3614 if (0 < borderWidth[i] && 0 < sliceWidth[i])
3615 factor = gfxFloat(borderWidth[i])/sliceWidth[i];
3616 else
3617 factor = 1;
3619 unitSize.width = borderWidth[i];
3620 unitSize.height = sliceHeight[j]*factor;
3621 fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
3622 fillStyleV = aStyleBorder.mBorderImageRepeatV;
3624 } else {
3625 // Corners are always stretched to fit the corner.
3626 unitSize.width = borderWidth[i];
3627 unitSize.height = borderHeight[j];
3628 fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
3629 fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
3632 nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]);
3633 nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]);
3634 nsIntRect intSubArea = subArea.ToOutsidePixels(nsPresContext::AppUnitsPerCSSPixel());
3636 renderer.DrawBorderImageComponent(aPresContext,
3637 aRenderingContext, aDirtyRect,
3638 destArea, CSSIntRect(intSubArea.x,
3639 intSubArea.y,
3640 intSubArea.width,
3641 intSubArea.height),
3642 fillStyleH, fillStyleV,
3643 unitSize, j * (RIGHT + 1) + i);
3648 // Begin table border-collapsing section
3649 // These functions were written to not disrupt the normal ones and yet satisfy some additional requirements
3650 // At some point, all functions should be unified to include the additional functionality that these provide
3652 static nscoord
3653 RoundIntToPixel(nscoord aValue,
3654 nscoord aTwipsPerPixel,
3655 bool aRoundDown = false)
3657 if (aTwipsPerPixel <= 0)
3658 // We must be rendering to a device that has a resolution greater than Twips!
3659 // In that case, aValue is as accurate as it's going to get.
3660 return aValue;
3662 nscoord halfPixel = NSToCoordRound(aTwipsPerPixel / 2.0f);
3663 nscoord extra = aValue % aTwipsPerPixel;
3664 nscoord finalValue = (!aRoundDown && (extra >= halfPixel)) ? aValue + (aTwipsPerPixel - extra) : aValue - extra;
3665 return finalValue;
3668 static nscoord
3669 RoundFloatToPixel(float aValue,
3670 nscoord aTwipsPerPixel,
3671 bool aRoundDown = false)
3673 return RoundIntToPixel(NSToCoordRound(aValue), aTwipsPerPixel, aRoundDown);
3676 static void SetPoly(const Rect& aRect, Point* poly)
3678 poly[0].x = aRect.x;
3679 poly[0].y = aRect.y;
3680 poly[1].x = aRect.x + aRect.width;
3681 poly[1].y = aRect.y;
3682 poly[2].x = aRect.x + aRect.width;
3683 poly[2].y = aRect.y + aRect.height;
3684 poly[3].x = aRect.x;
3685 poly[3].y = aRect.y + aRect.height;
3688 static void
3689 DrawSolidBorderSegment(nsRenderingContext& aContext,
3690 nsRect aRect,
3691 nscolor aColor,
3692 int32_t aAppUnitsPerDevPixel,
3693 nscoord aTwipsPerPixel,
3694 uint8_t aStartBevelSide = 0,
3695 nscoord aStartBevelOffset = 0,
3696 uint8_t aEndBevelSide = 0,
3697 nscoord aEndBevelOffset = 0)
3699 DrawTarget* drawTarget = aContext.GetDrawTarget();
3701 ColorPattern color(ToDeviceColor(aColor));
3702 DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
3704 if ((aRect.width == aTwipsPerPixel) || (aRect.height == aTwipsPerPixel) ||
3705 ((0 == aStartBevelOffset) && (0 == aEndBevelOffset))) {
3706 // simple line or rectangle
3707 if ((NS_SIDE_TOP == aStartBevelSide) || (NS_SIDE_BOTTOM == aStartBevelSide)) {
3708 if (1 == aRect.height)
3709 StrokeLineWithSnapping(aRect.TopLeft(), aRect.BottomLeft(),
3710 aAppUnitsPerDevPixel, *drawTarget,
3711 color, StrokeOptions(), drawOptions);
3712 else
3713 drawTarget->FillRect(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel,
3714 *drawTarget),
3715 color, drawOptions);
3717 else {
3718 if (1 == aRect.width)
3719 StrokeLineWithSnapping(aRect.TopLeft(), aRect.TopRight(),
3720 aAppUnitsPerDevPixel, *drawTarget,
3721 color, StrokeOptions(), drawOptions);
3722 else
3723 drawTarget->FillRect(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel,
3724 *drawTarget),
3725 color, drawOptions);
3728 else {
3729 // polygon with beveling
3730 Point poly[4];
3731 SetPoly(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, *drawTarget),
3732 poly);
3734 Float startBevelOffset =
3735 NSAppUnitsToFloatPixels(aStartBevelOffset, aAppUnitsPerDevPixel);
3736 switch(aStartBevelSide) {
3737 case NS_SIDE_TOP:
3738 poly[0].x += startBevelOffset;
3739 break;
3740 case NS_SIDE_BOTTOM:
3741 poly[3].x += startBevelOffset;
3742 break;
3743 case NS_SIDE_RIGHT:
3744 poly[1].y += startBevelOffset;
3745 break;
3746 case NS_SIDE_LEFT:
3747 poly[0].y += startBevelOffset;
3750 Float endBevelOffset =
3751 NSAppUnitsToFloatPixels(aEndBevelOffset, aAppUnitsPerDevPixel);
3752 switch(aEndBevelSide) {
3753 case NS_SIDE_TOP:
3754 poly[1].x -= endBevelOffset;
3755 break;
3756 case NS_SIDE_BOTTOM:
3757 poly[2].x -= endBevelOffset;
3758 break;
3759 case NS_SIDE_RIGHT:
3760 poly[2].y -= endBevelOffset;
3761 break;
3762 case NS_SIDE_LEFT:
3763 poly[3].y -= endBevelOffset;
3766 RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder();
3767 builder->MoveTo(poly[0]);
3768 builder->LineTo(poly[1]);
3769 builder->LineTo(poly[2]);
3770 builder->LineTo(poly[3]);
3771 builder->Close();
3772 RefPtr<Path> path = builder->Finish();
3773 drawTarget->Fill(path, color, drawOptions);
3777 static void
3778 GetDashInfo(nscoord aBorderLength,
3779 nscoord aDashLength,
3780 nscoord aTwipsPerPixel,
3781 int32_t& aNumDashSpaces,
3782 nscoord& aStartDashLength,
3783 nscoord& aEndDashLength)
3785 aNumDashSpaces = 0;
3786 if (aStartDashLength + aDashLength + aEndDashLength >= aBorderLength) {
3787 aStartDashLength = aBorderLength;
3788 aEndDashLength = 0;
3790 else {
3791 aNumDashSpaces = (aBorderLength - aDashLength)/ (2 * aDashLength); // round down
3792 nscoord extra = aBorderLength - aStartDashLength - aEndDashLength - (((2 * aNumDashSpaces) - 1) * aDashLength);
3793 if (extra > 0) {
3794 nscoord half = RoundIntToPixel(extra / 2, aTwipsPerPixel);
3795 aStartDashLength += half;
3796 aEndDashLength += (extra - half);
3801 void
3802 nsCSSRendering::DrawTableBorderSegment(nsRenderingContext& aContext,
3803 uint8_t aBorderStyle,
3804 nscolor aBorderColor,
3805 const nsStyleBackground* aBGColor,
3806 const nsRect& aBorder,
3807 int32_t aAppUnitsPerDevPixel,
3808 int32_t aAppUnitsPerCSSPixel,
3809 uint8_t aStartBevelSide,
3810 nscoord aStartBevelOffset,
3811 uint8_t aEndBevelSide,
3812 nscoord aEndBevelOffset)
3814 bool horizontal = ((NS_SIDE_TOP == aStartBevelSide) || (NS_SIDE_BOTTOM == aStartBevelSide));
3815 nscoord twipsPerPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerCSSPixel);
3816 uint8_t ridgeGroove = NS_STYLE_BORDER_STYLE_RIDGE;
3818 if ((twipsPerPixel >= aBorder.width) || (twipsPerPixel >= aBorder.height) ||
3819 (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle) || (NS_STYLE_BORDER_STYLE_DOTTED == aBorderStyle)) {
3820 // no beveling for 1 pixel border, dash or dot
3821 aStartBevelOffset = 0;
3822 aEndBevelOffset = 0;
3825 gfxContext *ctx = aContext.ThebesContext();
3826 AntialiasMode oldMode = ctx->CurrentAntialiasMode();
3827 ctx->SetAntialiasMode(AntialiasMode::NONE);
3829 ctx->SetColor(aBorderColor);
3831 switch (aBorderStyle) {
3832 case NS_STYLE_BORDER_STYLE_NONE:
3833 case NS_STYLE_BORDER_STYLE_HIDDEN:
3834 //NS_ASSERTION(false, "style of none or hidden");
3835 break;
3836 case NS_STYLE_BORDER_STYLE_DOTTED:
3837 case NS_STYLE_BORDER_STYLE_DASHED:
3839 nscoord dashLength = (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle) ? DASH_LENGTH : DOT_LENGTH;
3840 // make the dash length proportional to the border thickness
3841 dashLength *= (horizontal) ? aBorder.height : aBorder.width;
3842 // make the min dash length for the ends 1/2 the dash length
3843 nscoord minDashLength = (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle)
3844 ? RoundFloatToPixel(((float)dashLength) / 2.0f, twipsPerPixel) : dashLength;
3845 minDashLength = std::max(minDashLength, twipsPerPixel);
3846 nscoord numDashSpaces = 0;
3847 nscoord startDashLength = minDashLength;
3848 nscoord endDashLength = minDashLength;
3849 if (horizontal) {
3850 GetDashInfo(aBorder.width, dashLength, twipsPerPixel, numDashSpaces, startDashLength, endDashLength);
3851 nsRect rect(aBorder.x, aBorder.y, startDashLength, aBorder.height);
3852 DrawSolidBorderSegment(aContext, rect, aBorderColor, aAppUnitsPerDevPixel, twipsPerPixel);
3853 for (int32_t spaceX = 0; spaceX < numDashSpaces; spaceX++) {
3854 rect.x += rect.width + dashLength;
3855 rect.width = (spaceX == (numDashSpaces - 1)) ? endDashLength : dashLength;
3856 DrawSolidBorderSegment(aContext, rect, aBorderColor, aAppUnitsPerDevPixel, twipsPerPixel);
3859 else {
3860 GetDashInfo(aBorder.height, dashLength, twipsPerPixel, numDashSpaces, startDashLength, endDashLength);
3861 nsRect rect(aBorder.x, aBorder.y, aBorder.width, startDashLength);
3862 DrawSolidBorderSegment(aContext, rect, aBorderColor, aAppUnitsPerDevPixel, twipsPerPixel);
3863 for (int32_t spaceY = 0; spaceY < numDashSpaces; spaceY++) {
3864 rect.y += rect.height + dashLength;
3865 rect.height = (spaceY == (numDashSpaces - 1)) ? endDashLength : dashLength;
3866 DrawSolidBorderSegment(aContext, rect, aBorderColor, aAppUnitsPerDevPixel, twipsPerPixel);
3870 break;
3871 case NS_STYLE_BORDER_STYLE_GROOVE:
3872 ridgeGroove = NS_STYLE_BORDER_STYLE_GROOVE; // and fall through to ridge
3873 case NS_STYLE_BORDER_STYLE_RIDGE:
3874 if ((horizontal && (twipsPerPixel >= aBorder.height)) ||
3875 (!horizontal && (twipsPerPixel >= aBorder.width))) {
3876 // a one pixel border
3877 DrawSolidBorderSegment(aContext, aBorder, aBorderColor, aAppUnitsPerDevPixel, twipsPerPixel,
3878 aStartBevelSide, aStartBevelOffset,
3879 aEndBevelSide, aEndBevelOffset);
3881 else {
3882 nscoord startBevel = (aStartBevelOffset > 0)
3883 ? RoundFloatToPixel(0.5f * (float)aStartBevelOffset, twipsPerPixel, true) : 0;
3884 nscoord endBevel = (aEndBevelOffset > 0)
3885 ? RoundFloatToPixel(0.5f * (float)aEndBevelOffset, twipsPerPixel, true) : 0;
3886 mozilla::css::Side ridgeGrooveSide = (horizontal) ? NS_SIDE_TOP : NS_SIDE_LEFT;
3887 // FIXME: In theory, this should use the visited-dependent
3888 // background color, but I don't care.
3889 ctx->SetColor(
3890 MakeBevelColor(ridgeGrooveSide, ridgeGroove, aBGColor->mBackgroundColor, aBorderColor));
3891 nsRect rect(aBorder);
3892 nscoord half;
3893 if (horizontal) { // top, bottom
3894 half = RoundFloatToPixel(0.5f * (float)aBorder.height, twipsPerPixel);
3895 rect.height = half;
3896 if (NS_SIDE_TOP == aStartBevelSide) {
3897 rect.x += startBevel;
3898 rect.width -= startBevel;
3900 if (NS_SIDE_TOP == aEndBevelSide) {
3901 rect.width -= endBevel;
3903 DrawSolidBorderSegment(aContext, rect, aBorderColor, aAppUnitsPerDevPixel, twipsPerPixel, aStartBevelSide,
3904 startBevel, aEndBevelSide, endBevel);
3906 else { // left, right
3907 half = RoundFloatToPixel(0.5f * (float)aBorder.width, twipsPerPixel);
3908 rect.width = half;
3909 if (NS_SIDE_LEFT == aStartBevelSide) {
3910 rect.y += startBevel;
3911 rect.height -= startBevel;
3913 if (NS_SIDE_LEFT == aEndBevelSide) {
3914 rect.height -= endBevel;
3916 DrawSolidBorderSegment(aContext, rect, aBorderColor, aAppUnitsPerDevPixel, twipsPerPixel, aStartBevelSide,
3917 startBevel, aEndBevelSide, endBevel);
3920 rect = aBorder;
3921 ridgeGrooveSide = (NS_SIDE_TOP == ridgeGrooveSide) ? NS_SIDE_BOTTOM : NS_SIDE_RIGHT;
3922 // FIXME: In theory, this should use the visited-dependent
3923 // background color, but I don't care.
3924 ctx->SetColor(
3925 MakeBevelColor(ridgeGrooveSide, ridgeGroove, aBGColor->mBackgroundColor, aBorderColor));
3926 if (horizontal) {
3927 rect.y = rect.y + half;
3928 rect.height = aBorder.height - half;
3929 if (NS_SIDE_BOTTOM == aStartBevelSide) {
3930 rect.x += startBevel;
3931 rect.width -= startBevel;
3933 if (NS_SIDE_BOTTOM == aEndBevelSide) {
3934 rect.width -= endBevel;
3936 DrawSolidBorderSegment(aContext, rect, aBorderColor, aAppUnitsPerDevPixel, twipsPerPixel, aStartBevelSide,
3937 startBevel, aEndBevelSide, endBevel);
3939 else {
3940 rect.x = rect.x + half;
3941 rect.width = aBorder.width - half;
3942 if (NS_SIDE_RIGHT == aStartBevelSide) {
3943 rect.y += aStartBevelOffset - startBevel;
3944 rect.height -= startBevel;
3946 if (NS_SIDE_RIGHT == aEndBevelSide) {
3947 rect.height -= endBevel;
3949 DrawSolidBorderSegment(aContext, rect, aBorderColor, aAppUnitsPerDevPixel, twipsPerPixel, aStartBevelSide,
3950 startBevel, aEndBevelSide, endBevel);
3953 break;
3954 case NS_STYLE_BORDER_STYLE_DOUBLE:
3955 // We can only do "double" borders if the thickness of the border
3956 // is more than 2px. Otherwise, we fall through to painting a
3957 // solid border.
3958 if ((aBorder.width > 2*twipsPerPixel || horizontal) &&
3959 (aBorder.height > 2*twipsPerPixel || !horizontal)) {
3960 nscoord startBevel = (aStartBevelOffset > 0)
3961 ? RoundFloatToPixel(0.333333f * (float)aStartBevelOffset, twipsPerPixel) : 0;
3962 nscoord endBevel = (aEndBevelOffset > 0)
3963 ? RoundFloatToPixel(0.333333f * (float)aEndBevelOffset, twipsPerPixel) : 0;
3964 if (horizontal) { // top, bottom
3965 nscoord thirdHeight = RoundFloatToPixel(0.333333f * (float)aBorder.height, twipsPerPixel);
3967 // draw the top line or rect
3968 nsRect topRect(aBorder.x, aBorder.y, aBorder.width, thirdHeight);
3969 if (NS_SIDE_TOP == aStartBevelSide) {
3970 topRect.x += aStartBevelOffset - startBevel;
3971 topRect.width -= aStartBevelOffset - startBevel;
3973 if (NS_SIDE_TOP == aEndBevelSide) {
3974 topRect.width -= aEndBevelOffset - endBevel;
3976 DrawSolidBorderSegment(aContext, topRect, aBorderColor, aAppUnitsPerDevPixel, twipsPerPixel, aStartBevelSide,
3977 startBevel, aEndBevelSide, endBevel);
3979 // draw the botom line or rect
3980 nscoord heightOffset = aBorder.height - thirdHeight;
3981 nsRect bottomRect(aBorder.x, aBorder.y + heightOffset, aBorder.width, aBorder.height - heightOffset);
3982 if (NS_SIDE_BOTTOM == aStartBevelSide) {
3983 bottomRect.x += aStartBevelOffset - startBevel;
3984 bottomRect.width -= aStartBevelOffset - startBevel;
3986 if (NS_SIDE_BOTTOM == aEndBevelSide) {
3987 bottomRect.width -= aEndBevelOffset - endBevel;
3989 DrawSolidBorderSegment(aContext, bottomRect, aBorderColor, aAppUnitsPerDevPixel, twipsPerPixel, aStartBevelSide,
3990 startBevel, aEndBevelSide, endBevel);
3992 else { // left, right
3993 nscoord thirdWidth = RoundFloatToPixel(0.333333f * (float)aBorder.width, twipsPerPixel);
3995 nsRect leftRect(aBorder.x, aBorder.y, thirdWidth, aBorder.height);
3996 if (NS_SIDE_LEFT == aStartBevelSide) {
3997 leftRect.y += aStartBevelOffset - startBevel;
3998 leftRect.height -= aStartBevelOffset - startBevel;
4000 if (NS_SIDE_LEFT == aEndBevelSide) {
4001 leftRect.height -= aEndBevelOffset - endBevel;
4003 DrawSolidBorderSegment(aContext, leftRect, aBorderColor, aAppUnitsPerDevPixel, twipsPerPixel, aStartBevelSide,
4004 startBevel, aEndBevelSide, endBevel);
4006 nscoord widthOffset = aBorder.width - thirdWidth;
4007 nsRect rightRect(aBorder.x + widthOffset, aBorder.y, aBorder.width - widthOffset, aBorder.height);
4008 if (NS_SIDE_RIGHT == aStartBevelSide) {
4009 rightRect.y += aStartBevelOffset - startBevel;
4010 rightRect.height -= aStartBevelOffset - startBevel;
4012 if (NS_SIDE_RIGHT == aEndBevelSide) {
4013 rightRect.height -= aEndBevelOffset - endBevel;
4015 DrawSolidBorderSegment(aContext, rightRect, aBorderColor, aAppUnitsPerDevPixel, twipsPerPixel, aStartBevelSide,
4016 startBevel, aEndBevelSide, endBevel);
4018 break;
4020 // else fall through to solid
4021 case NS_STYLE_BORDER_STYLE_SOLID:
4022 DrawSolidBorderSegment(aContext, aBorder, aBorderColor, aAppUnitsPerDevPixel, twipsPerPixel, aStartBevelSide,
4023 aStartBevelOffset, aEndBevelSide, aEndBevelOffset);
4024 break;
4025 case NS_STYLE_BORDER_STYLE_OUTSET:
4026 case NS_STYLE_BORDER_STYLE_INSET:
4027 NS_ASSERTION(false, "inset, outset should have been converted to groove, ridge");
4028 break;
4029 case NS_STYLE_BORDER_STYLE_AUTO:
4030 NS_ASSERTION(false, "Unexpected 'auto' table border");
4031 break;
4034 ctx->SetAntialiasMode(oldMode);
4037 // End table border-collapsing section
4039 Rect
4040 nsCSSRendering::ExpandPaintingRectForDecorationLine(
4041 nsIFrame* aFrame,
4042 const uint8_t aStyle,
4043 const Rect& aClippedRect,
4044 const Float aICoordInFrame,
4045 const Float aCycleLength,
4046 bool aVertical)
4048 switch (aStyle) {
4049 case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
4050 case NS_STYLE_TEXT_DECORATION_STYLE_DASHED:
4051 case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
4052 break;
4053 default:
4054 NS_ERROR("Invalid style was specified");
4055 return aClippedRect;
4058 nsBlockFrame* block = nullptr;
4059 // Note that when we paint the decoration lines in relative positioned
4060 // box, we should paint them like all of the boxes are positioned as static.
4061 nscoord framePosInBlockAppUnits = 0;
4062 for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
4063 block = do_QueryFrame(f);
4064 if (block) {
4065 break;
4067 framePosInBlockAppUnits += aVertical ?
4068 f->GetNormalPosition().y : f->GetNormalPosition().x;
4071 NS_ENSURE_TRUE(block, aClippedRect);
4073 nsPresContext *pc = aFrame->PresContext();
4074 Float framePosInBlock = Float(pc->AppUnitsToGfxUnits(framePosInBlockAppUnits));
4075 int32_t rectPosInBlock =
4076 int32_t(NS_round(framePosInBlock + aICoordInFrame));
4077 int32_t extraStartEdge =
4078 rectPosInBlock - (rectPosInBlock / int32_t(aCycleLength) * aCycleLength);
4079 Rect rect(aClippedRect);
4080 if (aVertical) {
4081 rect.y -= extraStartEdge;
4082 rect.height += extraStartEdge;
4083 } else {
4084 rect.x -= extraStartEdge;
4085 rect.width += extraStartEdge;
4087 return rect;
4090 void
4091 nsCSSRendering::PaintDecorationLine(nsIFrame* aFrame,
4092 DrawTarget& aDrawTarget,
4093 const Rect& aDirtyRect,
4094 const nscolor aColor,
4095 const gfxPoint& aPt,
4096 const Float aICoordInFrame,
4097 const gfxSize& aLineSize,
4098 const gfxFloat aAscent,
4099 const gfxFloat aOffset,
4100 const uint8_t aDecoration,
4101 const uint8_t aStyle,
4102 bool aVertical,
4103 const gfxFloat aDescentLimit)
4105 NS_ASSERTION(aStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE, "aStyle is none");
4107 Rect rect = ToRect(
4108 GetTextDecorationRectInternal(aPt, aLineSize, aAscent, aOffset,
4109 aDecoration, aStyle, aVertical,
4110 aDescentLimit));
4111 if (rect.IsEmpty() || !rect.Intersects(aDirtyRect)) {
4112 return;
4115 if (aDecoration != NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE &&
4116 aDecoration != NS_STYLE_TEXT_DECORATION_LINE_OVERLINE &&
4117 aDecoration != NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
4118 NS_ERROR("Invalid decoration value!");
4119 return;
4122 Float lineThickness = std::max(NS_round(aLineSize.height), 1.0);
4124 ColorPattern color(ToDeviceColor(aColor));
4125 StrokeOptions strokeOptions(lineThickness);
4126 DrawOptions drawOptions;
4128 Float dash[2];
4130 AutoPopClips autoPopClips(&aDrawTarget);
4132 switch (aStyle) {
4133 case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
4134 case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE:
4135 break;
4136 case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
4137 autoPopClips.PushClipRect(rect);
4138 Float dashWidth = lineThickness * DOT_LENGTH * DASH_LENGTH;
4139 dash[0] = dashWidth;
4140 dash[1] = dashWidth;
4141 strokeOptions.mDashPattern = dash;
4142 strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
4143 strokeOptions.mLineCap = CapStyle::BUTT;
4144 rect = ExpandPaintingRectForDecorationLine(aFrame, aStyle, rect,
4145 aICoordInFrame,
4146 dashWidth * 2,
4147 aVertical);
4148 // We should continue to draw the last dash even if it is not in the rect.
4149 rect.width += dashWidth;
4150 break;
4152 case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED: {
4153 autoPopClips.PushClipRect(rect);
4154 Float dashWidth = lineThickness * DOT_LENGTH;
4155 if (lineThickness > 2.0) {
4156 dash[0] = 0.f;
4157 dash[1] = dashWidth * 2.f;
4158 strokeOptions.mLineCap = CapStyle::ROUND;
4159 } else {
4160 dash[0] = dashWidth;
4161 dash[1] = dashWidth;
4163 strokeOptions.mDashPattern = dash;
4164 strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
4165 rect = ExpandPaintingRectForDecorationLine(aFrame, aStyle, rect,
4166 aICoordInFrame,
4167 dashWidth * 2,
4168 aVertical);
4169 // We should continue to draw the last dot even if it is not in the rect.
4170 rect.width += dashWidth;
4171 break;
4173 case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
4174 autoPopClips.PushClipRect(rect);
4175 if (lineThickness > 2.0) {
4176 drawOptions.mAntialiasMode = AntialiasMode::SUBPIXEL;
4177 } else {
4178 // Don't use anti-aliasing here. Because looks like lighter color wavy
4179 // line at this case. And probably, users don't think the
4180 // non-anti-aliased wavy line is not pretty.
4181 drawOptions.mAntialiasMode = AntialiasMode::NONE;
4183 break;
4184 default:
4185 NS_ERROR("Invalid style value!");
4186 return;
4189 // The block-direction position should be set to the middle of the line.
4190 if (aVertical) {
4191 rect.x += lineThickness / 2;
4192 } else {
4193 rect.y += lineThickness / 2;
4196 switch (aStyle) {
4197 case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
4198 case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
4199 case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
4200 Point p1 = rect.TopLeft();
4201 Point p2 = aVertical ? rect.BottomLeft() : rect.TopRight();
4202 aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
4203 return;
4205 case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE: {
4207 * We are drawing double line as:
4209 * +-------------------------------------------+
4210 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4211 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4212 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4213 * | |
4214 * | |
4215 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4216 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4217 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4218 * +-------------------------------------------+
4220 Point p1 = rect.TopLeft();
4221 Point p2 = aVertical ? rect.BottomLeft() : rect.TopRight();
4222 aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
4224 if (aVertical) {
4225 rect.width -= lineThickness;
4226 } else {
4227 rect.height -= lineThickness;
4230 p1 = aVertical ? rect.TopRight() : rect.BottomLeft();
4231 p2 = rect.BottomRight();
4232 aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
4233 return;
4235 case NS_STYLE_TEXT_DECORATION_STYLE_WAVY: {
4237 * We are drawing wavy line as:
4239 * P: Path, X: Painted pixel
4241 * +---------------------------------------+
4242 * XX|X XXXXXX XXXXXX |
4243 * PP|PX XPPPPPPX XPPPPPPX | ^
4244 * XX|XPX XPXXXXXXPX XPXXXXXXPX| |
4245 * | XPX XPX XPX XPX XP|X |adv
4246 * | XPXXXXXXPX XPXXXXXXPX X|PX |
4247 * | XPPPPPPX XPPPPPPX |XPX v
4248 * | XXXXXX XXXXXX | XX
4249 * +---------------------------------------+
4250 * <---><---> ^
4251 * adv flatLengthAtVertex rightMost
4253 * 1. Always starts from top-left of the drawing area, however, we need
4254 * to draw the line from outside of the rect. Because the start
4255 * point of the line is not good style if we draw from inside it.
4256 * 2. First, draw horizontal line from outside the rect to top-left of
4257 * the rect;
4258 * 3. Goes down to bottom of the area at 45 degrees.
4259 * 4. Slides to right horizontaly, see |flatLengthAtVertex|.
4260 * 5. Goes up to top of the area at 45 degrees.
4261 * 6. Slides to right horizontaly.
4262 * 7. Repeat from 2 until reached to right-most edge of the area.
4264 * In the vertical case, swap horizontal and vertical coordinates and
4265 * directions in the above description.
4268 Float& rectICoord = aVertical ? rect.y : rect.x;
4269 Float& rectISize = aVertical ? rect.height : rect.width;
4270 const Float rectBSize = aVertical ? rect.width : rect.height;
4272 const Float adv = rectBSize - lineThickness;
4273 const Float flatLengthAtVertex =
4274 std::max((lineThickness - 1.0) * 2.0, 1.0);
4276 // Align the start of wavy lines to the nearest ancestor block.
4277 const Float cycleLength = 2 * (adv + flatLengthAtVertex);
4278 rect = ExpandPaintingRectForDecorationLine(aFrame, aStyle, rect,
4279 aICoordInFrame, cycleLength,
4280 aVertical);
4281 // figure out if we can trim whole cycles from the left and right edges
4282 // of the line, to try and avoid creating an unnecessarily long and
4283 // complex path
4284 const Float dirtyRectICoord = aVertical ? aDirtyRect.y : aDirtyRect.x;
4285 int32_t skipCycles = floor((dirtyRectICoord - rectICoord) / cycleLength);
4286 if (skipCycles > 0) {
4287 rectICoord += skipCycles * cycleLength;
4288 rectISize -= skipCycles * cycleLength;
4291 rectICoord += lineThickness / 2.0;
4292 Point pt(rect.TopLeft());
4293 Float& ptICoord = aVertical ? pt.y : pt.x;
4294 Float& ptBCoord = aVertical ? pt.x : pt.y;
4295 if (aVertical) {
4296 ptBCoord += adv + lineThickness / 2.0;
4298 Float iCoordLimit = ptICoord + rectISize + lineThickness;
4300 const Float dirtyRectIMost = aVertical ?
4301 aDirtyRect.YMost() : aDirtyRect.XMost();
4302 skipCycles = floor((iCoordLimit - dirtyRectIMost) / cycleLength);
4303 if (skipCycles > 0) {
4304 iCoordLimit -= skipCycles * cycleLength;
4307 RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
4308 RefPtr<Path> path;
4310 ptICoord -= lineThickness;
4311 builder->MoveTo(pt); // 1
4313 ptICoord = rectICoord;
4314 builder->LineTo(pt); // 2
4316 // In vertical mode, to go "down" relative to the text we need to
4317 // decrease the block coordinate, whereas in horizontal we increase
4318 // it. So the sense of this flag is effectively inverted.
4319 bool goDown = aVertical ? false : true;
4320 uint32_t iter = 0;
4321 while (ptICoord < iCoordLimit) {
4322 if (++iter > 1000) {
4323 // stroke the current path and start again, to avoid pathological
4324 // behavior in cairo with huge numbers of path segments
4325 path = builder->Finish();
4326 aDrawTarget.Stroke(path, color, strokeOptions, drawOptions);
4327 builder = aDrawTarget.CreatePathBuilder();
4328 builder->MoveTo(pt);
4329 iter = 0;
4331 ptICoord += adv;
4332 ptBCoord += goDown ? adv : -adv;
4334 builder->LineTo(pt); // 3 and 5
4336 ptICoord += flatLengthAtVertex;
4337 builder->LineTo(pt); // 4 and 6
4339 goDown = !goDown;
4341 path = builder->Finish();
4342 aDrawTarget.Stroke(path, color, strokeOptions, drawOptions);
4343 return;
4345 default:
4346 NS_ERROR("Invalid style value!");
4350 void
4351 nsCSSRendering::DecorationLineToPath(nsIFrame* aFrame,
4352 gfxContext* aGfxContext,
4353 const gfxRect& aDirtyRect,
4354 const nscolor aColor,
4355 const gfxPoint& aPt,
4356 const gfxFloat aICoordInFrame,
4357 const gfxSize& aLineSize,
4358 const gfxFloat aAscent,
4359 const gfxFloat aOffset,
4360 const uint8_t aDecoration,
4361 const uint8_t aStyle,
4362 bool aVertical,
4363 const gfxFloat aDescentLimit)
4365 NS_ASSERTION(aStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE, "aStyle is none");
4367 aGfxContext->NewPath();
4369 gfxRect rect =
4370 GetTextDecorationRectInternal(aPt, aLineSize, aAscent, aOffset,
4371 aDecoration, aStyle, aVertical,
4372 aDescentLimit);
4373 if (rect.IsEmpty() || !rect.Intersects(aDirtyRect)) {
4374 return;
4377 if (aDecoration != NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE &&
4378 aDecoration != NS_STYLE_TEXT_DECORATION_LINE_OVERLINE &&
4379 aDecoration != NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
4380 NS_ERROR("Invalid decoration value!");
4381 return;
4384 if (aStyle != NS_STYLE_TEXT_DECORATION_STYLE_SOLID) {
4385 // For the moment, we support only solid text decorations.
4386 return;
4389 gfxFloat lineThickness = std::max(NS_round(aLineSize.height), 1.0);
4391 // The block-direction position should be set to the middle of the line.
4392 if (aVertical) {
4393 rect.x += lineThickness / 2;
4394 aGfxContext->Rectangle
4395 (gfxRect(gfxPoint(rect.TopLeft() - gfxPoint(lineThickness / 2, 0.0)),
4396 gfxSize(lineThickness, rect.Height())));
4397 } else {
4398 rect.y += lineThickness / 2;
4399 aGfxContext->Rectangle
4400 (gfxRect(gfxPoint(rect.TopLeft() - gfxPoint(0.0, lineThickness / 2)),
4401 gfxSize(rect.Width(), lineThickness)));
4405 nsRect
4406 nsCSSRendering::GetTextDecorationRect(nsPresContext* aPresContext,
4407 const gfxSize& aLineSize,
4408 const gfxFloat aAscent,
4409 const gfxFloat aOffset,
4410 const uint8_t aDecoration,
4411 const uint8_t aStyle,
4412 bool aVertical,
4413 const gfxFloat aDescentLimit)
4415 NS_ASSERTION(aPresContext, "aPresContext is null");
4416 NS_ASSERTION(aStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE, "aStyle is none");
4418 gfxRect rect =
4419 GetTextDecorationRectInternal(gfxPoint(0, 0), aLineSize, aAscent, aOffset,
4420 aDecoration, aStyle, aVertical,
4421 aDescentLimit);
4422 // The rect values are already rounded to nearest device pixels.
4423 nsRect r;
4424 r.x = aPresContext->GfxUnitsToAppUnits(rect.X());
4425 r.y = aPresContext->GfxUnitsToAppUnits(rect.Y());
4426 r.width = aPresContext->GfxUnitsToAppUnits(rect.Width());
4427 r.height = aPresContext->GfxUnitsToAppUnits(rect.Height());
4428 return r;
4431 gfxRect
4432 nsCSSRendering::GetTextDecorationRectInternal(const gfxPoint& aPt,
4433 const gfxSize& aLineSize,
4434 const gfxFloat aAscent,
4435 const gfxFloat aOffset,
4436 const uint8_t aDecoration,
4437 const uint8_t aStyle,
4438 bool aVertical,
4439 const gfxFloat aDescentLimit)
4441 NS_ASSERTION(aStyle <= NS_STYLE_TEXT_DECORATION_STYLE_WAVY,
4442 "Invalid aStyle value");
4444 if (aStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE)
4445 return gfxRect(0, 0, 0, 0);
4447 bool canLiftUnderline = aDescentLimit >= 0.0;
4449 gfxFloat iCoord = aVertical ? aPt.y : aPt.x;
4450 gfxFloat bCoord = aVertical ? aPt.x : aPt.y;
4452 // 'left' and 'right' are relative to the line, so for vertical writing modes
4453 // they will actually become top and bottom of the rendered line.
4454 // Similarly, aLineSize.width and .height are actually length and thickness
4455 // of the line, which runs horizontally or vertically according to aVertical.
4456 const gfxFloat left = floor(iCoord + 0.5),
4457 right = floor(iCoord + aLineSize.width + 0.5);
4459 // We compute |r| as if for a horizontal text run, and then swap vertical
4460 // and horizontal coordinates at the end if vertical was requested.
4461 gfxRect r(left, 0, right - left, 0);
4463 gfxFloat lineThickness = NS_round(aLineSize.height);
4464 lineThickness = std::max(lineThickness, 1.0);
4466 gfxFloat ascent = NS_round(aAscent);
4467 gfxFloat descentLimit = floor(aDescentLimit);
4469 gfxFloat suggestedMaxRectHeight = std::max(std::min(ascent, descentLimit), 1.0);
4470 r.height = lineThickness;
4471 if (aStyle == NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE) {
4473 * We will draw double line as:
4475 * +-------------------------------------------+
4476 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4477 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4478 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4479 * | | ^
4480 * | | | gap
4481 * | | v
4482 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4483 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4484 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4485 * +-------------------------------------------+
4487 gfxFloat gap = NS_round(lineThickness / 2.0);
4488 gap = std::max(gap, 1.0);
4489 r.height = lineThickness * 2.0 + gap;
4490 if (canLiftUnderline) {
4491 if (r.Height() > suggestedMaxRectHeight) {
4492 // Don't shrink the line height, because the thickness has some meaning.
4493 // We can just shrink the gap at this time.
4494 r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0 + 1.0);
4497 } else if (aStyle == NS_STYLE_TEXT_DECORATION_STYLE_WAVY) {
4499 * We will draw wavy line as:
4501 * +-------------------------------------------+
4502 * |XXXXX XXXXXX XXXXXX | ^
4503 * |XXXXXX XXXXXXXX XXXXXXXX | | lineThickness
4504 * |XXXXXXX XXXXXXXXXX XXXXXXXXXX| v
4505 * | XXX XXX XXX XXX XX|
4506 * | XXXXXXXXXX XXXXXXXXXX X|
4507 * | XXXXXXXX XXXXXXXX |
4508 * | XXXXXX XXXXXX |
4509 * +-------------------------------------------+
4511 r.height = lineThickness > 2.0 ? lineThickness * 4.0 : lineThickness * 3.0;
4512 if (canLiftUnderline) {
4513 if (r.Height() > suggestedMaxRectHeight) {
4514 // Don't shrink the line height even if there is not enough space,
4515 // because the thickness has some meaning. E.g., the 1px wavy line and
4516 // 2px wavy line can be used for different meaning in IME selections
4517 // at same time.
4518 r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0);
4523 gfxFloat baseline = floor(bCoord + aAscent + 0.5);
4524 gfxFloat offset = 0.0;
4525 switch (aDecoration) {
4526 case NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE:
4527 offset = aOffset;
4528 if (canLiftUnderline) {
4529 if (descentLimit < -offset + r.Height()) {
4530 // If we can ignore the offset and the decoration line is overflowing,
4531 // we should align the bottom edge of the decoration line rect if it's
4532 // possible. Otherwise, we should lift up the top edge of the rect as
4533 // far as possible.
4534 gfxFloat offsetBottomAligned = -descentLimit + r.Height();
4535 gfxFloat offsetTopAligned = 0.0;
4536 offset = std::min(offsetBottomAligned, offsetTopAligned);
4539 break;
4540 case NS_STYLE_TEXT_DECORATION_LINE_OVERLINE:
4541 offset = aOffset - lineThickness + r.Height();
4542 break;
4543 case NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH: {
4544 gfxFloat extra = floor(r.Height() / 2.0 + 0.5);
4545 extra = std::max(extra, lineThickness);
4546 offset = aOffset - lineThickness + extra;
4547 break;
4549 default:
4550 NS_ERROR("Invalid decoration value!");
4553 if (aVertical) {
4554 r.y = baseline + floor(aOffset + 0.5); // this will need updating when we
4555 // support sideways-left orientation
4556 Swap(r.x, r.y);
4557 Swap(r.width, r.height);
4558 } else {
4559 r.y = baseline - floor(aOffset + 0.5);
4562 return r;
4565 // ------------------
4566 // ImageRenderer
4567 // ------------------
4568 nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame,
4569 const nsStyleImage* aImage,
4570 uint32_t aFlags)
4571 : mForFrame(aForFrame)
4572 , mImage(aImage)
4573 , mType(aImage->GetType())
4574 , mImageContainer(nullptr)
4575 , mGradientData(nullptr)
4576 , mPaintServerFrame(nullptr)
4577 , mIsReady(false)
4578 , mSize(0, 0)
4579 , mFlags(aFlags)
4583 nsImageRenderer::~nsImageRenderer()
4587 bool
4588 nsImageRenderer::PrepareImage()
4590 if (mImage->IsEmpty())
4591 return false;
4593 if (!mImage->IsComplete()) {
4594 // Make sure the image is actually decoding
4595 mImage->StartDecoding();
4597 // check again to see if we finished
4598 if (!mImage->IsComplete()) {
4599 // We can not prepare the image for rendering if it is not fully loaded.
4601 // Special case: If we requested a sync decode and we have an image, push
4602 // on through because the Draw() will do a sync decode then
4603 nsCOMPtr<imgIContainer> img;
4604 if (!((mFlags & FLAG_SYNC_DECODE_IMAGES) &&
4605 (mType == eStyleImageType_Image) &&
4606 (NS_SUCCEEDED(mImage->GetImageData()->GetImage(getter_AddRefs(img))))))
4607 return false;
4611 switch (mType) {
4612 case eStyleImageType_Image:
4614 nsCOMPtr<imgIContainer> srcImage;
4615 DebugOnly<nsresult> rv =
4616 mImage->GetImageData()->GetImage(getter_AddRefs(srcImage));
4617 NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv) && srcImage,
4618 "If GetImage() is failing, mImage->IsComplete() "
4619 "should have returned false");
4621 if (!mImage->GetCropRect()) {
4622 mImageContainer.swap(srcImage);
4623 } else {
4624 nsIntRect actualCropRect;
4625 bool isEntireImage;
4626 bool success =
4627 mImage->ComputeActualCropRect(actualCropRect, &isEntireImage);
4628 NS_ASSERTION(success, "ComputeActualCropRect() should not fail here");
4629 if (!success || actualCropRect.IsEmpty()) {
4630 // The cropped image has zero size
4631 return false;
4633 if (isEntireImage) {
4634 // The cropped image is identical to the source image
4635 mImageContainer.swap(srcImage);
4636 } else {
4637 nsCOMPtr<imgIContainer> subImage = ImageOps::Clip(srcImage, actualCropRect);
4638 mImageContainer.swap(subImage);
4641 mIsReady = true;
4642 break;
4644 case eStyleImageType_Gradient:
4645 mGradientData = mImage->GetGradientData();
4646 mIsReady = true;
4647 break;
4648 case eStyleImageType_Element:
4650 nsAutoString elementId =
4651 NS_LITERAL_STRING("#") + nsDependentString(mImage->GetElementId());
4652 nsCOMPtr<nsIURI> targetURI;
4653 nsCOMPtr<nsIURI> base = mForFrame->GetContent()->GetBaseURI();
4654 nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), elementId,
4655 mForFrame->GetContent()->GetCurrentDoc(), base);
4656 nsSVGPaintingProperty* property = nsSVGEffects::GetPaintingPropertyForURI(
4657 targetURI, mForFrame->FirstContinuation(),
4658 nsSVGEffects::BackgroundImageProperty());
4659 if (!property)
4660 return false;
4661 mPaintServerFrame = property->GetReferencedFrame();
4663 // If the referenced element doesn't have a frame we might still be able
4664 // to paint it if it's an <img>, <canvas>, or <video> element.
4665 if (!mPaintServerFrame) {
4666 mImageElementSurface =
4667 nsLayoutUtils::SurfaceFromElement(property->GetReferencedElement());
4668 if (!mImageElementSurface.mSourceSurface)
4669 return false;
4671 mIsReady = true;
4672 break;
4674 case eStyleImageType_Null:
4675 default:
4676 break;
4679 return mIsReady;
4682 nsSize
4683 CSSSizeOrRatio::ComputeConcreteSize() const
4685 NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute");
4686 if (mHasWidth && mHasHeight) {
4687 return nsSize(mWidth, mHeight);
4689 if (mHasWidth) {
4690 nscoord height = NSCoordSaturatingNonnegativeMultiply(
4691 mWidth,
4692 double(mRatio.height) / mRatio.width);
4693 return nsSize(mWidth, height);
4696 MOZ_ASSERT(mHasHeight);
4697 nscoord width = NSCoordSaturatingNonnegativeMultiply(
4698 mHeight,
4699 double(mRatio.width) / mRatio.height);
4700 return nsSize(width, mHeight);
4703 CSSSizeOrRatio
4704 nsImageRenderer::ComputeIntrinsicSize()
4706 NS_ASSERTION(mIsReady, "Ensure PrepareImage() has returned true "
4707 "before calling me");
4709 CSSSizeOrRatio result;
4710 switch (mType) {
4711 case eStyleImageType_Image:
4713 bool haveWidth, haveHeight;
4714 nsIntSize imageIntSize;
4715 nsLayoutUtils::ComputeSizeForDrawing(mImageContainer, imageIntSize,
4716 result.mRatio, haveWidth, haveHeight);
4717 if (haveWidth) {
4718 result.SetWidth(nsPresContext::CSSPixelsToAppUnits(imageIntSize.width));
4720 if (haveHeight) {
4721 result.SetHeight(nsPresContext::CSSPixelsToAppUnits(imageIntSize.height));
4723 break;
4725 case eStyleImageType_Element:
4727 // XXX element() should have the width/height of the referenced element,
4728 // and that element's ratio, if it matches. If it doesn't match, it
4729 // should have no width/height or ratio. See element() in CSS images:
4730 // <http://dev.w3.org/csswg/css-images-4/#element-notation>.
4731 // Make sure to change nsStyleBackground::Size::DependsOnFrameSize
4732 // when fixing this!
4733 if (mPaintServerFrame) {
4734 // SVG images have no intrinsic size
4735 if (!mPaintServerFrame->IsFrameOfType(nsIFrame::eSVG)) {
4736 // The intrinsic image size for a generic nsIFrame paint server is
4737 // the union of the border-box rects of all of its continuations,
4738 // rounded to device pixels.
4739 int32_t appUnitsPerDevPixel =
4740 mForFrame->PresContext()->AppUnitsPerDevPixel();
4741 result.SetSize(
4742 nsSVGIntegrationUtils::GetContinuationUnionSize(mPaintServerFrame).
4743 ToNearestPixels(appUnitsPerDevPixel).
4744 ToAppUnits(appUnitsPerDevPixel));
4746 } else {
4747 NS_ASSERTION(mImageElementSurface.mSourceSurface, "Surface should be ready.");
4748 gfxIntSize surfaceSize = mImageElementSurface.mSize;
4749 result.SetSize(
4750 nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width),
4751 nsPresContext::CSSPixelsToAppUnits(surfaceSize.height)));
4753 break;
4755 case eStyleImageType_Gradient:
4756 // Per <http://dev.w3.org/csswg/css3-images/#gradients>, gradients have no
4757 // intrinsic dimensions.
4758 case eStyleImageType_Null:
4759 default:
4760 break;
4763 return result;
4766 /* static */ nsSize
4767 nsImageRenderer::ComputeConcreteSize(const CSSSizeOrRatio& aSpecifiedSize,
4768 const CSSSizeOrRatio& aIntrinsicSize,
4769 const nsSize& aDefaultSize)
4771 // The specified size is fully specified, just use that
4772 if (aSpecifiedSize.IsConcrete()) {
4773 return aSpecifiedSize.ComputeConcreteSize();
4776 MOZ_ASSERT(!aSpecifiedSize.mHasWidth || !aSpecifiedSize.mHasHeight);
4778 if (!aSpecifiedSize.mHasWidth && !aSpecifiedSize.mHasHeight) {
4779 // no specified size, try using the intrinsic size
4780 if (aIntrinsicSize.CanComputeConcreteSize()) {
4781 return aIntrinsicSize.ComputeConcreteSize();
4784 if (aIntrinsicSize.mHasWidth) {
4785 return nsSize(aIntrinsicSize.mWidth, aDefaultSize.height);
4787 if (aIntrinsicSize.mHasHeight) {
4788 return nsSize(aDefaultSize.width, aIntrinsicSize.mHeight);
4791 // couldn't use the intrinsic size either, revert to using the default size
4792 return ComputeConstrainedSize(aDefaultSize,
4793 aIntrinsicSize.mRatio,
4794 CONTAIN);
4797 MOZ_ASSERT(aSpecifiedSize.mHasWidth || aSpecifiedSize.mHasHeight);
4799 // The specified height is partial, try to compute the missing part.
4800 if (aSpecifiedSize.mHasWidth) {
4801 nscoord height;
4802 if (aIntrinsicSize.HasRatio()) {
4803 height = NSCoordSaturatingNonnegativeMultiply(
4804 aSpecifiedSize.mWidth,
4805 double(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width);
4806 } else if (aIntrinsicSize.mHasHeight) {
4807 height = aIntrinsicSize.mHeight;
4808 } else {
4809 height = aDefaultSize.height;
4811 return nsSize(aSpecifiedSize.mWidth, height);
4814 MOZ_ASSERT(aSpecifiedSize.mHasHeight);
4815 nscoord width;
4816 if (aIntrinsicSize.HasRatio()) {
4817 width = NSCoordSaturatingNonnegativeMultiply(
4818 aSpecifiedSize.mHeight,
4819 double(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height);
4820 } else if (aIntrinsicSize.mHasWidth) {
4821 width = aIntrinsicSize.mWidth;
4822 } else {
4823 width = aDefaultSize.width;
4825 return nsSize(width, aSpecifiedSize.mHeight);
4828 /* static */ nsSize
4829 nsImageRenderer::ComputeConstrainedSize(const nsSize& aConstrainingSize,
4830 const nsSize& aIntrinsicRatio,
4831 FitType aFitType)
4833 if (aIntrinsicRatio.width <= 0 && aIntrinsicRatio.height <= 0) {
4834 return aConstrainingSize;
4837 float scaleX = double(aConstrainingSize.width) / aIntrinsicRatio.width;
4838 float scaleY = double(aConstrainingSize.height) / aIntrinsicRatio.height;
4839 nsSize size;
4840 if ((aFitType == CONTAIN) == (scaleX < scaleY)) {
4841 size.width = aConstrainingSize.width;
4842 size.height = NSCoordSaturatingNonnegativeMultiply(
4843 aIntrinsicRatio.height, scaleX);
4844 } else {
4845 size.width = NSCoordSaturatingNonnegativeMultiply(
4846 aIntrinsicRatio.width, scaleY);
4847 size.height = aConstrainingSize.height;
4849 return size;
4853 * mSize is the image's "preferred" size for this particular rendering, while
4854 * the drawn (aka concrete) size is the actual rendered size after accounting
4855 * for background-size etc.. The preferred size is most often the image's
4856 * intrinsic dimensions. But for images with incomplete intrinsic dimensions,
4857 * the preferred size varies, depending on the specified and default sizes, see
4858 * nsImageRenderer::Compute*Size.
4860 * This distinction is necessary because the components of a vector image are
4861 * specified with respect to its preferred size for a rendering situation, not
4862 * to its actual rendered size. For example, consider a 4px wide background
4863 * vector image with no height which contains a left-aligned
4864 * 2px wide black rectangle with height 100%. If the background-size width is
4865 * auto (or 4px), the vector image will render 4px wide, and the black rectangle
4866 * will be 2px wide. If the background-size width is 8px, the vector image will
4867 * render 8px wide, and the black rectangle will be 4px wide -- *not* 2px wide.
4868 * In both cases mSize.width will be 4px; but in the first case the returned
4869 * width will be 4px, while in the second case the returned width will be 8px.
4871 void
4872 nsImageRenderer::SetPreferredSize(const CSSSizeOrRatio& aIntrinsicSize,
4873 const nsSize& aDefaultSize)
4875 mSize.width = aIntrinsicSize.mHasWidth
4876 ? aIntrinsicSize.mWidth
4877 : aDefaultSize.width;
4878 mSize.height = aIntrinsicSize.mHasHeight
4879 ? aIntrinsicSize.mHeight
4880 : aDefaultSize.height;
4883 // Convert from nsImageRenderer flags to the flags we want to use for drawing in
4884 // the imgIContainer namespace.
4885 static uint32_t
4886 ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags)
4888 uint32_t drawFlags = imgIContainer::FLAG_NONE;
4889 if (aImageRendererFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
4890 drawFlags |= imgIContainer::FLAG_SYNC_DECODE;
4892 if (aImageRendererFlags & nsImageRenderer::FLAG_PAINTING_TO_WINDOW) {
4893 drawFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
4895 return drawFlags;
4898 DrawResult
4899 nsImageRenderer::Draw(nsPresContext* aPresContext,
4900 nsRenderingContext& aRenderingContext,
4901 const nsRect& aDirtyRect,
4902 const nsRect& aDest,
4903 const nsRect& aFill,
4904 const nsPoint& aAnchor,
4905 const CSSIntRect& aSrc)
4907 if (!mIsReady) {
4908 NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
4909 return DrawResult::TEMPORARY_ERROR;
4911 if (aDest.IsEmpty() || aFill.IsEmpty() ||
4912 mSize.width <= 0 || mSize.height <= 0) {
4913 return DrawResult::SUCCESS;
4916 GraphicsFilter filter = nsLayoutUtils::GetGraphicsFilterForFrame(mForFrame);
4918 switch (mType) {
4919 case eStyleImageType_Image:
4921 nsIntSize imageSize(nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
4922 nsPresContext::AppUnitsToIntCSSPixels(mSize.height));
4923 return
4924 nsLayoutUtils::DrawBackgroundImage(*aRenderingContext.ThebesContext(),
4925 aPresContext,
4926 mImageContainer, imageSize, filter,
4927 aDest, aFill, aAnchor, aDirtyRect,
4928 ConvertImageRendererToDrawFlags(mFlags));
4930 case eStyleImageType_Gradient:
4932 nsCSSRendering::PaintGradient(aPresContext, aRenderingContext,
4933 mGradientData, aDirtyRect,
4934 aDest, aFill, aSrc, mSize);
4935 return DrawResult::SUCCESS;
4937 case eStyleImageType_Element:
4939 nsRefPtr<gfxDrawable> drawable = DrawableForElement(aDest,
4940 aRenderingContext);
4941 if (!drawable) {
4942 NS_WARNING("Could not create drawable for element");
4943 return DrawResult::TEMPORARY_ERROR;
4946 gfxContext* ctx = aRenderingContext.ThebesContext();
4947 gfxContext::GraphicsOperator op = ctx->CurrentOperator();
4948 if (op != gfxContext::OPERATOR_OVER) {
4949 ctx->PushGroup(gfxContentType::COLOR_ALPHA);
4950 ctx->SetOperator(gfxContext::OPERATOR_OVER);
4953 nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
4954 nsLayoutUtils::DrawImage(*aRenderingContext.ThebesContext(),
4955 aPresContext, image,
4956 filter, aDest, aFill, aAnchor, aDirtyRect,
4957 ConvertImageRendererToDrawFlags(mFlags));
4959 if (op != gfxContext::OPERATOR_OVER) {
4960 ctx->PopGroupToSource();
4961 ctx->Paint();
4964 return DrawResult::SUCCESS;
4966 case eStyleImageType_Null:
4967 default:
4968 return DrawResult::SUCCESS;
4972 already_AddRefed<gfxDrawable>
4973 nsImageRenderer::DrawableForElement(const nsRect& aImageRect,
4974 nsRenderingContext& aRenderingContext)
4976 NS_ASSERTION(mType == eStyleImageType_Element,
4977 "DrawableForElement only makes sense if backed by an element");
4978 if (mPaintServerFrame) {
4979 int32_t appUnitsPerDevPixel = mForFrame->PresContext()->AppUnitsPerDevPixel();
4980 nsRect destRect = aImageRect - aImageRect.TopLeft();
4981 nsIntSize roundedOut = destRect.ToOutsidePixels(appUnitsPerDevPixel).Size();
4982 gfxIntSize imageSize(roundedOut.width, roundedOut.height);
4983 nsRefPtr<gfxDrawable> drawable =
4984 nsSVGIntegrationUtils::DrawableFromPaintServer(
4985 mPaintServerFrame, mForFrame, mSize, imageSize,
4986 aRenderingContext.GetDrawTarget(),
4987 aRenderingContext.ThebesContext()->CurrentMatrix(),
4988 mFlags & FLAG_SYNC_DECODE_IMAGES
4989 ? nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES
4990 : 0);
4992 return drawable.forget();
4994 NS_ASSERTION(mImageElementSurface.mSourceSurface, "Surface should be ready.");
4995 nsRefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(
4996 mImageElementSurface.mSourceSurface,
4997 mImageElementSurface.mSize);
4998 return drawable.forget();
5001 DrawResult
5002 nsImageRenderer::DrawBackground(nsPresContext* aPresContext,
5003 nsRenderingContext& aRenderingContext,
5004 const nsRect& aDest,
5005 const nsRect& aFill,
5006 const nsPoint& aAnchor,
5007 const nsRect& aDirty)
5009 if (!mIsReady) {
5010 NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
5011 return DrawResult::TEMPORARY_ERROR;
5013 if (aDest.IsEmpty() || aFill.IsEmpty() ||
5014 mSize.width <= 0 || mSize.height <= 0) {
5015 return DrawResult::SUCCESS;
5018 return Draw(aPresContext, aRenderingContext,
5019 aDirty, aDest, aFill, aAnchor,
5020 CSSIntRect(0, 0,
5021 nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
5022 nsPresContext::AppUnitsToIntCSSPixels(mSize.height)));
5026 * Compute the size and position of the master copy of the image. I.e., a single
5027 * tile used to fill the dest rect.
5028 * aFill The destination rect to be filled
5029 * aHFill and aVFill are the repeat patterns for the component -
5030 * NS_STYLE_BORDER_IMAGE_REPEAT_* - i.e., how a tiling unit is used to fill aFill
5031 * aUnitSize The size of the source rect in dest coords.
5033 static nsRect
5034 ComputeTile(const nsRect& aFill,
5035 uint8_t aHFill,
5036 uint8_t aVFill,
5037 const nsSize& aUnitSize)
5039 nsRect tile;
5040 switch (aHFill) {
5041 case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
5042 tile.x = aFill.x;
5043 tile.width = aFill.width;
5044 break;
5045 case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
5046 tile.x = aFill.x + aFill.width/2 - aUnitSize.width/2;
5047 tile.width = aUnitSize.width;
5048 break;
5049 case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
5050 tile.x = aFill.x;
5051 tile.width = aFill.width / ceil(gfxFloat(aFill.width)/aUnitSize.width);
5052 break;
5053 default:
5054 NS_NOTREACHED("unrecognized border-image fill style");
5057 switch (aVFill) {
5058 case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
5059 tile.y = aFill.y;
5060 tile.height = aFill.height;
5061 break;
5062 case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
5063 tile.y = aFill.y + aFill.height/2 - aUnitSize.height/2;
5064 tile.height = aUnitSize.height;
5065 break;
5066 case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
5067 tile.y = aFill.y;
5068 tile.height = aFill.height/ceil(gfxFloat(aFill.height)/aUnitSize.height);
5069 break;
5070 default:
5071 NS_NOTREACHED("unrecognized border-image fill style");
5074 return tile;
5078 * Returns true if the given set of arguments will require the tiles which fill
5079 * the dest rect to be scaled from the source tile. See comment on ComputeTile
5080 * for argument descriptions.
5082 static bool
5083 RequiresScaling(const nsRect& aFill,
5084 uint8_t aHFill,
5085 uint8_t aVFill,
5086 const nsSize& aUnitSize)
5088 // If we have no tiling in either direction, we can skip the intermediate
5089 // scaling step.
5090 return (aHFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH ||
5091 aVFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH) &&
5092 (aUnitSize.width != aFill.width ||
5093 aUnitSize.height != aFill.height);
5096 void
5097 nsImageRenderer::DrawBorderImageComponent(nsPresContext* aPresContext,
5098 nsRenderingContext& aRenderingContext,
5099 const nsRect& aDirtyRect,
5100 const nsRect& aFill,
5101 const CSSIntRect& aSrc,
5102 uint8_t aHFill,
5103 uint8_t aVFill,
5104 const nsSize& aUnitSize,
5105 uint8_t aIndex)
5107 if (!mIsReady) {
5108 NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
5109 return;
5111 if (aFill.IsEmpty() || aSrc.IsEmpty()) {
5112 return;
5115 if (mType == eStyleImageType_Image || mType == eStyleImageType_Element) {
5116 nsCOMPtr<imgIContainer> subImage;
5118 // Retrieve or create the subimage we'll draw.
5119 nsIntRect srcRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height);
5120 if (mType == eStyleImageType_Image) {
5121 if ((subImage = mImage->GetSubImage(aIndex)) == nullptr) {
5122 subImage = ImageOps::Clip(mImageContainer, srcRect);
5123 mImage->SetSubImage(aIndex, subImage);
5125 } else {
5126 // This path, for eStyleImageType_Element, is currently slower than it
5127 // needs to be because we don't cache anything. (In particular, if we have
5128 // to draw to a temporary surface inside ClippedImage, we don't cache that
5129 // temporary surface since we immediately throw the ClippedImage we create
5130 // here away.) However, if we did cache, we'd need to know when to
5131 // invalidate that cache, and it's not clear that it's worth the trouble
5132 // since using border-image with -moz-element is rare.
5134 nsRefPtr<gfxDrawable> drawable = DrawableForElement(nsRect(nsPoint(), mSize),
5135 aRenderingContext);
5136 if (!drawable) {
5137 NS_WARNING("Could not create drawable for element");
5138 return;
5141 nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
5142 subImage = ImageOps::Clip(image, srcRect);
5145 GraphicsFilter graphicsFilter =
5146 nsLayoutUtils::GetGraphicsFilterForFrame(mForFrame);
5148 if (!RequiresScaling(aFill, aHFill, aVFill, aUnitSize)) {
5149 nsLayoutUtils::DrawSingleImage(*aRenderingContext.ThebesContext(),
5150 aPresContext,
5151 subImage,
5152 graphicsFilter,
5153 aFill, aDirtyRect,
5154 nullptr,
5155 imgIContainer::FLAG_NONE);
5156 return;
5159 nsRect tile = ComputeTile(aFill, aHFill, aVFill, aUnitSize);
5160 nsLayoutUtils::DrawImage(*aRenderingContext.ThebesContext(),
5161 aPresContext,
5162 subImage,
5163 graphicsFilter,
5164 tile, aFill, tile.TopLeft(), aDirtyRect,
5165 imgIContainer::FLAG_NONE);
5166 return;
5169 nsRect destTile = RequiresScaling(aFill, aHFill, aVFill, aUnitSize)
5170 ? ComputeTile(aFill, aHFill, aVFill, aUnitSize)
5171 : aFill;
5173 Draw(aPresContext, aRenderingContext, aDirtyRect, destTile,
5174 aFill, destTile.TopLeft(), aSrc);
5177 bool
5178 nsImageRenderer::IsRasterImage()
5180 if (mType != eStyleImageType_Image || !mImageContainer)
5181 return false;
5182 return mImageContainer->GetType() == imgIContainer::TYPE_RASTER;
5185 bool
5186 nsImageRenderer::IsAnimatedImage()
5188 if (mType != eStyleImageType_Image || !mImageContainer)
5189 return false;
5190 bool animated = false;
5191 if (NS_SUCCEEDED(mImageContainer->GetAnimated(&animated)) && animated)
5192 return true;
5194 return false;
5197 already_AddRefed<mozilla::layers::ImageContainer>
5198 nsImageRenderer::GetContainer(LayerManager* aManager)
5200 if (mType != eStyleImageType_Image || !mImageContainer)
5201 return nullptr;
5203 nsRefPtr<ImageContainer> container;
5204 nsresult rv = mImageContainer->GetImageContainer(aManager, getter_AddRefs(container));
5205 NS_ENSURE_SUCCESS(rv, nullptr);
5206 return container.forget();
5209 #define MAX_BLUR_RADIUS 300
5210 #define MAX_SPREAD_RADIUS 50
5212 static inline gfxPoint ComputeBlurStdDev(nscoord aBlurRadius,
5213 int32_t aAppUnitsPerDevPixel,
5214 gfxFloat aScaleX,
5215 gfxFloat aScaleY)
5217 // http://dev.w3.org/csswg/css3-background/#box-shadow says that the
5218 // standard deviation of the blur should be half the given blur value.
5219 gfxFloat blurStdDev = gfxFloat(aBlurRadius) / gfxFloat(aAppUnitsPerDevPixel);
5221 return gfxPoint(std::min((blurStdDev * aScaleX),
5222 gfxFloat(MAX_BLUR_RADIUS)) / 2.0,
5223 std::min((blurStdDev * aScaleY),
5224 gfxFloat(MAX_BLUR_RADIUS)) / 2.0);
5227 static inline gfxIntSize
5228 ComputeBlurRadius(nscoord aBlurRadius,
5229 int32_t aAppUnitsPerDevPixel,
5230 gfxFloat aScaleX = 1.0,
5231 gfxFloat aScaleY = 1.0)
5233 gfxPoint scaledBlurStdDev = ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel,
5234 aScaleX, aScaleY);
5235 return
5236 gfxAlphaBoxBlur::CalculateBlurRadius(scaledBlurStdDev);
5239 // -----
5240 // nsContextBoxBlur
5241 // -----
5242 gfxContext*
5243 nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius,
5244 nscoord aBlurRadius,
5245 int32_t aAppUnitsPerDevPixel,
5246 gfxContext* aDestinationCtx,
5247 const nsRect& aDirtyRect,
5248 const gfxRect* aSkipRect,
5249 uint32_t aFlags)
5251 if (aRect.IsEmpty()) {
5252 mContext = nullptr;
5253 return nullptr;
5256 gfxFloat scaleX = 1;
5257 gfxFloat scaleY = 1;
5259 // Do blurs in device space when possible.
5260 // Chrome/Skia always does the blurs in device space
5261 // and will sometimes get incorrect results (e.g. rotated blurs)
5262 gfxMatrix transform = aDestinationCtx->CurrentMatrix();
5263 // XXX: we could probably handle negative scales but for now it's easier just to fallback
5264 if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 || transform._22 <= 0.0) {
5265 transform = gfxMatrix();
5266 } else {
5267 scaleX = transform._11;
5268 scaleY = transform._22;
5271 // compute a large or smaller blur radius
5272 gfxIntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
5273 gfxIntSize spreadRadius = gfxIntSize(std::min(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel),
5274 int32_t(MAX_SPREAD_RADIUS)),
5275 std::min(int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel),
5276 int32_t(MAX_SPREAD_RADIUS)));
5277 mDestinationCtx = aDestinationCtx;
5279 // If not blurring, draw directly onto the destination device
5280 if (blurRadius.width <= 0 && blurRadius.height <= 0 &&
5281 spreadRadius.width <= 0 && spreadRadius.height <= 0 &&
5282 !(aFlags & FORCE_MASK)) {
5283 mContext = aDestinationCtx;
5284 return mContext;
5287 // Convert from app units to device pixels
5288 gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel);
5290 gfxRect dirtyRect =
5291 nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
5292 dirtyRect.RoundOut();
5294 rect = transform.TransformBounds(rect);
5296 mPreTransformed = !transform.IsIdentity();
5298 // Create the temporary surface for blurring
5299 dirtyRect = transform.TransformBounds(dirtyRect);
5300 if (aSkipRect) {
5301 gfxRect skipRect = transform.TransformBounds(*aSkipRect);
5302 mContext = mAlphaBoxBlur.Init(rect, spreadRadius,
5303 blurRadius, &dirtyRect, &skipRect);
5304 } else {
5305 mContext = mAlphaBoxBlur.Init(rect, spreadRadius,
5306 blurRadius, &dirtyRect, nullptr);
5309 if (mContext) {
5310 // we don't need to blur if skipRect is equal to rect
5311 // and mContext will be nullptr
5312 mContext->Multiply(transform);
5314 return mContext;
5317 void
5318 nsContextBoxBlur::DoPaint()
5320 if (mContext == mDestinationCtx)
5321 return;
5323 gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx);
5325 if (mPreTransformed) {
5326 mDestinationCtx->SetMatrix(gfxMatrix());
5329 mAlphaBoxBlur.Paint(mDestinationCtx);
5332 gfxContext*
5333 nsContextBoxBlur::GetContext()
5335 return mContext;
5338 /* static */ nsMargin
5339 nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius,
5340 int32_t aAppUnitsPerDevPixel)
5342 gfxIntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel);
5344 nsMargin result;
5345 result.top = result.bottom = blurRadius.height * aAppUnitsPerDevPixel;
5346 result.left = result.right = blurRadius.width * aAppUnitsPerDevPixel;
5347 return result;
5350 /* static */ void
5351 nsContextBoxBlur::BlurRectangle(gfxContext* aDestinationCtx,
5352 const nsRect& aRect,
5353 int32_t aAppUnitsPerDevPixel,
5354 RectCornerRadii* aCornerRadii,
5355 nscoord aBlurRadius,
5356 const gfxRGBA& aShadowColor,
5357 const nsRect& aDirtyRect,
5358 const gfxRect& aSkipRect)
5360 DrawTarget& aDestDrawTarget = *aDestinationCtx->GetDrawTarget();
5362 if (aRect.IsEmpty()) {
5363 return;
5366 Rect shadowGfxRect = NSRectToRect(aRect, aAppUnitsPerDevPixel);
5368 if (aBlurRadius <= 0) {
5369 ColorPattern color(ToDeviceColor(aShadowColor));
5370 if (aCornerRadii) {
5371 RefPtr<Path> roundedRect = MakePathForRoundedRect(aDestDrawTarget,
5372 shadowGfxRect,
5373 *aCornerRadii);
5374 aDestDrawTarget.Fill(roundedRect, color);
5375 } else {
5376 aDestDrawTarget.FillRect(shadowGfxRect, color);
5378 return;
5381 gfxFloat scaleX = 1;
5382 gfxFloat scaleY = 1;
5384 // Do blurs in device space when possible.
5385 // Chrome/Skia always does the blurs in device space
5386 // and will sometimes get incorrect results (e.g. rotated blurs)
5387 gfxMatrix transform = aDestinationCtx->CurrentMatrix();
5388 // XXX: we could probably handle negative scales but for now it's easier just to fallback
5389 if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && transform._22 > 0.0) {
5390 scaleX = transform._11;
5391 scaleY = transform._22;
5392 aDestinationCtx->SetMatrix(gfxMatrix());
5393 } else {
5394 transform = gfxMatrix();
5397 gfxPoint blurStdDev = ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
5399 gfxRect dirtyRect =
5400 nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
5401 dirtyRect.RoundOut();
5403 gfxRect shadowThebesRect = transform.TransformBounds(ThebesRect(shadowGfxRect));
5404 dirtyRect = transform.TransformBounds(dirtyRect);
5405 gfxRect skipRect = transform.TransformBounds(aSkipRect);
5407 if (aCornerRadii) {
5408 aCornerRadii->Scale(scaleX, scaleY);
5411 gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx,
5412 shadowThebesRect,
5413 aCornerRadii,
5414 blurStdDev,
5415 aShadowColor,
5416 dirtyRect,
5417 skipRect);