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