Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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 "mozilla/DebugOnly.h"
12 #include "mozilla/HashFunctions.h"
13 #include "mozilla/MathAlgorithms.h"
15 #include "nsStyleConsts.h"
16 #include "nsPresContext.h"
17 #include "nsIFrame.h"
18 #include "nsPoint.h"
19 #include "nsRect.h"
20 #include "nsIPresShell.h"
21 #include "nsFrameManager.h"
22 #include "nsStyleContext.h"
23 #include "nsGkAtoms.h"
24 #include "nsCSSAnonBoxes.h"
25 #include "nsIContent.h"
26 #include "nsIDocumentInlines.h"
27 #include "nsIScrollableFrame.h"
28 #include "imgIRequest.h"
29 #include "imgIContainer.h"
30 #include "ImageOps.h"
31 #include "nsCSSRendering.h"
32 #include "nsCSSColorUtils.h"
33 #include "nsITheme.h"
34 #include "nsLayoutUtils.h"
35 #include "nsBlockFrame.h"
36 #include "gfxContext.h"
37 #include "nsRenderingContext.h"
38 #include "nsStyleStructInlines.h"
39 #include "nsCSSFrameConstructor.h"
40 #include "nsCSSProps.h"
41 #include "nsContentUtils.h"
42 #include "nsSVGEffects.h"
43 #include "nsSVGIntegrationUtils.h"
44 #include "gfxDrawable.h"
45 #include "GeckoProfiler.h"
46 #include "nsCSSRenderingBorders.h"
47 #include "mozilla/css/ImageLoader.h"
48 #include "ImageContainer.h"
49 #include "mozilla/Telemetry.h"
50 #include "gfxUtils.h"
51 #include "gfxColor.h"
52 #include "gfxGradientCache.h"
53 #include "GraphicsFilter.h"
54 #include <algorithm>
56 using namespace mozilla;
57 using namespace mozilla::css;
58 using namespace mozilla::gfx;
59 using mozilla::image::ImageOps;
60 using mozilla::CSSSizeOrRatio;
62 static int gFrameTreeLockCount = 0;
64 // To avoid storing this data on nsInlineFrame (bloat) and to avoid
65 // recalculating this for each frame in a continuation (perf), hold
66 // a cache of various coordinate information that we need in order
67 // to paint inline backgrounds.
68 struct InlineBackgroundData
69 {
70 InlineBackgroundData()
71 : mFrame(nullptr), mBlockFrame(nullptr)
72 {
73 }
75 ~InlineBackgroundData()
76 {
77 }
79 void Reset()
80 {
81 mBoundingBox.SetRect(0,0,0,0);
82 mContinuationPoint = mLineContinuationPoint = mUnbrokenWidth = 0;
83 mFrame = mBlockFrame = nullptr;
84 }
86 nsRect GetContinuousRect(nsIFrame* aFrame)
87 {
88 SetFrame(aFrame);
90 nscoord x;
91 if (mBidiEnabled) {
92 x = mLineContinuationPoint;
94 // Scan continuations on the same line as aFrame and accumulate the widths
95 // of frames that are to the left (if this is an LTR block) or right
96 // (if it's RTL) of the current one.
97 bool isRtlBlock = (mBlockFrame->StyleVisibility()->mDirection ==
98 NS_STYLE_DIRECTION_RTL);
99 nscoord curOffset = aFrame->GetOffsetTo(mBlockFrame).x;
101 // No need to use our GetPrevContinuation/GetNextContinuation methods
102 // here, since ib-split siblings are certainly not on the same line.
104 nsIFrame* inlineFrame = aFrame->GetPrevContinuation();
105 // If the continuation is fluid we know inlineFrame is not on the same line.
106 // If it's not fluid, we need to test further to be sure.
107 while (inlineFrame && !inlineFrame->GetNextInFlow() &&
108 AreOnSameLine(aFrame, inlineFrame)) {
109 nscoord frameXOffset = inlineFrame->GetOffsetTo(mBlockFrame).x;
110 if(isRtlBlock == (frameXOffset >= curOffset)) {
111 x += inlineFrame->GetSize().width;
112 }
113 inlineFrame = inlineFrame->GetPrevContinuation();
114 }
116 inlineFrame = aFrame->GetNextContinuation();
117 while (inlineFrame && !inlineFrame->GetPrevInFlow() &&
118 AreOnSameLine(aFrame, inlineFrame)) {
119 nscoord frameXOffset = inlineFrame->GetOffsetTo(mBlockFrame).x;
120 if(isRtlBlock == (frameXOffset >= curOffset)) {
121 x += inlineFrame->GetSize().width;
122 }
123 inlineFrame = inlineFrame->GetNextContinuation();
124 }
125 if (isRtlBlock) {
126 // aFrame itself is also to the right of its left edge, so add its width.
127 x += aFrame->GetSize().width;
128 // x is now the distance from the left edge of aFrame to the right edge
129 // of the unbroken content. Change it to indicate the distance from the
130 // left edge of the unbroken content to the left edge of aFrame.
131 x = mUnbrokenWidth - x;
132 }
133 } else {
134 x = mContinuationPoint;
135 }
137 // Assume background-origin: border and return a rect with offsets
138 // relative to (0,0). If we have a different background-origin,
139 // then our rect should be deflated appropriately by our caller.
140 return nsRect(-x, 0, mUnbrokenWidth, mFrame->GetSize().height);
141 }
143 nsRect GetBoundingRect(nsIFrame* aFrame)
144 {
145 SetFrame(aFrame);
147 // Move the offsets relative to (0,0) which puts the bounding box into
148 // our coordinate system rather than our parent's. We do this by
149 // moving it the back distance from us to the bounding box.
150 // This also assumes background-origin: border, so our caller will
151 // need to deflate us if needed.
152 nsRect boundingBox(mBoundingBox);
153 nsPoint point = mFrame->GetPosition();
154 boundingBox.MoveBy(-point.x, -point.y);
156 return boundingBox;
157 }
159 protected:
160 nsIFrame* mFrame;
161 nsBlockFrame* mBlockFrame;
162 nsRect mBoundingBox;
163 nscoord mContinuationPoint;
164 nscoord mUnbrokenWidth;
165 nscoord mLineContinuationPoint;
166 bool mBidiEnabled;
168 void SetFrame(nsIFrame* aFrame)
169 {
170 NS_PRECONDITION(aFrame, "Need a frame");
171 NS_ASSERTION(gFrameTreeLockCount > 0,
172 "Can't call this when frame tree is not locked");
174 if (aFrame == mFrame) {
175 return;
176 }
178 nsIFrame *prevContinuation = GetPrevContinuation(aFrame);
180 if (!prevContinuation || mFrame != prevContinuation) {
181 // Ok, we've got the wrong frame. We have to start from scratch.
182 Reset();
183 Init(aFrame);
184 return;
185 }
187 // Get our last frame's size and add its width to our continuation
188 // point before we cache the new frame.
189 mContinuationPoint += mFrame->GetSize().width;
191 // If this a new line, update mLineContinuationPoint.
192 if (mBidiEnabled &&
193 (aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) {
194 mLineContinuationPoint = mContinuationPoint;
195 }
197 mFrame = aFrame;
198 }
200 nsIFrame* GetPrevContinuation(nsIFrame* aFrame)
201 {
202 nsIFrame* prevCont = aFrame->GetPrevContinuation();
203 if (!prevCont &&
204 (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
205 nsIFrame* block = static_cast<nsIFrame*>
206 (aFrame->Properties().Get(nsIFrame::IBSplitPrevSibling()));
207 if (block) {
208 // The {ib} properties are only stored on first continuations
209 NS_ASSERTION(!block->GetPrevContinuation(),
210 "Incorrect value for IBSplitPrevSibling");
211 prevCont = static_cast<nsIFrame*>
212 (block->Properties().Get(nsIFrame::IBSplitPrevSibling()));
213 NS_ASSERTION(prevCont, "How did that happen?");
214 }
215 }
216 return prevCont;
217 }
219 nsIFrame* GetNextContinuation(nsIFrame* aFrame)
220 {
221 nsIFrame* nextCont = aFrame->GetNextContinuation();
222 if (!nextCont &&
223 (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
224 // The {ib} properties are only stored on first continuations
225 aFrame = aFrame->FirstContinuation();
226 nsIFrame* block = static_cast<nsIFrame*>
227 (aFrame->Properties().Get(nsIFrame::IBSplitSibling()));
228 if (block) {
229 nextCont = static_cast<nsIFrame*>
230 (block->Properties().Get(nsIFrame::IBSplitSibling()));
231 NS_ASSERTION(nextCont, "How did that happen?");
232 }
233 }
234 return nextCont;
235 }
237 void Init(nsIFrame* aFrame)
238 {
239 mBidiEnabled = aFrame->PresContext()->BidiEnabled();
240 if (mBidiEnabled) {
241 // Find the containing block frame
242 nsIFrame* frame = aFrame;
243 do {
244 frame = frame->GetParent();
245 mBlockFrame = do_QueryFrame(frame);
246 }
247 while (frame && frame->IsFrameOfType(nsIFrame::eLineParticipant));
249 NS_ASSERTION(mBlockFrame, "Cannot find containing block.");
250 }
252 // Start with the previous flow frame as our continuation point
253 // is the total of the widths of the previous frames.
254 nsIFrame* inlineFrame = GetPrevContinuation(aFrame);
256 while (inlineFrame) {
257 nsRect rect = inlineFrame->GetRect();
258 mContinuationPoint += rect.width;
259 if (mBidiEnabled && !AreOnSameLine(aFrame, inlineFrame)) {
260 mLineContinuationPoint += rect.width;
261 }
262 mUnbrokenWidth += rect.width;
263 mBoundingBox.UnionRect(mBoundingBox, rect);
264 inlineFrame = GetPrevContinuation(inlineFrame);
265 }
267 // Next add this frame and subsequent frames to the bounding box and
268 // unbroken width.
269 inlineFrame = aFrame;
270 while (inlineFrame) {
271 nsRect rect = inlineFrame->GetRect();
272 mUnbrokenWidth += rect.width;
273 mBoundingBox.UnionRect(mBoundingBox, rect);
274 inlineFrame = GetNextContinuation(inlineFrame);
275 }
277 mFrame = aFrame;
278 }
280 bool AreOnSameLine(nsIFrame* aFrame1, nsIFrame* aFrame2) {
281 bool isValid1, isValid2;
282 nsBlockInFlowLineIterator it1(mBlockFrame, aFrame1, &isValid1);
283 nsBlockInFlowLineIterator it2(mBlockFrame, aFrame2, &isValid2);
284 return isValid1 && isValid2 &&
285 // Make sure aFrame1 and aFrame2 are in the same continuation of
286 // mBlockFrame.
287 it1.GetContainer() == it2.GetContainer() &&
288 // And on the same line in it
289 it1.GetLine() == it2.GetLine();
290 }
291 };
293 // A resolved color stop --- with a specific position along the gradient line,
294 // and a Thebes color
295 struct ColorStop {
296 ColorStop(double aPosition, gfxRGBA aColor) :
297 mPosition(aPosition), mColor(aColor) {}
298 double mPosition; // along the gradient line; 0=start, 1=end
299 gfxRGBA mColor;
300 };
302 /* Local functions */
303 static void DrawBorderImage(nsPresContext* aPresContext,
304 nsRenderingContext& aRenderingContext,
305 nsIFrame* aForFrame,
306 const nsRect& aBorderArea,
307 const nsStyleBorder& aStyleBorder,
308 const nsRect& aDirtyRect);
310 static nscolor MakeBevelColor(mozilla::css::Side whichSide, uint8_t style,
311 nscolor aBackgroundColor,
312 nscolor aBorderColor);
314 static InlineBackgroundData* gInlineBGData = nullptr;
316 // Initialize any static variables used by nsCSSRendering.
317 void nsCSSRendering::Init()
318 {
319 NS_ASSERTION(!gInlineBGData, "Init called twice");
320 gInlineBGData = new InlineBackgroundData();
321 }
323 // Clean up any global variables used by nsCSSRendering.
324 void nsCSSRendering::Shutdown()
325 {
326 delete gInlineBGData;
327 gInlineBGData = nullptr;
328 }
330 /**
331 * Make a bevel color
332 */
333 static nscolor
334 MakeBevelColor(mozilla::css::Side whichSide, uint8_t style,
335 nscolor aBackgroundColor, nscolor aBorderColor)
336 {
338 nscolor colors[2];
339 nscolor theColor;
341 // Given a background color and a border color
342 // calculate the color used for the shading
343 NS_GetSpecial3DColors(colors, aBackgroundColor, aBorderColor);
345 if ((style == NS_STYLE_BORDER_STYLE_OUTSET) ||
346 (style == NS_STYLE_BORDER_STYLE_RIDGE)) {
347 // Flip colors for these two border styles
348 switch (whichSide) {
349 case NS_SIDE_BOTTOM: whichSide = NS_SIDE_TOP; break;
350 case NS_SIDE_RIGHT: whichSide = NS_SIDE_LEFT; break;
351 case NS_SIDE_TOP: whichSide = NS_SIDE_BOTTOM; break;
352 case NS_SIDE_LEFT: whichSide = NS_SIDE_RIGHT; break;
353 }
354 }
356 switch (whichSide) {
357 case NS_SIDE_BOTTOM:
358 theColor = colors[1];
359 break;
360 case NS_SIDE_RIGHT:
361 theColor = colors[1];
362 break;
363 case NS_SIDE_TOP:
364 theColor = colors[0];
365 break;
366 case NS_SIDE_LEFT:
367 default:
368 theColor = colors[0];
369 break;
370 }
371 return theColor;
372 }
374 //----------------------------------------------------------------------
375 // Thebes Border Rendering Code Start
377 /*
378 * Compute the float-pixel radii that should be used for drawing
379 * this border/outline, given the various input bits.
380 */
381 /* static */ void
382 nsCSSRendering::ComputePixelRadii(const nscoord *aAppUnitsRadii,
383 nscoord aAppUnitsPerPixel,
384 gfxCornerSizes *oBorderRadii)
385 {
386 gfxFloat radii[8];
387 NS_FOR_CSS_HALF_CORNERS(corner)
388 radii[corner] = gfxFloat(aAppUnitsRadii[corner]) / aAppUnitsPerPixel;
390 (*oBorderRadii)[C_TL] = gfxSize(radii[NS_CORNER_TOP_LEFT_X],
391 radii[NS_CORNER_TOP_LEFT_Y]);
392 (*oBorderRadii)[C_TR] = gfxSize(radii[NS_CORNER_TOP_RIGHT_X],
393 radii[NS_CORNER_TOP_RIGHT_Y]);
394 (*oBorderRadii)[C_BR] = gfxSize(radii[NS_CORNER_BOTTOM_RIGHT_X],
395 radii[NS_CORNER_BOTTOM_RIGHT_Y]);
396 (*oBorderRadii)[C_BL] = gfxSize(radii[NS_CORNER_BOTTOM_LEFT_X],
397 radii[NS_CORNER_BOTTOM_LEFT_Y]);
398 }
400 void
401 nsCSSRendering::PaintBorder(nsPresContext* aPresContext,
402 nsRenderingContext& aRenderingContext,
403 nsIFrame* aForFrame,
404 const nsRect& aDirtyRect,
405 const nsRect& aBorderArea,
406 nsStyleContext* aStyleContext,
407 int aSkipSides)
408 {
409 PROFILER_LABEL("nsCSSRendering", "PaintBorder");
410 nsStyleContext *styleIfVisited = aStyleContext->GetStyleIfVisited();
411 const nsStyleBorder *styleBorder = aStyleContext->StyleBorder();
412 // Don't check RelevantLinkVisited here, since we want to take the
413 // same amount of time whether or not it's true.
414 if (!styleIfVisited) {
415 PaintBorderWithStyleBorder(aPresContext, aRenderingContext, aForFrame,
416 aDirtyRect, aBorderArea, *styleBorder,
417 aStyleContext, aSkipSides);
418 return;
419 }
421 nsStyleBorder newStyleBorder(*styleBorder);
422 // We could do something fancy to avoid the TrackImage/UntrackImage
423 // work, but it doesn't seem worth it. (We need to call TrackImage
424 // since we're not going through nsRuleNode::ComputeBorderData.)
425 newStyleBorder.TrackImage(aPresContext);
427 NS_FOR_CSS_SIDES(side) {
428 newStyleBorder.SetBorderColor(side,
429 aStyleContext->GetVisitedDependentColor(
430 nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color)[side]));
431 }
432 PaintBorderWithStyleBorder(aPresContext, aRenderingContext, aForFrame,
433 aDirtyRect, aBorderArea, newStyleBorder,
434 aStyleContext, aSkipSides);
436 // We could do something fancy to avoid the TrackImage/UntrackImage
437 // work, but it doesn't seem worth it. (We need to call UntrackImage
438 // since we're not going through nsStyleBorder::Destroy.)
439 newStyleBorder.UntrackImage(aPresContext);
440 }
442 void
443 nsCSSRendering::PaintBorderWithStyleBorder(nsPresContext* aPresContext,
444 nsRenderingContext& aRenderingContext,
445 nsIFrame* aForFrame,
446 const nsRect& aDirtyRect,
447 const nsRect& aBorderArea,
448 const nsStyleBorder& aStyleBorder,
449 nsStyleContext* aStyleContext,
450 int aSkipSides)
451 {
452 nsMargin border;
453 nscoord twipsRadii[8];
454 nsCompatibility compatMode = aPresContext->CompatibilityMode();
456 SN("++ PaintBorder");
458 // Check to see if we have an appearance defined. If so, we let the theme
459 // renderer draw the border. DO not get the data from aForFrame, since the passed in style context
460 // may be different! Always use |aStyleContext|!
461 const nsStyleDisplay* displayData = aStyleContext->StyleDisplay();
462 if (displayData->mAppearance) {
463 nsITheme *theme = aPresContext->GetTheme();
464 if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame, displayData->mAppearance))
465 return; // Let the theme handle it.
466 }
468 if (aStyleBorder.IsBorderImageLoaded()) {
469 DrawBorderImage(aPresContext, aRenderingContext, aForFrame,
470 aBorderArea, aStyleBorder, aDirtyRect);
471 return;
472 }
474 // Get our style context's color struct.
475 const nsStyleColor* ourColor = aStyleContext->StyleColor();
477 // in NavQuirks mode we want to use the parent's context as a starting point
478 // for determining the background color
479 nsIFrame* bgFrame = nsCSSRendering::FindNonTransparentBackgroundFrame
480 (aForFrame, compatMode == eCompatibility_NavQuirks ? true : false);
481 nsStyleContext* bgContext = bgFrame->StyleContext();
482 nscolor bgColor =
483 bgContext->GetVisitedDependentColor(eCSSProperty_background_color);
485 border = aStyleBorder.GetComputedBorder();
486 if ((0 == border.left) && (0 == border.right) &&
487 (0 == border.top) && (0 == border.bottom)) {
488 // Empty border area
489 return;
490 }
492 nsSize frameSize = aForFrame->GetSize();
493 if (&aStyleBorder == aForFrame->StyleBorder() &&
494 frameSize == aBorderArea.Size()) {
495 aForFrame->GetBorderRadii(twipsRadii);
496 } else {
497 nsIFrame::ComputeBorderRadii(aStyleBorder.mBorderRadius, frameSize,
498 aBorderArea.Size(), aSkipSides, twipsRadii);
499 }
501 // Turn off rendering for all of the zero sized sides
502 if (aSkipSides & SIDE_BIT_TOP) border.top = 0;
503 if (aSkipSides & SIDE_BIT_RIGHT) border.right = 0;
504 if (aSkipSides & SIDE_BIT_BOTTOM) border.bottom = 0;
505 if (aSkipSides & SIDE_BIT_LEFT) border.left = 0;
507 // get the inside and outside parts of the border
508 nsRect outerRect(aBorderArea);
510 SF(" outerRect: %d %d %d %d\n", outerRect.x, outerRect.y, outerRect.width, outerRect.height);
512 // we can assume that we're already clipped to aDirtyRect -- I think? (!?)
514 // Get our conversion values
515 nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
517 // convert outer and inner rects
518 gfxRect oRect(nsLayoutUtils::RectToGfxRect(outerRect, twipsPerPixel));
520 // convert the border widths
521 gfxFloat borderWidths[4] = { gfxFloat(border.top / twipsPerPixel),
522 gfxFloat(border.right / twipsPerPixel),
523 gfxFloat(border.bottom / twipsPerPixel),
524 gfxFloat(border.left / twipsPerPixel) };
526 // convert the radii
527 gfxCornerSizes borderRadii;
528 ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii);
530 uint8_t borderStyles[4];
531 nscolor borderColors[4];
532 nsBorderColors *compositeColors[4];
534 // pull out styles, colors, composite colors
535 NS_FOR_CSS_SIDES (i) {
536 bool foreground;
537 borderStyles[i] = aStyleBorder.GetBorderStyle(i);
538 aStyleBorder.GetBorderColor(i, borderColors[i], foreground);
539 aStyleBorder.GetCompositeColors(i, &compositeColors[i]);
541 if (foreground)
542 borderColors[i] = ourColor->mColor;
543 }
545 SF(" borderStyles: %d %d %d %d\n", borderStyles[0], borderStyles[1], borderStyles[2], borderStyles[3]);
547 // start drawing
548 gfxContext *ctx = aRenderingContext.ThebesContext();
550 ctx->Save();
552 #if 0
553 // this will draw a transparent red backround underneath the oRect area
554 ctx->Save();
555 ctx->Rectangle(oRect);
556 ctx->SetColor(gfxRGBA(1.0, 0.0, 0.0, 0.5));
557 ctx->Fill();
558 ctx->Restore();
559 #endif
561 //SF ("borderRadii: %f %f %f %f\n", borderRadii[0], borderRadii[1], borderRadii[2], borderRadii[3]);
563 nsCSSBorderRenderer br(twipsPerPixel,
564 ctx,
565 oRect,
566 borderStyles,
567 borderWidths,
568 borderRadii,
569 borderColors,
570 compositeColors,
571 aSkipSides,
572 bgColor);
573 br.DrawBorders();
575 ctx->Restore();
577 SN();
578 }
580 static nsRect
581 GetOutlineInnerRect(nsIFrame* aFrame)
582 {
583 nsRect* savedOutlineInnerRect = static_cast<nsRect*>
584 (aFrame->Properties().Get(nsIFrame::OutlineInnerRectProperty()));
585 if (savedOutlineInnerRect)
586 return *savedOutlineInnerRect;
587 NS_NOTREACHED("we should have saved a frame property");
588 return nsRect(nsPoint(0, 0), aFrame->GetSize());
589 }
591 void
592 nsCSSRendering::PaintOutline(nsPresContext* aPresContext,
593 nsRenderingContext& aRenderingContext,
594 nsIFrame* aForFrame,
595 const nsRect& aDirtyRect,
596 const nsRect& aBorderArea,
597 nsStyleContext* aStyleContext)
598 {
599 nscoord twipsRadii[8];
601 // Get our style context's color struct.
602 const nsStyleOutline* ourOutline = aStyleContext->StyleOutline();
604 nscoord width;
605 ourOutline->GetOutlineWidth(width);
607 if (width == 0) {
608 // Empty outline
609 return;
610 }
612 nsIFrame* bgFrame = nsCSSRendering::FindNonTransparentBackgroundFrame
613 (aForFrame, false);
614 nsStyleContext* bgContext = bgFrame->StyleContext();
615 nscolor bgColor =
616 bgContext->GetVisitedDependentColor(eCSSProperty_background_color);
618 nsRect innerRect;
619 if (
620 #ifdef MOZ_XUL
621 aStyleContext->GetPseudoType() == nsCSSPseudoElements::ePseudo_XULTree
622 #else
623 false
624 #endif
625 ) {
626 innerRect = aBorderArea;
627 } else {
628 innerRect = GetOutlineInnerRect(aForFrame) + aBorderArea.TopLeft();
629 }
630 nscoord offset = ourOutline->mOutlineOffset;
631 innerRect.Inflate(offset, offset);
632 // If the dirty rect is completely inside the border area (e.g., only the
633 // content is being painted), then we can skip out now
634 // XXX this isn't exactly true for rounded borders, where the inside curves may
635 // encroach into the content area. A safer calculation would be to
636 // shorten insideRect by the radius one each side before performing this test.
637 if (innerRect.Contains(aDirtyRect))
638 return;
640 nsRect outerRect = innerRect;
641 outerRect.Inflate(width, width);
643 // get the radius for our outline
644 nsIFrame::ComputeBorderRadii(ourOutline->mOutlineRadius, aBorderArea.Size(),
645 outerRect.Size(), 0, twipsRadii);
647 // Get our conversion values
648 nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
650 // get the outer rectangles
651 gfxRect oRect(nsLayoutUtils::RectToGfxRect(outerRect, twipsPerPixel));
653 // convert the radii
654 nsMargin outlineMargin(width, width, width, width);
655 gfxCornerSizes outlineRadii;
656 ComputePixelRadii(twipsRadii, twipsPerPixel, &outlineRadii);
658 uint8_t outlineStyle = ourOutline->GetOutlineStyle();
659 uint8_t outlineStyles[4] = { outlineStyle,
660 outlineStyle,
661 outlineStyle,
662 outlineStyle };
664 // This handles treating the initial color as 'currentColor'; if we
665 // ever want 'invert' back we'll need to do a bit of work here too.
666 nscolor outlineColor =
667 aStyleContext->GetVisitedDependentColor(eCSSProperty_outline_color);
668 nscolor outlineColors[4] = { outlineColor,
669 outlineColor,
670 outlineColor,
671 outlineColor };
673 // convert the border widths
674 gfxFloat outlineWidths[4] = { gfxFloat(width / twipsPerPixel),
675 gfxFloat(width / twipsPerPixel),
676 gfxFloat(width / twipsPerPixel),
677 gfxFloat(width / twipsPerPixel) };
679 // start drawing
680 gfxContext *ctx = aRenderingContext.ThebesContext();
682 ctx->Save();
684 nsCSSBorderRenderer br(twipsPerPixel,
685 ctx,
686 oRect,
687 outlineStyles,
688 outlineWidths,
689 outlineRadii,
690 outlineColors,
691 nullptr, 0,
692 bgColor);
693 br.DrawBorders();
695 ctx->Restore();
697 SN();
698 }
700 void
701 nsCSSRendering::PaintFocus(nsPresContext* aPresContext,
702 nsRenderingContext& aRenderingContext,
703 const nsRect& aFocusRect,
704 nscolor aColor)
705 {
706 nscoord oneCSSPixel = nsPresContext::CSSPixelsToAppUnits(1);
707 nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
709 gfxRect focusRect(nsLayoutUtils::RectToGfxRect(aFocusRect, oneDevPixel));
711 gfxCornerSizes focusRadii;
712 {
713 nscoord twipsRadii[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
714 ComputePixelRadii(twipsRadii, oneDevPixel, &focusRadii);
715 }
716 gfxFloat focusWidths[4] = { gfxFloat(oneCSSPixel / oneDevPixel),
717 gfxFloat(oneCSSPixel / oneDevPixel),
718 gfxFloat(oneCSSPixel / oneDevPixel),
719 gfxFloat(oneCSSPixel / oneDevPixel) };
721 uint8_t focusStyles[4] = { NS_STYLE_BORDER_STYLE_DOTTED,
722 NS_STYLE_BORDER_STYLE_DOTTED,
723 NS_STYLE_BORDER_STYLE_DOTTED,
724 NS_STYLE_BORDER_STYLE_DOTTED };
725 nscolor focusColors[4] = { aColor, aColor, aColor, aColor };
727 gfxContext *ctx = aRenderingContext.ThebesContext();
729 ctx->Save();
731 // Because this renders a dotted border, the background color
732 // should not be used. Therefore, we provide a value that will
733 // be blatantly wrong if it ever does get used. (If this becomes
734 // something that CSS can style, this function will then have access
735 // to a style context and can use the same logic that PaintBorder
736 // and PaintOutline do.)
737 nsCSSBorderRenderer br(oneDevPixel,
738 ctx,
739 focusRect,
740 focusStyles,
741 focusWidths,
742 focusRadii,
743 focusColors,
744 nullptr, 0,
745 NS_RGB(255, 0, 0));
746 br.DrawBorders();
748 ctx->Restore();
750 SN();
751 }
753 // Thebes Border Rendering Code End
754 //----------------------------------------------------------------------
757 //----------------------------------------------------------------------
759 /**
760 * Computes the placement of a background image.
761 *
762 * @param aOriginBounds is the box to which the tiling position should be
763 * relative
764 * This should correspond to 'background-origin' for the frame,
765 * except when painting on the canvas, in which case the origin bounds
766 * should be the bounds of the root element's frame.
767 * @param aTopLeft the top-left corner where an image tile should be drawn
768 * @param aAnchorPoint a point which should be pixel-aligned by
769 * nsLayoutUtils::DrawImage. This is the same as aTopLeft, unless CSS
770 * specifies a percentage (including 'right' or 'bottom'), in which case
771 * it's that percentage within of aOriginBounds. So 'right' would set
772 * aAnchorPoint.x to aOriginBounds.XMost().
773 *
774 * Points are returned relative to aOriginBounds.
775 */
776 static void
777 ComputeBackgroundAnchorPoint(const nsStyleBackground::Layer& aLayer,
778 const nsSize& aOriginBounds,
779 const nsSize& aImageSize,
780 nsPoint* aTopLeft,
781 nsPoint* aAnchorPoint)
782 {
783 double percentX = aLayer.mPosition.mXPosition.mPercent;
784 nscoord lengthX = aLayer.mPosition.mXPosition.mLength;
785 aAnchorPoint->x = lengthX + NSToCoordRound(percentX*aOriginBounds.width);
786 aTopLeft->x = lengthX +
787 NSToCoordRound(percentX*(aOriginBounds.width - aImageSize.width));
789 double percentY = aLayer.mPosition.mYPosition.mPercent;
790 nscoord lengthY = aLayer.mPosition.mYPosition.mLength;
791 aAnchorPoint->y = lengthY + NSToCoordRound(percentY*aOriginBounds.height);
792 aTopLeft->y = lengthY +
793 NSToCoordRound(percentY*(aOriginBounds.height - aImageSize.height));
794 }
796 nsIFrame*
797 nsCSSRendering::FindNonTransparentBackgroundFrame(nsIFrame* aFrame,
798 bool aStartAtParent /*= false*/)
799 {
800 NS_ASSERTION(aFrame, "Cannot find NonTransparentBackgroundFrame in a null frame");
802 nsIFrame* frame = nullptr;
803 if (aStartAtParent) {
804 frame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
805 }
806 if (!frame) {
807 frame = aFrame;
808 }
810 while (frame) {
811 // No need to call GetVisitedDependentColor because it always uses
812 // this alpha component anyway.
813 if (NS_GET_A(frame->StyleBackground()->mBackgroundColor) > 0)
814 break;
816 if (frame->IsThemed())
817 break;
819 nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(frame);
820 if (!parent)
821 break;
823 frame = parent;
824 }
825 return frame;
826 }
828 // Returns true if aFrame is a canvas frame.
829 // We need to treat the viewport as canvas because, even though
830 // it does not actually paint a background, we need to get the right
831 // background style so we correctly detect transparent documents.
832 bool
833 nsCSSRendering::IsCanvasFrame(nsIFrame* aFrame)
834 {
835 nsIAtom* frameType = aFrame->GetType();
836 return frameType == nsGkAtoms::canvasFrame ||
837 frameType == nsGkAtoms::rootFrame ||
838 frameType == nsGkAtoms::pageContentFrame ||
839 frameType == nsGkAtoms::viewportFrame;
840 }
842 nsIFrame*
843 nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame)
844 {
845 const nsStyleBackground* result = aForFrame->StyleBackground();
847 // Check if we need to do propagation from BODY rather than HTML.
848 if (!result->IsTransparent()) {
849 return aForFrame;
850 }
852 nsIContent* content = aForFrame->GetContent();
853 // The root element content can't be null. We wouldn't know what
854 // frame to create for aFrame.
855 // Use |OwnerDoc| so it works during destruction.
856 if (!content) {
857 return aForFrame;
858 }
860 nsIDocument* document = content->OwnerDoc();
862 dom::Element* bodyContent = document->GetBodyElement();
863 // We need to null check the body node (bug 118829) since
864 // there are cases, thanks to the fix for bug 5569, where we
865 // will reflow a document with no body. In particular, if a
866 // SCRIPT element in the head blocks the parser and then has a
867 // SCRIPT that does "document.location.href = 'foo'", then
868 // nsParser::Terminate will call |DidBuildModel| methods
869 // through to the content sink, which will call |StartLayout|
870 // and thus |Initialize| on the pres shell. See bug 119351
871 // for the ugly details.
872 if (!bodyContent) {
873 return aForFrame;
874 }
876 nsIFrame *bodyFrame = bodyContent->GetPrimaryFrame();
877 if (!bodyFrame) {
878 return aForFrame;
879 }
881 return nsLayoutUtils::GetStyleFrame(bodyFrame);
882 }
884 /**
885 * |FindBackground| finds the correct style data to use to paint the
886 * background. It is responsible for handling the following two
887 * statements in section 14.2 of CSS2:
888 *
889 * The background of the box generated by the root element covers the
890 * entire canvas.
891 *
892 * For HTML documents, however, we recommend that authors specify the
893 * background for the BODY element rather than the HTML element. User
894 * agents should observe the following precedence rules to fill in the
895 * background: if the value of the 'background' property for the HTML
896 * element is different from 'transparent' then use it, else use the
897 * value of the 'background' property for the BODY element. If the
898 * resulting value is 'transparent', the rendering is undefined.
899 *
900 * Thus, in our implementation, it is responsible for ensuring that:
901 * + we paint the correct background on the |nsCanvasFrame|,
902 * |nsRootBoxFrame|, or |nsPageFrame|,
903 * + we don't paint the background on the root element, and
904 * + we don't paint the background on the BODY element in *some* cases,
905 * and for SGML-based HTML documents only.
906 *
907 * |FindBackground| returns true if a background should be painted, and
908 * the resulting style context to use for the background information
909 * will be filled in to |aBackground|.
910 */
911 nsStyleContext*
912 nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame)
913 {
914 return FindBackgroundStyleFrame(aForFrame)->StyleContext();
915 }
917 inline bool
918 FindElementBackground(nsIFrame* aForFrame, nsIFrame* aRootElementFrame,
919 nsStyleContext** aBackgroundSC)
920 {
921 if (aForFrame == aRootElementFrame) {
922 // We must have propagated our background to the viewport or canvas. Abort.
923 return false;
924 }
926 *aBackgroundSC = aForFrame->StyleContext();
928 // Return true unless the frame is for a BODY element whose background
929 // was propagated to the viewport.
931 nsIContent* content = aForFrame->GetContent();
932 if (!content || content->Tag() != nsGkAtoms::body)
933 return true; // not frame for a "body" element
934 // It could be a non-HTML "body" element but that's OK, we'd fail the
935 // bodyContent check below
937 if (aForFrame->StyleContext()->GetPseudo())
938 return true; // A pseudo-element frame.
940 // We should only look at the <html> background if we're in an HTML document
941 nsIDocument* document = content->OwnerDoc();
943 dom::Element* bodyContent = document->GetBodyElement();
944 if (bodyContent != content)
945 return true; // this wasn't the background that was propagated
947 // This can be called even when there's no root element yet, during frame
948 // construction, via nsLayoutUtils::FrameHasTransparency and
949 // nsContainerFrame::SyncFrameViewProperties.
950 if (!aRootElementFrame)
951 return true;
953 const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground();
954 return !htmlBG->IsTransparent();
955 }
957 bool
958 nsCSSRendering::FindBackground(nsIFrame* aForFrame,
959 nsStyleContext** aBackgroundSC)
960 {
961 nsIFrame* rootElementFrame =
962 aForFrame->PresContext()->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
963 if (IsCanvasFrame(aForFrame)) {
964 *aBackgroundSC = FindCanvasBackground(aForFrame, rootElementFrame);
965 return true;
966 } else {
967 return FindElementBackground(aForFrame, rootElementFrame, aBackgroundSC);
968 }
969 }
971 void
972 nsCSSRendering::BeginFrameTreesLocked()
973 {
974 ++gFrameTreeLockCount;
975 }
977 void
978 nsCSSRendering::EndFrameTreesLocked()
979 {
980 NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked");
981 --gFrameTreeLockCount;
982 if (gFrameTreeLockCount == 0) {
983 gInlineBGData->Reset();
984 }
985 }
987 void
988 nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext,
989 nsRenderingContext& aRenderingContext,
990 nsIFrame* aForFrame,
991 const nsRect& aFrameArea,
992 const nsRect& aDirtyRect,
993 float aOpacity)
994 {
995 const nsStyleBorder* styleBorder = aForFrame->StyleBorder();
996 nsCSSShadowArray* shadows = styleBorder->mBoxShadow;
997 if (!shadows)
998 return;
999 nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
1001 bool hasBorderRadius;
1002 bool nativeTheme; // mutually exclusive with hasBorderRadius
1003 gfxCornerSizes borderRadii;
1005 // Get any border radius, since box-shadow must also have rounded corners if the frame does
1006 const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay();
1007 nsITheme::Transparency transparency;
1008 if (aForFrame->IsThemed(styleDisplay, &transparency)) {
1009 // We don't respect border-radius for native-themed widgets
1010 hasBorderRadius = false;
1011 // For opaque (rectangular) theme widgets we can take the generic
1012 // border-box path with border-radius disabled.
1013 nativeTheme = transparency != nsITheme::eOpaque;
1014 } else {
1015 nativeTheme = false;
1016 nscoord twipsRadii[8];
1017 NS_ASSERTION(aFrameArea.Size() == aForFrame->VisualBorderRectRelativeToSelf().Size(),
1018 "unexpected size");
1019 hasBorderRadius = aForFrame->GetBorderRadii(twipsRadii);
1020 if (hasBorderRadius) {
1021 ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii);
1022 }
1023 }
1025 nsRect frameRect =
1026 nativeTheme ? aForFrame->GetVisualOverflowRectRelativeToSelf() + aFrameArea.TopLeft() : aFrameArea;
1027 gfxRect frameGfxRect(nsLayoutUtils::RectToGfxRect(frameRect, twipsPerPixel));
1028 frameGfxRect.Round();
1030 // We don't show anything that intersects with the frame we're blurring on. So tell the
1031 // blurrer not to do unnecessary work there.
1032 gfxRect skipGfxRect = frameGfxRect;
1033 bool useSkipGfxRect = true;
1034 if (nativeTheme) {
1035 // Optimize non-leaf native-themed frames by skipping computing pixels
1036 // in the padding-box. We assume the padding-box is going to be painted
1037 // opaquely for non-leaf frames.
1038 // XXX this may not be a safe assumption; we should make this go away
1039 // by optimizing box-shadow drawing more for the cases where we don't have a skip-rect.
1040 useSkipGfxRect = !aForFrame->IsLeaf();
1041 nsRect paddingRect =
1042 aForFrame->GetPaddingRect() - aForFrame->GetPosition() + aFrameArea.TopLeft();
1043 skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, twipsPerPixel);
1044 } else if (hasBorderRadius) {
1045 skipGfxRect.Deflate(gfxMargin(
1046 std::max(borderRadii[C_TL].height, borderRadii[C_TR].height), 0,
1047 std::max(borderRadii[C_BL].height, borderRadii[C_BR].height), 0));
1048 }
1050 for (uint32_t i = shadows->Length(); i > 0; --i) {
1051 nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1);
1052 if (shadowItem->mInset)
1053 continue;
1055 nsRect shadowRect = frameRect;
1056 shadowRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset);
1057 if (!nativeTheme) {
1058 shadowRect.Inflate(shadowItem->mSpread, shadowItem->mSpread);
1059 }
1061 // shadowRect won't include the blur, so make an extra rect here that includes the blur
1062 // for use in the even-odd rule below.
1063 nsRect shadowRectPlusBlur = shadowRect;
1064 nscoord blurRadius = shadowItem->mRadius;
1065 shadowRectPlusBlur.Inflate(
1066 nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, twipsPerPixel));
1068 gfxRect shadowGfxRectPlusBlur =
1069 nsLayoutUtils::RectToGfxRect(shadowRectPlusBlur, twipsPerPixel);
1070 shadowGfxRectPlusBlur.RoundOut();
1072 // Set the shadow color; if not specified, use the foreground color
1073 nscolor shadowColor;
1074 if (shadowItem->mHasColor)
1075 shadowColor = shadowItem->mColor;
1076 else
1077 shadowColor = aForFrame->StyleColor()->mColor;
1079 gfxRGBA gfxShadowColor(shadowColor);
1080 gfxShadowColor.a *= aOpacity;
1082 gfxContext* renderContext = aRenderingContext.ThebesContext();
1083 if (nativeTheme) {
1084 nsContextBoxBlur blurringArea;
1086 // When getting the widget shape from the native theme, we're going
1087 // to draw the widget into the shadow surface to create a mask.
1088 // We need to ensure that there actually *is* a shadow surface
1089 // and that we're not going to draw directly into renderContext.
1090 gfxContext* shadowContext =
1091 blurringArea.Init(shadowRect, shadowItem->mSpread,
1092 blurRadius, twipsPerPixel, renderContext, aDirtyRect,
1093 useSkipGfxRect ? &skipGfxRect : nullptr,
1094 nsContextBoxBlur::FORCE_MASK);
1095 if (!shadowContext)
1096 continue;
1098 // shadowContext is owned by either blurringArea or aRenderingContext.
1099 MOZ_ASSERT(shadowContext == blurringArea.GetContext());
1101 renderContext->Save();
1102 renderContext->SetColor(gfxShadowColor);
1104 // Draw the shape of the frame so it can be blurred. Recall how nsContextBoxBlur
1105 // doesn't make any temporary surfaces if blur is 0 and it just returns the original
1106 // surface? If we have no blur, we're painting this fill on the actual content surface
1107 // (renderContext == shadowContext) which is why we set up the color and clip
1108 // before doing this.
1110 // We don't clip the border-box from the shadow, nor any other box.
1111 // We assume that the native theme is going to paint over the shadow.
1113 // Draw the widget shape
1114 gfxContextMatrixAutoSaveRestore save(shadowContext);
1115 nsRefPtr<nsRenderingContext> wrapperCtx = new nsRenderingContext();
1116 wrapperCtx->Init(aPresContext->DeviceContext(), shadowContext);
1117 wrapperCtx->Translate(nsPoint(shadowItem->mXOffset,
1118 shadowItem->mYOffset));
1120 nsRect nativeRect;
1121 nativeRect.IntersectRect(frameRect, aDirtyRect);
1122 aPresContext->GetTheme()->DrawWidgetBackground(wrapperCtx, aForFrame,
1123 styleDisplay->mAppearance, aFrameArea, nativeRect);
1125 blurringArea.DoPaint();
1126 renderContext->Restore();
1127 } else {
1128 renderContext->Save();
1129 // Clip out the area of the actual frame so the shadow is not shown within
1130 // the frame
1131 renderContext->NewPath();
1132 renderContext->Rectangle(shadowGfxRectPlusBlur);
1133 if (hasBorderRadius) {
1134 renderContext->RoundedRectangle(frameGfxRect, borderRadii);
1135 } else {
1136 renderContext->Rectangle(frameGfxRect);
1137 }
1139 renderContext->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD);
1140 renderContext->Clip();
1142 gfxCornerSizes clipRectRadii;
1143 if (hasBorderRadius) {
1144 gfxFloat spreadDistance = shadowItem->mSpread / twipsPerPixel;
1146 gfxFloat borderSizes[4];
1148 borderSizes[NS_SIDE_LEFT] = spreadDistance;
1149 borderSizes[NS_SIDE_TOP] = spreadDistance;
1150 borderSizes[NS_SIDE_RIGHT] = spreadDistance;
1151 borderSizes[NS_SIDE_BOTTOM] = spreadDistance;
1153 nsCSSBorderRenderer::ComputeOuterRadii(borderRadii, borderSizes,
1154 &clipRectRadii);
1156 }
1157 nsContextBoxBlur::BlurRectangle(renderContext,
1158 shadowRect,
1159 twipsPerPixel,
1160 hasBorderRadius ? &clipRectRadii : nullptr,
1161 blurRadius,
1162 gfxShadowColor,
1163 aDirtyRect,
1164 skipGfxRect);
1165 renderContext->Restore();
1166 }
1168 }
1169 }
1171 void
1172 nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext,
1173 nsRenderingContext& aRenderingContext,
1174 nsIFrame* aForFrame,
1175 const nsRect& aFrameArea,
1176 const nsRect& aDirtyRect)
1177 {
1178 const nsStyleBorder* styleBorder = aForFrame->StyleBorder();
1179 nsCSSShadowArray* shadows = styleBorder->mBoxShadow;
1180 if (!shadows)
1181 return;
1182 if (aForFrame->IsThemed() && aForFrame->GetContent() &&
1183 !nsContentUtils::IsChromeDoc(aForFrame->GetContent()->GetCurrentDoc())) {
1184 // There's no way of getting hold of a shape corresponding to a
1185 // "padding-box" for native-themed widgets, so just don't draw
1186 // inner box-shadows for them. But we allow chrome to paint inner
1187 // box shadows since chrome can be aware of the platform theme.
1188 return;
1189 }
1191 // Get any border radius, since box-shadow must also have rounded corners
1192 // if the frame does.
1193 nscoord twipsRadii[8];
1194 NS_ASSERTION(aForFrame->GetType() == nsGkAtoms::fieldSetFrame ||
1195 aFrameArea.Size() == aForFrame->GetSize(), "unexpected size");
1196 bool hasBorderRadius = aForFrame->GetBorderRadii(twipsRadii);
1197 nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
1199 nsRect paddingRect = aFrameArea;
1200 nsMargin border = aForFrame->GetUsedBorder();
1201 aForFrame->ApplySkipSides(border);
1202 paddingRect.Deflate(border);
1204 gfxCornerSizes innerRadii;
1205 if (hasBorderRadius) {
1206 gfxCornerSizes borderRadii;
1208 ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii);
1209 gfxFloat borderSizes[4] = {
1210 gfxFloat(border.top / twipsPerPixel),
1211 gfxFloat(border.right / twipsPerPixel),
1212 gfxFloat(border.bottom / twipsPerPixel),
1213 gfxFloat(border.left / twipsPerPixel)
1214 };
1215 nsCSSBorderRenderer::ComputeInnerRadii(borderRadii, borderSizes,
1216 &innerRadii);
1217 }
1219 for (uint32_t i = shadows->Length(); i > 0; --i) {
1220 nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1);
1221 if (!shadowItem->mInset)
1222 continue;
1224 /*
1225 * shadowRect: the frame's padding rect
1226 * shadowPaintRect: the area to paint on the temp surface, larger than shadowRect
1227 * so that blurs still happen properly near the edges
1228 * shadowClipRect: the area on the temporary surface within shadowPaintRect
1229 * that we will NOT paint in
1230 */
1231 nscoord blurRadius = shadowItem->mRadius;
1232 nsMargin blurMargin =
1233 nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, twipsPerPixel);
1234 nsRect shadowPaintRect = paddingRect;
1235 shadowPaintRect.Inflate(blurMargin);
1237 nsRect shadowClipRect = paddingRect;
1238 shadowClipRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset);
1239 shadowClipRect.Deflate(shadowItem->mSpread, shadowItem->mSpread);
1241 gfxCornerSizes clipRectRadii;
1242 if (hasBorderRadius) {
1243 // Calculate the radii the inner clipping rect will have
1244 gfxFloat spreadDistance = shadowItem->mSpread / twipsPerPixel;
1245 gfxFloat borderSizes[4] = {0, 0, 0, 0};
1247 // See PaintBoxShadowOuter and bug 514670
1248 if (innerRadii[C_TL].width > 0 || innerRadii[C_BL].width > 0) {
1249 borderSizes[NS_SIDE_LEFT] = spreadDistance;
1250 }
1252 if (innerRadii[C_TL].height > 0 || innerRadii[C_TR].height > 0) {
1253 borderSizes[NS_SIDE_TOP] = spreadDistance;
1254 }
1256 if (innerRadii[C_TR].width > 0 || innerRadii[C_BR].width > 0) {
1257 borderSizes[NS_SIDE_RIGHT] = spreadDistance;
1258 }
1260 if (innerRadii[C_BL].height > 0 || innerRadii[C_BR].height > 0) {
1261 borderSizes[NS_SIDE_BOTTOM] = spreadDistance;
1262 }
1264 nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes,
1265 &clipRectRadii);
1266 }
1268 // Set the "skip rect" to the area within the frame that we don't paint in,
1269 // including after blurring.
1270 nsRect skipRect = shadowClipRect;
1271 skipRect.Deflate(blurMargin);
1272 gfxRect skipGfxRect = nsLayoutUtils::RectToGfxRect(skipRect, twipsPerPixel);
1273 if (hasBorderRadius) {
1274 skipGfxRect.Deflate(gfxMargin(
1275 std::max(clipRectRadii[C_TL].height, clipRectRadii[C_TR].height), 0,
1276 std::max(clipRectRadii[C_BL].height, clipRectRadii[C_BR].height), 0));
1277 }
1279 // When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area
1280 // unchanged. And by construction the gfxSkipRect is not touched by the
1281 // rendered shadow (even after blurring), so those pixels must be completely
1282 // transparent in the shadow, so drawing them changes nothing.
1283 gfxContext* renderContext = aRenderingContext.ThebesContext();
1284 nsContextBoxBlur blurringArea;
1285 gfxContext* shadowContext =
1286 blurringArea.Init(shadowPaintRect, 0, blurRadius, twipsPerPixel,
1287 renderContext, aDirtyRect, &skipGfxRect);
1288 if (!shadowContext)
1289 continue;
1291 // shadowContext is owned by either blurringArea or aRenderingContext.
1292 MOZ_ASSERT(shadowContext == renderContext ||
1293 shadowContext == blurringArea.GetContext());
1295 // Set the shadow color; if not specified, use the foreground color
1296 nscolor shadowColor;
1297 if (shadowItem->mHasColor)
1298 shadowColor = shadowItem->mColor;
1299 else
1300 shadowColor = aForFrame->StyleColor()->mColor;
1302 renderContext->Save();
1303 renderContext->SetColor(gfxRGBA(shadowColor));
1305 // Clip the context to the area of the frame's padding rect, so no part of the
1306 // shadow is painted outside. Also cut out anything beyond where the inset shadow
1307 // will be.
1308 gfxRect shadowGfxRect =
1309 nsLayoutUtils::RectToGfxRect(paddingRect, twipsPerPixel);
1310 shadowGfxRect.Round();
1311 renderContext->NewPath();
1312 if (hasBorderRadius)
1313 renderContext->RoundedRectangle(shadowGfxRect, innerRadii, false);
1314 else
1315 renderContext->Rectangle(shadowGfxRect);
1316 renderContext->Clip();
1318 // Fill the surface minus the area within the frame that we should
1319 // not paint in, and blur and apply it.
1320 gfxRect shadowPaintGfxRect =
1321 nsLayoutUtils::RectToGfxRect(shadowPaintRect, twipsPerPixel);
1322 shadowPaintGfxRect.RoundOut();
1323 gfxRect shadowClipGfxRect =
1324 nsLayoutUtils::RectToGfxRect(shadowClipRect, twipsPerPixel);
1325 shadowClipGfxRect.Round();
1326 shadowContext->NewPath();
1327 shadowContext->Rectangle(shadowPaintGfxRect);
1328 if (hasBorderRadius)
1329 shadowContext->RoundedRectangle(shadowClipGfxRect, clipRectRadii, false);
1330 else
1331 shadowContext->Rectangle(shadowClipGfxRect);
1332 shadowContext->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD);
1333 shadowContext->Fill();
1335 blurringArea.DoPaint();
1336 renderContext->Restore();
1337 }
1338 }
1340 void
1341 nsCSSRendering::PaintBackground(nsPresContext* aPresContext,
1342 nsRenderingContext& aRenderingContext,
1343 nsIFrame* aForFrame,
1344 const nsRect& aDirtyRect,
1345 const nsRect& aBorderArea,
1346 uint32_t aFlags,
1347 nsRect* aBGClipRect,
1348 int32_t aLayer)
1349 {
1350 PROFILER_LABEL("nsCSSRendering", "PaintBackground");
1351 NS_PRECONDITION(aForFrame,
1352 "Frame is expected to be provided to PaintBackground");
1354 nsStyleContext *sc;
1355 if (!FindBackground(aForFrame, &sc)) {
1356 // We don't want to bail out if moz-appearance is set on a root
1357 // node. If it has a parent content node, bail because it's not
1358 // a root, otherwise keep going in order to let the theme stuff
1359 // draw the background. The canvas really should be drawing the
1360 // bg, but there's no way to hook that up via css.
1361 if (!aForFrame->StyleDisplay()->mAppearance) {
1362 return;
1363 }
1365 nsIContent* content = aForFrame->GetContent();
1366 if (!content || content->GetParent()) {
1367 return;
1368 }
1370 sc = aForFrame->StyleContext();
1371 }
1373 PaintBackgroundWithSC(aPresContext, aRenderingContext, aForFrame,
1374 aDirtyRect, aBorderArea, sc,
1375 *aForFrame->StyleBorder(), aFlags,
1376 aBGClipRect, aLayer);
1377 }
1379 void
1380 nsCSSRendering::PaintBackgroundColor(nsPresContext* aPresContext,
1381 nsRenderingContext& aRenderingContext,
1382 nsIFrame* aForFrame,
1383 const nsRect& aDirtyRect,
1384 const nsRect& aBorderArea,
1385 uint32_t aFlags)
1386 {
1387 PROFILER_LABEL("nsCSSRendering", "PaintBackgroundColor");
1388 NS_PRECONDITION(aForFrame,
1389 "Frame is expected to be provided to PaintBackground");
1391 nsStyleContext *sc;
1392 if (!FindBackground(aForFrame, &sc)) {
1393 // We don't want to bail out if moz-appearance is set on a root
1394 // node. If it has a parent content node, bail because it's not
1395 // a root, other wise keep going in order to let the theme stuff
1396 // draw the background. The canvas really should be drawing the
1397 // bg, but there's no way to hook that up via css.
1398 if (!aForFrame->StyleDisplay()->mAppearance) {
1399 return;
1400 }
1402 nsIContent* content = aForFrame->GetContent();
1403 if (!content || content->GetParent()) {
1404 return;
1405 }
1407 sc = aForFrame->StyleContext();
1408 }
1410 PaintBackgroundColorWithSC(aPresContext, aRenderingContext, aForFrame,
1411 aDirtyRect, aBorderArea, sc,
1412 *aForFrame->StyleBorder(), aFlags);
1413 }
1415 static bool
1416 IsOpaqueBorderEdge(const nsStyleBorder& aBorder, mozilla::css::Side aSide)
1417 {
1418 if (aBorder.GetComputedBorder().Side(aSide) == 0)
1419 return true;
1420 switch (aBorder.GetBorderStyle(aSide)) {
1421 case NS_STYLE_BORDER_STYLE_SOLID:
1422 case NS_STYLE_BORDER_STYLE_GROOVE:
1423 case NS_STYLE_BORDER_STYLE_RIDGE:
1424 case NS_STYLE_BORDER_STYLE_INSET:
1425 case NS_STYLE_BORDER_STYLE_OUTSET:
1426 break;
1427 default:
1428 return false;
1429 }
1431 // If we're using a border image, assume it's not fully opaque,
1432 // because we may not even have the image loaded at this point, and
1433 // even if we did, checking whether the relevant tile is fully
1434 // opaque would be too much work.
1435 if (aBorder.mBorderImageSource.GetType() != eStyleImageType_Null)
1436 return false;
1438 nscolor color;
1439 bool isForeground;
1440 aBorder.GetBorderColor(aSide, color, isForeground);
1442 // We don't know the foreground color here, so if it's being used
1443 // we must assume it might be transparent.
1444 if (isForeground)
1445 return false;
1447 return NS_GET_A(color) == 255;
1448 }
1450 /**
1451 * Returns true if all border edges are either missing or opaque.
1452 */
1453 static bool
1454 IsOpaqueBorder(const nsStyleBorder& aBorder)
1455 {
1456 if (aBorder.mBorderColors)
1457 return false;
1458 NS_FOR_CSS_SIDES(i) {
1459 if (!IsOpaqueBorderEdge(aBorder, i))
1460 return false;
1461 }
1462 return true;
1463 }
1465 static inline void
1466 SetupDirtyRects(const nsRect& aBGClipArea, const nsRect& aCallerDirtyRect,
1467 nscoord aAppUnitsPerPixel,
1468 /* OUT: */
1469 nsRect* aDirtyRect, gfxRect* aDirtyRectGfx)
1470 {
1471 aDirtyRect->IntersectRect(aBGClipArea, aCallerDirtyRect);
1473 // Compute the Thebes equivalent of the dirtyRect.
1474 *aDirtyRectGfx = nsLayoutUtils::RectToGfxRect(*aDirtyRect, aAppUnitsPerPixel);
1475 NS_WARN_IF_FALSE(aDirtyRect->IsEmpty() || !aDirtyRectGfx->IsEmpty(),
1476 "converted dirty rect should not be empty");
1477 NS_ABORT_IF_FALSE(!aDirtyRect->IsEmpty() || aDirtyRectGfx->IsEmpty(),
1478 "second should be empty if first is");
1479 }
1481 struct BackgroundClipState {
1482 nsRect mBGClipArea; // Affected by mClippedRadii
1483 nsRect mAdditionalBGClipArea; // Not affected by mClippedRadii
1484 nsRect mDirtyRect;
1485 gfxRect mDirtyRectGfx;
1487 gfxCornerSizes mClippedRadii;
1488 bool mRadiiAreOuter;
1489 bool mHasAdditionalBGClipArea;
1491 // Whether we are being asked to draw with a caller provided background
1492 // clipping area. If this is true we also disable rounded corners.
1493 bool mCustomClip;
1494 };
1496 static void
1497 GetBackgroundClip(gfxContext *aCtx, uint8_t aBackgroundClip,
1498 uint8_t aBackgroundAttachment,
1499 nsIFrame* aForFrame, const nsRect& aBorderArea,
1500 const nsRect& aCallerDirtyRect, bool aHaveRoundedCorners,
1501 const gfxCornerSizes& aBGRadii, nscoord aAppUnitsPerPixel,
1502 /* out */ BackgroundClipState* aClipState)
1503 {
1504 aClipState->mBGClipArea = aBorderArea;
1505 aClipState->mHasAdditionalBGClipArea = false;
1506 aClipState->mCustomClip = false;
1507 aClipState->mRadiiAreOuter = true;
1508 aClipState->mClippedRadii = aBGRadii;
1509 if (aForFrame->GetType() == nsGkAtoms::scrollFrame &&
1510 NS_STYLE_BG_ATTACHMENT_LOCAL == aBackgroundAttachment) {
1511 // As of this writing, this is still in discussion in the CSS Working Group
1512 // http://lists.w3.org/Archives/Public/www-style/2013Jul/0250.html
1514 // The rectangle for 'background-clip' scrolls with the content,
1515 // but the background is also clipped at a non-scrolling 'padding-box'
1516 // like the content. (See below.)
1517 // Therefore, only 'content-box' makes a difference here.
1518 if (aBackgroundClip == NS_STYLE_BG_CLIP_CONTENT) {
1519 nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
1520 // Clip at a rectangle attached to the scrolled content.
1521 aClipState->mHasAdditionalBGClipArea = true;
1522 aClipState->mAdditionalBGClipArea = nsRect(
1523 aClipState->mBGClipArea.TopLeft()
1524 + scrollableFrame->GetScrolledFrame()->GetPosition()
1525 // For the dir=rtl case:
1526 + scrollableFrame->GetScrollRange().TopLeft(),
1527 scrollableFrame->GetScrolledRect().Size());
1528 nsMargin padding = aForFrame->GetUsedPadding();
1529 // padding-bottom is ignored on scrollable frames:
1530 // https://bugzilla.mozilla.org/show_bug.cgi?id=748518
1531 padding.bottom = 0;
1532 aForFrame->ApplySkipSides(padding);
1533 aClipState->mAdditionalBGClipArea.Deflate(padding);
1534 }
1536 // Also clip at a non-scrolling, rounded-corner 'padding-box',
1537 // same as the scrolled content because of the 'overflow' property.
1538 aBackgroundClip = NS_STYLE_BG_CLIP_PADDING;
1539 }
1541 if (aBackgroundClip != NS_STYLE_BG_CLIP_BORDER) {
1542 nsMargin border = aForFrame->GetUsedBorder();
1543 if (aBackgroundClip == NS_STYLE_BG_CLIP_MOZ_ALMOST_PADDING) {
1544 // Reduce |border| by 1px (device pixels) on all sides, if
1545 // possible, so that we don't get antialiasing seams between the
1546 // background and border.
1547 border.top = std::max(0, border.top - aAppUnitsPerPixel);
1548 border.right = std::max(0, border.right - aAppUnitsPerPixel);
1549 border.bottom = std::max(0, border.bottom - aAppUnitsPerPixel);
1550 border.left = std::max(0, border.left - aAppUnitsPerPixel);
1551 } else if (aBackgroundClip != NS_STYLE_BG_CLIP_PADDING) {
1552 NS_ASSERTION(aBackgroundClip == NS_STYLE_BG_CLIP_CONTENT,
1553 "unexpected background-clip");
1554 border += aForFrame->GetUsedPadding();
1555 }
1556 aForFrame->ApplySkipSides(border);
1557 aClipState->mBGClipArea.Deflate(border);
1559 if (aHaveRoundedCorners) {
1560 gfxFloat borderSizes[4] = {
1561 gfxFloat(border.top / aAppUnitsPerPixel),
1562 gfxFloat(border.right / aAppUnitsPerPixel),
1563 gfxFloat(border.bottom / aAppUnitsPerPixel),
1564 gfxFloat(border.left / aAppUnitsPerPixel)
1565 };
1566 nsCSSBorderRenderer::ComputeInnerRadii(aBGRadii, borderSizes,
1567 &aClipState->mClippedRadii);
1568 aClipState->mRadiiAreOuter = false;
1569 }
1570 }
1572 if (!aHaveRoundedCorners && aClipState->mHasAdditionalBGClipArea) {
1573 // Do the intersection here to account for the fast path(?) below.
1574 aClipState->mBGClipArea =
1575 aClipState->mBGClipArea.Intersect(aClipState->mAdditionalBGClipArea);
1576 aClipState->mHasAdditionalBGClipArea = false;
1577 }
1579 SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, aAppUnitsPerPixel,
1580 &aClipState->mDirtyRect, &aClipState->mDirtyRectGfx);
1581 }
1583 static void
1584 SetupBackgroundClip(BackgroundClipState& aClipState, gfxContext *aCtx,
1585 bool aHaveRoundedCorners, nscoord aAppUnitsPerPixel,
1586 gfxContextAutoSaveRestore* aAutoSR)
1587 {
1588 if (aClipState.mDirtyRectGfx.IsEmpty()) {
1589 // Our caller won't draw anything under this condition, so no need
1590 // to set more up.
1591 return;
1592 }
1594 if (aClipState.mCustomClip) {
1595 // We don't support custom clips and rounded corners, arguably a bug, but
1596 // table painting seems to depend on it.
1597 return;
1598 }
1600 // If we have rounded corners, clip all subsequent drawing to the
1601 // rounded rectangle defined by bgArea and bgRadii (we don't know
1602 // whether the rounded corners intrude on the dirtyRect or not).
1603 // Do not do this if we have a caller-provided clip rect --
1604 // as above with bgArea, arguably a bug, but table painting seems
1605 // to depend on it.
1607 if (aHaveRoundedCorners || aClipState.mHasAdditionalBGClipArea) {
1608 aAutoSR->Reset(aCtx);
1609 }
1611 if (aClipState.mHasAdditionalBGClipArea) {
1612 gfxRect bgAreaGfx = nsLayoutUtils::RectToGfxRect(
1613 aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
1614 bgAreaGfx.Round();
1615 bgAreaGfx.Condition();
1616 aCtx->NewPath();
1617 aCtx->Rectangle(bgAreaGfx, true);
1618 aCtx->Clip();
1619 }
1621 if (aHaveRoundedCorners) {
1622 gfxRect bgAreaGfx =
1623 nsLayoutUtils::RectToGfxRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
1624 bgAreaGfx.Round();
1625 bgAreaGfx.Condition();
1627 if (bgAreaGfx.IsEmpty()) {
1628 // I think it's become possible to hit this since
1629 // http://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
1630 NS_WARNING("converted background area should not be empty");
1631 // Make our caller not do anything.
1632 aClipState.mDirtyRectGfx.SizeTo(gfxSize(0.0, 0.0));
1633 return;
1634 }
1636 aCtx->NewPath();
1637 aCtx->RoundedRectangle(bgAreaGfx, aClipState.mClippedRadii, aClipState.mRadiiAreOuter);
1638 aCtx->Clip();
1639 }
1640 }
1642 static void
1643 DrawBackgroundColor(BackgroundClipState& aClipState, gfxContext *aCtx,
1644 bool aHaveRoundedCorners, nscoord aAppUnitsPerPixel)
1645 {
1646 if (aClipState.mDirtyRectGfx.IsEmpty()) {
1647 // Our caller won't draw anything under this condition, so no need
1648 // to set more up.
1649 return;
1650 }
1652 // We don't support custom clips and rounded corners, arguably a bug, but
1653 // table painting seems to depend on it.
1654 if (!aHaveRoundedCorners || aClipState.mCustomClip) {
1655 aCtx->NewPath();
1656 aCtx->Rectangle(aClipState.mDirtyRectGfx, true);
1657 aCtx->Fill();
1658 return;
1659 }
1661 gfxRect bgAreaGfx =
1662 nsLayoutUtils::RectToGfxRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
1663 bgAreaGfx.Round();
1664 bgAreaGfx.Condition();
1666 if (bgAreaGfx.IsEmpty()) {
1667 // I think it's become possible to hit this since
1668 // http://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
1669 NS_WARNING("converted background area should not be empty");
1670 // Make our caller not do anything.
1671 aClipState.mDirtyRectGfx.SizeTo(gfxSize(0.0, 0.0));
1672 return;
1673 }
1675 aCtx->Save();
1676 gfxRect dirty = bgAreaGfx.Intersect(aClipState.mDirtyRectGfx);
1678 aCtx->NewPath();
1679 aCtx->Rectangle(dirty, true);
1680 aCtx->Clip();
1682 if (aClipState.mHasAdditionalBGClipArea) {
1683 gfxRect bgAdditionalAreaGfx = nsLayoutUtils::RectToGfxRect(
1684 aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
1685 bgAdditionalAreaGfx.Round();
1686 bgAdditionalAreaGfx.Condition();
1687 aCtx->NewPath();
1688 aCtx->Rectangle(bgAdditionalAreaGfx, true);
1689 aCtx->Clip();
1690 }
1692 aCtx->NewPath();
1693 aCtx->RoundedRectangle(bgAreaGfx, aClipState.mClippedRadii,
1694 aClipState.mRadiiAreOuter);
1696 aCtx->Fill();
1697 aCtx->Restore();
1698 }
1700 nscolor
1701 nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext,
1702 nsStyleContext* aStyleContext,
1703 nsIFrame* aFrame,
1704 bool& aDrawBackgroundImage,
1705 bool& aDrawBackgroundColor)
1706 {
1707 aDrawBackgroundImage = true;
1708 aDrawBackgroundColor = true;
1710 if (aFrame->HonorPrintBackgroundSettings()) {
1711 aDrawBackgroundImage = aPresContext->GetBackgroundImageDraw();
1712 aDrawBackgroundColor = aPresContext->GetBackgroundColorDraw();
1713 }
1715 const nsStyleBackground *bg = aStyleContext->StyleBackground();
1716 nscolor bgColor;
1717 if (aDrawBackgroundColor) {
1718 bgColor =
1719 aStyleContext->GetVisitedDependentColor(eCSSProperty_background_color);
1720 if (NS_GET_A(bgColor) == 0) {
1721 aDrawBackgroundColor = false;
1722 }
1723 } else {
1724 // If GetBackgroundColorDraw() is false, we are still expected to
1725 // draw color in the background of any frame that's not completely
1726 // transparent, but we are expected to use white instead of whatever
1727 // color was specified.
1728 bgColor = NS_RGB(255, 255, 255);
1729 if (aDrawBackgroundImage || !bg->IsTransparent()) {
1730 aDrawBackgroundColor = true;
1731 } else {
1732 bgColor = NS_RGBA(0,0,0,0);
1733 }
1734 }
1736 // We can skip painting the background color if a background image is opaque.
1737 if (aDrawBackgroundColor &&
1738 bg->BottomLayer().mRepeat.mXRepeat == NS_STYLE_BG_REPEAT_REPEAT &&
1739 bg->BottomLayer().mRepeat.mYRepeat == NS_STYLE_BG_REPEAT_REPEAT &&
1740 bg->BottomLayer().mImage.IsOpaque() &&
1741 bg->BottomLayer().mBlendMode == NS_STYLE_BLEND_NORMAL) {
1742 aDrawBackgroundColor = false;
1743 }
1745 return bgColor;
1746 }
1748 static gfxFloat
1749 ConvertGradientValueToPixels(const nsStyleCoord& aCoord,
1750 gfxFloat aFillLength,
1751 int32_t aAppUnitsPerPixel)
1752 {
1753 switch (aCoord.GetUnit()) {
1754 case eStyleUnit_Percent:
1755 return aCoord.GetPercentValue() * aFillLength;
1756 case eStyleUnit_Coord:
1757 return NSAppUnitsToFloatPixels(aCoord.GetCoordValue(), aAppUnitsPerPixel);
1758 case eStyleUnit_Calc: {
1759 const nsStyleCoord::Calc *calc = aCoord.GetCalcValue();
1760 return calc->mPercent * aFillLength +
1761 NSAppUnitsToFloatPixels(calc->mLength, aAppUnitsPerPixel);
1762 }
1763 default:
1764 NS_WARNING("Unexpected coord unit");
1765 return 0;
1766 }
1767 }
1769 // Given a box with size aBoxSize and origin (0,0), and an angle aAngle,
1770 // and a starting point for the gradient line aStart, find the endpoint of
1771 // the gradient line --- the intersection of the gradient line with a line
1772 // perpendicular to aAngle that passes through the farthest corner in the
1773 // direction aAngle.
1774 static gfxPoint
1775 ComputeGradientLineEndFromAngle(const gfxPoint& aStart,
1776 double aAngle,
1777 const gfxSize& aBoxSize)
1778 {
1779 double dx = cos(-aAngle);
1780 double dy = sin(-aAngle);
1781 gfxPoint farthestCorner(dx > 0 ? aBoxSize.width : 0,
1782 dy > 0 ? aBoxSize.height : 0);
1783 gfxPoint delta = farthestCorner - aStart;
1784 double u = delta.x*dy - delta.y*dx;
1785 return farthestCorner + gfxPoint(-u*dy, u*dx);
1786 }
1788 // Compute the start and end points of the gradient line for a linear gradient.
1789 static void
1790 ComputeLinearGradientLine(nsPresContext* aPresContext,
1791 nsStyleGradient* aGradient,
1792 const gfxSize& aBoxSize,
1793 gfxPoint* aLineStart,
1794 gfxPoint* aLineEnd)
1795 {
1796 if (aGradient->mBgPosX.GetUnit() == eStyleUnit_None) {
1797 double angle;
1798 if (aGradient->mAngle.IsAngleValue()) {
1799 angle = aGradient->mAngle.GetAngleValueInRadians();
1800 if (!aGradient->mLegacySyntax) {
1801 angle = M_PI_2 - angle;
1802 }
1803 } else {
1804 angle = -M_PI_2; // defaults to vertical gradient starting from top
1805 }
1806 gfxPoint center(aBoxSize.width/2, aBoxSize.height/2);
1807 *aLineEnd = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
1808 *aLineStart = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineEnd;
1809 } else if (!aGradient->mLegacySyntax) {
1810 float xSign = aGradient->mBgPosX.GetPercentValue() * 2 - 1;
1811 float ySign = 1 - aGradient->mBgPosY.GetPercentValue() * 2;
1812 double angle = atan2(ySign * aBoxSize.width, xSign * aBoxSize.height);
1813 gfxPoint center(aBoxSize.width/2, aBoxSize.height/2);
1814 *aLineEnd = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
1815 *aLineStart = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineEnd;
1816 } else {
1817 int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
1818 *aLineStart = gfxPoint(
1819 ConvertGradientValueToPixels(aGradient->mBgPosX, aBoxSize.width,
1820 appUnitsPerPixel),
1821 ConvertGradientValueToPixels(aGradient->mBgPosY, aBoxSize.height,
1822 appUnitsPerPixel));
1823 if (aGradient->mAngle.IsAngleValue()) {
1824 MOZ_ASSERT(aGradient->mLegacySyntax);
1825 double angle = aGradient->mAngle.GetAngleValueInRadians();
1826 *aLineEnd = ComputeGradientLineEndFromAngle(*aLineStart, angle, aBoxSize);
1827 } else {
1828 // No angle, the line end is just the reflection of the start point
1829 // through the center of the box
1830 *aLineEnd = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineStart;
1831 }
1832 }
1833 }
1835 // Compute the start and end points of the gradient line for a radial gradient.
1836 // Also returns the horizontal and vertical radii defining the circle or
1837 // ellipse to use.
1838 static void
1839 ComputeRadialGradientLine(nsPresContext* aPresContext,
1840 nsStyleGradient* aGradient,
1841 const gfxSize& aBoxSize,
1842 gfxPoint* aLineStart,
1843 gfxPoint* aLineEnd,
1844 double* aRadiusX,
1845 double* aRadiusY)
1846 {
1847 if (aGradient->mBgPosX.GetUnit() == eStyleUnit_None) {
1848 // Default line start point is the center of the box
1849 *aLineStart = gfxPoint(aBoxSize.width/2, aBoxSize.height/2);
1850 } else {
1851 int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
1852 *aLineStart = gfxPoint(
1853 ConvertGradientValueToPixels(aGradient->mBgPosX, aBoxSize.width,
1854 appUnitsPerPixel),
1855 ConvertGradientValueToPixels(aGradient->mBgPosY, aBoxSize.height,
1856 appUnitsPerPixel));
1857 }
1859 // Compute gradient shape: the x and y radii of an ellipse.
1860 double radiusX, radiusY;
1861 double leftDistance = Abs(aLineStart->x);
1862 double rightDistance = Abs(aBoxSize.width - aLineStart->x);
1863 double topDistance = Abs(aLineStart->y);
1864 double bottomDistance = Abs(aBoxSize.height - aLineStart->y);
1865 switch (aGradient->mSize) {
1866 case NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE:
1867 radiusX = std::min(leftDistance, rightDistance);
1868 radiusY = std::min(topDistance, bottomDistance);
1869 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
1870 radiusX = radiusY = std::min(radiusX, radiusY);
1871 }
1872 break;
1873 case NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER: {
1874 // Compute x and y distances to nearest corner
1875 double offsetX = std::min(leftDistance, rightDistance);
1876 double offsetY = std::min(topDistance, bottomDistance);
1877 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
1878 radiusX = radiusY = NS_hypot(offsetX, offsetY);
1879 } else {
1880 // maintain aspect ratio
1881 radiusX = offsetX*M_SQRT2;
1882 radiusY = offsetY*M_SQRT2;
1883 }
1884 break;
1885 }
1886 case NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE:
1887 radiusX = std::max(leftDistance, rightDistance);
1888 radiusY = std::max(topDistance, bottomDistance);
1889 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
1890 radiusX = radiusY = std::max(radiusX, radiusY);
1891 }
1892 break;
1893 case NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER: {
1894 // Compute x and y distances to nearest corner
1895 double offsetX = std::max(leftDistance, rightDistance);
1896 double offsetY = std::max(topDistance, bottomDistance);
1897 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
1898 radiusX = radiusY = NS_hypot(offsetX, offsetY);
1899 } else {
1900 // maintain aspect ratio
1901 radiusX = offsetX*M_SQRT2;
1902 radiusY = offsetY*M_SQRT2;
1903 }
1904 break;
1905 }
1906 case NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE: {
1907 int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
1908 radiusX = ConvertGradientValueToPixels(aGradient->mRadiusX,
1909 aBoxSize.width, appUnitsPerPixel);
1910 radiusY = ConvertGradientValueToPixels(aGradient->mRadiusY,
1911 aBoxSize.height, appUnitsPerPixel);
1912 break;
1913 }
1914 default:
1915 radiusX = radiusY = 0;
1916 NS_ABORT_IF_FALSE(false, "unknown radial gradient sizing method");
1917 }
1918 *aRadiusX = radiusX;
1919 *aRadiusY = radiusY;
1921 double angle;
1922 if (aGradient->mAngle.IsAngleValue()) {
1923 angle = aGradient->mAngle.GetAngleValueInRadians();
1924 } else {
1925 // Default angle is 0deg
1926 angle = 0.0;
1927 }
1929 // The gradient line end point is where the gradient line intersects
1930 // the ellipse.
1931 *aLineEnd = *aLineStart + gfxPoint(radiusX*cos(-angle), radiusY*sin(-angle));
1932 }
1934 // Returns aFrac*aC2 + (1 - aFrac)*C1. The interpolation is done
1935 // in unpremultiplied space, which is what SVG gradients and cairo
1936 // gradients expect.
1937 static gfxRGBA
1938 InterpolateColor(const gfxRGBA& aC1, const gfxRGBA& aC2, double aFrac)
1939 {
1940 double other = 1 - aFrac;
1941 return gfxRGBA(aC2.r*aFrac + aC1.r*other,
1942 aC2.g*aFrac + aC1.g*other,
1943 aC2.b*aFrac + aC1.b*other,
1944 aC2.a*aFrac + aC1.a*other);
1945 }
1947 static nscoord
1948 FindTileStart(nscoord aDirtyCoord, nscoord aTilePos, nscoord aTileDim)
1949 {
1950 NS_ASSERTION(aTileDim > 0, "Non-positive tile dimension");
1951 double multiples = floor(double(aDirtyCoord - aTilePos)/aTileDim);
1952 return NSToCoordRound(multiples*aTileDim + aTilePos);
1953 }
1955 void
1956 nsCSSRendering::PaintGradient(nsPresContext* aPresContext,
1957 nsRenderingContext& aRenderingContext,
1958 nsStyleGradient* aGradient,
1959 const nsRect& aDirtyRect,
1960 const nsRect& aDest,
1961 const nsRect& aFillArea,
1962 const CSSIntRect& aSrc,
1963 const nsSize& aIntrinsicSize)
1964 {
1965 PROFILER_LABEL("nsCSSRendering", "PaintGradient");
1966 Telemetry::AutoTimer<Telemetry::GRADIENT_DURATION, Telemetry::Microsecond> gradientTimer;
1967 if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
1968 return;
1969 }
1971 gfxContext *ctx = aRenderingContext.ThebesContext();
1972 nscoord appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
1973 gfxSize srcSize = gfxSize(gfxFloat(aIntrinsicSize.width)/appUnitsPerDevPixel,
1974 gfxFloat(aIntrinsicSize.height)/appUnitsPerDevPixel);
1976 bool cellContainsFill = aDest.Contains(aFillArea);
1978 // Compute "gradient line" start and end relative to the intrinsic size of
1979 // the gradient.
1980 gfxPoint lineStart, lineEnd;
1981 double radiusX = 0, radiusY = 0; // for radial gradients only
1982 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
1983 ComputeLinearGradientLine(aPresContext, aGradient, srcSize,
1984 &lineStart, &lineEnd);
1985 } else {
1986 ComputeRadialGradientLine(aPresContext, aGradient, srcSize,
1987 &lineStart, &lineEnd, &radiusX, &radiusY);
1988 }
1989 gfxFloat lineLength = NS_hypot(lineEnd.x - lineStart.x,
1990 lineEnd.y - lineStart.y);
1992 NS_ABORT_IF_FALSE(aGradient->mStops.Length() >= 2,
1993 "The parser should reject gradients with less than two stops");
1995 // Build color stop array and compute stop positions
1996 nsTArray<ColorStop> stops;
1997 // If there is a run of stops before stop i that did not have specified
1998 // positions, then this is the index of the first stop in that run, otherwise
1999 // it's -1.
2000 int32_t firstUnsetPosition = -1;
2001 for (uint32_t i = 0; i < aGradient->mStops.Length(); ++i) {
2002 const nsStyleGradientStop& stop = aGradient->mStops[i];
2003 double position;
2004 switch (stop.mLocation.GetUnit()) {
2005 case eStyleUnit_None:
2006 if (i == 0) {
2007 // First stop defaults to position 0.0
2008 position = 0.0;
2009 } else if (i == aGradient->mStops.Length() - 1) {
2010 // Last stop defaults to position 1.0
2011 position = 1.0;
2012 } else {
2013 // Other stops with no specified position get their position assigned
2014 // later by interpolation, see below.
2015 // Remeber where the run of stops with no specified position starts,
2016 // if it starts here.
2017 if (firstUnsetPosition < 0) {
2018 firstUnsetPosition = i;
2019 }
2020 stops.AppendElement(ColorStop(0, stop.mColor));
2021 continue;
2022 }
2023 break;
2024 case eStyleUnit_Percent:
2025 position = stop.mLocation.GetPercentValue();
2026 break;
2027 case eStyleUnit_Coord:
2028 position = lineLength < 1e-6 ? 0.0 :
2029 stop.mLocation.GetCoordValue() / appUnitsPerDevPixel / lineLength;
2030 break;
2031 case eStyleUnit_Calc:
2032 nsStyleCoord::Calc *calc;
2033 calc = stop.mLocation.GetCalcValue();
2034 position = calc->mPercent +
2035 ((lineLength < 1e-6) ? 0.0 :
2036 (NSAppUnitsToFloatPixels(calc->mLength, appUnitsPerDevPixel) / lineLength));
2037 break;
2038 default:
2039 NS_ABORT_IF_FALSE(false, "Unknown stop position type");
2040 }
2042 if (i > 0) {
2043 // Prevent decreasing stop positions by advancing this position
2044 // to the previous stop position, if necessary
2045 position = std::max(position, stops[i - 1].mPosition);
2046 }
2047 stops.AppendElement(ColorStop(position, stop.mColor));
2048 if (firstUnsetPosition > 0) {
2049 // Interpolate positions for all stops that didn't have a specified position
2050 double p = stops[firstUnsetPosition - 1].mPosition;
2051 double d = (stops[i].mPosition - p)/(i - firstUnsetPosition + 1);
2052 for (uint32_t j = firstUnsetPosition; j < i; ++j) {
2053 p += d;
2054 stops[j].mPosition = p;
2055 }
2056 firstUnsetPosition = -1;
2057 }
2058 }
2060 // Eliminate negative-position stops if the gradient is radial.
2061 double firstStop = stops[0].mPosition;
2062 if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && firstStop < 0.0) {
2063 if (aGradient->mRepeating) {
2064 // Choose an instance of the repeated pattern that gives us all positive
2065 // stop-offsets.
2066 double lastStop = stops[stops.Length() - 1].mPosition;
2067 double stopDelta = lastStop - firstStop;
2068 // If all the stops are in approximately the same place then logic below
2069 // will kick in that makes us draw just the last stop color, so don't
2070 // try to do anything in that case. We certainly need to avoid
2071 // dividing by zero.
2072 if (stopDelta >= 1e-6) {
2073 double instanceCount = ceil(-firstStop/stopDelta);
2074 // Advance stops by instanceCount multiples of the period of the
2075 // repeating gradient.
2076 double offset = instanceCount*stopDelta;
2077 for (uint32_t i = 0; i < stops.Length(); i++) {
2078 stops[i].mPosition += offset;
2079 }
2080 }
2081 } else {
2082 // Move negative-position stops to position 0.0. We may also need
2083 // to set the color of the stop to the color the gradient should have
2084 // at the center of the ellipse.
2085 for (uint32_t i = 0; i < stops.Length(); i++) {
2086 double pos = stops[i].mPosition;
2087 if (pos < 0.0) {
2088 stops[i].mPosition = 0.0;
2089 // If this is the last stop, we don't need to adjust the color,
2090 // it will fill the entire area.
2091 if (i < stops.Length() - 1) {
2092 double nextPos = stops[i + 1].mPosition;
2093 // If nextPos is approximately equal to pos, then we don't
2094 // need to adjust the color of this stop because it's
2095 // not going to be displayed.
2096 // If nextPos is negative, we don't need to adjust the color of
2097 // this stop since it's not going to be displayed because
2098 // nextPos will also be moved to 0.0.
2099 if (nextPos >= 0.0 && nextPos - pos >= 1e-6) {
2100 // Compute how far the new position 0.0 is along the interval
2101 // between pos and nextPos.
2102 // XXX Color interpolation (in cairo, too) should use the
2103 // CSS 'color-interpolation' property!
2104 double frac = (0.0 - pos)/(nextPos - pos);
2105 stops[i].mColor =
2106 InterpolateColor(stops[i].mColor, stops[i + 1].mColor, frac);
2107 }
2108 }
2109 }
2110 }
2111 }
2112 firstStop = stops[0].mPosition;
2113 NS_ABORT_IF_FALSE(firstStop >= 0.0, "Failed to fix stop offsets");
2114 }
2116 if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && !aGradient->mRepeating) {
2117 // Direct2D can only handle a particular class of radial gradients because
2118 // of the way the it specifies gradients. Setting firstStop to 0, when we
2119 // can, will help us stay on the fast path. Currently we don't do this
2120 // for repeating gradients but we could by adjusting the stop collection
2121 // to start at 0
2122 firstStop = 0;
2123 }
2125 double lastStop = stops[stops.Length() - 1].mPosition;
2126 // Cairo gradients must have stop positions in the range [0, 1]. So,
2127 // stop positions will be normalized below by subtracting firstStop and then
2128 // multiplying by stopScale.
2129 double stopScale;
2130 double stopOrigin = firstStop;
2131 double stopEnd = lastStop;
2132 double stopDelta = lastStop - firstStop;
2133 bool zeroRadius = aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR &&
2134 (radiusX < 1e-6 || radiusY < 1e-6);
2135 if (stopDelta < 1e-6 || lineLength < 1e-6 || zeroRadius) {
2136 // Stops are all at the same place. Map all stops to 0.0.
2137 // For repeating radial gradients, or for any radial gradients with
2138 // a zero radius, we need to fill with the last stop color, so just set
2139 // both radii to 0.
2140 if (aGradient->mRepeating || zeroRadius) {
2141 radiusX = radiusY = 0.0;
2142 }
2143 stopDelta = 0.0;
2144 lastStop = firstStop;
2145 }
2147 // Don't normalize non-repeating or degenerate gradients below 0..1
2148 // This keeps the gradient line as large as the box and doesn't
2149 // lets us avoiding having to get padding correct for stops
2150 // at 0 and 1
2151 if (!aGradient->mRepeating || stopDelta == 0.0) {
2152 stopOrigin = std::min(stopOrigin, 0.0);
2153 stopEnd = std::max(stopEnd, 1.0);
2154 }
2155 stopScale = 1.0/(stopEnd - stopOrigin);
2157 // Create the gradient pattern.
2158 nsRefPtr<gfxPattern> gradientPattern;
2159 bool forceRepeatToCoverTiles = false;
2160 gfxMatrix matrix;
2161 if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
2162 // Compute the actual gradient line ends we need to pass to cairo after
2163 // stops have been normalized.
2164 gfxPoint gradientStart = lineStart + (lineEnd - lineStart)*stopOrigin;
2165 gfxPoint gradientEnd = lineStart + (lineEnd - lineStart)*stopEnd;
2166 gfxPoint gradientStopStart = lineStart + (lineEnd - lineStart)*firstStop;
2167 gfxPoint gradientStopEnd = lineStart + (lineEnd - lineStart)*lastStop;
2169 if (stopDelta == 0.0) {
2170 // Stops are all at the same place. For repeating gradients, this will
2171 // just paint the last stop color. We don't need to do anything.
2172 // For non-repeating gradients, this should render as two colors, one
2173 // on each "side" of the gradient line segment, which is a point. All
2174 // our stops will be at 0.0; we just need to set the direction vector
2175 // correctly.
2176 gradientEnd = gradientStart + (lineEnd - lineStart);
2177 gradientStopEnd = gradientStopStart + (lineEnd - lineStart);
2178 }
2180 gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y,
2181 gradientEnd.x, gradientEnd.y);
2183 // When the gradient line is parallel to the x axis from the left edge
2184 // to the right edge of a tile, then we can repeat by just repeating the
2185 // gradient.
2186 if (!cellContainsFill &&
2187 ((gradientStopStart.y == gradientStopEnd.y && gradientStopStart.x == 0 &&
2188 gradientStopEnd.x == srcSize.width) ||
2189 (gradientStopStart.x == gradientStopEnd.x && gradientStopStart.y == 0 &&
2190 gradientStopEnd.y == srcSize.height))) {
2191 forceRepeatToCoverTiles = true;
2192 }
2193 } else {
2194 NS_ASSERTION(firstStop >= 0.0,
2195 "Negative stops not allowed for radial gradients");
2197 // To form an ellipse, we'll stretch a circle vertically, if necessary.
2198 // So our radii are based on radiusX.
2199 double innerRadius = radiusX*stopOrigin;
2200 double outerRadius = radiusX*stopEnd;
2201 if (stopDelta == 0.0) {
2202 // Stops are all at the same place. See above (except we now have
2203 // the inside vs. outside of an ellipse).
2204 outerRadius = innerRadius + 1;
2205 }
2206 gradientPattern = new gfxPattern(lineStart.x, lineStart.y, innerRadius,
2207 lineStart.x, lineStart.y, outerRadius);
2208 if (radiusX != radiusY) {
2209 // Stretch the circles into ellipses vertically by setting a transform
2210 // in the pattern.
2211 // Recall that this is the transform from user space to pattern space.
2212 // So to stretch the ellipse by factor of P vertically, we scale
2213 // user coordinates by 1/P.
2214 matrix.Translate(lineStart);
2215 matrix.Scale(1.0, radiusX/radiusY);
2216 matrix.Translate(-lineStart);
2217 }
2218 }
2219 // Use a pattern transform to take account of source and dest rects
2220 matrix.Translate(gfxPoint(aPresContext->CSSPixelsToDevPixels(aSrc.x),
2221 aPresContext->CSSPixelsToDevPixels(aSrc.y)));
2222 matrix.Scale(gfxFloat(aPresContext->CSSPixelsToAppUnits(aSrc.width))/aDest.width,
2223 gfxFloat(aPresContext->CSSPixelsToAppUnits(aSrc.height))/aDest.height);
2224 gradientPattern->SetMatrix(matrix);
2226 if (gradientPattern->CairoStatus())
2227 return;
2229 if (stopDelta == 0.0) {
2230 // Non-repeating gradient with all stops in same place -> just add
2231 // first stop and last stop, both at position 0.
2232 // Repeating gradient with all stops in the same place, or radial
2233 // gradient with radius of 0 -> just paint the last stop color.
2234 // We use firstStop offset to keep |stops| with same units (will later normalize to 0).
2235 gfxRGBA firstColor(stops[0].mColor);
2236 gfxRGBA lastColor(stops.LastElement().mColor);
2237 stops.Clear();
2239 if (!aGradient->mRepeating && !zeroRadius) {
2240 stops.AppendElement(ColorStop(firstStop, firstColor));
2241 }
2242 stops.AppendElement(ColorStop(firstStop, lastColor));
2243 }
2245 bool isRepeat = aGradient->mRepeating || forceRepeatToCoverTiles;
2247 // Now set normalized color stops in pattern.
2248 if (!ctx->IsCairo()) {
2249 // Offscreen gradient surface cache (not a tile):
2250 // On some backends (e.g. D2D), the GradientStops object holds an offscreen surface
2251 // which is a lookup table used to evaluate the gradient. This surface can use
2252 // much memory (ram and/or GPU ram) and can be expensive to create. So we cache it.
2253 // The cache key correlates 1:1 with the arguments for CreateGradientStops (also the implied backend type)
2254 // Note that GradientStop is a simple struct with a stop value (while GradientStops has the surface).
2255 nsTArray<gfx::GradientStop> rawStops(stops.Length());
2256 rawStops.SetLength(stops.Length());
2257 for(uint32_t i = 0; i < stops.Length(); i++) {
2258 rawStops[i].color = gfx::Color(stops[i].mColor.r, stops[i].mColor.g, stops[i].mColor.b, stops[i].mColor.a);
2259 rawStops[i].offset = stopScale * (stops[i].mPosition - stopOrigin);
2260 }
2261 mozilla::RefPtr<mozilla::gfx::GradientStops> gs =
2262 gfxGradientCache::GetOrCreateGradientStops(ctx->GetDrawTarget(),
2263 rawStops,
2264 isRepeat ? gfx::ExtendMode::REPEAT : gfx::ExtendMode::CLAMP);
2265 gradientPattern->SetColorStops(gs);
2266 } else {
2267 for (uint32_t i = 0; i < stops.Length(); i++) {
2268 double pos = stopScale*(stops[i].mPosition - stopOrigin);
2269 gradientPattern->AddColorStop(pos, stops[i].mColor);
2270 }
2271 // Set repeat mode. Default cairo extend mode is PAD.
2272 if (isRepeat) {
2273 gradientPattern->SetExtend(gfxPattern::EXTEND_REPEAT);
2274 }
2275 }
2277 // Paint gradient tiles. This isn't terribly efficient, but doing it this
2278 // way is simple and sure to get pixel-snapping right. We could speed things
2279 // up by drawing tiles into temporary surfaces and copying those to the
2280 // destination, but after pixel-snapping tiles may not all be the same size.
2281 nsRect dirty;
2282 if (!dirty.IntersectRect(aDirtyRect, aFillArea))
2283 return;
2285 gfxRect areaToFill =
2286 nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel);
2287 gfxMatrix ctm = ctx->CurrentMatrix();
2288 bool isCTMPreservingAxisAlignedRectangles = ctm.PreservesAxisAlignedRectangles();
2290 // xStart/yStart are the top-left corner of the top-left tile.
2291 nscoord xStart = FindTileStart(dirty.x, aDest.x, aDest.width);
2292 nscoord yStart = FindTileStart(dirty.y, aDest.y, aDest.height);
2293 nscoord xEnd = forceRepeatToCoverTiles ? xStart + aDest.width : dirty.XMost();
2294 nscoord yEnd = forceRepeatToCoverTiles ? yStart + aDest.height : dirty.YMost();
2296 // x and y are the top-left corner of the tile to draw
2297 for (nscoord y = yStart; y < yEnd; y += aDest.height) {
2298 for (nscoord x = xStart; x < xEnd; x += aDest.width) {
2299 // The coordinates of the tile
2300 gfxRect tileRect = nsLayoutUtils::RectToGfxRect(
2301 nsRect(x, y, aDest.width, aDest.height),
2302 appUnitsPerDevPixel);
2303 // The actual area to fill with this tile is the intersection of this
2304 // tile with the overall area we're supposed to be filling
2305 gfxRect fillRect =
2306 forceRepeatToCoverTiles ? areaToFill : tileRect.Intersect(areaToFill);
2307 // Try snapping the fill rect. Snap its top-left and bottom-right
2308 // independently to preserve the orientation.
2309 gfxPoint snappedFillRectTopLeft = fillRect.TopLeft();
2310 gfxPoint snappedFillRectTopRight = fillRect.TopRight();
2311 gfxPoint snappedFillRectBottomRight = fillRect.BottomRight();
2312 // Snap three points instead of just two to ensure we choose the
2313 // correct orientation if there's a reflection.
2314 if (isCTMPreservingAxisAlignedRectangles &&
2315 ctx->UserToDevicePixelSnapped(snappedFillRectTopLeft, true) &&
2316 ctx->UserToDevicePixelSnapped(snappedFillRectBottomRight, true) &&
2317 ctx->UserToDevicePixelSnapped(snappedFillRectTopRight, true)) {
2318 if (snappedFillRectTopLeft.x == snappedFillRectBottomRight.x ||
2319 snappedFillRectTopLeft.y == snappedFillRectBottomRight.y) {
2320 // Nothing to draw; avoid scaling by zero and other weirdness that
2321 // could put the context in an error state.
2322 continue;
2323 }
2324 // Set the context's transform to the transform that maps fillRect to
2325 // snappedFillRect. The part of the gradient that was going to
2326 // exactly fill fillRect will fill snappedFillRect instead.
2327 gfxMatrix transform = gfxUtils::TransformRectToRect(fillRect,
2328 snappedFillRectTopLeft, snappedFillRectTopRight,
2329 snappedFillRectBottomRight);
2330 ctx->SetMatrix(transform);
2331 }
2332 ctx->NewPath();
2333 ctx->Rectangle(fillRect);
2334 ctx->Translate(tileRect.TopLeft());
2335 ctx->SetPattern(gradientPattern);
2336 ctx->Fill();
2337 ctx->SetMatrix(ctm);
2338 }
2339 }
2340 }
2342 void
2343 nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext,
2344 nsRenderingContext& aRenderingContext,
2345 nsIFrame* aForFrame,
2346 const nsRect& aDirtyRect,
2347 const nsRect& aBorderArea,
2348 nsStyleContext* aBackgroundSC,
2349 const nsStyleBorder& aBorder,
2350 uint32_t aFlags,
2351 nsRect* aBGClipRect,
2352 int32_t aLayer)
2353 {
2354 NS_PRECONDITION(aForFrame,
2355 "Frame is expected to be provided to PaintBackground");
2357 // Check to see if we have an appearance defined. If so, we let the theme
2358 // renderer draw the background and bail out.
2359 // XXXzw this ignores aBGClipRect.
2360 const nsStyleDisplay* displayData = aForFrame->StyleDisplay();
2361 if (displayData->mAppearance) {
2362 nsITheme *theme = aPresContext->GetTheme();
2363 if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame,
2364 displayData->mAppearance)) {
2365 nsRect drawing(aBorderArea);
2366 theme->GetWidgetOverflow(aPresContext->DeviceContext(),
2367 aForFrame, displayData->mAppearance, &drawing);
2368 drawing.IntersectRect(drawing, aDirtyRect);
2369 theme->DrawWidgetBackground(&aRenderingContext, aForFrame,
2370 displayData->mAppearance, aBorderArea,
2371 drawing);
2372 return;
2373 }
2374 }
2376 // For canvas frames (in the CSS sense) we draw the background color using
2377 // a solid color item that gets added in nsLayoutUtils::PaintFrame,
2378 // or nsSubDocumentFrame::BuildDisplayList (bug 488242). (The solid
2379 // color may be moved into nsDisplayCanvasBackground by
2380 // nsPresShell::AddCanvasBackgroundColorItem, and painted by
2381 // nsDisplayCanvasBackground directly.) Either way we don't need to
2382 // paint the background color here.
2383 bool isCanvasFrame = IsCanvasFrame(aForFrame);
2385 // Determine whether we are drawing background images and/or
2386 // background colors.
2387 bool drawBackgroundImage;
2388 bool drawBackgroundColor;
2390 nscolor bgColor = DetermineBackgroundColor(aPresContext,
2391 aBackgroundSC,
2392 aForFrame,
2393 drawBackgroundImage,
2394 drawBackgroundColor);
2396 // If we're drawing a specific layer, we don't want to draw the
2397 // background color.
2398 const nsStyleBackground *bg = aBackgroundSC->StyleBackground();
2399 if (drawBackgroundColor && aLayer >= 0) {
2400 drawBackgroundColor = false;
2401 }
2403 // At this point, drawBackgroundImage and drawBackgroundColor are
2404 // true if and only if we are actually supposed to paint an image or
2405 // color into aDirtyRect, respectively.
2406 if (!drawBackgroundImage && !drawBackgroundColor)
2407 return;
2409 // Compute the outermost boundary of the area that might be painted.
2410 gfxContext *ctx = aRenderingContext.ThebesContext();
2411 nscoord appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
2413 // Same coordinate space as aBorderArea & aBGClipRect
2414 gfxCornerSizes bgRadii;
2415 bool haveRoundedCorners;
2416 {
2417 nscoord radii[8];
2418 nsSize frameSize = aForFrame->GetSize();
2419 if (&aBorder == aForFrame->StyleBorder() &&
2420 frameSize == aBorderArea.Size()) {
2421 haveRoundedCorners = aForFrame->GetBorderRadii(radii);
2422 } else {
2423 haveRoundedCorners = nsIFrame::ComputeBorderRadii(aBorder.mBorderRadius,
2424 frameSize, aBorderArea.Size(),
2425 aForFrame->GetSkipSides(), radii);
2426 }
2427 if (haveRoundedCorners)
2428 ComputePixelRadii(radii, appUnitsPerPixel, &bgRadii);
2429 }
2431 // The 'bgClipArea' (used only by the image tiling logic, far below)
2432 // is the caller-provided aBGClipRect if any, or else the area
2433 // determined by the value of 'background-clip' in
2434 // SetupCurrentBackgroundClip. (Arguably it should be the
2435 // intersection, but that breaks the table painter -- in particular,
2436 // taking the intersection breaks reftests/bugs/403249-1[ab].)
2437 BackgroundClipState clipState;
2438 uint8_t currentBackgroundClip;
2439 bool isSolidBorder;
2440 if (aBGClipRect) {
2441 clipState.mBGClipArea = *aBGClipRect;
2442 clipState.mCustomClip = true;
2443 SetupDirtyRects(clipState.mBGClipArea, aDirtyRect, appUnitsPerPixel,
2444 &clipState.mDirtyRect, &clipState.mDirtyRectGfx);
2445 } else {
2446 // The background is rendered over the 'background-clip' area,
2447 // which is normally equal to the border area but may be reduced
2448 // to the padding area by CSS. Also, if the border is solid, we
2449 // don't need to draw outside the padding area. In either case,
2450 // if the borders are rounded, make sure we use the same inner
2451 // radii as the border code will.
2452 // The background-color is drawn based on the bottom
2453 // background-clip.
2454 currentBackgroundClip = bg->BottomLayer().mClip;
2455 isSolidBorder =
2456 (aFlags & PAINTBG_WILL_PAINT_BORDER) && IsOpaqueBorder(aBorder);
2457 if (isSolidBorder && currentBackgroundClip == NS_STYLE_BG_CLIP_BORDER) {
2458 // If we have rounded corners, we need to inflate the background
2459 // drawing area a bit to avoid seams between the border and
2460 // background.
2461 currentBackgroundClip = haveRoundedCorners ?
2462 NS_STYLE_BG_CLIP_MOZ_ALMOST_PADDING : NS_STYLE_BG_CLIP_PADDING;
2463 }
2465 GetBackgroundClip(ctx, currentBackgroundClip, bg->BottomLayer().mAttachment,
2466 aForFrame, aBorderArea,
2467 aDirtyRect, haveRoundedCorners, bgRadii, appUnitsPerPixel,
2468 &clipState);
2469 }
2471 // If we might be using a background color, go ahead and set it now.
2472 if (drawBackgroundColor && !isCanvasFrame)
2473 ctx->SetColor(gfxRGBA(bgColor));
2475 gfxContextAutoSaveRestore autoSR;
2477 // If there is no background image, draw a color. (If there is
2478 // neither a background image nor a color, we wouldn't have gotten
2479 // this far.)
2480 if (!drawBackgroundImage) {
2481 if (!isCanvasFrame) {
2482 DrawBackgroundColor(clipState, ctx, haveRoundedCorners, appUnitsPerPixel);
2483 }
2484 return;
2485 }
2487 if (bg->mImageCount < 1) {
2488 // Return if there are no background layers, all work from this point
2489 // onwards happens iteratively on these.
2490 return;
2491 }
2493 // Validate the layer range before we start iterating.
2494 int32_t startLayer = aLayer;
2495 int32_t nLayers = 1;
2496 if (startLayer < 0) {
2497 startLayer = (int32_t)bg->mImageCount - 1;
2498 nLayers = bg->mImageCount;
2499 }
2501 // Ensure we get invalidated for loads of the image. We need to do
2502 // this here because this might be the only code that knows about the
2503 // association of the style data with the frame.
2504 if (aBackgroundSC != aForFrame->StyleContext()) {
2505 NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT_WITH_RANGE(i, bg, startLayer, nLayers) {
2506 aForFrame->AssociateImage(bg->mLayers[i].mImage, aPresContext);
2507 }
2508 }
2510 // The background color is rendered over the entire dirty area,
2511 // even if the image isn't.
2512 if (drawBackgroundColor && !isCanvasFrame) {
2513 DrawBackgroundColor(clipState, ctx, haveRoundedCorners, appUnitsPerPixel);
2514 }
2516 if (drawBackgroundImage) {
2517 bool clipSet = false;
2518 NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT_WITH_RANGE(i, bg, bg->mImageCount - 1,
2519 nLayers + (bg->mImageCount -
2520 startLayer - 1)) {
2521 const nsStyleBackground::Layer &layer = bg->mLayers[i];
2522 if (!aBGClipRect) {
2523 uint8_t newBackgroundClip = layer.mClip;
2524 if (isSolidBorder && newBackgroundClip == NS_STYLE_BG_CLIP_BORDER) {
2525 newBackgroundClip = haveRoundedCorners ?
2526 NS_STYLE_BG_CLIP_MOZ_ALMOST_PADDING : NS_STYLE_BG_CLIP_PADDING;
2527 }
2528 if (currentBackgroundClip != newBackgroundClip || !clipSet) {
2529 currentBackgroundClip = newBackgroundClip;
2530 // If clipSet is false that means this is the bottom layer and we
2531 // already called GetBackgroundClip above and it stored its results
2532 // in clipState.
2533 if (clipSet) {
2534 GetBackgroundClip(ctx, currentBackgroundClip, layer.mAttachment, aForFrame,
2535 aBorderArea, aDirtyRect, haveRoundedCorners,
2536 bgRadii, appUnitsPerPixel, &clipState);
2537 }
2538 SetupBackgroundClip(clipState, ctx, haveRoundedCorners,
2539 appUnitsPerPixel, &autoSR);
2540 clipSet = true;
2541 }
2542 }
2543 if ((aLayer < 0 || i == (uint32_t)startLayer) &&
2544 !clipState.mDirtyRectGfx.IsEmpty()) {
2545 nsBackgroundLayerState state = PrepareBackgroundLayer(aPresContext, aForFrame,
2546 aFlags, aBorderArea, clipState.mBGClipArea, *bg, layer);
2547 if (!state.mFillArea.IsEmpty()) {
2548 if (state.mCompositingOp != gfxContext::OPERATOR_OVER) {
2549 NS_ASSERTION(ctx->CurrentOperator() == gfxContext::OPERATOR_OVER,
2550 "It is assumed the initial operator is OPERATOR_OVER, when it is restored later");
2551 ctx->SetOperator(state.mCompositingOp);
2552 }
2553 state.mImageRenderer.DrawBackground(aPresContext, aRenderingContext,
2554 state.mDestArea, state.mFillArea,
2555 state.mAnchor + aBorderArea.TopLeft(),
2556 clipState.mDirtyRect);
2557 if (state.mCompositingOp != gfxContext::OPERATOR_OVER) {
2558 ctx->SetOperator(gfxContext::OPERATOR_OVER);
2559 }
2560 }
2561 }
2562 }
2563 }
2564 }
2566 void
2567 nsCSSRendering::PaintBackgroundColorWithSC(nsPresContext* aPresContext,
2568 nsRenderingContext& aRenderingContext,
2569 nsIFrame* aForFrame,
2570 const nsRect& aDirtyRect,
2571 const nsRect& aBorderArea,
2572 nsStyleContext* aBackgroundSC,
2573 const nsStyleBorder& aBorder,
2574 uint32_t aFlags)
2575 {
2576 NS_PRECONDITION(aForFrame,
2577 "Frame is expected to be provided to PaintBackground");
2579 // Check to see if we have an appearance defined. If so, we let the theme
2580 // renderer draw the background and bail out.
2581 const nsStyleDisplay* displayData = aForFrame->StyleDisplay();
2582 if (displayData->mAppearance) {
2583 nsITheme *theme = aPresContext->GetTheme();
2584 if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame,
2585 displayData->mAppearance)) {
2586 NS_ERROR("Shouldn't be trying to paint a background color if we are themed!");
2587 return;
2588 }
2589 }
2591 NS_ASSERTION(!IsCanvasFrame(aForFrame), "Should not be trying to paint a background color for canvas frames!");
2593 // Determine whether we are drawing background images and/or
2594 // background colors.
2595 bool drawBackgroundImage;
2596 bool drawBackgroundColor;
2597 nscolor bgColor = DetermineBackgroundColor(aPresContext,
2598 aBackgroundSC,
2599 aForFrame,
2600 drawBackgroundImage,
2601 drawBackgroundColor);
2603 NS_ASSERTION(drawBackgroundImage || drawBackgroundColor,
2604 "Should not be trying to paint a background if we don't have one");
2605 if (!drawBackgroundColor) {
2606 return;
2607 }
2609 // Compute the outermost boundary of the area that might be painted.
2610 gfxContext *ctx = aRenderingContext.ThebesContext();
2611 nscoord appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
2613 // Same coordinate space as aBorderArea
2614 gfxCornerSizes bgRadii;
2615 bool haveRoundedCorners;
2616 {
2617 nscoord radii[8];
2618 nsSize frameSize = aForFrame->GetSize();
2619 if (&aBorder == aForFrame->StyleBorder() &&
2620 frameSize == aBorderArea.Size()) {
2621 haveRoundedCorners = aForFrame->GetBorderRadii(radii);
2622 } else {
2623 haveRoundedCorners = nsIFrame::ComputeBorderRadii(aBorder.mBorderRadius,
2624 frameSize, aBorderArea.Size(),
2625 aForFrame->GetSkipSides(), radii);
2626 }
2627 if (haveRoundedCorners)
2628 ComputePixelRadii(radii, appUnitsPerPixel, &bgRadii);
2629 }
2631 // The background is rendered over the 'background-clip' area,
2632 // which is normally equal to the border area but may be reduced
2633 // to the padding area by CSS. Also, if the border is solid, we
2634 // don't need to draw outside the padding area. In either case,
2635 // if the borders are rounded, make sure we use the same inner
2636 // radii as the border code will.
2637 // The background-color is drawn based on the bottom
2638 // background-clip.
2639 const nsStyleBackground *bg = aBackgroundSC->StyleBackground();
2640 uint8_t currentBackgroundClip = bg->BottomLayer().mClip;
2641 bool isSolidBorder =
2642 (aFlags & PAINTBG_WILL_PAINT_BORDER) && IsOpaqueBorder(aBorder);
2643 if (isSolidBorder && currentBackgroundClip == NS_STYLE_BG_CLIP_BORDER) {
2644 // If we have rounded corners, we need to inflate the background
2645 // drawing area a bit to avoid seams between the border and
2646 // background.
2647 currentBackgroundClip = haveRoundedCorners ?
2648 NS_STYLE_BG_CLIP_MOZ_ALMOST_PADDING : NS_STYLE_BG_CLIP_PADDING;
2649 }
2651 BackgroundClipState clipState;
2652 GetBackgroundClip(ctx, currentBackgroundClip, bg->BottomLayer().mAttachment,
2653 aForFrame, aBorderArea,
2654 aDirtyRect, haveRoundedCorners, bgRadii, appUnitsPerPixel,
2655 &clipState);
2657 ctx->SetColor(gfxRGBA(bgColor));
2659 gfxContextAutoSaveRestore autoSR;
2660 DrawBackgroundColor(clipState, ctx, haveRoundedCorners, appUnitsPerPixel);
2661 }
2663 static inline bool
2664 IsTransformed(nsIFrame* aForFrame, nsIFrame* aTopFrame)
2665 {
2666 for (nsIFrame* f = aForFrame; f != aTopFrame; f = f->GetParent()) {
2667 if (f->IsTransformed()) {
2668 return true;
2669 }
2670 }
2671 return false;
2672 }
2674 nsRect
2675 nsCSSRendering::ComputeBackgroundPositioningArea(nsPresContext* aPresContext,
2676 nsIFrame* aForFrame,
2677 const nsRect& aBorderArea,
2678 const nsStyleBackground& aBackground,
2679 const nsStyleBackground::Layer& aLayer,
2680 nsIFrame** aAttachedToFrame)
2681 {
2682 // Compute background origin area relative to aBorderArea now as we may need
2683 // it to compute the effective image size for a CSS gradient.
2684 nsRect bgPositioningArea(0, 0, 0, 0);
2686 nsIAtom* frameType = aForFrame->GetType();
2687 nsIFrame* geometryFrame = aForFrame;
2688 if (frameType == nsGkAtoms::inlineFrame) {
2689 // XXXjwalden Strictly speaking this is not quite faithful to how
2690 // background-break is supposed to interact with background-origin values,
2691 // but it's a non-trivial amount of work to make it fully conformant, and
2692 // until the specification is more finalized (and assuming background-break
2693 // even makes the cut) it doesn't make sense to hammer out exact behavior.
2694 switch (aBackground.mBackgroundInlinePolicy) {
2695 case NS_STYLE_BG_INLINE_POLICY_EACH_BOX:
2696 bgPositioningArea = nsRect(nsPoint(0,0), aBorderArea.Size());
2697 break;
2698 case NS_STYLE_BG_INLINE_POLICY_BOUNDING_BOX:
2699 bgPositioningArea = gInlineBGData->GetBoundingRect(aForFrame);
2700 break;
2701 default:
2702 NS_ERROR("Unknown background-inline-policy value! "
2703 "Please, teach me what to do.");
2704 case NS_STYLE_BG_INLINE_POLICY_CONTINUOUS:
2705 bgPositioningArea = gInlineBGData->GetContinuousRect(aForFrame);
2706 break;
2707 }
2708 } else if (frameType == nsGkAtoms::canvasFrame) {
2709 geometryFrame = aForFrame->GetFirstPrincipalChild();
2710 // geometryFrame might be null if this canvas is a page created
2711 // as an overflow container (e.g. the in-flow content has already
2712 // finished and this page only displays the continuations of
2713 // absolutely positioned content).
2714 if (geometryFrame) {
2715 bgPositioningArea = geometryFrame->GetRect();
2716 }
2717 } else if (frameType == nsGkAtoms::scrollFrame &&
2718 NS_STYLE_BG_ATTACHMENT_LOCAL == aLayer.mAttachment) {
2719 nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
2720 bgPositioningArea = nsRect(
2721 scrollableFrame->GetScrolledFrame()->GetPosition()
2722 // For the dir=rtl case:
2723 + scrollableFrame->GetScrollRange().TopLeft(),
2724 scrollableFrame->GetScrolledRect().Size());
2725 // The ScrolledRect’s size does not include the borders or scrollbars,
2726 // reverse the handling of background-origin
2727 // compared to the common case below.
2728 if (aLayer.mOrigin == NS_STYLE_BG_ORIGIN_BORDER) {
2729 nsMargin border = geometryFrame->GetUsedBorder();
2730 geometryFrame->ApplySkipSides(border);
2731 bgPositioningArea.Inflate(border);
2732 bgPositioningArea.Inflate(scrollableFrame->GetActualScrollbarSizes());
2733 } else if (aLayer.mOrigin != NS_STYLE_BG_ORIGIN_PADDING) {
2734 nsMargin padding = geometryFrame->GetUsedPadding();
2735 geometryFrame->ApplySkipSides(padding);
2736 bgPositioningArea.Deflate(padding);
2737 NS_ASSERTION(aLayer.mOrigin == NS_STYLE_BG_ORIGIN_CONTENT,
2738 "unknown background-origin value");
2739 }
2740 *aAttachedToFrame = aForFrame;
2741 return bgPositioningArea;
2742 } else {
2743 bgPositioningArea = nsRect(nsPoint(0,0), aBorderArea.Size());
2744 }
2746 // Background images are tiled over the 'background-clip' area
2747 // but the origin of the tiling is based on the 'background-origin' area
2748 if (aLayer.mOrigin != NS_STYLE_BG_ORIGIN_BORDER && geometryFrame) {
2749 nsMargin border = geometryFrame->GetUsedBorder();
2750 if (aLayer.mOrigin != NS_STYLE_BG_ORIGIN_PADDING) {
2751 border += geometryFrame->GetUsedPadding();
2752 NS_ASSERTION(aLayer.mOrigin == NS_STYLE_BG_ORIGIN_CONTENT,
2753 "unknown background-origin value");
2754 }
2755 geometryFrame->ApplySkipSides(border);
2756 bgPositioningArea.Deflate(border);
2757 }
2759 nsIFrame* attachedToFrame = aForFrame;
2760 if (NS_STYLE_BG_ATTACHMENT_FIXED == aLayer.mAttachment) {
2761 // If it's a fixed background attachment, then the image is placed
2762 // relative to the viewport, which is the area of the root frame
2763 // in a screen context or the page content frame in a print context.
2764 attachedToFrame = aPresContext->PresShell()->FrameManager()->GetRootFrame();
2765 NS_ASSERTION(attachedToFrame, "no root frame");
2766 nsIFrame* pageContentFrame = nullptr;
2767 if (aPresContext->IsPaginated()) {
2768 pageContentFrame =
2769 nsLayoutUtils::GetClosestFrameOfType(aForFrame, nsGkAtoms::pageContentFrame);
2770 if (pageContentFrame) {
2771 attachedToFrame = pageContentFrame;
2772 }
2773 // else this is an embedded shell and its root frame is what we want
2774 }
2776 // Set the background positioning area to the viewport's area
2777 // (relative to aForFrame)
2778 bgPositioningArea =
2779 nsRect(-aForFrame->GetOffsetTo(attachedToFrame), attachedToFrame->GetSize());
2781 if (!pageContentFrame) {
2782 // Subtract the size of scrollbars.
2783 nsIScrollableFrame* scrollableFrame =
2784 aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
2785 if (scrollableFrame) {
2786 nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes();
2787 bgPositioningArea.Deflate(scrollbars);
2788 }
2789 }
2790 }
2791 *aAttachedToFrame = attachedToFrame;
2793 return bgPositioningArea;
2794 }
2796 // Apply the CSS image sizing algorithm as it applies to background images.
2797 // See http://www.w3.org/TR/css3-background/#the-background-size .
2798 // aIntrinsicSize is the size that the background image 'would like to be'.
2799 // It can be found by calling nsImageRenderer::ComputeIntrinsicSize.
2800 static nsSize
2801 ComputeDrawnSizeForBackground(const CSSSizeOrRatio& aIntrinsicSize,
2802 const nsSize& aBgPositioningArea,
2803 const nsStyleBackground::Size& aLayerSize)
2804 {
2805 // Size is dictated by cover or contain rules.
2806 if (aLayerSize.mWidthType == nsStyleBackground::Size::eContain ||
2807 aLayerSize.mWidthType == nsStyleBackground::Size::eCover) {
2808 nsImageRenderer::FitType fitType =
2809 aLayerSize.mWidthType == nsStyleBackground::Size::eCover
2810 ? nsImageRenderer::COVER
2811 : nsImageRenderer::CONTAIN;
2812 return nsImageRenderer::ComputeConstrainedSize(aBgPositioningArea,
2813 aIntrinsicSize.mRatio,
2814 fitType);
2815 }
2817 // No cover/contain constraint, use default algorithm.
2818 CSSSizeOrRatio specifiedSize;
2819 if (aLayerSize.mWidthType == nsStyleBackground::Size::eLengthPercentage) {
2820 specifiedSize.SetWidth(
2821 aLayerSize.ResolveWidthLengthPercentage(aBgPositioningArea));
2822 }
2823 if (aLayerSize.mHeightType == nsStyleBackground::Size::eLengthPercentage) {
2824 specifiedSize.SetHeight(
2825 aLayerSize.ResolveHeightLengthPercentage(aBgPositioningArea));
2826 }
2828 return nsImageRenderer::ComputeConcreteSize(specifiedSize,
2829 aIntrinsicSize,
2830 aBgPositioningArea);
2831 }
2833 nsBackgroundLayerState
2834 nsCSSRendering::PrepareBackgroundLayer(nsPresContext* aPresContext,
2835 nsIFrame* aForFrame,
2836 uint32_t aFlags,
2837 const nsRect& aBorderArea,
2838 const nsRect& aBGClipRect,
2839 const nsStyleBackground& aBackground,
2840 const nsStyleBackground::Layer& aLayer)
2841 {
2842 /*
2843 * The background properties we need to keep in mind when drawing background
2844 * layers are:
2845 *
2846 * background-image
2847 * background-repeat
2848 * background-attachment
2849 * background-position
2850 * background-clip
2851 * background-origin
2852 * background-size
2853 * background-break (-moz-background-inline-policy)
2854 * background-blend-mode
2855 *
2856 * (background-color applies to the entire element and not to individual
2857 * layers, so it is irrelevant to this method.)
2858 *
2859 * These properties have the following dependencies upon each other when
2860 * determining rendering:
2861 *
2862 * background-image
2863 * no dependencies
2864 * background-repeat
2865 * no dependencies
2866 * background-attachment
2867 * no dependencies
2868 * background-position
2869 * depends upon background-size (for the image's scaled size) and
2870 * background-break (for the background positioning area)
2871 * background-clip
2872 * no dependencies
2873 * background-origin
2874 * depends upon background-attachment (only in the case where that value
2875 * is 'fixed')
2876 * background-size
2877 * depends upon background-break (for the background positioning area for
2878 * resolving percentages), background-image (for the image's intrinsic
2879 * size), background-repeat (if that value is 'round'), and
2880 * background-origin (for the background painting area, when
2881 * background-repeat is 'round')
2882 * background-break
2883 * depends upon background-origin (specifying how the boxes making up the
2884 * background positioning area are determined)
2885 *
2886 * As a result of only-if dependencies we don't strictly do a topological
2887 * sort of the above properties when processing, but it's pretty close to one:
2888 *
2889 * background-clip (by caller)
2890 * background-image
2891 * background-break, background-origin
2892 * background-attachment (postfix for background-{origin,break} if 'fixed')
2893 * background-size
2894 * background-position
2895 * background-repeat
2896 */
2898 uint32_t irFlags = 0;
2899 if (aFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) {
2900 irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
2901 }
2902 if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) {
2903 irFlags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
2904 }
2906 nsBackgroundLayerState state(aForFrame, &aLayer.mImage, irFlags);
2907 if (!state.mImageRenderer.PrepareImage()) {
2908 // There's no image or it's not ready to be painted.
2909 return state;
2910 }
2912 // The frame to which the background is attached
2913 nsIFrame* attachedToFrame = aForFrame;
2914 // Compute background origin area relative to aBorderArea now as we may need
2915 // it to compute the effective image size for a CSS gradient.
2916 nsRect bgPositioningArea =
2917 ComputeBackgroundPositioningArea(aPresContext, aForFrame, aBorderArea,
2918 aBackground, aLayer, &attachedToFrame);
2920 // For background-attachment:fixed backgrounds, we'll limit the area
2921 // where the background can be drawn to the viewport.
2922 nsRect bgClipRect = aBGClipRect;
2924 // Compute the anchor point.
2925 //
2926 // relative to aBorderArea.TopLeft() (which is where the top-left
2927 // of aForFrame's border-box will be rendered)
2928 nsPoint imageTopLeft;
2929 if (NS_STYLE_BG_ATTACHMENT_FIXED == aLayer.mAttachment) {
2930 if ((aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) &&
2931 !IsTransformed(aForFrame, attachedToFrame)) {
2932 // Clip background-attachment:fixed backgrounds to the viewport, if we're
2933 // painting to the screen and not transformed. This avoids triggering
2934 // tiling in common cases, without affecting output since drawing is
2935 // always clipped to the viewport when we draw to the screen. (But it's
2936 // not a pure optimization since it can affect the values of pixels at the
2937 // edge of the viewport --- whether they're sampled from a putative "next
2938 // tile" or not.)
2939 bgClipRect.IntersectRect(bgClipRect, bgPositioningArea + aBorderArea.TopLeft());
2940 }
2941 }
2943 // Scale the image as specified for background-size and as required for
2944 // proper background positioning when background-position is defined with
2945 // percentages.
2946 CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize();
2947 nsSize bgPositionSize = bgPositioningArea.Size();
2948 nsSize imageSize = ComputeDrawnSizeForBackground(intrinsicSize,
2949 bgPositionSize,
2950 aLayer.mSize);
2951 if (imageSize.width <= 0 || imageSize.height <= 0)
2952 return state;
2954 state.mImageRenderer.SetPreferredSize(intrinsicSize,
2955 imageSize);
2957 // Compute the position of the background now that the background's size is
2958 // determined.
2959 ComputeBackgroundAnchorPoint(aLayer, bgPositionSize, imageSize,
2960 &imageTopLeft, &state.mAnchor);
2961 imageTopLeft += bgPositioningArea.TopLeft();
2962 state.mAnchor += bgPositioningArea.TopLeft();
2964 state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize);
2965 state.mFillArea = state.mDestArea;
2966 int repeatX = aLayer.mRepeat.mXRepeat;
2967 int repeatY = aLayer.mRepeat.mYRepeat;
2968 if (repeatX == NS_STYLE_BG_REPEAT_REPEAT) {
2969 state.mFillArea.x = bgClipRect.x;
2970 state.mFillArea.width = bgClipRect.width;
2971 }
2972 if (repeatY == NS_STYLE_BG_REPEAT_REPEAT) {
2973 state.mFillArea.y = bgClipRect.y;
2974 state.mFillArea.height = bgClipRect.height;
2975 }
2976 state.mFillArea.IntersectRect(state.mFillArea, bgClipRect);
2978 state.mCompositingOp = GetGFXBlendMode(aLayer.mBlendMode);
2980 return state;
2981 }
2983 nsRect
2984 nsCSSRendering::GetBackgroundLayerRect(nsPresContext* aPresContext,
2985 nsIFrame* aForFrame,
2986 const nsRect& aBorderArea,
2987 const nsRect& aClipRect,
2988 const nsStyleBackground& aBackground,
2989 const nsStyleBackground::Layer& aLayer,
2990 uint32_t aFlags)
2991 {
2992 nsBackgroundLayerState state =
2993 PrepareBackgroundLayer(aPresContext, aForFrame, aFlags, aBorderArea,
2994 aClipRect, aBackground, aLayer);
2995 return state.mFillArea;
2996 }
2998 /* static */ bool
2999 nsCSSRendering::IsBackgroundImageDecodedForStyleContextAndLayer(
3000 const nsStyleBackground *aBackground, uint32_t aLayer)
3001 {
3002 const nsStyleImage* image = &aBackground->mLayers[aLayer].mImage;
3003 if (image->GetType() == eStyleImageType_Image) {
3004 nsCOMPtr<imgIContainer> img;
3005 if (NS_SUCCEEDED(image->GetImageData()->GetImage(getter_AddRefs(img)))) {
3006 if (!img->IsDecoded()) {
3007 return false;
3008 }
3009 }
3010 }
3011 return true;
3012 }
3014 /* static */ bool
3015 nsCSSRendering::AreAllBackgroundImagesDecodedForFrame(nsIFrame* aFrame)
3016 {
3017 const nsStyleBackground *bg = aFrame->StyleContext()->StyleBackground();
3018 NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(i, bg) {
3019 if (!IsBackgroundImageDecodedForStyleContextAndLayer(bg, i)) {
3020 return false;
3021 }
3022 }
3023 return true;
3024 }
3026 static void
3027 DrawBorderImage(nsPresContext* aPresContext,
3028 nsRenderingContext& aRenderingContext,
3029 nsIFrame* aForFrame,
3030 const nsRect& aBorderArea,
3031 const nsStyleBorder& aStyleBorder,
3032 const nsRect& aDirtyRect)
3033 {
3034 NS_PRECONDITION(aStyleBorder.IsBorderImageLoaded(),
3035 "drawing border image that isn't successfully loaded");
3037 if (aDirtyRect.IsEmpty())
3038 return;
3040 nsImageRenderer renderer(aForFrame, &aStyleBorder.mBorderImageSource, 0);
3042 // Ensure we get invalidated for loads and animations of the image.
3043 // We need to do this here because this might be the only code that
3044 // knows about the association of the style data with the frame.
3045 // XXX We shouldn't really... since if anybody is passing in a
3046 // different style, they'll potentially have the wrong size for the
3047 // border too.
3048 aForFrame->AssociateImage(aStyleBorder.mBorderImageSource, aPresContext);
3050 if (!renderer.PrepareImage()) {
3051 return;
3052 }
3054 // Determine the border image area, which by default corresponds to the
3055 // border box but can be modified by 'border-image-outset'.
3056 nsRect borderImgArea(aBorderArea);
3057 borderImgArea.Inflate(aStyleBorder.GetImageOutset());
3059 // Calculate the image size used to compute slice points.
3060 CSSSizeOrRatio intrinsicSize = renderer.ComputeIntrinsicSize();
3061 nsSize imageSize = nsImageRenderer::ComputeConcreteSize(CSSSizeOrRatio(),
3062 intrinsicSize,
3063 borderImgArea.Size());
3064 renderer.SetPreferredSize(intrinsicSize, imageSize);
3066 // Compute the used values of 'border-image-slice' and 'border-image-width';
3067 // we do them together because the latter can depend on the former.
3068 nsMargin slice;
3069 nsMargin border;
3070 NS_FOR_CSS_SIDES(s) {
3071 nsStyleCoord coord = aStyleBorder.mBorderImageSlice.Get(s);
3072 int32_t imgDimension = NS_SIDE_IS_VERTICAL(s)
3073 ? imageSize.width : imageSize.height;
3074 nscoord borderDimension = NS_SIDE_IS_VERTICAL(s)
3075 ? borderImgArea.width : borderImgArea.height;
3076 double value;
3077 switch (coord.GetUnit()) {
3078 case eStyleUnit_Percent:
3079 value = coord.GetPercentValue() * imgDimension;
3080 break;
3081 case eStyleUnit_Factor:
3082 value = nsPresContext::CSSPixelsToAppUnits(
3083 NS_lround(coord.GetFactorValue()));
3084 break;
3085 default:
3086 NS_NOTREACHED("unexpected CSS unit for image slice");
3087 value = 0;
3088 break;
3089 }
3090 if (value < 0)
3091 value = 0;
3092 if (value > imgDimension)
3093 value = imgDimension;
3094 slice.Side(s) = value;
3096 nsMargin borderWidths(aStyleBorder.GetComputedBorder());
3097 coord = aStyleBorder.mBorderImageWidth.Get(s);
3098 switch (coord.GetUnit()) {
3099 case eStyleUnit_Coord: // absolute dimension
3100 value = coord.GetCoordValue();
3101 break;
3102 case eStyleUnit_Percent:
3103 value = coord.GetPercentValue() * borderDimension;
3104 break;
3105 case eStyleUnit_Factor:
3106 value = coord.GetFactorValue() * borderWidths.Side(s);
3107 break;
3108 case eStyleUnit_Auto: // same as the slice value, in CSS pixels
3109 value = slice.Side(s);
3110 break;
3111 default:
3112 NS_NOTREACHED("unexpected CSS unit for border image area division");
3113 value = 0;
3114 break;
3115 }
3116 // NSToCoordRoundWithClamp rounds towards infinity, but that's OK
3117 // because we expect value to be non-negative.
3118 MOZ_ASSERT(value >= 0);
3119 border.Side(s) = NSToCoordRoundWithClamp(value);
3120 MOZ_ASSERT(border.Side(s) >= 0);
3121 }
3123 // "If two opposite border-image-width offsets are large enough that they
3124 // overlap, their used values are proportionately reduced until they no
3125 // longer overlap."
3126 uint32_t combinedBorderWidth = uint32_t(border.left) +
3127 uint32_t(border.right);
3128 double scaleX = combinedBorderWidth > uint32_t(borderImgArea.width)
3129 ? borderImgArea.width / double(combinedBorderWidth)
3130 : 1.0;
3131 uint32_t combinedBorderHeight = uint32_t(border.top) +
3132 uint32_t(border.bottom);
3133 double scaleY = combinedBorderHeight > uint32_t(borderImgArea.height)
3134 ? borderImgArea.height / double(combinedBorderHeight)
3135 : 1.0;
3136 double scale = std::min(scaleX, scaleY);
3137 if (scale < 1.0) {
3138 border.left *= scale;
3139 border.right *= scale;
3140 border.top *= scale;
3141 border.bottom *= scale;
3142 NS_ASSERTION(border.left + border.right <= borderImgArea.width &&
3143 border.top + border.bottom <= borderImgArea.height,
3144 "rounding error in width reduction???");
3145 }
3147 // These helper tables recharacterize the 'slice' and 'width' margins
3148 // in a more convenient form: they are the x/y/width/height coords
3149 // required for various bands of the border, and they have been transformed
3150 // to be relative to the innerRect (for 'slice') or the page (for 'border').
3151 enum {
3152 LEFT, MIDDLE, RIGHT,
3153 TOP = LEFT, BOTTOM = RIGHT
3154 };
3155 const nscoord borderX[3] = {
3156 borderImgArea.x + 0,
3157 borderImgArea.x + border.left,
3158 borderImgArea.x + borderImgArea.width - border.right,
3159 };
3160 const nscoord borderY[3] = {
3161 borderImgArea.y + 0,
3162 borderImgArea.y + border.top,
3163 borderImgArea.y + borderImgArea.height - border.bottom,
3164 };
3165 const nscoord borderWidth[3] = {
3166 border.left,
3167 borderImgArea.width - border.left - border.right,
3168 border.right,
3169 };
3170 const nscoord borderHeight[3] = {
3171 border.top,
3172 borderImgArea.height - border.top - border.bottom,
3173 border.bottom,
3174 };
3175 const int32_t sliceX[3] = {
3176 0,
3177 slice.left,
3178 imageSize.width - slice.right,
3179 };
3180 const int32_t sliceY[3] = {
3181 0,
3182 slice.top,
3183 imageSize.height - slice.bottom,
3184 };
3185 const int32_t sliceWidth[3] = {
3186 slice.left,
3187 std::max(imageSize.width - slice.left - slice.right, 0),
3188 slice.right,
3189 };
3190 const int32_t sliceHeight[3] = {
3191 slice.top,
3192 std::max(imageSize.height - slice.top - slice.bottom, 0),
3193 slice.bottom,
3194 };
3196 for (int i = LEFT; i <= RIGHT; i++) {
3197 for (int j = TOP; j <= BOTTOM; j++) {
3198 uint8_t fillStyleH, fillStyleV;
3199 nsSize unitSize;
3201 if (i == MIDDLE && j == MIDDLE) {
3202 // Discard the middle portion unless set to fill.
3203 if (NS_STYLE_BORDER_IMAGE_SLICE_NOFILL ==
3204 aStyleBorder.mBorderImageFill) {
3205 continue;
3206 }
3208 NS_ASSERTION(NS_STYLE_BORDER_IMAGE_SLICE_FILL ==
3209 aStyleBorder.mBorderImageFill,
3210 "Unexpected border image fill");
3212 // css-background:
3213 // The middle image's width is scaled by the same factor as the
3214 // top image unless that factor is zero or infinity, in which
3215 // case the scaling factor of the bottom is substituted, and
3216 // failing that, the width is not scaled. The height of the
3217 // middle image is scaled by the same factor as the left image
3218 // unless that factor is zero or infinity, in which case the
3219 // scaling factor of the right image is substituted, and failing
3220 // that, the height is not scaled.
3221 gfxFloat hFactor, vFactor;
3223 if (0 < border.left && 0 < slice.left)
3224 vFactor = gfxFloat(border.left)/slice.left;
3225 else if (0 < border.right && 0 < slice.right)
3226 vFactor = gfxFloat(border.right)/slice.right;
3227 else
3228 vFactor = 1;
3230 if (0 < border.top && 0 < slice.top)
3231 hFactor = gfxFloat(border.top)/slice.top;
3232 else if (0 < border.bottom && 0 < slice.bottom)
3233 hFactor = gfxFloat(border.bottom)/slice.bottom;
3234 else
3235 hFactor = 1;
3237 unitSize.width = sliceWidth[i]*hFactor;
3238 unitSize.height = sliceHeight[j]*vFactor;
3239 fillStyleH = aStyleBorder.mBorderImageRepeatH;
3240 fillStyleV = aStyleBorder.mBorderImageRepeatV;
3242 } else if (i == MIDDLE) { // top, bottom
3243 // Sides are always stretched to the thickness of their border,
3244 // and stretched proportionately on the other axis.
3245 gfxFloat factor;
3246 if (0 < borderHeight[j] && 0 < sliceHeight[j])
3247 factor = gfxFloat(borderHeight[j])/sliceHeight[j];
3248 else
3249 factor = 1;
3251 unitSize.width = sliceWidth[i]*factor;
3252 unitSize.height = borderHeight[j];
3253 fillStyleH = aStyleBorder.mBorderImageRepeatH;
3254 fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
3256 } else if (j == MIDDLE) { // left, right
3257 gfxFloat factor;
3258 if (0 < borderWidth[i] && 0 < sliceWidth[i])
3259 factor = gfxFloat(borderWidth[i])/sliceWidth[i];
3260 else
3261 factor = 1;
3263 unitSize.width = borderWidth[i];
3264 unitSize.height = sliceHeight[j]*factor;
3265 fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
3266 fillStyleV = aStyleBorder.mBorderImageRepeatV;
3268 } else {
3269 // Corners are always stretched to fit the corner.
3270 unitSize.width = borderWidth[i];
3271 unitSize.height = borderHeight[j];
3272 fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
3273 fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
3274 }
3276 nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]);
3277 nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]);
3278 nsIntRect intSubArea = subArea.ToOutsidePixels(nsPresContext::AppUnitsPerCSSPixel());
3280 renderer.DrawBorderImageComponent(aPresContext,
3281 aRenderingContext, aDirtyRect,
3282 destArea, CSSIntRect(intSubArea.x,
3283 intSubArea.y,
3284 intSubArea.width,
3285 intSubArea.height),
3286 fillStyleH, fillStyleV,
3287 unitSize, j * (RIGHT + 1) + i);
3288 }
3289 }
3290 }
3292 // Begin table border-collapsing section
3293 // These functions were written to not disrupt the normal ones and yet satisfy some additional requirements
3294 // At some point, all functions should be unified to include the additional functionality that these provide
3296 static nscoord
3297 RoundIntToPixel(nscoord aValue,
3298 nscoord aTwipsPerPixel,
3299 bool aRoundDown = false)
3300 {
3301 if (aTwipsPerPixel <= 0)
3302 // We must be rendering to a device that has a resolution greater than Twips!
3303 // In that case, aValue is as accurate as it's going to get.
3304 return aValue;
3306 nscoord halfPixel = NSToCoordRound(aTwipsPerPixel / 2.0f);
3307 nscoord extra = aValue % aTwipsPerPixel;
3308 nscoord finalValue = (!aRoundDown && (extra >= halfPixel)) ? aValue + (aTwipsPerPixel - extra) : aValue - extra;
3309 return finalValue;
3310 }
3312 static nscoord
3313 RoundFloatToPixel(float aValue,
3314 nscoord aTwipsPerPixel,
3315 bool aRoundDown = false)
3316 {
3317 return RoundIntToPixel(NSToCoordRound(aValue), aTwipsPerPixel, aRoundDown);
3318 }
3320 static void
3321 SetPoly(const nsRect& aRect,
3322 nsPoint* poly)
3323 {
3324 poly[0].x = aRect.x;
3325 poly[0].y = aRect.y;
3326 poly[1].x = aRect.x + aRect.width;
3327 poly[1].y = aRect.y;
3328 poly[2].x = aRect.x + aRect.width;
3329 poly[2].y = aRect.y + aRect.height;
3330 poly[3].x = aRect.x;
3331 poly[3].y = aRect.y + aRect.height;
3332 poly[4].x = aRect.x;
3333 poly[4].y = aRect.y;
3334 }
3336 static void
3337 DrawSolidBorderSegment(nsRenderingContext& aContext,
3338 nsRect aRect,
3339 nscoord aTwipsPerPixel,
3340 uint8_t aStartBevelSide = 0,
3341 nscoord aStartBevelOffset = 0,
3342 uint8_t aEndBevelSide = 0,
3343 nscoord aEndBevelOffset = 0)
3344 {
3346 if ((aRect.width == aTwipsPerPixel) || (aRect.height == aTwipsPerPixel) ||
3347 ((0 == aStartBevelOffset) && (0 == aEndBevelOffset))) {
3348 // simple line or rectangle
3349 if ((NS_SIDE_TOP == aStartBevelSide) || (NS_SIDE_BOTTOM == aStartBevelSide)) {
3350 if (1 == aRect.height)
3351 aContext.DrawLine(aRect.TopLeft(), aRect.BottomLeft());
3352 else
3353 aContext.FillRect(aRect);
3354 }
3355 else {
3356 if (1 == aRect.width)
3357 aContext.DrawLine(aRect.TopLeft(), aRect.TopRight());
3358 else
3359 aContext.FillRect(aRect);
3360 }
3361 }
3362 else {
3363 // polygon with beveling
3364 nsPoint poly[5];
3365 SetPoly(aRect, poly);
3366 switch(aStartBevelSide) {
3367 case NS_SIDE_TOP:
3368 poly[0].x += aStartBevelOffset;
3369 poly[4].x = poly[0].x;
3370 break;
3371 case NS_SIDE_BOTTOM:
3372 poly[3].x += aStartBevelOffset;
3373 break;
3374 case NS_SIDE_RIGHT:
3375 poly[1].y += aStartBevelOffset;
3376 break;
3377 case NS_SIDE_LEFT:
3378 poly[0].y += aStartBevelOffset;
3379 poly[4].y = poly[0].y;
3380 }
3382 switch(aEndBevelSide) {
3383 case NS_SIDE_TOP:
3384 poly[1].x -= aEndBevelOffset;
3385 break;
3386 case NS_SIDE_BOTTOM:
3387 poly[2].x -= aEndBevelOffset;
3388 break;
3389 case NS_SIDE_RIGHT:
3390 poly[2].y -= aEndBevelOffset;
3391 break;
3392 case NS_SIDE_LEFT:
3393 poly[3].y -= aEndBevelOffset;
3394 }
3396 aContext.FillPolygon(poly, 5);
3397 }
3400 }
3402 static void
3403 GetDashInfo(nscoord aBorderLength,
3404 nscoord aDashLength,
3405 nscoord aTwipsPerPixel,
3406 int32_t& aNumDashSpaces,
3407 nscoord& aStartDashLength,
3408 nscoord& aEndDashLength)
3409 {
3410 aNumDashSpaces = 0;
3411 if (aStartDashLength + aDashLength + aEndDashLength >= aBorderLength) {
3412 aStartDashLength = aBorderLength;
3413 aEndDashLength = 0;
3414 }
3415 else {
3416 aNumDashSpaces = (aBorderLength - aDashLength)/ (2 * aDashLength); // round down
3417 nscoord extra = aBorderLength - aStartDashLength - aEndDashLength - (((2 * aNumDashSpaces) - 1) * aDashLength);
3418 if (extra > 0) {
3419 nscoord half = RoundIntToPixel(extra / 2, aTwipsPerPixel);
3420 aStartDashLength += half;
3421 aEndDashLength += (extra - half);
3422 }
3423 }
3424 }
3426 void
3427 nsCSSRendering::DrawTableBorderSegment(nsRenderingContext& aContext,
3428 uint8_t aBorderStyle,
3429 nscolor aBorderColor,
3430 const nsStyleBackground* aBGColor,
3431 const nsRect& aBorder,
3432 int32_t aAppUnitsPerCSSPixel,
3433 uint8_t aStartBevelSide,
3434 nscoord aStartBevelOffset,
3435 uint8_t aEndBevelSide,
3436 nscoord aEndBevelOffset)
3437 {
3438 aContext.SetColor (aBorderColor);
3440 bool horizontal = ((NS_SIDE_TOP == aStartBevelSide) || (NS_SIDE_BOTTOM == aStartBevelSide));
3441 nscoord twipsPerPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerCSSPixel);
3442 uint8_t ridgeGroove = NS_STYLE_BORDER_STYLE_RIDGE;
3444 if ((twipsPerPixel >= aBorder.width) || (twipsPerPixel >= aBorder.height) ||
3445 (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle) || (NS_STYLE_BORDER_STYLE_DOTTED == aBorderStyle)) {
3446 // no beveling for 1 pixel border, dash or dot
3447 aStartBevelOffset = 0;
3448 aEndBevelOffset = 0;
3449 }
3451 gfxContext *ctx = aContext.ThebesContext();
3452 gfxContext::AntialiasMode oldMode = ctx->CurrentAntialiasMode();
3453 ctx->SetAntialiasMode(gfxContext::MODE_ALIASED);
3455 switch (aBorderStyle) {
3456 case NS_STYLE_BORDER_STYLE_NONE:
3457 case NS_STYLE_BORDER_STYLE_HIDDEN:
3458 //NS_ASSERTION(false, "style of none or hidden");
3459 break;
3460 case NS_STYLE_BORDER_STYLE_DOTTED:
3461 case NS_STYLE_BORDER_STYLE_DASHED:
3462 {
3463 nscoord dashLength = (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle) ? DASH_LENGTH : DOT_LENGTH;
3464 // make the dash length proportional to the border thickness
3465 dashLength *= (horizontal) ? aBorder.height : aBorder.width;
3466 // make the min dash length for the ends 1/2 the dash length
3467 nscoord minDashLength = (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle)
3468 ? RoundFloatToPixel(((float)dashLength) / 2.0f, twipsPerPixel) : dashLength;
3469 minDashLength = std::max(minDashLength, twipsPerPixel);
3470 nscoord numDashSpaces = 0;
3471 nscoord startDashLength = minDashLength;
3472 nscoord endDashLength = minDashLength;
3473 if (horizontal) {
3474 GetDashInfo(aBorder.width, dashLength, twipsPerPixel, numDashSpaces, startDashLength, endDashLength);
3475 nsRect rect(aBorder.x, aBorder.y, startDashLength, aBorder.height);
3476 DrawSolidBorderSegment(aContext, rect, twipsPerPixel);
3477 for (int32_t spaceX = 0; spaceX < numDashSpaces; spaceX++) {
3478 rect.x += rect.width + dashLength;
3479 rect.width = (spaceX == (numDashSpaces - 1)) ? endDashLength : dashLength;
3480 DrawSolidBorderSegment(aContext, rect, twipsPerPixel);
3481 }
3482 }
3483 else {
3484 GetDashInfo(aBorder.height, dashLength, twipsPerPixel, numDashSpaces, startDashLength, endDashLength);
3485 nsRect rect(aBorder.x, aBorder.y, aBorder.width, startDashLength);
3486 DrawSolidBorderSegment(aContext, rect, twipsPerPixel);
3487 for (int32_t spaceY = 0; spaceY < numDashSpaces; spaceY++) {
3488 rect.y += rect.height + dashLength;
3489 rect.height = (spaceY == (numDashSpaces - 1)) ? endDashLength : dashLength;
3490 DrawSolidBorderSegment(aContext, rect, twipsPerPixel);
3491 }
3492 }
3493 }
3494 break;
3495 case NS_STYLE_BORDER_STYLE_GROOVE:
3496 ridgeGroove = NS_STYLE_BORDER_STYLE_GROOVE; // and fall through to ridge
3497 case NS_STYLE_BORDER_STYLE_RIDGE:
3498 if ((horizontal && (twipsPerPixel >= aBorder.height)) ||
3499 (!horizontal && (twipsPerPixel >= aBorder.width))) {
3500 // a one pixel border
3501 DrawSolidBorderSegment(aContext, aBorder, twipsPerPixel, aStartBevelSide, aStartBevelOffset,
3502 aEndBevelSide, aEndBevelOffset);
3503 }
3504 else {
3505 nscoord startBevel = (aStartBevelOffset > 0)
3506 ? RoundFloatToPixel(0.5f * (float)aStartBevelOffset, twipsPerPixel, true) : 0;
3507 nscoord endBevel = (aEndBevelOffset > 0)
3508 ? RoundFloatToPixel(0.5f * (float)aEndBevelOffset, twipsPerPixel, true) : 0;
3509 mozilla::css::Side ridgeGrooveSide = (horizontal) ? NS_SIDE_TOP : NS_SIDE_LEFT;
3510 // FIXME: In theory, this should use the visited-dependent
3511 // background color, but I don't care.
3512 aContext.SetColor (
3513 MakeBevelColor(ridgeGrooveSide, ridgeGroove, aBGColor->mBackgroundColor, aBorderColor));
3514 nsRect rect(aBorder);
3515 nscoord half;
3516 if (horizontal) { // top, bottom
3517 half = RoundFloatToPixel(0.5f * (float)aBorder.height, twipsPerPixel);
3518 rect.height = half;
3519 if (NS_SIDE_TOP == aStartBevelSide) {
3520 rect.x += startBevel;
3521 rect.width -= startBevel;
3522 }
3523 if (NS_SIDE_TOP == aEndBevelSide) {
3524 rect.width -= endBevel;
3525 }
3526 DrawSolidBorderSegment(aContext, rect, twipsPerPixel, aStartBevelSide,
3527 startBevel, aEndBevelSide, endBevel);
3528 }
3529 else { // left, right
3530 half = RoundFloatToPixel(0.5f * (float)aBorder.width, twipsPerPixel);
3531 rect.width = half;
3532 if (NS_SIDE_LEFT == aStartBevelSide) {
3533 rect.y += startBevel;
3534 rect.height -= startBevel;
3535 }
3536 if (NS_SIDE_LEFT == aEndBevelSide) {
3537 rect.height -= endBevel;
3538 }
3539 DrawSolidBorderSegment(aContext, rect, twipsPerPixel, aStartBevelSide,
3540 startBevel, aEndBevelSide, endBevel);
3541 }
3543 rect = aBorder;
3544 ridgeGrooveSide = (NS_SIDE_TOP == ridgeGrooveSide) ? NS_SIDE_BOTTOM : NS_SIDE_RIGHT;
3545 // FIXME: In theory, this should use the visited-dependent
3546 // background color, but I don't care.
3547 aContext.SetColor (
3548 MakeBevelColor(ridgeGrooveSide, ridgeGroove, aBGColor->mBackgroundColor, aBorderColor));
3549 if (horizontal) {
3550 rect.y = rect.y + half;
3551 rect.height = aBorder.height - half;
3552 if (NS_SIDE_BOTTOM == aStartBevelSide) {
3553 rect.x += startBevel;
3554 rect.width -= startBevel;
3555 }
3556 if (NS_SIDE_BOTTOM == aEndBevelSide) {
3557 rect.width -= endBevel;
3558 }
3559 DrawSolidBorderSegment(aContext, rect, twipsPerPixel, aStartBevelSide,
3560 startBevel, aEndBevelSide, endBevel);
3561 }
3562 else {
3563 rect.x = rect.x + half;
3564 rect.width = aBorder.width - half;
3565 if (NS_SIDE_RIGHT == aStartBevelSide) {
3566 rect.y += aStartBevelOffset - startBevel;
3567 rect.height -= startBevel;
3568 }
3569 if (NS_SIDE_RIGHT == aEndBevelSide) {
3570 rect.height -= endBevel;
3571 }
3572 DrawSolidBorderSegment(aContext, rect, twipsPerPixel, aStartBevelSide,
3573 startBevel, aEndBevelSide, endBevel);
3574 }
3575 }
3576 break;
3577 case NS_STYLE_BORDER_STYLE_DOUBLE:
3578 // We can only do "double" borders if the thickness of the border
3579 // is more than 2px. Otherwise, we fall through to painting a
3580 // solid border.
3581 if ((aBorder.width > 2*twipsPerPixel || horizontal) &&
3582 (aBorder.height > 2*twipsPerPixel || !horizontal)) {
3583 nscoord startBevel = (aStartBevelOffset > 0)
3584 ? RoundFloatToPixel(0.333333f * (float)aStartBevelOffset, twipsPerPixel) : 0;
3585 nscoord endBevel = (aEndBevelOffset > 0)
3586 ? RoundFloatToPixel(0.333333f * (float)aEndBevelOffset, twipsPerPixel) : 0;
3587 if (horizontal) { // top, bottom
3588 nscoord thirdHeight = RoundFloatToPixel(0.333333f * (float)aBorder.height, twipsPerPixel);
3590 // draw the top line or rect
3591 nsRect topRect(aBorder.x, aBorder.y, aBorder.width, thirdHeight);
3592 if (NS_SIDE_TOP == aStartBevelSide) {
3593 topRect.x += aStartBevelOffset - startBevel;
3594 topRect.width -= aStartBevelOffset - startBevel;
3595 }
3596 if (NS_SIDE_TOP == aEndBevelSide) {
3597 topRect.width -= aEndBevelOffset - endBevel;
3598 }
3599 DrawSolidBorderSegment(aContext, topRect, twipsPerPixel, aStartBevelSide,
3600 startBevel, aEndBevelSide, endBevel);
3602 // draw the botom line or rect
3603 nscoord heightOffset = aBorder.height - thirdHeight;
3604 nsRect bottomRect(aBorder.x, aBorder.y + heightOffset, aBorder.width, aBorder.height - heightOffset);
3605 if (NS_SIDE_BOTTOM == aStartBevelSide) {
3606 bottomRect.x += aStartBevelOffset - startBevel;
3607 bottomRect.width -= aStartBevelOffset - startBevel;
3608 }
3609 if (NS_SIDE_BOTTOM == aEndBevelSide) {
3610 bottomRect.width -= aEndBevelOffset - endBevel;
3611 }
3612 DrawSolidBorderSegment(aContext, bottomRect, twipsPerPixel, aStartBevelSide,
3613 startBevel, aEndBevelSide, endBevel);
3614 }
3615 else { // left, right
3616 nscoord thirdWidth = RoundFloatToPixel(0.333333f * (float)aBorder.width, twipsPerPixel);
3618 nsRect leftRect(aBorder.x, aBorder.y, thirdWidth, aBorder.height);
3619 if (NS_SIDE_LEFT == aStartBevelSide) {
3620 leftRect.y += aStartBevelOffset - startBevel;
3621 leftRect.height -= aStartBevelOffset - startBevel;
3622 }
3623 if (NS_SIDE_LEFT == aEndBevelSide) {
3624 leftRect.height -= aEndBevelOffset - endBevel;
3625 }
3626 DrawSolidBorderSegment(aContext, leftRect, twipsPerPixel, aStartBevelSide,
3627 startBevel, aEndBevelSide, endBevel);
3629 nscoord widthOffset = aBorder.width - thirdWidth;
3630 nsRect rightRect(aBorder.x + widthOffset, aBorder.y, aBorder.width - widthOffset, aBorder.height);
3631 if (NS_SIDE_RIGHT == aStartBevelSide) {
3632 rightRect.y += aStartBevelOffset - startBevel;
3633 rightRect.height -= aStartBevelOffset - startBevel;
3634 }
3635 if (NS_SIDE_RIGHT == aEndBevelSide) {
3636 rightRect.height -= aEndBevelOffset - endBevel;
3637 }
3638 DrawSolidBorderSegment(aContext, rightRect, twipsPerPixel, aStartBevelSide,
3639 startBevel, aEndBevelSide, endBevel);
3640 }
3641 break;
3642 }
3643 // else fall through to solid
3644 case NS_STYLE_BORDER_STYLE_SOLID:
3645 DrawSolidBorderSegment(aContext, aBorder, twipsPerPixel, aStartBevelSide,
3646 aStartBevelOffset, aEndBevelSide, aEndBevelOffset);
3647 break;
3648 case NS_STYLE_BORDER_STYLE_OUTSET:
3649 case NS_STYLE_BORDER_STYLE_INSET:
3650 NS_ASSERTION(false, "inset, outset should have been converted to groove, ridge");
3651 break;
3652 case NS_STYLE_BORDER_STYLE_AUTO:
3653 NS_ASSERTION(false, "Unexpected 'auto' table border");
3654 break;
3655 }
3657 ctx->SetAntialiasMode(oldMode);
3658 }
3660 // End table border-collapsing section
3662 gfxRect
3663 nsCSSRendering::ExpandPaintingRectForDecorationLine(nsIFrame* aFrame,
3664 const uint8_t aStyle,
3665 const gfxRect& aClippedRect,
3666 const gfxFloat aXInFrame,
3667 const gfxFloat aCycleLength)
3668 {
3669 switch (aStyle) {
3670 case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
3671 case NS_STYLE_TEXT_DECORATION_STYLE_DASHED:
3672 case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
3673 break;
3674 default:
3675 NS_ERROR("Invalid style was specified");
3676 return aClippedRect;
3677 }
3679 nsBlockFrame* block = nullptr;
3680 // Note that when we paint the decoration lines in relative positioned
3681 // box, we should paint them like all of the boxes are positioned as static.
3682 nscoord frameXInBlockAppUnits = 0;
3683 for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
3684 block = do_QueryFrame(f);
3685 if (block) {
3686 break;
3687 }
3688 frameXInBlockAppUnits += f->GetNormalPosition().x;
3689 }
3691 NS_ENSURE_TRUE(block, aClippedRect);
3693 nsPresContext *pc = aFrame->PresContext();
3694 gfxFloat frameXInBlock = pc->AppUnitsToGfxUnits(frameXInBlockAppUnits);
3695 int32_t rectXInBlock = int32_t(NS_round(frameXInBlock + aXInFrame));
3696 int32_t extraLeft =
3697 rectXInBlock - (rectXInBlock / int32_t(aCycleLength) * aCycleLength);
3698 gfxRect rect(aClippedRect);
3699 rect.x -= extraLeft;
3700 rect.width += extraLeft;
3701 return rect;
3702 }
3704 void
3705 nsCSSRendering::PaintDecorationLine(nsIFrame* aFrame,
3706 gfxContext* aGfxContext,
3707 const gfxRect& aDirtyRect,
3708 const nscolor aColor,
3709 const gfxPoint& aPt,
3710 const gfxFloat aXInFrame,
3711 const gfxSize& aLineSize,
3712 const gfxFloat aAscent,
3713 const gfxFloat aOffset,
3714 const uint8_t aDecoration,
3715 const uint8_t aStyle,
3716 const gfxFloat aDescentLimit)
3717 {
3718 NS_ASSERTION(aStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE, "aStyle is none");
3720 gfxRect rect =
3721 GetTextDecorationRectInternal(aPt, aLineSize, aAscent, aOffset,
3722 aDecoration, aStyle, aDescentLimit);
3723 if (rect.IsEmpty() || !rect.Intersects(aDirtyRect)) {
3724 return;
3725 }
3727 if (aDecoration != NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE &&
3728 aDecoration != NS_STYLE_TEXT_DECORATION_LINE_OVERLINE &&
3729 aDecoration != NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
3730 NS_ERROR("Invalid decoration value!");
3731 return;
3732 }
3734 gfxFloat lineHeight = std::max(NS_round(aLineSize.height), 1.0);
3735 bool contextIsSaved = false;
3737 gfxFloat oldLineWidth;
3738 nsRefPtr<gfxPattern> oldPattern;
3740 switch (aStyle) {
3741 case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
3742 case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE:
3743 oldLineWidth = aGfxContext->CurrentLineWidth();
3744 oldPattern = aGfxContext->GetPattern();
3745 break;
3746 case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
3747 aGfxContext->Save();
3748 contextIsSaved = true;
3749 aGfxContext->Clip(rect);
3750 gfxFloat dashWidth = lineHeight * DOT_LENGTH * DASH_LENGTH;
3751 gfxFloat dash[2] = { dashWidth, dashWidth };
3752 aGfxContext->SetLineCap(gfxContext::LINE_CAP_BUTT);
3753 aGfxContext->SetDash(dash, 2, 0.0);
3754 rect = ExpandPaintingRectForDecorationLine(aFrame, aStyle, rect,
3755 aXInFrame, dashWidth * 2);
3756 // We should continue to draw the last dash even if it is not in the rect.
3757 rect.width += dashWidth;
3758 break;
3759 }
3760 case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED: {
3761 aGfxContext->Save();
3762 contextIsSaved = true;
3763 aGfxContext->Clip(rect);
3764 gfxFloat dashWidth = lineHeight * DOT_LENGTH;
3765 gfxFloat dash[2];
3766 if (lineHeight > 2.0) {
3767 dash[0] = 0.0;
3768 dash[1] = dashWidth * 2.0;
3769 aGfxContext->SetLineCap(gfxContext::LINE_CAP_ROUND);
3770 } else {
3771 dash[0] = dashWidth;
3772 dash[1] = dashWidth;
3773 }
3774 aGfxContext->SetDash(dash, 2, 0.0);
3775 rect = ExpandPaintingRectForDecorationLine(aFrame, aStyle, rect,
3776 aXInFrame, dashWidth * 2);
3777 // We should continue to draw the last dot even if it is not in the rect.
3778 rect.width += dashWidth;
3779 break;
3780 }
3781 case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
3782 aGfxContext->Save();
3783 contextIsSaved = true;
3784 aGfxContext->Clip(rect);
3785 if (lineHeight > 2.0) {
3786 aGfxContext->SetAntialiasMode(gfxContext::MODE_COVERAGE);
3787 } else {
3788 // Don't use anti-aliasing here. Because looks like lighter color wavy
3789 // line at this case. And probably, users don't think the
3790 // non-anti-aliased wavy line is not pretty.
3791 aGfxContext->SetAntialiasMode(gfxContext::MODE_ALIASED);
3792 }
3793 break;
3794 default:
3795 NS_ERROR("Invalid style value!");
3796 return;
3797 }
3799 // The y position should be set to the middle of the line.
3800 rect.y += lineHeight / 2;
3802 aGfxContext->SetColor(gfxRGBA(aColor));
3803 aGfxContext->SetLineWidth(lineHeight);
3804 switch (aStyle) {
3805 case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
3806 aGfxContext->NewPath();
3807 aGfxContext->MoveTo(rect.TopLeft());
3808 aGfxContext->LineTo(rect.TopRight());
3809 aGfxContext->Stroke();
3810 break;
3811 case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE:
3812 /**
3813 * We are drawing double line as:
3814 *
3815 * +-------------------------------------------+
3816 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
3817 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineHeight
3818 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
3819 * | |
3820 * | |
3821 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
3822 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineHeight
3823 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
3824 * +-------------------------------------------+
3825 */
3826 aGfxContext->NewPath();
3827 aGfxContext->MoveTo(rect.TopLeft());
3828 aGfxContext->LineTo(rect.TopRight());
3829 rect.height -= lineHeight;
3830 aGfxContext->MoveTo(rect.BottomLeft());
3831 aGfxContext->LineTo(rect.BottomRight());
3832 aGfxContext->Stroke();
3833 break;
3834 case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
3835 case NS_STYLE_TEXT_DECORATION_STYLE_DASHED:
3836 aGfxContext->NewPath();
3837 aGfxContext->MoveTo(rect.TopLeft());
3838 aGfxContext->LineTo(rect.TopRight());
3839 aGfxContext->Stroke();
3840 break;
3841 case NS_STYLE_TEXT_DECORATION_STYLE_WAVY: {
3842 /**
3843 * We are drawing wavy line as:
3844 *
3845 * P: Path, X: Painted pixel
3846 *
3847 * +---------------------------------------+
3848 * XX|X XXXXXX XXXXXX |
3849 * PP|PX XPPPPPPX XPPPPPPX | ^
3850 * XX|XPX XPXXXXXXPX XPXXXXXXPX| |
3851 * | XPX XPX XPX XPX XP|X |adv
3852 * | XPXXXXXXPX XPXXXXXXPX X|PX |
3853 * | XPPPPPPX XPPPPPPX |XPX v
3854 * | XXXXXX XXXXXX | XX
3855 * +---------------------------------------+
3856 * <---><---> ^
3857 * adv flatLengthAtVertex rightMost
3858 *
3859 * 1. Always starts from top-left of the drawing area, however, we need
3860 * to draw the line from outside of the rect. Because the start
3861 * point of the line is not good style if we draw from inside it.
3862 * 2. First, draw horizontal line from outside the rect to top-left of
3863 * the rect;
3864 * 3. Goes down to bottom of the area at 45 degrees.
3865 * 4. Slides to right horizontaly, see |flatLengthAtVertex|.
3866 * 5. Goes up to top of the area at 45 degrees.
3867 * 6. Slides to right horizontaly.
3868 * 7. Repeat from 2 until reached to right-most edge of the area.
3869 */
3871 gfxFloat adv = rect.Height() - lineHeight;
3872 gfxFloat flatLengthAtVertex = std::max((lineHeight - 1.0) * 2.0, 1.0);
3874 // Align the start of wavy lines to the nearest ancestor block.
3875 gfxFloat cycleLength = 2 * (adv + flatLengthAtVertex);
3876 rect = ExpandPaintingRectForDecorationLine(aFrame, aStyle, rect,
3877 aXInFrame, cycleLength);
3878 // figure out if we can trim whole cycles from the left and right edges
3879 // of the line, to try and avoid creating an unnecessarily long and
3880 // complex path
3881 int32_t skipCycles = floor((aDirtyRect.x - rect.x) / cycleLength);
3882 if (skipCycles > 0) {
3883 rect.x += skipCycles * cycleLength;
3884 rect.width -= skipCycles * cycleLength;
3885 }
3887 rect.x += lineHeight / 2.0;
3888 gfxPoint pt(rect.TopLeft());
3889 gfxFloat rightMost = pt.x + rect.Width() + lineHeight;
3891 skipCycles = floor((rightMost - aDirtyRect.XMost()) / cycleLength);
3892 if (skipCycles > 0) {
3893 rightMost -= skipCycles * cycleLength;
3894 }
3896 aGfxContext->NewPath();
3898 pt.x -= lineHeight;
3899 aGfxContext->MoveTo(pt); // 1
3901 pt.x = rect.X();
3902 aGfxContext->LineTo(pt); // 2
3904 bool goDown = true;
3905 uint32_t iter = 0;
3906 while (pt.x < rightMost) {
3907 if (++iter > 1000) {
3908 // stroke the current path and start again, to avoid pathological
3909 // behavior in cairo with huge numbers of path segments
3910 aGfxContext->Stroke();
3911 aGfxContext->NewPath();
3912 aGfxContext->MoveTo(pt);
3913 iter = 0;
3914 }
3915 pt.x += adv;
3916 pt.y += goDown ? adv : -adv;
3918 aGfxContext->LineTo(pt); // 3 and 5
3920 pt.x += flatLengthAtVertex;
3921 aGfxContext->LineTo(pt); // 4 and 6
3923 goDown = !goDown;
3924 }
3925 aGfxContext->Stroke();
3926 break;
3927 }
3928 default:
3929 NS_ERROR("Invalid style value!");
3930 break;
3931 }
3933 if (contextIsSaved) {
3934 aGfxContext->Restore();
3935 } else {
3936 aGfxContext->SetPattern(oldPattern);
3937 aGfxContext->SetLineWidth(oldLineWidth);
3938 }
3939 }
3941 void
3942 nsCSSRendering::DecorationLineToPath(nsIFrame* aFrame,
3943 gfxContext* aGfxContext,
3944 const gfxRect& aDirtyRect,
3945 const nscolor aColor,
3946 const gfxPoint& aPt,
3947 const gfxFloat aXInFrame,
3948 const gfxSize& aLineSize,
3949 const gfxFloat aAscent,
3950 const gfxFloat aOffset,
3951 const uint8_t aDecoration,
3952 const uint8_t aStyle,
3953 const gfxFloat aDescentLimit)
3954 {
3955 NS_ASSERTION(aStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE, "aStyle is none");
3957 aGfxContext->NewPath();
3959 gfxRect rect =
3960 GetTextDecorationRectInternal(aPt, aLineSize, aAscent, aOffset,
3961 aDecoration, aStyle, aDescentLimit);
3962 if (rect.IsEmpty() || !rect.Intersects(aDirtyRect)) {
3963 return;
3964 }
3966 if (aDecoration != NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE &&
3967 aDecoration != NS_STYLE_TEXT_DECORATION_LINE_OVERLINE &&
3968 aDecoration != NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
3969 NS_ERROR("Invalid decoration value!");
3970 return;
3971 }
3973 if (aStyle != NS_STYLE_TEXT_DECORATION_STYLE_SOLID) {
3974 // For the moment, we support only solid text decorations.
3975 return;
3976 }
3978 gfxFloat lineHeight = std::max(NS_round(aLineSize.height), 1.0);
3980 // The y position should be set to the middle of the line.
3981 rect.y += lineHeight / 2;
3983 aGfxContext->Rectangle
3984 (gfxRect(gfxPoint(rect.TopLeft() - gfxPoint(0.0, lineHeight / 2)),
3985 gfxSize(rect.Width(), lineHeight)));
3986 }
3988 nsRect
3989 nsCSSRendering::GetTextDecorationRect(nsPresContext* aPresContext,
3990 const gfxSize& aLineSize,
3991 const gfxFloat aAscent,
3992 const gfxFloat aOffset,
3993 const uint8_t aDecoration,
3994 const uint8_t aStyle,
3995 const gfxFloat aDescentLimit)
3996 {
3997 NS_ASSERTION(aPresContext, "aPresContext is null");
3998 NS_ASSERTION(aStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE, "aStyle is none");
4000 gfxRect rect =
4001 GetTextDecorationRectInternal(gfxPoint(0, 0), aLineSize, aAscent, aOffset,
4002 aDecoration, aStyle, aDescentLimit);
4003 // The rect values are already rounded to nearest device pixels.
4004 nsRect r;
4005 r.x = aPresContext->GfxUnitsToAppUnits(rect.X());
4006 r.y = aPresContext->GfxUnitsToAppUnits(rect.Y());
4007 r.width = aPresContext->GfxUnitsToAppUnits(rect.Width());
4008 r.height = aPresContext->GfxUnitsToAppUnits(rect.Height());
4009 return r;
4010 }
4012 gfxRect
4013 nsCSSRendering::GetTextDecorationRectInternal(const gfxPoint& aPt,
4014 const gfxSize& aLineSize,
4015 const gfxFloat aAscent,
4016 const gfxFloat aOffset,
4017 const uint8_t aDecoration,
4018 const uint8_t aStyle,
4019 const gfxFloat aDescentLimit)
4020 {
4021 NS_ASSERTION(aStyle <= NS_STYLE_TEXT_DECORATION_STYLE_WAVY,
4022 "Invalid aStyle value");
4024 if (aStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE)
4025 return gfxRect(0, 0, 0, 0);
4027 bool canLiftUnderline = aDescentLimit >= 0.0;
4029 const gfxFloat left = floor(aPt.x + 0.5),
4030 right = floor(aPt.x + aLineSize.width + 0.5);
4031 gfxRect r(left, 0, right - left, 0);
4033 gfxFloat lineHeight = NS_round(aLineSize.height);
4034 lineHeight = std::max(lineHeight, 1.0);
4036 gfxFloat ascent = NS_round(aAscent);
4037 gfxFloat descentLimit = floor(aDescentLimit);
4039 gfxFloat suggestedMaxRectHeight = std::max(std::min(ascent, descentLimit), 1.0);
4040 r.height = lineHeight;
4041 if (aStyle == NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE) {
4042 /**
4043 * We will draw double line as:
4044 *
4045 * +-------------------------------------------+
4046 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4047 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineHeight
4048 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4049 * | | ^
4050 * | | | gap
4051 * | | v
4052 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4053 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineHeight
4054 * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4055 * +-------------------------------------------+
4056 */
4057 gfxFloat gap = NS_round(lineHeight / 2.0);
4058 gap = std::max(gap, 1.0);
4059 r.height = lineHeight * 2.0 + gap;
4060 if (canLiftUnderline) {
4061 if (r.Height() > suggestedMaxRectHeight) {
4062 // Don't shrink the line height, because the thickness has some meaning.
4063 // We can just shrink the gap at this time.
4064 r.height = std::max(suggestedMaxRectHeight, lineHeight * 2.0 + 1.0);
4065 }
4066 }
4067 } else if (aStyle == NS_STYLE_TEXT_DECORATION_STYLE_WAVY) {
4068 /**
4069 * We will draw wavy line as:
4070 *
4071 * +-------------------------------------------+
4072 * |XXXXX XXXXXX XXXXXX | ^
4073 * |XXXXXX XXXXXXXX XXXXXXXX | | lineHeight
4074 * |XXXXXXX XXXXXXXXXX XXXXXXXXXX| v
4075 * | XXX XXX XXX XXX XX|
4076 * | XXXXXXXXXX XXXXXXXXXX X|
4077 * | XXXXXXXX XXXXXXXX |
4078 * | XXXXXX XXXXXX |
4079 * +-------------------------------------------+
4080 */
4081 r.height = lineHeight > 2.0 ? lineHeight * 4.0 : lineHeight * 3.0;
4082 if (canLiftUnderline) {
4083 if (r.Height() > suggestedMaxRectHeight) {
4084 // Don't shrink the line height even if there is not enough space,
4085 // because the thickness has some meaning. E.g., the 1px wavy line and
4086 // 2px wavy line can be used for different meaning in IME selections
4087 // at same time.
4088 r.height = std::max(suggestedMaxRectHeight, lineHeight * 2.0);
4089 }
4090 }
4091 }
4093 gfxFloat baseline = floor(aPt.y + aAscent + 0.5);
4094 gfxFloat offset = 0.0;
4095 switch (aDecoration) {
4096 case NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE:
4097 offset = aOffset;
4098 if (canLiftUnderline) {
4099 if (descentLimit < -offset + r.Height()) {
4100 // If we can ignore the offset and the decoration line is overflowing,
4101 // we should align the bottom edge of the decoration line rect if it's
4102 // possible. Otherwise, we should lift up the top edge of the rect as
4103 // far as possible.
4104 gfxFloat offsetBottomAligned = -descentLimit + r.Height();
4105 gfxFloat offsetTopAligned = 0.0;
4106 offset = std::min(offsetBottomAligned, offsetTopAligned);
4107 }
4108 }
4109 break;
4110 case NS_STYLE_TEXT_DECORATION_LINE_OVERLINE:
4111 offset = aOffset - lineHeight + r.Height();
4112 break;
4113 case NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH: {
4114 gfxFloat extra = floor(r.Height() / 2.0 + 0.5);
4115 extra = std::max(extra, lineHeight);
4116 offset = aOffset - lineHeight + extra;
4117 break;
4118 }
4119 default:
4120 NS_ERROR("Invalid decoration value!");
4121 }
4122 r.y = baseline - floor(offset + 0.5);
4123 return r;
4124 }
4126 // ------------------
4127 // ImageRenderer
4128 // ------------------
4129 nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame,
4130 const nsStyleImage* aImage,
4131 uint32_t aFlags)
4132 : mForFrame(aForFrame)
4133 , mImage(aImage)
4134 , mType(aImage->GetType())
4135 , mImageContainer(nullptr)
4136 , mGradientData(nullptr)
4137 , mPaintServerFrame(nullptr)
4138 , mIsReady(false)
4139 , mSize(0, 0)
4140 , mFlags(aFlags)
4141 {
4142 }
4144 nsImageRenderer::~nsImageRenderer()
4145 {
4146 }
4148 bool
4149 nsImageRenderer::PrepareImage()
4150 {
4151 if (mImage->IsEmpty())
4152 return false;
4154 if (!mImage->IsComplete()) {
4155 // Make sure the image is actually decoding
4156 mImage->StartDecoding();
4158 // check again to see if we finished
4159 if (!mImage->IsComplete()) {
4160 // We can not prepare the image for rendering if it is not fully loaded.
4161 //
4162 // Special case: If we requested a sync decode and we have an image, push
4163 // on through because the Draw() will do a sync decode then
4164 nsCOMPtr<imgIContainer> img;
4165 if (!((mFlags & FLAG_SYNC_DECODE_IMAGES) &&
4166 (mType == eStyleImageType_Image) &&
4167 (NS_SUCCEEDED(mImage->GetImageData()->GetImage(getter_AddRefs(img))))))
4168 return false;
4169 }
4170 }
4172 switch (mType) {
4173 case eStyleImageType_Image:
4174 {
4175 nsCOMPtr<imgIContainer> srcImage;
4176 DebugOnly<nsresult> rv =
4177 mImage->GetImageData()->GetImage(getter_AddRefs(srcImage));
4178 NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv) && srcImage,
4179 "If GetImage() is failing, mImage->IsComplete() "
4180 "should have returned false");
4182 if (!mImage->GetCropRect()) {
4183 mImageContainer.swap(srcImage);
4184 } else {
4185 nsIntRect actualCropRect;
4186 bool isEntireImage;
4187 bool success =
4188 mImage->ComputeActualCropRect(actualCropRect, &isEntireImage);
4189 NS_ASSERTION(success, "ComputeActualCropRect() should not fail here");
4190 if (!success || actualCropRect.IsEmpty()) {
4191 // The cropped image has zero size
4192 return false;
4193 }
4194 if (isEntireImage) {
4195 // The cropped image is identical to the source image
4196 mImageContainer.swap(srcImage);
4197 } else {
4198 nsCOMPtr<imgIContainer> subImage = ImageOps::Clip(srcImage, actualCropRect);
4199 mImageContainer.swap(subImage);
4200 }
4201 }
4202 mIsReady = true;
4203 break;
4204 }
4205 case eStyleImageType_Gradient:
4206 mGradientData = mImage->GetGradientData();
4207 mIsReady = true;
4208 break;
4209 case eStyleImageType_Element:
4210 {
4211 nsAutoString elementId =
4212 NS_LITERAL_STRING("#") + nsDependentString(mImage->GetElementId());
4213 nsCOMPtr<nsIURI> targetURI;
4214 nsCOMPtr<nsIURI> base = mForFrame->GetContent()->GetBaseURI();
4215 nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), elementId,
4216 mForFrame->GetContent()->GetCurrentDoc(), base);
4217 nsSVGPaintingProperty* property = nsSVGEffects::GetPaintingPropertyForURI(
4218 targetURI, mForFrame->FirstContinuation(),
4219 nsSVGEffects::BackgroundImageProperty());
4220 if (!property)
4221 return false;
4222 mPaintServerFrame = property->GetReferencedFrame();
4224 // If the referenced element doesn't have a frame we might still be able
4225 // to paint it if it's an <img>, <canvas>, or <video> element.
4226 if (!mPaintServerFrame) {
4227 mImageElementSurface =
4228 nsLayoutUtils::SurfaceFromElement(property->GetReferencedElement());
4229 if (!mImageElementSurface.mSourceSurface)
4230 return false;
4231 }
4232 mIsReady = true;
4233 break;
4234 }
4235 case eStyleImageType_Null:
4236 default:
4237 break;
4238 }
4240 return mIsReady;
4241 }
4243 nsSize
4244 CSSSizeOrRatio::ComputeConcreteSize() const
4245 {
4246 NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute");
4247 if (mHasWidth && mHasHeight) {
4248 return nsSize(mWidth, mHeight);
4249 }
4250 if (mHasWidth) {
4251 nscoord height = NSCoordSaturatingNonnegativeMultiply(
4252 mWidth,
4253 double(mRatio.height) / mRatio.width);
4254 return nsSize(mWidth, height);
4255 }
4257 MOZ_ASSERT(mHasHeight);
4258 nscoord width = NSCoordSaturatingNonnegativeMultiply(
4259 mHeight,
4260 double(mRatio.width) / mRatio.height);
4261 return nsSize(width, mHeight);
4262 }
4264 CSSSizeOrRatio
4265 nsImageRenderer::ComputeIntrinsicSize()
4266 {
4267 NS_ASSERTION(mIsReady, "Ensure PrepareImage() has returned true "
4268 "before calling me");
4270 CSSSizeOrRatio result;
4271 switch (mType) {
4272 case eStyleImageType_Image:
4273 {
4274 bool haveWidth, haveHeight;
4275 nsIntSize imageIntSize;
4276 nsLayoutUtils::ComputeSizeForDrawing(mImageContainer, imageIntSize,
4277 result.mRatio, haveWidth, haveHeight);
4278 if (haveWidth) {
4279 result.SetWidth(nsPresContext::CSSPixelsToAppUnits(imageIntSize.width));
4280 }
4281 if (haveHeight) {
4282 result.SetHeight(nsPresContext::CSSPixelsToAppUnits(imageIntSize.height));
4283 }
4284 break;
4285 }
4286 case eStyleImageType_Element:
4287 {
4288 // XXX element() should have the width/height of the referenced element,
4289 // and that element's ratio, if it matches. If it doesn't match, it
4290 // should have no width/height or ratio. See element() in CSS images:
4291 // <http://dev.w3.org/csswg/css-images-4/#element-notation>.
4292 // Make sure to change nsStyleBackground::Size::DependsOnFrameSize
4293 // when fixing this!
4294 if (mPaintServerFrame) {
4295 // SVG images have no intrinsic size
4296 if (!mPaintServerFrame->IsFrameOfType(nsIFrame::eSVG)) {
4297 // The intrinsic image size for a generic nsIFrame paint server is
4298 // the union of the border-box rects of all of its continuations,
4299 // rounded to device pixels.
4300 int32_t appUnitsPerDevPixel =
4301 mForFrame->PresContext()->AppUnitsPerDevPixel();
4302 result.SetSize(
4303 nsSVGIntegrationUtils::GetContinuationUnionSize(mPaintServerFrame).
4304 ToNearestPixels(appUnitsPerDevPixel).
4305 ToAppUnits(appUnitsPerDevPixel));
4306 }
4307 } else {
4308 NS_ASSERTION(mImageElementSurface.mSourceSurface, "Surface should be ready.");
4309 gfxIntSize surfaceSize = mImageElementSurface.mSize;
4310 result.SetSize(
4311 nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width),
4312 nsPresContext::CSSPixelsToAppUnits(surfaceSize.height)));
4313 }
4314 break;
4315 }
4316 case eStyleImageType_Gradient:
4317 // Per <http://dev.w3.org/csswg/css3-images/#gradients>, gradients have no
4318 // intrinsic dimensions.
4319 case eStyleImageType_Null:
4320 default:
4321 break;
4322 }
4324 return result;
4325 }
4327 /* static */ nsSize
4328 nsImageRenderer::ComputeConcreteSize(const CSSSizeOrRatio& aSpecifiedSize,
4329 const CSSSizeOrRatio& aIntrinsicSize,
4330 const nsSize& aDefaultSize)
4331 {
4332 // The specified size is fully specified, just use that
4333 if (aSpecifiedSize.IsConcrete()) {
4334 return aSpecifiedSize.ComputeConcreteSize();
4335 }
4337 MOZ_ASSERT(!aSpecifiedSize.mHasWidth || !aSpecifiedSize.mHasHeight);
4339 if (!aSpecifiedSize.mHasWidth && !aSpecifiedSize.mHasHeight) {
4340 // no specified size, try using the intrinsic size
4341 if (aIntrinsicSize.CanComputeConcreteSize()) {
4342 return aIntrinsicSize.ComputeConcreteSize();
4343 }
4345 if (aIntrinsicSize.mHasWidth) {
4346 return nsSize(aIntrinsicSize.mWidth, aDefaultSize.height);
4347 }
4348 if (aIntrinsicSize.mHasHeight) {
4349 return nsSize(aDefaultSize.width, aIntrinsicSize.mHeight);
4350 }
4352 // couldn't use the intrinsic size either, revert to using the default size
4353 return ComputeConstrainedSize(aDefaultSize,
4354 aIntrinsicSize.mRatio,
4355 CONTAIN);
4356 }
4358 MOZ_ASSERT(aSpecifiedSize.mHasWidth || aSpecifiedSize.mHasHeight);
4360 // The specified height is partial, try to compute the missing part.
4361 if (aSpecifiedSize.mHasWidth) {
4362 nscoord height;
4363 if (aIntrinsicSize.HasRatio()) {
4364 height = NSCoordSaturatingNonnegativeMultiply(
4365 aSpecifiedSize.mWidth,
4366 double(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width);
4367 } else if (aIntrinsicSize.mHasHeight) {
4368 height = aIntrinsicSize.mHeight;
4369 } else {
4370 height = aDefaultSize.height;
4371 }
4372 return nsSize(aSpecifiedSize.mWidth, height);
4373 }
4375 MOZ_ASSERT(aSpecifiedSize.mHasHeight);
4376 nscoord width;
4377 if (aIntrinsicSize.HasRatio()) {
4378 width = NSCoordSaturatingNonnegativeMultiply(
4379 aSpecifiedSize.mHeight,
4380 double(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height);
4381 } else if (aIntrinsicSize.mHasWidth) {
4382 width = aIntrinsicSize.mWidth;
4383 } else {
4384 width = aDefaultSize.width;
4385 }
4386 return nsSize(width, aSpecifiedSize.mHeight);
4387 }
4389 /* static */ nsSize
4390 nsImageRenderer::ComputeConstrainedSize(const nsSize& aConstrainingSize,
4391 const nsSize& aIntrinsicRatio,
4392 FitType aFitType)
4393 {
4394 if (aIntrinsicRatio.width <= 0 && aIntrinsicRatio.height <= 0) {
4395 return aConstrainingSize;
4396 }
4398 float scaleX = double(aConstrainingSize.width) / aIntrinsicRatio.width;
4399 float scaleY = double(aConstrainingSize.height) / aIntrinsicRatio.height;
4400 nsSize size;
4401 if ((aFitType == CONTAIN) == (scaleX < scaleY)) {
4402 size.width = aConstrainingSize.width;
4403 size.height = NSCoordSaturatingNonnegativeMultiply(
4404 aIntrinsicRatio.height, scaleX);
4405 } else {
4406 size.width = NSCoordSaturatingNonnegativeMultiply(
4407 aIntrinsicRatio.width, scaleY);
4408 size.height = aConstrainingSize.height;
4409 }
4410 return size;
4411 }
4413 /**
4414 * mSize is the image's "preferred" size for this particular rendering, while
4415 * the drawn (aka concrete) size is the actual rendered size after accounting
4416 * for background-size etc.. The preferred size is most often the image's
4417 * intrinsic dimensions. But for images with incomplete intrinsic dimensions,
4418 * the preferred size varies, depending on the specified and default sizes, see
4419 * nsImageRenderer::Compute*Size.
4420 *
4421 * This distinction is necessary because the components of a vector image are
4422 * specified with respect to its preferred size for a rendering situation, not
4423 * to its actual rendered size. For example, consider a 4px wide background
4424 * vector image with no height which contains a left-aligned
4425 * 2px wide black rectangle with height 100%. If the background-size width is
4426 * auto (or 4px), the vector image will render 4px wide, and the black rectangle
4427 * will be 2px wide. If the background-size width is 8px, the vector image will
4428 * render 8px wide, and the black rectangle will be 4px wide -- *not* 2px wide.
4429 * In both cases mSize.width will be 4px; but in the first case the returned
4430 * width will be 4px, while in the second case the returned width will be 8px.
4431 */
4432 void
4433 nsImageRenderer::SetPreferredSize(const CSSSizeOrRatio& aIntrinsicSize,
4434 const nsSize& aDefaultSize)
4435 {
4436 mSize.width = aIntrinsicSize.mHasWidth
4437 ? aIntrinsicSize.mWidth
4438 : aDefaultSize.width;
4439 mSize.height = aIntrinsicSize.mHasHeight
4440 ? aIntrinsicSize.mHeight
4441 : aDefaultSize.height;
4442 }
4444 // Convert from nsImageRenderer flags to the flags we want to use for drawing in
4445 // the imgIContainer namespace.
4446 static uint32_t
4447 ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags)
4448 {
4449 uint32_t drawFlags = imgIContainer::FLAG_NONE;
4450 if (aImageRendererFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
4451 drawFlags |= imgIContainer::FLAG_SYNC_DECODE;
4452 }
4453 if (aImageRendererFlags & nsImageRenderer::FLAG_PAINTING_TO_WINDOW) {
4454 drawFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
4455 }
4456 return drawFlags;
4457 }
4459 void
4460 nsImageRenderer::Draw(nsPresContext* aPresContext,
4461 nsRenderingContext& aRenderingContext,
4462 const nsRect& aDirtyRect,
4463 const nsRect& aFill,
4464 const nsRect& aDest,
4465 const CSSIntRect& aSrc)
4466 {
4467 if (!mIsReady) {
4468 NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
4469 return;
4470 }
4471 if (aDest.IsEmpty() || aFill.IsEmpty() ||
4472 mSize.width <= 0 || mSize.height <= 0) {
4473 return;
4474 }
4476 GraphicsFilter graphicsFilter =
4477 nsLayoutUtils::GetGraphicsFilterForFrame(mForFrame);
4479 switch (mType) {
4480 case eStyleImageType_Image:
4481 {
4482 nsLayoutUtils::DrawSingleImage(&aRenderingContext, mImageContainer,
4483 graphicsFilter, aFill, aDirtyRect,
4484 nullptr,
4485 ConvertImageRendererToDrawFlags(mFlags));
4486 return;
4487 }
4488 case eStyleImageType_Gradient:
4489 {
4490 nsCSSRendering::PaintGradient(aPresContext, aRenderingContext,
4491 mGradientData, aDirtyRect,
4492 aDest, aFill, aSrc, mSize);
4493 return;
4494 }
4495 case eStyleImageType_Element:
4496 {
4497 nsRefPtr<gfxDrawable> drawable = DrawableForElement(aDest,
4498 aRenderingContext);
4499 if (!drawable) {
4500 NS_WARNING("Could not create drawable for element");
4501 return;
4502 }
4503 nsLayoutUtils::DrawPixelSnapped(&aRenderingContext, drawable, graphicsFilter,
4504 aDest, aFill, aDest.TopLeft(), aDirtyRect);
4505 return;
4506 }
4507 case eStyleImageType_Null:
4508 default:
4509 return;
4510 }
4511 }
4513 already_AddRefed<gfxDrawable>
4514 nsImageRenderer::DrawableForElement(const nsRect& aImageRect,
4515 nsRenderingContext& aRenderingContext)
4516 {
4517 NS_ASSERTION(mType == eStyleImageType_Element,
4518 "DrawableForElement only makes sense if backed by an element");
4519 if (mPaintServerFrame) {
4520 int32_t appUnitsPerDevPixel = mForFrame->PresContext()->AppUnitsPerDevPixel();
4521 nsRect destRect = aImageRect - aImageRect.TopLeft();
4522 nsIntSize roundedOut = destRect.ToOutsidePixels(appUnitsPerDevPixel).Size();
4523 gfxIntSize imageSize(roundedOut.width, roundedOut.height);
4524 nsRefPtr<gfxDrawable> drawable =
4525 nsSVGIntegrationUtils::DrawableFromPaintServer(
4526 mPaintServerFrame, mForFrame, mSize, imageSize,
4527 aRenderingContext.ThebesContext()->CurrentMatrix(),
4528 mFlags & FLAG_SYNC_DECODE_IMAGES
4529 ? nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES
4530 : 0);
4532 return drawable.forget();
4533 }
4534 NS_ASSERTION(mImageElementSurface.mSourceSurface, "Surface should be ready.");
4535 nsRefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(
4536 mImageElementSurface.mSourceSurface,
4537 mImageElementSurface.mSize);
4538 return drawable.forget();
4539 }
4541 void
4542 nsImageRenderer::DrawBackground(nsPresContext* aPresContext,
4543 nsRenderingContext& aRenderingContext,
4544 const nsRect& aDest,
4545 const nsRect& aFill,
4546 const nsPoint& aAnchor,
4547 const nsRect& aDirty)
4548 {
4549 if (!mIsReady) {
4550 NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
4551 return;
4552 }
4553 if (aDest.IsEmpty() || aFill.IsEmpty() ||
4554 mSize.width <= 0 || mSize.height <= 0) {
4555 return;
4556 }
4558 if (mType == eStyleImageType_Image) {
4559 GraphicsFilter graphicsFilter =
4560 nsLayoutUtils::GetGraphicsFilterForFrame(mForFrame);
4562 nsLayoutUtils::DrawBackgroundImage(&aRenderingContext, mImageContainer,
4563 nsIntSize(nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
4564 nsPresContext::AppUnitsToIntCSSPixels(mSize.height)),
4565 graphicsFilter,
4566 aDest, aFill, aAnchor, aDirty,
4567 ConvertImageRendererToDrawFlags(mFlags));
4568 return;
4569 }
4571 Draw(aPresContext, aRenderingContext,
4572 aDirty, aFill, aDest,
4573 CSSIntRect(0, 0,
4574 nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
4575 nsPresContext::AppUnitsToIntCSSPixels(mSize.height)));
4576 }
4578 /**
4579 * Compute the size and position of the master copy of the image. I.e., a single
4580 * tile used to fill the dest rect.
4581 * aFill The destination rect to be filled
4582 * aHFill and aVFill are the repeat patterns for the component -
4583 * NS_STYLE_BORDER_IMAGE_REPEAT_* - i.e., how a tiling unit is used to fill aFill
4584 * aUnitSize The size of the source rect in dest coords.
4585 */
4586 static nsRect
4587 ComputeTile(const nsRect& aFill,
4588 uint8_t aHFill,
4589 uint8_t aVFill,
4590 const nsSize& aUnitSize)
4591 {
4592 nsRect tile;
4593 switch (aHFill) {
4594 case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
4595 tile.x = aFill.x;
4596 tile.width = aFill.width;
4597 break;
4598 case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
4599 tile.x = aFill.x + aFill.width/2 - aUnitSize.width/2;
4600 tile.width = aUnitSize.width;
4601 break;
4602 case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
4603 tile.x = aFill.x;
4604 tile.width = aFill.width / ceil(gfxFloat(aFill.width)/aUnitSize.width);
4605 break;
4606 default:
4607 NS_NOTREACHED("unrecognized border-image fill style");
4608 }
4610 switch (aVFill) {
4611 case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
4612 tile.y = aFill.y;
4613 tile.height = aFill.height;
4614 break;
4615 case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
4616 tile.y = aFill.y + aFill.height/2 - aUnitSize.height/2;
4617 tile.height = aUnitSize.height;
4618 break;
4619 case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
4620 tile.y = aFill.y;
4621 tile.height = aFill.height/ceil(gfxFloat(aFill.height)/aUnitSize.height);
4622 break;
4623 default:
4624 NS_NOTREACHED("unrecognized border-image fill style");
4625 }
4627 return tile;
4628 }
4630 /**
4631 * Returns true if the given set of arguments will require the tiles which fill
4632 * the dest rect to be scaled from the source tile. See comment on ComputeTile
4633 * for argument descriptions.
4634 */
4635 static bool
4636 RequiresScaling(const nsRect& aFill,
4637 uint8_t aHFill,
4638 uint8_t aVFill,
4639 const nsSize& aUnitSize)
4640 {
4641 // If we have no tiling in either direction, we can skip the intermediate
4642 // scaling step.
4643 return (aHFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH ||
4644 aVFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH) &&
4645 (aUnitSize.width != aFill.width ||
4646 aUnitSize.height != aFill.height);
4647 }
4649 void
4650 nsImageRenderer::DrawBorderImageComponent(nsPresContext* aPresContext,
4651 nsRenderingContext& aRenderingContext,
4652 const nsRect& aDirtyRect,
4653 const nsRect& aFill,
4654 const CSSIntRect& aSrc,
4655 uint8_t aHFill,
4656 uint8_t aVFill,
4657 const nsSize& aUnitSize,
4658 uint8_t aIndex)
4659 {
4660 if (!mIsReady) {
4661 NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
4662 return;
4663 }
4664 if (aFill.IsEmpty() || aSrc.IsEmpty()) {
4665 return;
4666 }
4668 if (mType == eStyleImageType_Image) {
4669 nsCOMPtr<imgIContainer> subImage;
4670 if ((subImage = mImage->GetSubImage(aIndex)) == nullptr) {
4671 subImage = ImageOps::Clip(mImageContainer, nsIntRect(aSrc.x,
4672 aSrc.y,
4673 aSrc.width,
4674 aSrc.height));
4675 mImage->SetSubImage(aIndex, subImage);
4676 }
4678 GraphicsFilter graphicsFilter =
4679 nsLayoutUtils::GetGraphicsFilterForFrame(mForFrame);
4681 if (!RequiresScaling(aFill, aHFill, aVFill, aUnitSize)) {
4682 nsLayoutUtils::DrawSingleImage(&aRenderingContext,
4683 subImage,
4684 graphicsFilter,
4685 aFill, aDirtyRect,
4686 nullptr,
4687 imgIContainer::FLAG_NONE);
4688 return;
4689 }
4691 nsRect tile = ComputeTile(aFill, aHFill, aVFill, aUnitSize);
4692 nsLayoutUtils::DrawImage(&aRenderingContext,
4693 subImage,
4694 graphicsFilter,
4695 tile, aFill, tile.TopLeft(), aDirtyRect,
4696 imgIContainer::FLAG_NONE);
4697 return;
4698 }
4700 nsRect destTile = RequiresScaling(aFill, aHFill, aVFill, aUnitSize)
4701 ? ComputeTile(aFill, aHFill, aVFill, aUnitSize)
4702 : aFill;
4704 if (mType == eStyleImageType_Element) {
4705 // This path is horribly slow - we read and copy the source nine times(!)
4706 // It could be easily optimised by only reading the source once and caching
4707 // it. It could be further optimised by caching the sub-images between draws
4708 // but that would be a bit harder because you would have to know when to
4709 // invalidate the cache. A special case optimisation would be when
4710 // border-image-slice is proportional to the border widths, in which case
4711 // the subimages do not need to be independently scaled, then we don't need
4712 // subimages at all.
4713 // In any case, such optimisations are probably not worth doing because it
4714 // seems unlikely anyone would use -moz-element as the source for a border
4715 // image.
4717 // draw the source image slice into an intermediate surface
4718 nsPresContext* presContext = mForFrame->PresContext();
4719 gfxRect srcRect = gfxRect(presContext->CSSPixelsToDevPixels(aSrc.x),
4720 presContext->CSSPixelsToDevPixels(aSrc.y),
4721 presContext->CSSPixelsToDevPixels(aSrc.width),
4722 presContext->CSSPixelsToDevPixels(aSrc.height));
4723 RefPtr<DrawTarget> srcSlice = gfxPlatform::GetPlatform()->
4724 CreateOffscreenContentDrawTarget(IntSize(srcRect.width, srcRect.height),
4725 SurfaceFormat::B8G8R8A8);
4726 nsRefPtr<gfxContext> ctx = new gfxContext(srcSlice);
4728 // grab the entire source
4729 nsRefPtr<gfxDrawable> drawable = DrawableForElement(nsRect(nsPoint(), mSize),
4730 aRenderingContext);
4731 if (!drawable) {
4732 NS_WARNING("Could not create drawable for element");
4733 return;
4734 }
4736 // draw the source into our intermediate surface
4737 GraphicsFilter graphicsFilter =
4738 nsLayoutUtils::GetGraphicsFilterForFrame(mForFrame);
4739 gfxMatrix transform;
4740 transform.Translate(gfxPoint(srcRect.x, srcRect.y));
4741 bool success = drawable->Draw(ctx,
4742 gfxRect(0, 0, srcRect.width, srcRect.height),
4743 false,
4744 graphicsFilter,
4745 transform);
4746 if (!success) {
4747 NS_WARNING("Could not copy element image");
4748 return;
4749 }
4751 // Ensure that drawing the image gets flushed to the target.
4752 ctx = nullptr;
4754 // draw the slice
4755 nsRefPtr<gfxSurfaceDrawable> srcSliceDrawable =
4756 new gfxSurfaceDrawable(srcSlice,
4757 gfxIntSize(srcRect.width, srcRect.height));
4758 nsPoint anchor(nsPresContext::CSSPixelsToAppUnits(aSrc.x),
4759 nsPresContext::CSSPixelsToAppUnits(aSrc.y));
4760 nsLayoutUtils::DrawPixelSnapped(&aRenderingContext, srcSliceDrawable,
4761 graphicsFilter, destTile, aFill,
4762 anchor, aDirtyRect);
4764 return;
4765 }
4767 Draw(aPresContext, aRenderingContext, aDirtyRect, aFill, destTile, aSrc);
4768 }
4770 bool
4771 nsImageRenderer::IsRasterImage()
4772 {
4773 if (mType != eStyleImageType_Image || !mImageContainer)
4774 return false;
4775 return mImageContainer->GetType() == imgIContainer::TYPE_RASTER;
4776 }
4778 bool
4779 nsImageRenderer::IsAnimatedImage()
4780 {
4781 if (mType != eStyleImageType_Image || !mImageContainer)
4782 return false;
4783 bool animated = false;
4784 if (NS_SUCCEEDED(mImageContainer->GetAnimated(&animated)) && animated)
4785 return true;
4787 return false;
4788 }
4790 already_AddRefed<mozilla::layers::ImageContainer>
4791 nsImageRenderer::GetContainer(LayerManager* aManager)
4792 {
4793 if (mType != eStyleImageType_Image || !mImageContainer)
4794 return nullptr;
4796 nsRefPtr<ImageContainer> container;
4797 nsresult rv = mImageContainer->GetImageContainer(aManager, getter_AddRefs(container));
4798 NS_ENSURE_SUCCESS(rv, nullptr);
4799 return container.forget();
4800 }
4802 #define MAX_BLUR_RADIUS 300
4803 #define MAX_SPREAD_RADIUS 50
4805 static inline gfxPoint ComputeBlurStdDev(nscoord aBlurRadius,
4806 int32_t aAppUnitsPerDevPixel,
4807 gfxFloat aScaleX,
4808 gfxFloat aScaleY)
4809 {
4810 // http://dev.w3.org/csswg/css3-background/#box-shadow says that the
4811 // standard deviation of the blur should be half the given blur value.
4812 gfxFloat blurStdDev = gfxFloat(aBlurRadius) / gfxFloat(aAppUnitsPerDevPixel);
4814 return gfxPoint(std::min((blurStdDev * aScaleX),
4815 gfxFloat(MAX_BLUR_RADIUS)) / 2.0,
4816 std::min((blurStdDev * aScaleY),
4817 gfxFloat(MAX_BLUR_RADIUS)) / 2.0);
4818 }
4820 static inline gfxIntSize
4821 ComputeBlurRadius(nscoord aBlurRadius,
4822 int32_t aAppUnitsPerDevPixel,
4823 gfxFloat aScaleX = 1.0,
4824 gfxFloat aScaleY = 1.0)
4825 {
4826 gfxPoint scaledBlurStdDev = ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel,
4827 aScaleX, aScaleY);
4828 return
4829 gfxAlphaBoxBlur::CalculateBlurRadius(scaledBlurStdDev);
4830 }
4832 // -----
4833 // nsContextBoxBlur
4834 // -----
4835 gfxContext*
4836 nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius,
4837 nscoord aBlurRadius,
4838 int32_t aAppUnitsPerDevPixel,
4839 gfxContext* aDestinationCtx,
4840 const nsRect& aDirtyRect,
4841 const gfxRect* aSkipRect,
4842 uint32_t aFlags)
4843 {
4844 if (aRect.IsEmpty()) {
4845 mContext = nullptr;
4846 return nullptr;
4847 }
4849 gfxFloat scaleX = 1;
4850 gfxFloat scaleY = 1;
4852 // Do blurs in device space when possible.
4853 // Chrome/Skia always does the blurs in device space
4854 // and will sometimes get incorrect results (e.g. rotated blurs)
4855 gfxMatrix transform = aDestinationCtx->CurrentMatrix();
4856 // XXX: we could probably handle negative scales but for now it's easier just to fallback
4857 if (transform.HasNonAxisAlignedTransform() || transform.xx <= 0.0 || transform.yy <= 0.0) {
4858 transform = gfxMatrix();
4859 } else {
4860 scaleX = transform.xx;
4861 scaleY = transform.yy;
4862 }
4864 // compute a large or smaller blur radius
4865 gfxIntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
4866 gfxIntSize spreadRadius = gfxIntSize(std::min(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel),
4867 int32_t(MAX_SPREAD_RADIUS)),
4868 std::min(int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel),
4869 int32_t(MAX_SPREAD_RADIUS)));
4870 mDestinationCtx = aDestinationCtx;
4872 // If not blurring, draw directly onto the destination device
4873 if (blurRadius.width <= 0 && blurRadius.height <= 0 &&
4874 spreadRadius.width <= 0 && spreadRadius.height <= 0 &&
4875 !(aFlags & FORCE_MASK)) {
4876 mContext = aDestinationCtx;
4877 return mContext;
4878 }
4880 // Convert from app units to device pixels
4881 gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel);
4883 gfxRect dirtyRect =
4884 nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
4885 dirtyRect.RoundOut();
4887 rect = transform.TransformBounds(rect);
4889 mPreTransformed = !transform.IsIdentity();
4891 // Create the temporary surface for blurring
4892 dirtyRect = transform.TransformBounds(dirtyRect);
4893 if (aSkipRect) {
4894 gfxRect skipRect = transform.TransformBounds(*aSkipRect);
4895 mContext = blur.Init(rect, spreadRadius,
4896 blurRadius, &dirtyRect, &skipRect);
4897 } else {
4898 mContext = blur.Init(rect, spreadRadius,
4899 blurRadius, &dirtyRect, nullptr);
4900 }
4902 if (mContext) {
4903 // we don't need to blur if skipRect is equal to rect
4904 // and mContext will be nullptr
4905 mContext->Multiply(transform);
4906 }
4907 return mContext;
4908 }
4910 void
4911 nsContextBoxBlur::DoPaint()
4912 {
4913 if (mContext == mDestinationCtx)
4914 return;
4916 gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx);
4918 if (mPreTransformed) {
4919 mDestinationCtx->IdentityMatrix();
4920 }
4922 blur.Paint(mDestinationCtx);
4923 }
4925 gfxContext*
4926 nsContextBoxBlur::GetContext()
4927 {
4928 return mContext;
4929 }
4931 /* static */ nsMargin
4932 nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius,
4933 int32_t aAppUnitsPerDevPixel)
4934 {
4935 gfxIntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel);
4937 nsMargin result;
4938 result.top = blurRadius.height * aAppUnitsPerDevPixel;
4939 result.right = blurRadius.width * aAppUnitsPerDevPixel;
4940 result.bottom = blurRadius.height * aAppUnitsPerDevPixel;
4941 result.left = blurRadius.width * aAppUnitsPerDevPixel;
4942 return result;
4943 }
4945 /* static */ void
4946 nsContextBoxBlur::BlurRectangle(gfxContext* aDestinationCtx,
4947 const nsRect& aRect,
4948 int32_t aAppUnitsPerDevPixel,
4949 gfxCornerSizes* aCornerRadii,
4950 nscoord aBlurRadius,
4951 const gfxRGBA& aShadowColor,
4952 const nsRect& aDirtyRect,
4953 const gfxRect& aSkipRect)
4954 {
4955 if (aRect.IsEmpty()) {
4956 return;
4957 }
4959 gfxRect shadowGfxRect =
4960 nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel);
4962 if (aBlurRadius <= 0) {
4963 aDestinationCtx->SetColor(aShadowColor);
4964 aDestinationCtx->NewPath();
4965 if (aCornerRadii) {
4966 aDestinationCtx->RoundedRectangle(shadowGfxRect, *aCornerRadii);
4967 } else {
4968 aDestinationCtx->Rectangle(shadowGfxRect);
4969 }
4971 aDestinationCtx->Fill();
4972 return;
4973 }
4975 gfxFloat scaleX = 1;
4976 gfxFloat scaleY = 1;
4978 // Do blurs in device space when possible.
4979 // Chrome/Skia always does the blurs in device space
4980 // and will sometimes get incorrect results (e.g. rotated blurs)
4981 gfxMatrix transform = aDestinationCtx->CurrentMatrix();
4982 // XXX: we could probably handle negative scales but for now it's easier just to fallback
4983 if (!transform.HasNonAxisAlignedTransform() && transform.xx > 0.0 && transform.yy > 0.0) {
4984 scaleX = transform.xx;
4985 scaleY = transform.yy;
4986 aDestinationCtx->IdentityMatrix();
4987 } else {
4988 transform = gfxMatrix();
4989 }
4991 gfxPoint blurStdDev = ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
4993 gfxRect dirtyRect =
4994 nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
4995 dirtyRect.RoundOut();
4997 shadowGfxRect = transform.TransformBounds(shadowGfxRect);
4998 dirtyRect = transform.TransformBounds(dirtyRect);
4999 gfxRect skipRect = transform.TransformBounds(aSkipRect);
5001 if (aCornerRadii) {
5002 aCornerRadii->Scale(scaleX, scaleY);
5003 }
5005 gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx,
5006 shadowGfxRect,
5007 aCornerRadii,
5008 blurStdDev,
5009 aShadowColor,
5010 dirtyRect,
5011 skipRect);
5012 }