layout/base/nsCSSRendering.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

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 }

mercurial