|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 // vim:cindent:ts=2:et:sw=2: |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 /* utility functions for drawing borders and backgrounds */ |
|
8 |
|
9 #include <ctime> |
|
10 |
|
11 #include "mozilla/DebugOnly.h" |
|
12 #include "mozilla/HashFunctions.h" |
|
13 #include "mozilla/MathAlgorithms.h" |
|
14 |
|
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> |
|
55 |
|
56 using namespace mozilla; |
|
57 using namespace mozilla::css; |
|
58 using namespace mozilla::gfx; |
|
59 using mozilla::image::ImageOps; |
|
60 using mozilla::CSSSizeOrRatio; |
|
61 |
|
62 static int gFrameTreeLockCount = 0; |
|
63 |
|
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 } |
|
74 |
|
75 ~InlineBackgroundData() |
|
76 { |
|
77 } |
|
78 |
|
79 void Reset() |
|
80 { |
|
81 mBoundingBox.SetRect(0,0,0,0); |
|
82 mContinuationPoint = mLineContinuationPoint = mUnbrokenWidth = 0; |
|
83 mFrame = mBlockFrame = nullptr; |
|
84 } |
|
85 |
|
86 nsRect GetContinuousRect(nsIFrame* aFrame) |
|
87 { |
|
88 SetFrame(aFrame); |
|
89 |
|
90 nscoord x; |
|
91 if (mBidiEnabled) { |
|
92 x = mLineContinuationPoint; |
|
93 |
|
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; |
|
100 |
|
101 // No need to use our GetPrevContinuation/GetNextContinuation methods |
|
102 // here, since ib-split siblings are certainly not on the same line. |
|
103 |
|
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 } |
|
115 |
|
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 } |
|
136 |
|
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 } |
|
142 |
|
143 nsRect GetBoundingRect(nsIFrame* aFrame) |
|
144 { |
|
145 SetFrame(aFrame); |
|
146 |
|
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); |
|
155 |
|
156 return boundingBox; |
|
157 } |
|
158 |
|
159 protected: |
|
160 nsIFrame* mFrame; |
|
161 nsBlockFrame* mBlockFrame; |
|
162 nsRect mBoundingBox; |
|
163 nscoord mContinuationPoint; |
|
164 nscoord mUnbrokenWidth; |
|
165 nscoord mLineContinuationPoint; |
|
166 bool mBidiEnabled; |
|
167 |
|
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"); |
|
173 |
|
174 if (aFrame == mFrame) { |
|
175 return; |
|
176 } |
|
177 |
|
178 nsIFrame *prevContinuation = GetPrevContinuation(aFrame); |
|
179 |
|
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 } |
|
186 |
|
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; |
|
190 |
|
191 // If this a new line, update mLineContinuationPoint. |
|
192 if (mBidiEnabled && |
|
193 (aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) { |
|
194 mLineContinuationPoint = mContinuationPoint; |
|
195 } |
|
196 |
|
197 mFrame = aFrame; |
|
198 } |
|
199 |
|
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 } |
|
218 |
|
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 } |
|
236 |
|
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)); |
|
248 |
|
249 NS_ASSERTION(mBlockFrame, "Cannot find containing block."); |
|
250 } |
|
251 |
|
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); |
|
255 |
|
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 } |
|
266 |
|
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 } |
|
276 |
|
277 mFrame = aFrame; |
|
278 } |
|
279 |
|
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 }; |
|
292 |
|
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 }; |
|
301 |
|
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); |
|
309 |
|
310 static nscolor MakeBevelColor(mozilla::css::Side whichSide, uint8_t style, |
|
311 nscolor aBackgroundColor, |
|
312 nscolor aBorderColor); |
|
313 |
|
314 static InlineBackgroundData* gInlineBGData = nullptr; |
|
315 |
|
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 } |
|
322 |
|
323 // Clean up any global variables used by nsCSSRendering. |
|
324 void nsCSSRendering::Shutdown() |
|
325 { |
|
326 delete gInlineBGData; |
|
327 gInlineBGData = nullptr; |
|
328 } |
|
329 |
|
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 { |
|
337 |
|
338 nscolor colors[2]; |
|
339 nscolor theColor; |
|
340 |
|
341 // Given a background color and a border color |
|
342 // calculate the color used for the shading |
|
343 NS_GetSpecial3DColors(colors, aBackgroundColor, aBorderColor); |
|
344 |
|
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 } |
|
355 |
|
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 } |
|
373 |
|
374 //---------------------------------------------------------------------- |
|
375 // Thebes Border Rendering Code Start |
|
376 |
|
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; |
|
389 |
|
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 } |
|
399 |
|
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 } |
|
420 |
|
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); |
|
426 |
|
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); |
|
435 |
|
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 } |
|
441 |
|
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(); |
|
455 |
|
456 SN("++ PaintBorder"); |
|
457 |
|
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 } |
|
467 |
|
468 if (aStyleBorder.IsBorderImageLoaded()) { |
|
469 DrawBorderImage(aPresContext, aRenderingContext, aForFrame, |
|
470 aBorderArea, aStyleBorder, aDirtyRect); |
|
471 return; |
|
472 } |
|
473 |
|
474 // Get our style context's color struct. |
|
475 const nsStyleColor* ourColor = aStyleContext->StyleColor(); |
|
476 |
|
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); |
|
484 |
|
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 } |
|
491 |
|
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 } |
|
500 |
|
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; |
|
506 |
|
507 // get the inside and outside parts of the border |
|
508 nsRect outerRect(aBorderArea); |
|
509 |
|
510 SF(" outerRect: %d %d %d %d\n", outerRect.x, outerRect.y, outerRect.width, outerRect.height); |
|
511 |
|
512 // we can assume that we're already clipped to aDirtyRect -- I think? (!?) |
|
513 |
|
514 // Get our conversion values |
|
515 nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1); |
|
516 |
|
517 // convert outer and inner rects |
|
518 gfxRect oRect(nsLayoutUtils::RectToGfxRect(outerRect, twipsPerPixel)); |
|
519 |
|
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) }; |
|
525 |
|
526 // convert the radii |
|
527 gfxCornerSizes borderRadii; |
|
528 ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii); |
|
529 |
|
530 uint8_t borderStyles[4]; |
|
531 nscolor borderColors[4]; |
|
532 nsBorderColors *compositeColors[4]; |
|
533 |
|
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]); |
|
540 |
|
541 if (foreground) |
|
542 borderColors[i] = ourColor->mColor; |
|
543 } |
|
544 |
|
545 SF(" borderStyles: %d %d %d %d\n", borderStyles[0], borderStyles[1], borderStyles[2], borderStyles[3]); |
|
546 |
|
547 // start drawing |
|
548 gfxContext *ctx = aRenderingContext.ThebesContext(); |
|
549 |
|
550 ctx->Save(); |
|
551 |
|
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 |
|
560 |
|
561 //SF ("borderRadii: %f %f %f %f\n", borderRadii[0], borderRadii[1], borderRadii[2], borderRadii[3]); |
|
562 |
|
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(); |
|
574 |
|
575 ctx->Restore(); |
|
576 |
|
577 SN(); |
|
578 } |
|
579 |
|
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 } |
|
590 |
|
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]; |
|
600 |
|
601 // Get our style context's color struct. |
|
602 const nsStyleOutline* ourOutline = aStyleContext->StyleOutline(); |
|
603 |
|
604 nscoord width; |
|
605 ourOutline->GetOutlineWidth(width); |
|
606 |
|
607 if (width == 0) { |
|
608 // Empty outline |
|
609 return; |
|
610 } |
|
611 |
|
612 nsIFrame* bgFrame = nsCSSRendering::FindNonTransparentBackgroundFrame |
|
613 (aForFrame, false); |
|
614 nsStyleContext* bgContext = bgFrame->StyleContext(); |
|
615 nscolor bgColor = |
|
616 bgContext->GetVisitedDependentColor(eCSSProperty_background_color); |
|
617 |
|
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; |
|
639 |
|
640 nsRect outerRect = innerRect; |
|
641 outerRect.Inflate(width, width); |
|
642 |
|
643 // get the radius for our outline |
|
644 nsIFrame::ComputeBorderRadii(ourOutline->mOutlineRadius, aBorderArea.Size(), |
|
645 outerRect.Size(), 0, twipsRadii); |
|
646 |
|
647 // Get our conversion values |
|
648 nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1); |
|
649 |
|
650 // get the outer rectangles |
|
651 gfxRect oRect(nsLayoutUtils::RectToGfxRect(outerRect, twipsPerPixel)); |
|
652 |
|
653 // convert the radii |
|
654 nsMargin outlineMargin(width, width, width, width); |
|
655 gfxCornerSizes outlineRadii; |
|
656 ComputePixelRadii(twipsRadii, twipsPerPixel, &outlineRadii); |
|
657 |
|
658 uint8_t outlineStyle = ourOutline->GetOutlineStyle(); |
|
659 uint8_t outlineStyles[4] = { outlineStyle, |
|
660 outlineStyle, |
|
661 outlineStyle, |
|
662 outlineStyle }; |
|
663 |
|
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 }; |
|
672 |
|
673 // convert the border widths |
|
674 gfxFloat outlineWidths[4] = { gfxFloat(width / twipsPerPixel), |
|
675 gfxFloat(width / twipsPerPixel), |
|
676 gfxFloat(width / twipsPerPixel), |
|
677 gfxFloat(width / twipsPerPixel) }; |
|
678 |
|
679 // start drawing |
|
680 gfxContext *ctx = aRenderingContext.ThebesContext(); |
|
681 |
|
682 ctx->Save(); |
|
683 |
|
684 nsCSSBorderRenderer br(twipsPerPixel, |
|
685 ctx, |
|
686 oRect, |
|
687 outlineStyles, |
|
688 outlineWidths, |
|
689 outlineRadii, |
|
690 outlineColors, |
|
691 nullptr, 0, |
|
692 bgColor); |
|
693 br.DrawBorders(); |
|
694 |
|
695 ctx->Restore(); |
|
696 |
|
697 SN(); |
|
698 } |
|
699 |
|
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); |
|
708 |
|
709 gfxRect focusRect(nsLayoutUtils::RectToGfxRect(aFocusRect, oneDevPixel)); |
|
710 |
|
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) }; |
|
720 |
|
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 }; |
|
726 |
|
727 gfxContext *ctx = aRenderingContext.ThebesContext(); |
|
728 |
|
729 ctx->Save(); |
|
730 |
|
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(); |
|
747 |
|
748 ctx->Restore(); |
|
749 |
|
750 SN(); |
|
751 } |
|
752 |
|
753 // Thebes Border Rendering Code End |
|
754 //---------------------------------------------------------------------- |
|
755 |
|
756 |
|
757 //---------------------------------------------------------------------- |
|
758 |
|
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)); |
|
788 |
|
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 } |
|
795 |
|
796 nsIFrame* |
|
797 nsCSSRendering::FindNonTransparentBackgroundFrame(nsIFrame* aFrame, |
|
798 bool aStartAtParent /*= false*/) |
|
799 { |
|
800 NS_ASSERTION(aFrame, "Cannot find NonTransparentBackgroundFrame in a null frame"); |
|
801 |
|
802 nsIFrame* frame = nullptr; |
|
803 if (aStartAtParent) { |
|
804 frame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame); |
|
805 } |
|
806 if (!frame) { |
|
807 frame = aFrame; |
|
808 } |
|
809 |
|
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; |
|
815 |
|
816 if (frame->IsThemed()) |
|
817 break; |
|
818 |
|
819 nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(frame); |
|
820 if (!parent) |
|
821 break; |
|
822 |
|
823 frame = parent; |
|
824 } |
|
825 return frame; |
|
826 } |
|
827 |
|
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 } |
|
841 |
|
842 nsIFrame* |
|
843 nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame) |
|
844 { |
|
845 const nsStyleBackground* result = aForFrame->StyleBackground(); |
|
846 |
|
847 // Check if we need to do propagation from BODY rather than HTML. |
|
848 if (!result->IsTransparent()) { |
|
849 return aForFrame; |
|
850 } |
|
851 |
|
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 } |
|
859 |
|
860 nsIDocument* document = content->OwnerDoc(); |
|
861 |
|
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 } |
|
875 |
|
876 nsIFrame *bodyFrame = bodyContent->GetPrimaryFrame(); |
|
877 if (!bodyFrame) { |
|
878 return aForFrame; |
|
879 } |
|
880 |
|
881 return nsLayoutUtils::GetStyleFrame(bodyFrame); |
|
882 } |
|
883 |
|
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 } |
|
916 |
|
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 } |
|
925 |
|
926 *aBackgroundSC = aForFrame->StyleContext(); |
|
927 |
|
928 // Return true unless the frame is for a BODY element whose background |
|
929 // was propagated to the viewport. |
|
930 |
|
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 |
|
936 |
|
937 if (aForFrame->StyleContext()->GetPseudo()) |
|
938 return true; // A pseudo-element frame. |
|
939 |
|
940 // We should only look at the <html> background if we're in an HTML document |
|
941 nsIDocument* document = content->OwnerDoc(); |
|
942 |
|
943 dom::Element* bodyContent = document->GetBodyElement(); |
|
944 if (bodyContent != content) |
|
945 return true; // this wasn't the background that was propagated |
|
946 |
|
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; |
|
952 |
|
953 const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground(); |
|
954 return !htmlBG->IsTransparent(); |
|
955 } |
|
956 |
|
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 } |
|
970 |
|
971 void |
|
972 nsCSSRendering::BeginFrameTreesLocked() |
|
973 { |
|
974 ++gFrameTreeLockCount; |
|
975 } |
|
976 |
|
977 void |
|
978 nsCSSRendering::EndFrameTreesLocked() |
|
979 { |
|
980 NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked"); |
|
981 --gFrameTreeLockCount; |
|
982 if (gFrameTreeLockCount == 0) { |
|
983 gInlineBGData->Reset(); |
|
984 } |
|
985 } |
|
986 |
|
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); |
|
1000 |
|
1001 bool hasBorderRadius; |
|
1002 bool nativeTheme; // mutually exclusive with hasBorderRadius |
|
1003 gfxCornerSizes borderRadii; |
|
1004 |
|
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 } |
|
1024 |
|
1025 nsRect frameRect = |
|
1026 nativeTheme ? aForFrame->GetVisualOverflowRectRelativeToSelf() + aFrameArea.TopLeft() : aFrameArea; |
|
1027 gfxRect frameGfxRect(nsLayoutUtils::RectToGfxRect(frameRect, twipsPerPixel)); |
|
1028 frameGfxRect.Round(); |
|
1029 |
|
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 } |
|
1049 |
|
1050 for (uint32_t i = shadows->Length(); i > 0; --i) { |
|
1051 nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1); |
|
1052 if (shadowItem->mInset) |
|
1053 continue; |
|
1054 |
|
1055 nsRect shadowRect = frameRect; |
|
1056 shadowRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset); |
|
1057 if (!nativeTheme) { |
|
1058 shadowRect.Inflate(shadowItem->mSpread, shadowItem->mSpread); |
|
1059 } |
|
1060 |
|
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)); |
|
1067 |
|
1068 gfxRect shadowGfxRectPlusBlur = |
|
1069 nsLayoutUtils::RectToGfxRect(shadowRectPlusBlur, twipsPerPixel); |
|
1070 shadowGfxRectPlusBlur.RoundOut(); |
|
1071 |
|
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; |
|
1078 |
|
1079 gfxRGBA gfxShadowColor(shadowColor); |
|
1080 gfxShadowColor.a *= aOpacity; |
|
1081 |
|
1082 gfxContext* renderContext = aRenderingContext.ThebesContext(); |
|
1083 if (nativeTheme) { |
|
1084 nsContextBoxBlur blurringArea; |
|
1085 |
|
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; |
|
1097 |
|
1098 // shadowContext is owned by either blurringArea or aRenderingContext. |
|
1099 MOZ_ASSERT(shadowContext == blurringArea.GetContext()); |
|
1100 |
|
1101 renderContext->Save(); |
|
1102 renderContext->SetColor(gfxShadowColor); |
|
1103 |
|
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. |
|
1109 |
|
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. |
|
1112 |
|
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)); |
|
1119 |
|
1120 nsRect nativeRect; |
|
1121 nativeRect.IntersectRect(frameRect, aDirtyRect); |
|
1122 aPresContext->GetTheme()->DrawWidgetBackground(wrapperCtx, aForFrame, |
|
1123 styleDisplay->mAppearance, aFrameArea, nativeRect); |
|
1124 |
|
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 } |
|
1138 |
|
1139 renderContext->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); |
|
1140 renderContext->Clip(); |
|
1141 |
|
1142 gfxCornerSizes clipRectRadii; |
|
1143 if (hasBorderRadius) { |
|
1144 gfxFloat spreadDistance = shadowItem->mSpread / twipsPerPixel; |
|
1145 |
|
1146 gfxFloat borderSizes[4]; |
|
1147 |
|
1148 borderSizes[NS_SIDE_LEFT] = spreadDistance; |
|
1149 borderSizes[NS_SIDE_TOP] = spreadDistance; |
|
1150 borderSizes[NS_SIDE_RIGHT] = spreadDistance; |
|
1151 borderSizes[NS_SIDE_BOTTOM] = spreadDistance; |
|
1152 |
|
1153 nsCSSBorderRenderer::ComputeOuterRadii(borderRadii, borderSizes, |
|
1154 &clipRectRadii); |
|
1155 |
|
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 } |
|
1167 |
|
1168 } |
|
1169 } |
|
1170 |
|
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 } |
|
1190 |
|
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); |
|
1198 |
|
1199 nsRect paddingRect = aFrameArea; |
|
1200 nsMargin border = aForFrame->GetUsedBorder(); |
|
1201 aForFrame->ApplySkipSides(border); |
|
1202 paddingRect.Deflate(border); |
|
1203 |
|
1204 gfxCornerSizes innerRadii; |
|
1205 if (hasBorderRadius) { |
|
1206 gfxCornerSizes borderRadii; |
|
1207 |
|
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 } |
|
1218 |
|
1219 for (uint32_t i = shadows->Length(); i > 0; --i) { |
|
1220 nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1); |
|
1221 if (!shadowItem->mInset) |
|
1222 continue; |
|
1223 |
|
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); |
|
1236 |
|
1237 nsRect shadowClipRect = paddingRect; |
|
1238 shadowClipRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset); |
|
1239 shadowClipRect.Deflate(shadowItem->mSpread, shadowItem->mSpread); |
|
1240 |
|
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}; |
|
1246 |
|
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 } |
|
1251 |
|
1252 if (innerRadii[C_TL].height > 0 || innerRadii[C_TR].height > 0) { |
|
1253 borderSizes[NS_SIDE_TOP] = spreadDistance; |
|
1254 } |
|
1255 |
|
1256 if (innerRadii[C_TR].width > 0 || innerRadii[C_BR].width > 0) { |
|
1257 borderSizes[NS_SIDE_RIGHT] = spreadDistance; |
|
1258 } |
|
1259 |
|
1260 if (innerRadii[C_BL].height > 0 || innerRadii[C_BR].height > 0) { |
|
1261 borderSizes[NS_SIDE_BOTTOM] = spreadDistance; |
|
1262 } |
|
1263 |
|
1264 nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes, |
|
1265 &clipRectRadii); |
|
1266 } |
|
1267 |
|
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 } |
|
1278 |
|
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; |
|
1290 |
|
1291 // shadowContext is owned by either blurringArea or aRenderingContext. |
|
1292 MOZ_ASSERT(shadowContext == renderContext || |
|
1293 shadowContext == blurringArea.GetContext()); |
|
1294 |
|
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; |
|
1301 |
|
1302 renderContext->Save(); |
|
1303 renderContext->SetColor(gfxRGBA(shadowColor)); |
|
1304 |
|
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(); |
|
1317 |
|
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(); |
|
1334 |
|
1335 blurringArea.DoPaint(); |
|
1336 renderContext->Restore(); |
|
1337 } |
|
1338 } |
|
1339 |
|
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"); |
|
1353 |
|
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 } |
|
1364 |
|
1365 nsIContent* content = aForFrame->GetContent(); |
|
1366 if (!content || content->GetParent()) { |
|
1367 return; |
|
1368 } |
|
1369 |
|
1370 sc = aForFrame->StyleContext(); |
|
1371 } |
|
1372 |
|
1373 PaintBackgroundWithSC(aPresContext, aRenderingContext, aForFrame, |
|
1374 aDirtyRect, aBorderArea, sc, |
|
1375 *aForFrame->StyleBorder(), aFlags, |
|
1376 aBGClipRect, aLayer); |
|
1377 } |
|
1378 |
|
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"); |
|
1390 |
|
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 } |
|
1401 |
|
1402 nsIContent* content = aForFrame->GetContent(); |
|
1403 if (!content || content->GetParent()) { |
|
1404 return; |
|
1405 } |
|
1406 |
|
1407 sc = aForFrame->StyleContext(); |
|
1408 } |
|
1409 |
|
1410 PaintBackgroundColorWithSC(aPresContext, aRenderingContext, aForFrame, |
|
1411 aDirtyRect, aBorderArea, sc, |
|
1412 *aForFrame->StyleBorder(), aFlags); |
|
1413 } |
|
1414 |
|
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 } |
|
1430 |
|
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; |
|
1437 |
|
1438 nscolor color; |
|
1439 bool isForeground; |
|
1440 aBorder.GetBorderColor(aSide, color, isForeground); |
|
1441 |
|
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; |
|
1446 |
|
1447 return NS_GET_A(color) == 255; |
|
1448 } |
|
1449 |
|
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 } |
|
1464 |
|
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); |
|
1472 |
|
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 } |
|
1480 |
|
1481 struct BackgroundClipState { |
|
1482 nsRect mBGClipArea; // Affected by mClippedRadii |
|
1483 nsRect mAdditionalBGClipArea; // Not affected by mClippedRadii |
|
1484 nsRect mDirtyRect; |
|
1485 gfxRect mDirtyRectGfx; |
|
1486 |
|
1487 gfxCornerSizes mClippedRadii; |
|
1488 bool mRadiiAreOuter; |
|
1489 bool mHasAdditionalBGClipArea; |
|
1490 |
|
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 }; |
|
1495 |
|
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 |
|
1513 |
|
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 } |
|
1535 |
|
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 } |
|
1540 |
|
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); |
|
1558 |
|
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 } |
|
1571 |
|
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 } |
|
1578 |
|
1579 SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, aAppUnitsPerPixel, |
|
1580 &aClipState->mDirtyRect, &aClipState->mDirtyRectGfx); |
|
1581 } |
|
1582 |
|
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 } |
|
1593 |
|
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 } |
|
1599 |
|
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. |
|
1606 |
|
1607 if (aHaveRoundedCorners || aClipState.mHasAdditionalBGClipArea) { |
|
1608 aAutoSR->Reset(aCtx); |
|
1609 } |
|
1610 |
|
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 } |
|
1620 |
|
1621 if (aHaveRoundedCorners) { |
|
1622 gfxRect bgAreaGfx = |
|
1623 nsLayoutUtils::RectToGfxRect(aClipState.mBGClipArea, aAppUnitsPerPixel); |
|
1624 bgAreaGfx.Round(); |
|
1625 bgAreaGfx.Condition(); |
|
1626 |
|
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 } |
|
1635 |
|
1636 aCtx->NewPath(); |
|
1637 aCtx->RoundedRectangle(bgAreaGfx, aClipState.mClippedRadii, aClipState.mRadiiAreOuter); |
|
1638 aCtx->Clip(); |
|
1639 } |
|
1640 } |
|
1641 |
|
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 } |
|
1651 |
|
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 } |
|
1660 |
|
1661 gfxRect bgAreaGfx = |
|
1662 nsLayoutUtils::RectToGfxRect(aClipState.mBGClipArea, aAppUnitsPerPixel); |
|
1663 bgAreaGfx.Round(); |
|
1664 bgAreaGfx.Condition(); |
|
1665 |
|
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 } |
|
1674 |
|
1675 aCtx->Save(); |
|
1676 gfxRect dirty = bgAreaGfx.Intersect(aClipState.mDirtyRectGfx); |
|
1677 |
|
1678 aCtx->NewPath(); |
|
1679 aCtx->Rectangle(dirty, true); |
|
1680 aCtx->Clip(); |
|
1681 |
|
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 } |
|
1691 |
|
1692 aCtx->NewPath(); |
|
1693 aCtx->RoundedRectangle(bgAreaGfx, aClipState.mClippedRadii, |
|
1694 aClipState.mRadiiAreOuter); |
|
1695 |
|
1696 aCtx->Fill(); |
|
1697 aCtx->Restore(); |
|
1698 } |
|
1699 |
|
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; |
|
1709 |
|
1710 if (aFrame->HonorPrintBackgroundSettings()) { |
|
1711 aDrawBackgroundImage = aPresContext->GetBackgroundImageDraw(); |
|
1712 aDrawBackgroundColor = aPresContext->GetBackgroundColorDraw(); |
|
1713 } |
|
1714 |
|
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 } |
|
1735 |
|
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 } |
|
1744 |
|
1745 return bgColor; |
|
1746 } |
|
1747 |
|
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 } |
|
1768 |
|
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 } |
|
1787 |
|
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 } |
|
1834 |
|
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 } |
|
1858 |
|
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; |
|
1920 |
|
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 } |
|
1928 |
|
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 } |
|
1933 |
|
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 } |
|
1946 |
|
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 } |
|
1954 |
|
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 } |
|
1970 |
|
1971 gfxContext *ctx = aRenderingContext.ThebesContext(); |
|
1972 nscoord appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel(); |
|
1973 gfxSize srcSize = gfxSize(gfxFloat(aIntrinsicSize.width)/appUnitsPerDevPixel, |
|
1974 gfxFloat(aIntrinsicSize.height)/appUnitsPerDevPixel); |
|
1975 |
|
1976 bool cellContainsFill = aDest.Contains(aFillArea); |
|
1977 |
|
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); |
|
1991 |
|
1992 NS_ABORT_IF_FALSE(aGradient->mStops.Length() >= 2, |
|
1993 "The parser should reject gradients with less than two stops"); |
|
1994 |
|
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 } |
|
2041 |
|
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 } |
|
2059 |
|
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 } |
|
2115 |
|
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 } |
|
2124 |
|
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 } |
|
2146 |
|
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); |
|
2156 |
|
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; |
|
2168 |
|
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 } |
|
2179 |
|
2180 gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y, |
|
2181 gradientEnd.x, gradientEnd.y); |
|
2182 |
|
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"); |
|
2196 |
|
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); |
|
2225 |
|
2226 if (gradientPattern->CairoStatus()) |
|
2227 return; |
|
2228 |
|
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(); |
|
2238 |
|
2239 if (!aGradient->mRepeating && !zeroRadius) { |
|
2240 stops.AppendElement(ColorStop(firstStop, firstColor)); |
|
2241 } |
|
2242 stops.AppendElement(ColorStop(firstStop, lastColor)); |
|
2243 } |
|
2244 |
|
2245 bool isRepeat = aGradient->mRepeating || forceRepeatToCoverTiles; |
|
2246 |
|
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 } |
|
2276 |
|
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; |
|
2284 |
|
2285 gfxRect areaToFill = |
|
2286 nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel); |
|
2287 gfxMatrix ctm = ctx->CurrentMatrix(); |
|
2288 bool isCTMPreservingAxisAlignedRectangles = ctm.PreservesAxisAlignedRectangles(); |
|
2289 |
|
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(); |
|
2295 |
|
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 } |
|
2341 |
|
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"); |
|
2356 |
|
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 } |
|
2375 |
|
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); |
|
2384 |
|
2385 // Determine whether we are drawing background images and/or |
|
2386 // background colors. |
|
2387 bool drawBackgroundImage; |
|
2388 bool drawBackgroundColor; |
|
2389 |
|
2390 nscolor bgColor = DetermineBackgroundColor(aPresContext, |
|
2391 aBackgroundSC, |
|
2392 aForFrame, |
|
2393 drawBackgroundImage, |
|
2394 drawBackgroundColor); |
|
2395 |
|
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 } |
|
2402 |
|
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; |
|
2408 |
|
2409 // Compute the outermost boundary of the area that might be painted. |
|
2410 gfxContext *ctx = aRenderingContext.ThebesContext(); |
|
2411 nscoord appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel(); |
|
2412 |
|
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 } |
|
2430 |
|
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 } |
|
2464 |
|
2465 GetBackgroundClip(ctx, currentBackgroundClip, bg->BottomLayer().mAttachment, |
|
2466 aForFrame, aBorderArea, |
|
2467 aDirtyRect, haveRoundedCorners, bgRadii, appUnitsPerPixel, |
|
2468 &clipState); |
|
2469 } |
|
2470 |
|
2471 // If we might be using a background color, go ahead and set it now. |
|
2472 if (drawBackgroundColor && !isCanvasFrame) |
|
2473 ctx->SetColor(gfxRGBA(bgColor)); |
|
2474 |
|
2475 gfxContextAutoSaveRestore autoSR; |
|
2476 |
|
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 } |
|
2486 |
|
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 } |
|
2492 |
|
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 } |
|
2500 |
|
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 } |
|
2509 |
|
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 } |
|
2515 |
|
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 } |
|
2565 |
|
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"); |
|
2578 |
|
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 } |
|
2590 |
|
2591 NS_ASSERTION(!IsCanvasFrame(aForFrame), "Should not be trying to paint a background color for canvas frames!"); |
|
2592 |
|
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); |
|
2602 |
|
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 } |
|
2608 |
|
2609 // Compute the outermost boundary of the area that might be painted. |
|
2610 gfxContext *ctx = aRenderingContext.ThebesContext(); |
|
2611 nscoord appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel(); |
|
2612 |
|
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 } |
|
2630 |
|
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 } |
|
2650 |
|
2651 BackgroundClipState clipState; |
|
2652 GetBackgroundClip(ctx, currentBackgroundClip, bg->BottomLayer().mAttachment, |
|
2653 aForFrame, aBorderArea, |
|
2654 aDirtyRect, haveRoundedCorners, bgRadii, appUnitsPerPixel, |
|
2655 &clipState); |
|
2656 |
|
2657 ctx->SetColor(gfxRGBA(bgColor)); |
|
2658 |
|
2659 gfxContextAutoSaveRestore autoSR; |
|
2660 DrawBackgroundColor(clipState, ctx, haveRoundedCorners, appUnitsPerPixel); |
|
2661 } |
|
2662 |
|
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 } |
|
2673 |
|
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); |
|
2685 |
|
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 } |
|
2745 |
|
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 } |
|
2758 |
|
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 } |
|
2775 |
|
2776 // Set the background positioning area to the viewport's area |
|
2777 // (relative to aForFrame) |
|
2778 bgPositioningArea = |
|
2779 nsRect(-aForFrame->GetOffsetTo(attachedToFrame), attachedToFrame->GetSize()); |
|
2780 |
|
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; |
|
2792 |
|
2793 return bgPositioningArea; |
|
2794 } |
|
2795 |
|
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 } |
|
2816 |
|
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 } |
|
2827 |
|
2828 return nsImageRenderer::ComputeConcreteSize(specifiedSize, |
|
2829 aIntrinsicSize, |
|
2830 aBgPositioningArea); |
|
2831 } |
|
2832 |
|
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 */ |
|
2897 |
|
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 } |
|
2905 |
|
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 } |
|
2911 |
|
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); |
|
2919 |
|
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; |
|
2923 |
|
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 } |
|
2942 |
|
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; |
|
2953 |
|
2954 state.mImageRenderer.SetPreferredSize(intrinsicSize, |
|
2955 imageSize); |
|
2956 |
|
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(); |
|
2963 |
|
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); |
|
2977 |
|
2978 state.mCompositingOp = GetGFXBlendMode(aLayer.mBlendMode); |
|
2979 |
|
2980 return state; |
|
2981 } |
|
2982 |
|
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 } |
|
2997 |
|
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 } |
|
3013 |
|
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 } |
|
3025 |
|
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"); |
|
3036 |
|
3037 if (aDirtyRect.IsEmpty()) |
|
3038 return; |
|
3039 |
|
3040 nsImageRenderer renderer(aForFrame, &aStyleBorder.mBorderImageSource, 0); |
|
3041 |
|
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); |
|
3049 |
|
3050 if (!renderer.PrepareImage()) { |
|
3051 return; |
|
3052 } |
|
3053 |
|
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()); |
|
3058 |
|
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); |
|
3065 |
|
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; |
|
3095 |
|
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 } |
|
3122 |
|
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 } |
|
3146 |
|
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 }; |
|
3195 |
|
3196 for (int i = LEFT; i <= RIGHT; i++) { |
|
3197 for (int j = TOP; j <= BOTTOM; j++) { |
|
3198 uint8_t fillStyleH, fillStyleV; |
|
3199 nsSize unitSize; |
|
3200 |
|
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 } |
|
3207 |
|
3208 NS_ASSERTION(NS_STYLE_BORDER_IMAGE_SLICE_FILL == |
|
3209 aStyleBorder.mBorderImageFill, |
|
3210 "Unexpected border image fill"); |
|
3211 |
|
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; |
|
3222 |
|
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; |
|
3229 |
|
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; |
|
3236 |
|
3237 unitSize.width = sliceWidth[i]*hFactor; |
|
3238 unitSize.height = sliceHeight[j]*vFactor; |
|
3239 fillStyleH = aStyleBorder.mBorderImageRepeatH; |
|
3240 fillStyleV = aStyleBorder.mBorderImageRepeatV; |
|
3241 |
|
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; |
|
3250 |
|
3251 unitSize.width = sliceWidth[i]*factor; |
|
3252 unitSize.height = borderHeight[j]; |
|
3253 fillStyleH = aStyleBorder.mBorderImageRepeatH; |
|
3254 fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH; |
|
3255 |
|
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; |
|
3262 |
|
3263 unitSize.width = borderWidth[i]; |
|
3264 unitSize.height = sliceHeight[j]*factor; |
|
3265 fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH; |
|
3266 fillStyleV = aStyleBorder.mBorderImageRepeatV; |
|
3267 |
|
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 } |
|
3275 |
|
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()); |
|
3279 |
|
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 } |
|
3291 |
|
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 |
|
3295 |
|
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; |
|
3305 |
|
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 } |
|
3311 |
|
3312 static nscoord |
|
3313 RoundFloatToPixel(float aValue, |
|
3314 nscoord aTwipsPerPixel, |
|
3315 bool aRoundDown = false) |
|
3316 { |
|
3317 return RoundIntToPixel(NSToCoordRound(aValue), aTwipsPerPixel, aRoundDown); |
|
3318 } |
|
3319 |
|
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 } |
|
3335 |
|
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 { |
|
3345 |
|
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 } |
|
3381 |
|
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 } |
|
3395 |
|
3396 aContext.FillPolygon(poly, 5); |
|
3397 } |
|
3398 |
|
3399 |
|
3400 } |
|
3401 |
|
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 } |
|
3425 |
|
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); |
|
3439 |
|
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; |
|
3443 |
|
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 } |
|
3450 |
|
3451 gfxContext *ctx = aContext.ThebesContext(); |
|
3452 gfxContext::AntialiasMode oldMode = ctx->CurrentAntialiasMode(); |
|
3453 ctx->SetAntialiasMode(gfxContext::MODE_ALIASED); |
|
3454 |
|
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 } |
|
3542 |
|
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); |
|
3589 |
|
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); |
|
3601 |
|
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); |
|
3617 |
|
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); |
|
3628 |
|
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 } |
|
3656 |
|
3657 ctx->SetAntialiasMode(oldMode); |
|
3658 } |
|
3659 |
|
3660 // End table border-collapsing section |
|
3661 |
|
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 } |
|
3678 |
|
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 } |
|
3690 |
|
3691 NS_ENSURE_TRUE(block, aClippedRect); |
|
3692 |
|
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 } |
|
3703 |
|
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"); |
|
3719 |
|
3720 gfxRect rect = |
|
3721 GetTextDecorationRectInternal(aPt, aLineSize, aAscent, aOffset, |
|
3722 aDecoration, aStyle, aDescentLimit); |
|
3723 if (rect.IsEmpty() || !rect.Intersects(aDirtyRect)) { |
|
3724 return; |
|
3725 } |
|
3726 |
|
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 } |
|
3733 |
|
3734 gfxFloat lineHeight = std::max(NS_round(aLineSize.height), 1.0); |
|
3735 bool contextIsSaved = false; |
|
3736 |
|
3737 gfxFloat oldLineWidth; |
|
3738 nsRefPtr<gfxPattern> oldPattern; |
|
3739 |
|
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 } |
|
3798 |
|
3799 // The y position should be set to the middle of the line. |
|
3800 rect.y += lineHeight / 2; |
|
3801 |
|
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 */ |
|
3870 |
|
3871 gfxFloat adv = rect.Height() - lineHeight; |
|
3872 gfxFloat flatLengthAtVertex = std::max((lineHeight - 1.0) * 2.0, 1.0); |
|
3873 |
|
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 } |
|
3886 |
|
3887 rect.x += lineHeight / 2.0; |
|
3888 gfxPoint pt(rect.TopLeft()); |
|
3889 gfxFloat rightMost = pt.x + rect.Width() + lineHeight; |
|
3890 |
|
3891 skipCycles = floor((rightMost - aDirtyRect.XMost()) / cycleLength); |
|
3892 if (skipCycles > 0) { |
|
3893 rightMost -= skipCycles * cycleLength; |
|
3894 } |
|
3895 |
|
3896 aGfxContext->NewPath(); |
|
3897 |
|
3898 pt.x -= lineHeight; |
|
3899 aGfxContext->MoveTo(pt); // 1 |
|
3900 |
|
3901 pt.x = rect.X(); |
|
3902 aGfxContext->LineTo(pt); // 2 |
|
3903 |
|
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; |
|
3917 |
|
3918 aGfxContext->LineTo(pt); // 3 and 5 |
|
3919 |
|
3920 pt.x += flatLengthAtVertex; |
|
3921 aGfxContext->LineTo(pt); // 4 and 6 |
|
3922 |
|
3923 goDown = !goDown; |
|
3924 } |
|
3925 aGfxContext->Stroke(); |
|
3926 break; |
|
3927 } |
|
3928 default: |
|
3929 NS_ERROR("Invalid style value!"); |
|
3930 break; |
|
3931 } |
|
3932 |
|
3933 if (contextIsSaved) { |
|
3934 aGfxContext->Restore(); |
|
3935 } else { |
|
3936 aGfxContext->SetPattern(oldPattern); |
|
3937 aGfxContext->SetLineWidth(oldLineWidth); |
|
3938 } |
|
3939 } |
|
3940 |
|
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"); |
|
3956 |
|
3957 aGfxContext->NewPath(); |
|
3958 |
|
3959 gfxRect rect = |
|
3960 GetTextDecorationRectInternal(aPt, aLineSize, aAscent, aOffset, |
|
3961 aDecoration, aStyle, aDescentLimit); |
|
3962 if (rect.IsEmpty() || !rect.Intersects(aDirtyRect)) { |
|
3963 return; |
|
3964 } |
|
3965 |
|
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 } |
|
3972 |
|
3973 if (aStyle != NS_STYLE_TEXT_DECORATION_STYLE_SOLID) { |
|
3974 // For the moment, we support only solid text decorations. |
|
3975 return; |
|
3976 } |
|
3977 |
|
3978 gfxFloat lineHeight = std::max(NS_round(aLineSize.height), 1.0); |
|
3979 |
|
3980 // The y position should be set to the middle of the line. |
|
3981 rect.y += lineHeight / 2; |
|
3982 |
|
3983 aGfxContext->Rectangle |
|
3984 (gfxRect(gfxPoint(rect.TopLeft() - gfxPoint(0.0, lineHeight / 2)), |
|
3985 gfxSize(rect.Width(), lineHeight))); |
|
3986 } |
|
3987 |
|
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"); |
|
3999 |
|
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 } |
|
4011 |
|
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"); |
|
4023 |
|
4024 if (aStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) |
|
4025 return gfxRect(0, 0, 0, 0); |
|
4026 |
|
4027 bool canLiftUnderline = aDescentLimit >= 0.0; |
|
4028 |
|
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); |
|
4032 |
|
4033 gfxFloat lineHeight = NS_round(aLineSize.height); |
|
4034 lineHeight = std::max(lineHeight, 1.0); |
|
4035 |
|
4036 gfxFloat ascent = NS_round(aAscent); |
|
4037 gfxFloat descentLimit = floor(aDescentLimit); |
|
4038 |
|
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 } |
|
4092 |
|
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 } |
|
4125 |
|
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 } |
|
4143 |
|
4144 nsImageRenderer::~nsImageRenderer() |
|
4145 { |
|
4146 } |
|
4147 |
|
4148 bool |
|
4149 nsImageRenderer::PrepareImage() |
|
4150 { |
|
4151 if (mImage->IsEmpty()) |
|
4152 return false; |
|
4153 |
|
4154 if (!mImage->IsComplete()) { |
|
4155 // Make sure the image is actually decoding |
|
4156 mImage->StartDecoding(); |
|
4157 |
|
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 } |
|
4171 |
|
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"); |
|
4181 |
|
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(); |
|
4223 |
|
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 } |
|
4239 |
|
4240 return mIsReady; |
|
4241 } |
|
4242 |
|
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 } |
|
4256 |
|
4257 MOZ_ASSERT(mHasHeight); |
|
4258 nscoord width = NSCoordSaturatingNonnegativeMultiply( |
|
4259 mHeight, |
|
4260 double(mRatio.width) / mRatio.height); |
|
4261 return nsSize(width, mHeight); |
|
4262 } |
|
4263 |
|
4264 CSSSizeOrRatio |
|
4265 nsImageRenderer::ComputeIntrinsicSize() |
|
4266 { |
|
4267 NS_ASSERTION(mIsReady, "Ensure PrepareImage() has returned true " |
|
4268 "before calling me"); |
|
4269 |
|
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 } |
|
4323 |
|
4324 return result; |
|
4325 } |
|
4326 |
|
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 } |
|
4336 |
|
4337 MOZ_ASSERT(!aSpecifiedSize.mHasWidth || !aSpecifiedSize.mHasHeight); |
|
4338 |
|
4339 if (!aSpecifiedSize.mHasWidth && !aSpecifiedSize.mHasHeight) { |
|
4340 // no specified size, try using the intrinsic size |
|
4341 if (aIntrinsicSize.CanComputeConcreteSize()) { |
|
4342 return aIntrinsicSize.ComputeConcreteSize(); |
|
4343 } |
|
4344 |
|
4345 if (aIntrinsicSize.mHasWidth) { |
|
4346 return nsSize(aIntrinsicSize.mWidth, aDefaultSize.height); |
|
4347 } |
|
4348 if (aIntrinsicSize.mHasHeight) { |
|
4349 return nsSize(aDefaultSize.width, aIntrinsicSize.mHeight); |
|
4350 } |
|
4351 |
|
4352 // couldn't use the intrinsic size either, revert to using the default size |
|
4353 return ComputeConstrainedSize(aDefaultSize, |
|
4354 aIntrinsicSize.mRatio, |
|
4355 CONTAIN); |
|
4356 } |
|
4357 |
|
4358 MOZ_ASSERT(aSpecifiedSize.mHasWidth || aSpecifiedSize.mHasHeight); |
|
4359 |
|
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 } |
|
4374 |
|
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 } |
|
4388 |
|
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 } |
|
4397 |
|
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 } |
|
4412 |
|
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 } |
|
4443 |
|
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 } |
|
4458 |
|
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 } |
|
4475 |
|
4476 GraphicsFilter graphicsFilter = |
|
4477 nsLayoutUtils::GetGraphicsFilterForFrame(mForFrame); |
|
4478 |
|
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 } |
|
4512 |
|
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); |
|
4531 |
|
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 } |
|
4540 |
|
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 } |
|
4557 |
|
4558 if (mType == eStyleImageType_Image) { |
|
4559 GraphicsFilter graphicsFilter = |
|
4560 nsLayoutUtils::GetGraphicsFilterForFrame(mForFrame); |
|
4561 |
|
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 } |
|
4570 |
|
4571 Draw(aPresContext, aRenderingContext, |
|
4572 aDirty, aFill, aDest, |
|
4573 CSSIntRect(0, 0, |
|
4574 nsPresContext::AppUnitsToIntCSSPixels(mSize.width), |
|
4575 nsPresContext::AppUnitsToIntCSSPixels(mSize.height))); |
|
4576 } |
|
4577 |
|
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 } |
|
4609 |
|
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 } |
|
4626 |
|
4627 return tile; |
|
4628 } |
|
4629 |
|
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 } |
|
4648 |
|
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 } |
|
4667 |
|
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 } |
|
4677 |
|
4678 GraphicsFilter graphicsFilter = |
|
4679 nsLayoutUtils::GetGraphicsFilterForFrame(mForFrame); |
|
4680 |
|
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 } |
|
4690 |
|
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 } |
|
4699 |
|
4700 nsRect destTile = RequiresScaling(aFill, aHFill, aVFill, aUnitSize) |
|
4701 ? ComputeTile(aFill, aHFill, aVFill, aUnitSize) |
|
4702 : aFill; |
|
4703 |
|
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. |
|
4716 |
|
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); |
|
4727 |
|
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 } |
|
4735 |
|
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 } |
|
4750 |
|
4751 // Ensure that drawing the image gets flushed to the target. |
|
4752 ctx = nullptr; |
|
4753 |
|
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); |
|
4763 |
|
4764 return; |
|
4765 } |
|
4766 |
|
4767 Draw(aPresContext, aRenderingContext, aDirtyRect, aFill, destTile, aSrc); |
|
4768 } |
|
4769 |
|
4770 bool |
|
4771 nsImageRenderer::IsRasterImage() |
|
4772 { |
|
4773 if (mType != eStyleImageType_Image || !mImageContainer) |
|
4774 return false; |
|
4775 return mImageContainer->GetType() == imgIContainer::TYPE_RASTER; |
|
4776 } |
|
4777 |
|
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; |
|
4786 |
|
4787 return false; |
|
4788 } |
|
4789 |
|
4790 already_AddRefed<mozilla::layers::ImageContainer> |
|
4791 nsImageRenderer::GetContainer(LayerManager* aManager) |
|
4792 { |
|
4793 if (mType != eStyleImageType_Image || !mImageContainer) |
|
4794 return nullptr; |
|
4795 |
|
4796 nsRefPtr<ImageContainer> container; |
|
4797 nsresult rv = mImageContainer->GetImageContainer(aManager, getter_AddRefs(container)); |
|
4798 NS_ENSURE_SUCCESS(rv, nullptr); |
|
4799 return container.forget(); |
|
4800 } |
|
4801 |
|
4802 #define MAX_BLUR_RADIUS 300 |
|
4803 #define MAX_SPREAD_RADIUS 50 |
|
4804 |
|
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); |
|
4813 |
|
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 } |
|
4819 |
|
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 } |
|
4831 |
|
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 } |
|
4848 |
|
4849 gfxFloat scaleX = 1; |
|
4850 gfxFloat scaleY = 1; |
|
4851 |
|
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 } |
|
4863 |
|
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; |
|
4871 |
|
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 } |
|
4879 |
|
4880 // Convert from app units to device pixels |
|
4881 gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel); |
|
4882 |
|
4883 gfxRect dirtyRect = |
|
4884 nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel); |
|
4885 dirtyRect.RoundOut(); |
|
4886 |
|
4887 rect = transform.TransformBounds(rect); |
|
4888 |
|
4889 mPreTransformed = !transform.IsIdentity(); |
|
4890 |
|
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 } |
|
4901 |
|
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 } |
|
4909 |
|
4910 void |
|
4911 nsContextBoxBlur::DoPaint() |
|
4912 { |
|
4913 if (mContext == mDestinationCtx) |
|
4914 return; |
|
4915 |
|
4916 gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx); |
|
4917 |
|
4918 if (mPreTransformed) { |
|
4919 mDestinationCtx->IdentityMatrix(); |
|
4920 } |
|
4921 |
|
4922 blur.Paint(mDestinationCtx); |
|
4923 } |
|
4924 |
|
4925 gfxContext* |
|
4926 nsContextBoxBlur::GetContext() |
|
4927 { |
|
4928 return mContext; |
|
4929 } |
|
4930 |
|
4931 /* static */ nsMargin |
|
4932 nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius, |
|
4933 int32_t aAppUnitsPerDevPixel) |
|
4934 { |
|
4935 gfxIntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel); |
|
4936 |
|
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 } |
|
4944 |
|
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 } |
|
4958 |
|
4959 gfxRect shadowGfxRect = |
|
4960 nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel); |
|
4961 |
|
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 } |
|
4970 |
|
4971 aDestinationCtx->Fill(); |
|
4972 return; |
|
4973 } |
|
4974 |
|
4975 gfxFloat scaleX = 1; |
|
4976 gfxFloat scaleY = 1; |
|
4977 |
|
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 } |
|
4990 |
|
4991 gfxPoint blurStdDev = ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY); |
|
4992 |
|
4993 gfxRect dirtyRect = |
|
4994 nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel); |
|
4995 dirtyRect.RoundOut(); |
|
4996 |
|
4997 shadowGfxRect = transform.TransformBounds(shadowGfxRect); |
|
4998 dirtyRect = transform.TransformBounds(dirtyRect); |
|
4999 gfxRect skipRect = transform.TransformBounds(aSkipRect); |
|
5000 |
|
5001 if (aCornerRadii) { |
|
5002 aCornerRadii->Scale(scaleX, scaleY); |
|
5003 } |
|
5004 |
|
5005 gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx, |
|
5006 shadowGfxRect, |
|
5007 aCornerRadii, |
|
5008 blurStdDev, |
|
5009 aShadowColor, |
|
5010 dirtyRect, |
|
5011 skipRect); |
|
5012 } |