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