michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: // vim:cindent:ts=2:et:sw=2: michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* utility functions for drawing borders and backgrounds */ michael@0: michael@0: #include michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/HashFunctions.h" michael@0: #include "mozilla/MathAlgorithms.h" michael@0: michael@0: #include "nsStyleConsts.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsPoint.h" michael@0: #include "nsRect.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsFrameManager.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsCSSAnonBoxes.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDocumentInlines.h" michael@0: #include "nsIScrollableFrame.h" michael@0: #include "imgIRequest.h" michael@0: #include "imgIContainer.h" michael@0: #include "ImageOps.h" michael@0: #include "nsCSSRendering.h" michael@0: #include "nsCSSColorUtils.h" michael@0: #include "nsITheme.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsBlockFrame.h" michael@0: #include "gfxContext.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsStyleStructInlines.h" michael@0: #include "nsCSSFrameConstructor.h" michael@0: #include "nsCSSProps.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsSVGEffects.h" michael@0: #include "nsSVGIntegrationUtils.h" michael@0: #include "gfxDrawable.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "nsCSSRenderingBorders.h" michael@0: #include "mozilla/css/ImageLoader.h" michael@0: #include "ImageContainer.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "gfxUtils.h" michael@0: #include "gfxColor.h" michael@0: #include "gfxGradientCache.h" michael@0: #include "GraphicsFilter.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::css; michael@0: using namespace mozilla::gfx; michael@0: using mozilla::image::ImageOps; michael@0: using mozilla::CSSSizeOrRatio; michael@0: michael@0: static int gFrameTreeLockCount = 0; michael@0: michael@0: // To avoid storing this data on nsInlineFrame (bloat) and to avoid michael@0: // recalculating this for each frame in a continuation (perf), hold michael@0: // a cache of various coordinate information that we need in order michael@0: // to paint inline backgrounds. michael@0: struct InlineBackgroundData michael@0: { michael@0: InlineBackgroundData() michael@0: : mFrame(nullptr), mBlockFrame(nullptr) michael@0: { michael@0: } michael@0: michael@0: ~InlineBackgroundData() michael@0: { michael@0: } michael@0: michael@0: void Reset() michael@0: { michael@0: mBoundingBox.SetRect(0,0,0,0); michael@0: mContinuationPoint = mLineContinuationPoint = mUnbrokenWidth = 0; michael@0: mFrame = mBlockFrame = nullptr; michael@0: } michael@0: michael@0: nsRect GetContinuousRect(nsIFrame* aFrame) michael@0: { michael@0: SetFrame(aFrame); michael@0: michael@0: nscoord x; michael@0: if (mBidiEnabled) { michael@0: x = mLineContinuationPoint; michael@0: michael@0: // Scan continuations on the same line as aFrame and accumulate the widths michael@0: // of frames that are to the left (if this is an LTR block) or right michael@0: // (if it's RTL) of the current one. michael@0: bool isRtlBlock = (mBlockFrame->StyleVisibility()->mDirection == michael@0: NS_STYLE_DIRECTION_RTL); michael@0: nscoord curOffset = aFrame->GetOffsetTo(mBlockFrame).x; michael@0: michael@0: // No need to use our GetPrevContinuation/GetNextContinuation methods michael@0: // here, since ib-split siblings are certainly not on the same line. michael@0: michael@0: nsIFrame* inlineFrame = aFrame->GetPrevContinuation(); michael@0: // If the continuation is fluid we know inlineFrame is not on the same line. michael@0: // If it's not fluid, we need to test further to be sure. michael@0: while (inlineFrame && !inlineFrame->GetNextInFlow() && michael@0: AreOnSameLine(aFrame, inlineFrame)) { michael@0: nscoord frameXOffset = inlineFrame->GetOffsetTo(mBlockFrame).x; michael@0: if(isRtlBlock == (frameXOffset >= curOffset)) { michael@0: x += inlineFrame->GetSize().width; michael@0: } michael@0: inlineFrame = inlineFrame->GetPrevContinuation(); michael@0: } michael@0: michael@0: inlineFrame = aFrame->GetNextContinuation(); michael@0: while (inlineFrame && !inlineFrame->GetPrevInFlow() && michael@0: AreOnSameLine(aFrame, inlineFrame)) { michael@0: nscoord frameXOffset = inlineFrame->GetOffsetTo(mBlockFrame).x; michael@0: if(isRtlBlock == (frameXOffset >= curOffset)) { michael@0: x += inlineFrame->GetSize().width; michael@0: } michael@0: inlineFrame = inlineFrame->GetNextContinuation(); michael@0: } michael@0: if (isRtlBlock) { michael@0: // aFrame itself is also to the right of its left edge, so add its width. michael@0: x += aFrame->GetSize().width; michael@0: // x is now the distance from the left edge of aFrame to the right edge michael@0: // of the unbroken content. Change it to indicate the distance from the michael@0: // left edge of the unbroken content to the left edge of aFrame. michael@0: x = mUnbrokenWidth - x; michael@0: } michael@0: } else { michael@0: x = mContinuationPoint; michael@0: } michael@0: michael@0: // Assume background-origin: border and return a rect with offsets michael@0: // relative to (0,0). If we have a different background-origin, michael@0: // then our rect should be deflated appropriately by our caller. michael@0: return nsRect(-x, 0, mUnbrokenWidth, mFrame->GetSize().height); michael@0: } michael@0: michael@0: nsRect GetBoundingRect(nsIFrame* aFrame) michael@0: { michael@0: SetFrame(aFrame); michael@0: michael@0: // Move the offsets relative to (0,0) which puts the bounding box into michael@0: // our coordinate system rather than our parent's. We do this by michael@0: // moving it the back distance from us to the bounding box. michael@0: // This also assumes background-origin: border, so our caller will michael@0: // need to deflate us if needed. michael@0: nsRect boundingBox(mBoundingBox); michael@0: nsPoint point = mFrame->GetPosition(); michael@0: boundingBox.MoveBy(-point.x, -point.y); michael@0: michael@0: return boundingBox; michael@0: } michael@0: michael@0: protected: michael@0: nsIFrame* mFrame; michael@0: nsBlockFrame* mBlockFrame; michael@0: nsRect mBoundingBox; michael@0: nscoord mContinuationPoint; michael@0: nscoord mUnbrokenWidth; michael@0: nscoord mLineContinuationPoint; michael@0: bool mBidiEnabled; michael@0: michael@0: void SetFrame(nsIFrame* aFrame) michael@0: { michael@0: NS_PRECONDITION(aFrame, "Need a frame"); michael@0: NS_ASSERTION(gFrameTreeLockCount > 0, michael@0: "Can't call this when frame tree is not locked"); michael@0: michael@0: if (aFrame == mFrame) { michael@0: return; michael@0: } michael@0: michael@0: nsIFrame *prevContinuation = GetPrevContinuation(aFrame); michael@0: michael@0: if (!prevContinuation || mFrame != prevContinuation) { michael@0: // Ok, we've got the wrong frame. We have to start from scratch. michael@0: Reset(); michael@0: Init(aFrame); michael@0: return; michael@0: } michael@0: michael@0: // Get our last frame's size and add its width to our continuation michael@0: // point before we cache the new frame. michael@0: mContinuationPoint += mFrame->GetSize().width; michael@0: michael@0: // If this a new line, update mLineContinuationPoint. michael@0: if (mBidiEnabled && michael@0: (aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) { michael@0: mLineContinuationPoint = mContinuationPoint; michael@0: } michael@0: michael@0: mFrame = aFrame; michael@0: } michael@0: michael@0: nsIFrame* GetPrevContinuation(nsIFrame* aFrame) michael@0: { michael@0: nsIFrame* prevCont = aFrame->GetPrevContinuation(); michael@0: if (!prevCont && michael@0: (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) { michael@0: nsIFrame* block = static_cast michael@0: (aFrame->Properties().Get(nsIFrame::IBSplitPrevSibling())); michael@0: if (block) { michael@0: // The {ib} properties are only stored on first continuations michael@0: NS_ASSERTION(!block->GetPrevContinuation(), michael@0: "Incorrect value for IBSplitPrevSibling"); michael@0: prevCont = static_cast michael@0: (block->Properties().Get(nsIFrame::IBSplitPrevSibling())); michael@0: NS_ASSERTION(prevCont, "How did that happen?"); michael@0: } michael@0: } michael@0: return prevCont; michael@0: } michael@0: michael@0: nsIFrame* GetNextContinuation(nsIFrame* aFrame) michael@0: { michael@0: nsIFrame* nextCont = aFrame->GetNextContinuation(); michael@0: if (!nextCont && michael@0: (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) { michael@0: // The {ib} properties are only stored on first continuations michael@0: aFrame = aFrame->FirstContinuation(); michael@0: nsIFrame* block = static_cast michael@0: (aFrame->Properties().Get(nsIFrame::IBSplitSibling())); michael@0: if (block) { michael@0: nextCont = static_cast michael@0: (block->Properties().Get(nsIFrame::IBSplitSibling())); michael@0: NS_ASSERTION(nextCont, "How did that happen?"); michael@0: } michael@0: } michael@0: return nextCont; michael@0: } michael@0: michael@0: void Init(nsIFrame* aFrame) michael@0: { michael@0: mBidiEnabled = aFrame->PresContext()->BidiEnabled(); michael@0: if (mBidiEnabled) { michael@0: // Find the containing block frame michael@0: nsIFrame* frame = aFrame; michael@0: do { michael@0: frame = frame->GetParent(); michael@0: mBlockFrame = do_QueryFrame(frame); michael@0: } michael@0: while (frame && frame->IsFrameOfType(nsIFrame::eLineParticipant)); michael@0: michael@0: NS_ASSERTION(mBlockFrame, "Cannot find containing block."); michael@0: } michael@0: michael@0: // Start with the previous flow frame as our continuation point michael@0: // is the total of the widths of the previous frames. michael@0: nsIFrame* inlineFrame = GetPrevContinuation(aFrame); michael@0: michael@0: while (inlineFrame) { michael@0: nsRect rect = inlineFrame->GetRect(); michael@0: mContinuationPoint += rect.width; michael@0: if (mBidiEnabled && !AreOnSameLine(aFrame, inlineFrame)) { michael@0: mLineContinuationPoint += rect.width; michael@0: } michael@0: mUnbrokenWidth += rect.width; michael@0: mBoundingBox.UnionRect(mBoundingBox, rect); michael@0: inlineFrame = GetPrevContinuation(inlineFrame); michael@0: } michael@0: michael@0: // Next add this frame and subsequent frames to the bounding box and michael@0: // unbroken width. michael@0: inlineFrame = aFrame; michael@0: while (inlineFrame) { michael@0: nsRect rect = inlineFrame->GetRect(); michael@0: mUnbrokenWidth += rect.width; michael@0: mBoundingBox.UnionRect(mBoundingBox, rect); michael@0: inlineFrame = GetNextContinuation(inlineFrame); michael@0: } michael@0: michael@0: mFrame = aFrame; michael@0: } michael@0: michael@0: bool AreOnSameLine(nsIFrame* aFrame1, nsIFrame* aFrame2) { michael@0: bool isValid1, isValid2; michael@0: nsBlockInFlowLineIterator it1(mBlockFrame, aFrame1, &isValid1); michael@0: nsBlockInFlowLineIterator it2(mBlockFrame, aFrame2, &isValid2); michael@0: return isValid1 && isValid2 && michael@0: // Make sure aFrame1 and aFrame2 are in the same continuation of michael@0: // mBlockFrame. michael@0: it1.GetContainer() == it2.GetContainer() && michael@0: // And on the same line in it michael@0: it1.GetLine() == it2.GetLine(); michael@0: } michael@0: }; michael@0: michael@0: // A resolved color stop --- with a specific position along the gradient line, michael@0: // and a Thebes color michael@0: struct ColorStop { michael@0: ColorStop(double aPosition, gfxRGBA aColor) : michael@0: mPosition(aPosition), mColor(aColor) {} michael@0: double mPosition; // along the gradient line; 0=start, 1=end michael@0: gfxRGBA mColor; michael@0: }; michael@0: michael@0: /* Local functions */ michael@0: static void DrawBorderImage(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsIFrame* aForFrame, michael@0: const nsRect& aBorderArea, michael@0: const nsStyleBorder& aStyleBorder, michael@0: const nsRect& aDirtyRect); michael@0: michael@0: static nscolor MakeBevelColor(mozilla::css::Side whichSide, uint8_t style, michael@0: nscolor aBackgroundColor, michael@0: nscolor aBorderColor); michael@0: michael@0: static InlineBackgroundData* gInlineBGData = nullptr; michael@0: michael@0: // Initialize any static variables used by nsCSSRendering. michael@0: void nsCSSRendering::Init() michael@0: { michael@0: NS_ASSERTION(!gInlineBGData, "Init called twice"); michael@0: gInlineBGData = new InlineBackgroundData(); michael@0: } michael@0: michael@0: // Clean up any global variables used by nsCSSRendering. michael@0: void nsCSSRendering::Shutdown() michael@0: { michael@0: delete gInlineBGData; michael@0: gInlineBGData = nullptr; michael@0: } michael@0: michael@0: /** michael@0: * Make a bevel color michael@0: */ michael@0: static nscolor michael@0: MakeBevelColor(mozilla::css::Side whichSide, uint8_t style, michael@0: nscolor aBackgroundColor, nscolor aBorderColor) michael@0: { michael@0: michael@0: nscolor colors[2]; michael@0: nscolor theColor; michael@0: michael@0: // Given a background color and a border color michael@0: // calculate the color used for the shading michael@0: NS_GetSpecial3DColors(colors, aBackgroundColor, aBorderColor); michael@0: michael@0: if ((style == NS_STYLE_BORDER_STYLE_OUTSET) || michael@0: (style == NS_STYLE_BORDER_STYLE_RIDGE)) { michael@0: // Flip colors for these two border styles michael@0: switch (whichSide) { michael@0: case NS_SIDE_BOTTOM: whichSide = NS_SIDE_TOP; break; michael@0: case NS_SIDE_RIGHT: whichSide = NS_SIDE_LEFT; break; michael@0: case NS_SIDE_TOP: whichSide = NS_SIDE_BOTTOM; break; michael@0: case NS_SIDE_LEFT: whichSide = NS_SIDE_RIGHT; break; michael@0: } michael@0: } michael@0: michael@0: switch (whichSide) { michael@0: case NS_SIDE_BOTTOM: michael@0: theColor = colors[1]; michael@0: break; michael@0: case NS_SIDE_RIGHT: michael@0: theColor = colors[1]; michael@0: break; michael@0: case NS_SIDE_TOP: michael@0: theColor = colors[0]; michael@0: break; michael@0: case NS_SIDE_LEFT: michael@0: default: michael@0: theColor = colors[0]; michael@0: break; michael@0: } michael@0: return theColor; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Thebes Border Rendering Code Start michael@0: michael@0: /* michael@0: * Compute the float-pixel radii that should be used for drawing michael@0: * this border/outline, given the various input bits. michael@0: */ michael@0: /* static */ void michael@0: nsCSSRendering::ComputePixelRadii(const nscoord *aAppUnitsRadii, michael@0: nscoord aAppUnitsPerPixel, michael@0: gfxCornerSizes *oBorderRadii) michael@0: { michael@0: gfxFloat radii[8]; michael@0: NS_FOR_CSS_HALF_CORNERS(corner) michael@0: radii[corner] = gfxFloat(aAppUnitsRadii[corner]) / aAppUnitsPerPixel; michael@0: michael@0: (*oBorderRadii)[C_TL] = gfxSize(radii[NS_CORNER_TOP_LEFT_X], michael@0: radii[NS_CORNER_TOP_LEFT_Y]); michael@0: (*oBorderRadii)[C_TR] = gfxSize(radii[NS_CORNER_TOP_RIGHT_X], michael@0: radii[NS_CORNER_TOP_RIGHT_Y]); michael@0: (*oBorderRadii)[C_BR] = gfxSize(radii[NS_CORNER_BOTTOM_RIGHT_X], michael@0: radii[NS_CORNER_BOTTOM_RIGHT_Y]); michael@0: (*oBorderRadii)[C_BL] = gfxSize(radii[NS_CORNER_BOTTOM_LEFT_X], michael@0: radii[NS_CORNER_BOTTOM_LEFT_Y]); michael@0: } michael@0: michael@0: void michael@0: nsCSSRendering::PaintBorder(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsIFrame* aForFrame, michael@0: const nsRect& aDirtyRect, michael@0: const nsRect& aBorderArea, michael@0: nsStyleContext* aStyleContext, michael@0: int aSkipSides) michael@0: { michael@0: PROFILER_LABEL("nsCSSRendering", "PaintBorder"); michael@0: nsStyleContext *styleIfVisited = aStyleContext->GetStyleIfVisited(); michael@0: const nsStyleBorder *styleBorder = aStyleContext->StyleBorder(); michael@0: // Don't check RelevantLinkVisited here, since we want to take the michael@0: // same amount of time whether or not it's true. michael@0: if (!styleIfVisited) { michael@0: PaintBorderWithStyleBorder(aPresContext, aRenderingContext, aForFrame, michael@0: aDirtyRect, aBorderArea, *styleBorder, michael@0: aStyleContext, aSkipSides); michael@0: return; michael@0: } michael@0: michael@0: nsStyleBorder newStyleBorder(*styleBorder); michael@0: // We could do something fancy to avoid the TrackImage/UntrackImage michael@0: // work, but it doesn't seem worth it. (We need to call TrackImage michael@0: // since we're not going through nsRuleNode::ComputeBorderData.) michael@0: newStyleBorder.TrackImage(aPresContext); michael@0: michael@0: NS_FOR_CSS_SIDES(side) { michael@0: newStyleBorder.SetBorderColor(side, michael@0: aStyleContext->GetVisitedDependentColor( michael@0: nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color)[side])); michael@0: } michael@0: PaintBorderWithStyleBorder(aPresContext, aRenderingContext, aForFrame, michael@0: aDirtyRect, aBorderArea, newStyleBorder, michael@0: aStyleContext, aSkipSides); michael@0: michael@0: // We could do something fancy to avoid the TrackImage/UntrackImage michael@0: // work, but it doesn't seem worth it. (We need to call UntrackImage michael@0: // since we're not going through nsStyleBorder::Destroy.) michael@0: newStyleBorder.UntrackImage(aPresContext); michael@0: } michael@0: michael@0: void michael@0: nsCSSRendering::PaintBorderWithStyleBorder(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsIFrame* aForFrame, michael@0: const nsRect& aDirtyRect, michael@0: const nsRect& aBorderArea, michael@0: const nsStyleBorder& aStyleBorder, michael@0: nsStyleContext* aStyleContext, michael@0: int aSkipSides) michael@0: { michael@0: nsMargin border; michael@0: nscoord twipsRadii[8]; michael@0: nsCompatibility compatMode = aPresContext->CompatibilityMode(); michael@0: michael@0: SN("++ PaintBorder"); michael@0: michael@0: // Check to see if we have an appearance defined. If so, we let the theme michael@0: // renderer draw the border. DO not get the data from aForFrame, since the passed in style context michael@0: // may be different! Always use |aStyleContext|! michael@0: const nsStyleDisplay* displayData = aStyleContext->StyleDisplay(); michael@0: if (displayData->mAppearance) { michael@0: nsITheme *theme = aPresContext->GetTheme(); michael@0: if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame, displayData->mAppearance)) michael@0: return; // Let the theme handle it. michael@0: } michael@0: michael@0: if (aStyleBorder.IsBorderImageLoaded()) { michael@0: DrawBorderImage(aPresContext, aRenderingContext, aForFrame, michael@0: aBorderArea, aStyleBorder, aDirtyRect); michael@0: return; michael@0: } michael@0: michael@0: // Get our style context's color struct. michael@0: const nsStyleColor* ourColor = aStyleContext->StyleColor(); michael@0: michael@0: // in NavQuirks mode we want to use the parent's context as a starting point michael@0: // for determining the background color michael@0: nsIFrame* bgFrame = nsCSSRendering::FindNonTransparentBackgroundFrame michael@0: (aForFrame, compatMode == eCompatibility_NavQuirks ? true : false); michael@0: nsStyleContext* bgContext = bgFrame->StyleContext(); michael@0: nscolor bgColor = michael@0: bgContext->GetVisitedDependentColor(eCSSProperty_background_color); michael@0: michael@0: border = aStyleBorder.GetComputedBorder(); michael@0: if ((0 == border.left) && (0 == border.right) && michael@0: (0 == border.top) && (0 == border.bottom)) { michael@0: // Empty border area michael@0: return; michael@0: } michael@0: michael@0: nsSize frameSize = aForFrame->GetSize(); michael@0: if (&aStyleBorder == aForFrame->StyleBorder() && michael@0: frameSize == aBorderArea.Size()) { michael@0: aForFrame->GetBorderRadii(twipsRadii); michael@0: } else { michael@0: nsIFrame::ComputeBorderRadii(aStyleBorder.mBorderRadius, frameSize, michael@0: aBorderArea.Size(), aSkipSides, twipsRadii); michael@0: } michael@0: michael@0: // Turn off rendering for all of the zero sized sides michael@0: if (aSkipSides & SIDE_BIT_TOP) border.top = 0; michael@0: if (aSkipSides & SIDE_BIT_RIGHT) border.right = 0; michael@0: if (aSkipSides & SIDE_BIT_BOTTOM) border.bottom = 0; michael@0: if (aSkipSides & SIDE_BIT_LEFT) border.left = 0; michael@0: michael@0: // get the inside and outside parts of the border michael@0: nsRect outerRect(aBorderArea); michael@0: michael@0: SF(" outerRect: %d %d %d %d\n", outerRect.x, outerRect.y, outerRect.width, outerRect.height); michael@0: michael@0: // we can assume that we're already clipped to aDirtyRect -- I think? (!?) michael@0: michael@0: // Get our conversion values michael@0: nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1); michael@0: michael@0: // convert outer and inner rects michael@0: gfxRect oRect(nsLayoutUtils::RectToGfxRect(outerRect, twipsPerPixel)); michael@0: michael@0: // convert the border widths michael@0: gfxFloat borderWidths[4] = { gfxFloat(border.top / twipsPerPixel), michael@0: gfxFloat(border.right / twipsPerPixel), michael@0: gfxFloat(border.bottom / twipsPerPixel), michael@0: gfxFloat(border.left / twipsPerPixel) }; michael@0: michael@0: // convert the radii michael@0: gfxCornerSizes borderRadii; michael@0: ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii); michael@0: michael@0: uint8_t borderStyles[4]; michael@0: nscolor borderColors[4]; michael@0: nsBorderColors *compositeColors[4]; michael@0: michael@0: // pull out styles, colors, composite colors michael@0: NS_FOR_CSS_SIDES (i) { michael@0: bool foreground; michael@0: borderStyles[i] = aStyleBorder.GetBorderStyle(i); michael@0: aStyleBorder.GetBorderColor(i, borderColors[i], foreground); michael@0: aStyleBorder.GetCompositeColors(i, &compositeColors[i]); michael@0: michael@0: if (foreground) michael@0: borderColors[i] = ourColor->mColor; michael@0: } michael@0: michael@0: SF(" borderStyles: %d %d %d %d\n", borderStyles[0], borderStyles[1], borderStyles[2], borderStyles[3]); michael@0: michael@0: // start drawing michael@0: gfxContext *ctx = aRenderingContext.ThebesContext(); michael@0: michael@0: ctx->Save(); michael@0: michael@0: #if 0 michael@0: // this will draw a transparent red backround underneath the oRect area michael@0: ctx->Save(); michael@0: ctx->Rectangle(oRect); michael@0: ctx->SetColor(gfxRGBA(1.0, 0.0, 0.0, 0.5)); michael@0: ctx->Fill(); michael@0: ctx->Restore(); michael@0: #endif michael@0: michael@0: //SF ("borderRadii: %f %f %f %f\n", borderRadii[0], borderRadii[1], borderRadii[2], borderRadii[3]); michael@0: michael@0: nsCSSBorderRenderer br(twipsPerPixel, michael@0: ctx, michael@0: oRect, michael@0: borderStyles, michael@0: borderWidths, michael@0: borderRadii, michael@0: borderColors, michael@0: compositeColors, michael@0: aSkipSides, michael@0: bgColor); michael@0: br.DrawBorders(); michael@0: michael@0: ctx->Restore(); michael@0: michael@0: SN(); michael@0: } michael@0: michael@0: static nsRect michael@0: GetOutlineInnerRect(nsIFrame* aFrame) michael@0: { michael@0: nsRect* savedOutlineInnerRect = static_cast michael@0: (aFrame->Properties().Get(nsIFrame::OutlineInnerRectProperty())); michael@0: if (savedOutlineInnerRect) michael@0: return *savedOutlineInnerRect; michael@0: NS_NOTREACHED("we should have saved a frame property"); michael@0: return nsRect(nsPoint(0, 0), aFrame->GetSize()); michael@0: } michael@0: michael@0: void michael@0: nsCSSRendering::PaintOutline(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsIFrame* aForFrame, michael@0: const nsRect& aDirtyRect, michael@0: const nsRect& aBorderArea, michael@0: nsStyleContext* aStyleContext) michael@0: { michael@0: nscoord twipsRadii[8]; michael@0: michael@0: // Get our style context's color struct. michael@0: const nsStyleOutline* ourOutline = aStyleContext->StyleOutline(); michael@0: michael@0: nscoord width; michael@0: ourOutline->GetOutlineWidth(width); michael@0: michael@0: if (width == 0) { michael@0: // Empty outline michael@0: return; michael@0: } michael@0: michael@0: nsIFrame* bgFrame = nsCSSRendering::FindNonTransparentBackgroundFrame michael@0: (aForFrame, false); michael@0: nsStyleContext* bgContext = bgFrame->StyleContext(); michael@0: nscolor bgColor = michael@0: bgContext->GetVisitedDependentColor(eCSSProperty_background_color); michael@0: michael@0: nsRect innerRect; michael@0: if ( michael@0: #ifdef MOZ_XUL michael@0: aStyleContext->GetPseudoType() == nsCSSPseudoElements::ePseudo_XULTree michael@0: #else michael@0: false michael@0: #endif michael@0: ) { michael@0: innerRect = aBorderArea; michael@0: } else { michael@0: innerRect = GetOutlineInnerRect(aForFrame) + aBorderArea.TopLeft(); michael@0: } michael@0: nscoord offset = ourOutline->mOutlineOffset; michael@0: innerRect.Inflate(offset, offset); michael@0: // If the dirty rect is completely inside the border area (e.g., only the michael@0: // content is being painted), then we can skip out now michael@0: // XXX this isn't exactly true for rounded borders, where the inside curves may michael@0: // encroach into the content area. A safer calculation would be to michael@0: // shorten insideRect by the radius one each side before performing this test. michael@0: if (innerRect.Contains(aDirtyRect)) michael@0: return; michael@0: michael@0: nsRect outerRect = innerRect; michael@0: outerRect.Inflate(width, width); michael@0: michael@0: // get the radius for our outline michael@0: nsIFrame::ComputeBorderRadii(ourOutline->mOutlineRadius, aBorderArea.Size(), michael@0: outerRect.Size(), 0, twipsRadii); michael@0: michael@0: // Get our conversion values michael@0: nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1); michael@0: michael@0: // get the outer rectangles michael@0: gfxRect oRect(nsLayoutUtils::RectToGfxRect(outerRect, twipsPerPixel)); michael@0: michael@0: // convert the radii michael@0: nsMargin outlineMargin(width, width, width, width); michael@0: gfxCornerSizes outlineRadii; michael@0: ComputePixelRadii(twipsRadii, twipsPerPixel, &outlineRadii); michael@0: michael@0: uint8_t outlineStyle = ourOutline->GetOutlineStyle(); michael@0: uint8_t outlineStyles[4] = { outlineStyle, michael@0: outlineStyle, michael@0: outlineStyle, michael@0: outlineStyle }; michael@0: michael@0: // This handles treating the initial color as 'currentColor'; if we michael@0: // ever want 'invert' back we'll need to do a bit of work here too. michael@0: nscolor outlineColor = michael@0: aStyleContext->GetVisitedDependentColor(eCSSProperty_outline_color); michael@0: nscolor outlineColors[4] = { outlineColor, michael@0: outlineColor, michael@0: outlineColor, michael@0: outlineColor }; michael@0: michael@0: // convert the border widths michael@0: gfxFloat outlineWidths[4] = { gfxFloat(width / twipsPerPixel), michael@0: gfxFloat(width / twipsPerPixel), michael@0: gfxFloat(width / twipsPerPixel), michael@0: gfxFloat(width / twipsPerPixel) }; michael@0: michael@0: // start drawing michael@0: gfxContext *ctx = aRenderingContext.ThebesContext(); michael@0: michael@0: ctx->Save(); michael@0: michael@0: nsCSSBorderRenderer br(twipsPerPixel, michael@0: ctx, michael@0: oRect, michael@0: outlineStyles, michael@0: outlineWidths, michael@0: outlineRadii, michael@0: outlineColors, michael@0: nullptr, 0, michael@0: bgColor); michael@0: br.DrawBorders(); michael@0: michael@0: ctx->Restore(); michael@0: michael@0: SN(); michael@0: } michael@0: michael@0: void michael@0: nsCSSRendering::PaintFocus(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: const nsRect& aFocusRect, michael@0: nscolor aColor) michael@0: { michael@0: nscoord oneCSSPixel = nsPresContext::CSSPixelsToAppUnits(1); michael@0: nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1); michael@0: michael@0: gfxRect focusRect(nsLayoutUtils::RectToGfxRect(aFocusRect, oneDevPixel)); michael@0: michael@0: gfxCornerSizes focusRadii; michael@0: { michael@0: nscoord twipsRadii[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; michael@0: ComputePixelRadii(twipsRadii, oneDevPixel, &focusRadii); michael@0: } michael@0: gfxFloat focusWidths[4] = { gfxFloat(oneCSSPixel / oneDevPixel), michael@0: gfxFloat(oneCSSPixel / oneDevPixel), michael@0: gfxFloat(oneCSSPixel / oneDevPixel), michael@0: gfxFloat(oneCSSPixel / oneDevPixel) }; michael@0: michael@0: uint8_t focusStyles[4] = { NS_STYLE_BORDER_STYLE_DOTTED, michael@0: NS_STYLE_BORDER_STYLE_DOTTED, michael@0: NS_STYLE_BORDER_STYLE_DOTTED, michael@0: NS_STYLE_BORDER_STYLE_DOTTED }; michael@0: nscolor focusColors[4] = { aColor, aColor, aColor, aColor }; michael@0: michael@0: gfxContext *ctx = aRenderingContext.ThebesContext(); michael@0: michael@0: ctx->Save(); michael@0: michael@0: // Because this renders a dotted border, the background color michael@0: // should not be used. Therefore, we provide a value that will michael@0: // be blatantly wrong if it ever does get used. (If this becomes michael@0: // something that CSS can style, this function will then have access michael@0: // to a style context and can use the same logic that PaintBorder michael@0: // and PaintOutline do.) michael@0: nsCSSBorderRenderer br(oneDevPixel, michael@0: ctx, michael@0: focusRect, michael@0: focusStyles, michael@0: focusWidths, michael@0: focusRadii, michael@0: focusColors, michael@0: nullptr, 0, michael@0: NS_RGB(255, 0, 0)); michael@0: br.DrawBorders(); michael@0: michael@0: ctx->Restore(); michael@0: michael@0: SN(); michael@0: } michael@0: michael@0: // Thebes Border Rendering Code End michael@0: //---------------------------------------------------------------------- michael@0: michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: /** michael@0: * Computes the placement of a background image. michael@0: * michael@0: * @param aOriginBounds is the box to which the tiling position should be michael@0: * relative michael@0: * This should correspond to 'background-origin' for the frame, michael@0: * except when painting on the canvas, in which case the origin bounds michael@0: * should be the bounds of the root element's frame. michael@0: * @param aTopLeft the top-left corner where an image tile should be drawn michael@0: * @param aAnchorPoint a point which should be pixel-aligned by michael@0: * nsLayoutUtils::DrawImage. This is the same as aTopLeft, unless CSS michael@0: * specifies a percentage (including 'right' or 'bottom'), in which case michael@0: * it's that percentage within of aOriginBounds. So 'right' would set michael@0: * aAnchorPoint.x to aOriginBounds.XMost(). michael@0: * michael@0: * Points are returned relative to aOriginBounds. michael@0: */ michael@0: static void michael@0: ComputeBackgroundAnchorPoint(const nsStyleBackground::Layer& aLayer, michael@0: const nsSize& aOriginBounds, michael@0: const nsSize& aImageSize, michael@0: nsPoint* aTopLeft, michael@0: nsPoint* aAnchorPoint) michael@0: { michael@0: double percentX = aLayer.mPosition.mXPosition.mPercent; michael@0: nscoord lengthX = aLayer.mPosition.mXPosition.mLength; michael@0: aAnchorPoint->x = lengthX + NSToCoordRound(percentX*aOriginBounds.width); michael@0: aTopLeft->x = lengthX + michael@0: NSToCoordRound(percentX*(aOriginBounds.width - aImageSize.width)); michael@0: michael@0: double percentY = aLayer.mPosition.mYPosition.mPercent; michael@0: nscoord lengthY = aLayer.mPosition.mYPosition.mLength; michael@0: aAnchorPoint->y = lengthY + NSToCoordRound(percentY*aOriginBounds.height); michael@0: aTopLeft->y = lengthY + michael@0: NSToCoordRound(percentY*(aOriginBounds.height - aImageSize.height)); michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsCSSRendering::FindNonTransparentBackgroundFrame(nsIFrame* aFrame, michael@0: bool aStartAtParent /*= false*/) michael@0: { michael@0: NS_ASSERTION(aFrame, "Cannot find NonTransparentBackgroundFrame in a null frame"); michael@0: michael@0: nsIFrame* frame = nullptr; michael@0: if (aStartAtParent) { michael@0: frame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame); michael@0: } michael@0: if (!frame) { michael@0: frame = aFrame; michael@0: } michael@0: michael@0: while (frame) { michael@0: // No need to call GetVisitedDependentColor because it always uses michael@0: // this alpha component anyway. michael@0: if (NS_GET_A(frame->StyleBackground()->mBackgroundColor) > 0) michael@0: break; michael@0: michael@0: if (frame->IsThemed()) michael@0: break; michael@0: michael@0: nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(frame); michael@0: if (!parent) michael@0: break; michael@0: michael@0: frame = parent; michael@0: } michael@0: return frame; michael@0: } michael@0: michael@0: // Returns true if aFrame is a canvas frame. michael@0: // We need to treat the viewport as canvas because, even though michael@0: // it does not actually paint a background, we need to get the right michael@0: // background style so we correctly detect transparent documents. michael@0: bool michael@0: nsCSSRendering::IsCanvasFrame(nsIFrame* aFrame) michael@0: { michael@0: nsIAtom* frameType = aFrame->GetType(); michael@0: return frameType == nsGkAtoms::canvasFrame || michael@0: frameType == nsGkAtoms::rootFrame || michael@0: frameType == nsGkAtoms::pageContentFrame || michael@0: frameType == nsGkAtoms::viewportFrame; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame) michael@0: { michael@0: const nsStyleBackground* result = aForFrame->StyleBackground(); michael@0: michael@0: // Check if we need to do propagation from BODY rather than HTML. michael@0: if (!result->IsTransparent()) { michael@0: return aForFrame; michael@0: } michael@0: michael@0: nsIContent* content = aForFrame->GetContent(); michael@0: // The root element content can't be null. We wouldn't know what michael@0: // frame to create for aFrame. michael@0: // Use |OwnerDoc| so it works during destruction. michael@0: if (!content) { michael@0: return aForFrame; michael@0: } michael@0: michael@0: nsIDocument* document = content->OwnerDoc(); michael@0: michael@0: dom::Element* bodyContent = document->GetBodyElement(); michael@0: // We need to null check the body node (bug 118829) since michael@0: // there are cases, thanks to the fix for bug 5569, where we michael@0: // will reflow a document with no body. In particular, if a michael@0: // SCRIPT element in the head blocks the parser and then has a michael@0: // SCRIPT that does "document.location.href = 'foo'", then michael@0: // nsParser::Terminate will call |DidBuildModel| methods michael@0: // through to the content sink, which will call |StartLayout| michael@0: // and thus |Initialize| on the pres shell. See bug 119351 michael@0: // for the ugly details. michael@0: if (!bodyContent) { michael@0: return aForFrame; michael@0: } michael@0: michael@0: nsIFrame *bodyFrame = bodyContent->GetPrimaryFrame(); michael@0: if (!bodyFrame) { michael@0: return aForFrame; michael@0: } michael@0: michael@0: return nsLayoutUtils::GetStyleFrame(bodyFrame); michael@0: } michael@0: michael@0: /** michael@0: * |FindBackground| finds the correct style data to use to paint the michael@0: * background. It is responsible for handling the following two michael@0: * statements in section 14.2 of CSS2: michael@0: * michael@0: * The background of the box generated by the root element covers the michael@0: * entire canvas. michael@0: * michael@0: * For HTML documents, however, we recommend that authors specify the michael@0: * background for the BODY element rather than the HTML element. User michael@0: * agents should observe the following precedence rules to fill in the michael@0: * background: if the value of the 'background' property for the HTML michael@0: * element is different from 'transparent' then use it, else use the michael@0: * value of the 'background' property for the BODY element. If the michael@0: * resulting value is 'transparent', the rendering is undefined. michael@0: * michael@0: * Thus, in our implementation, it is responsible for ensuring that: michael@0: * + we paint the correct background on the |nsCanvasFrame|, michael@0: * |nsRootBoxFrame|, or |nsPageFrame|, michael@0: * + we don't paint the background on the root element, and michael@0: * + we don't paint the background on the BODY element in *some* cases, michael@0: * and for SGML-based HTML documents only. michael@0: * michael@0: * |FindBackground| returns true if a background should be painted, and michael@0: * the resulting style context to use for the background information michael@0: * will be filled in to |aBackground|. michael@0: */ michael@0: nsStyleContext* michael@0: nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame) michael@0: { michael@0: return FindBackgroundStyleFrame(aForFrame)->StyleContext(); michael@0: } michael@0: michael@0: inline bool michael@0: FindElementBackground(nsIFrame* aForFrame, nsIFrame* aRootElementFrame, michael@0: nsStyleContext** aBackgroundSC) michael@0: { michael@0: if (aForFrame == aRootElementFrame) { michael@0: // We must have propagated our background to the viewport or canvas. Abort. michael@0: return false; michael@0: } michael@0: michael@0: *aBackgroundSC = aForFrame->StyleContext(); michael@0: michael@0: // Return true unless the frame is for a BODY element whose background michael@0: // was propagated to the viewport. michael@0: michael@0: nsIContent* content = aForFrame->GetContent(); michael@0: if (!content || content->Tag() != nsGkAtoms::body) michael@0: return true; // not frame for a "body" element michael@0: // It could be a non-HTML "body" element but that's OK, we'd fail the michael@0: // bodyContent check below michael@0: michael@0: if (aForFrame->StyleContext()->GetPseudo()) michael@0: return true; // A pseudo-element frame. michael@0: michael@0: // We should only look at the background if we're in an HTML document michael@0: nsIDocument* document = content->OwnerDoc(); michael@0: michael@0: dom::Element* bodyContent = document->GetBodyElement(); michael@0: if (bodyContent != content) michael@0: return true; // this wasn't the background that was propagated michael@0: michael@0: // This can be called even when there's no root element yet, during frame michael@0: // construction, via nsLayoutUtils::FrameHasTransparency and michael@0: // nsContainerFrame::SyncFrameViewProperties. michael@0: if (!aRootElementFrame) michael@0: return true; michael@0: michael@0: const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground(); michael@0: return !htmlBG->IsTransparent(); michael@0: } michael@0: michael@0: bool michael@0: nsCSSRendering::FindBackground(nsIFrame* aForFrame, michael@0: nsStyleContext** aBackgroundSC) michael@0: { michael@0: nsIFrame* rootElementFrame = michael@0: aForFrame->PresContext()->PresShell()->FrameConstructor()->GetRootElementStyleFrame(); michael@0: if (IsCanvasFrame(aForFrame)) { michael@0: *aBackgroundSC = FindCanvasBackground(aForFrame, rootElementFrame); michael@0: return true; michael@0: } else { michael@0: return FindElementBackground(aForFrame, rootElementFrame, aBackgroundSC); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCSSRendering::BeginFrameTreesLocked() michael@0: { michael@0: ++gFrameTreeLockCount; michael@0: } michael@0: michael@0: void michael@0: nsCSSRendering::EndFrameTreesLocked() michael@0: { michael@0: NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked"); michael@0: --gFrameTreeLockCount; michael@0: if (gFrameTreeLockCount == 0) { michael@0: gInlineBGData->Reset(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsIFrame* aForFrame, michael@0: const nsRect& aFrameArea, michael@0: const nsRect& aDirtyRect, michael@0: float aOpacity) michael@0: { michael@0: const nsStyleBorder* styleBorder = aForFrame->StyleBorder(); michael@0: nsCSSShadowArray* shadows = styleBorder->mBoxShadow; michael@0: if (!shadows) michael@0: return; michael@0: nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1); michael@0: michael@0: bool hasBorderRadius; michael@0: bool nativeTheme; // mutually exclusive with hasBorderRadius michael@0: gfxCornerSizes borderRadii; michael@0: michael@0: // Get any border radius, since box-shadow must also have rounded corners if the frame does michael@0: const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay(); michael@0: nsITheme::Transparency transparency; michael@0: if (aForFrame->IsThemed(styleDisplay, &transparency)) { michael@0: // We don't respect border-radius for native-themed widgets michael@0: hasBorderRadius = false; michael@0: // For opaque (rectangular) theme widgets we can take the generic michael@0: // border-box path with border-radius disabled. michael@0: nativeTheme = transparency != nsITheme::eOpaque; michael@0: } else { michael@0: nativeTheme = false; michael@0: nscoord twipsRadii[8]; michael@0: NS_ASSERTION(aFrameArea.Size() == aForFrame->VisualBorderRectRelativeToSelf().Size(), michael@0: "unexpected size"); michael@0: hasBorderRadius = aForFrame->GetBorderRadii(twipsRadii); michael@0: if (hasBorderRadius) { michael@0: ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii); michael@0: } michael@0: } michael@0: michael@0: nsRect frameRect = michael@0: nativeTheme ? aForFrame->GetVisualOverflowRectRelativeToSelf() + aFrameArea.TopLeft() : aFrameArea; michael@0: gfxRect frameGfxRect(nsLayoutUtils::RectToGfxRect(frameRect, twipsPerPixel)); michael@0: frameGfxRect.Round(); michael@0: michael@0: // We don't show anything that intersects with the frame we're blurring on. So tell the michael@0: // blurrer not to do unnecessary work there. michael@0: gfxRect skipGfxRect = frameGfxRect; michael@0: bool useSkipGfxRect = true; michael@0: if (nativeTheme) { michael@0: // Optimize non-leaf native-themed frames by skipping computing pixels michael@0: // in the padding-box. We assume the padding-box is going to be painted michael@0: // opaquely for non-leaf frames. michael@0: // XXX this may not be a safe assumption; we should make this go away michael@0: // by optimizing box-shadow drawing more for the cases where we don't have a skip-rect. michael@0: useSkipGfxRect = !aForFrame->IsLeaf(); michael@0: nsRect paddingRect = michael@0: aForFrame->GetPaddingRect() - aForFrame->GetPosition() + aFrameArea.TopLeft(); michael@0: skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, twipsPerPixel); michael@0: } else if (hasBorderRadius) { michael@0: skipGfxRect.Deflate(gfxMargin( michael@0: std::max(borderRadii[C_TL].height, borderRadii[C_TR].height), 0, michael@0: std::max(borderRadii[C_BL].height, borderRadii[C_BR].height), 0)); michael@0: } michael@0: michael@0: for (uint32_t i = shadows->Length(); i > 0; --i) { michael@0: nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1); michael@0: if (shadowItem->mInset) michael@0: continue; michael@0: michael@0: nsRect shadowRect = frameRect; michael@0: shadowRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset); michael@0: if (!nativeTheme) { michael@0: shadowRect.Inflate(shadowItem->mSpread, shadowItem->mSpread); michael@0: } michael@0: michael@0: // shadowRect won't include the blur, so make an extra rect here that includes the blur michael@0: // for use in the even-odd rule below. michael@0: nsRect shadowRectPlusBlur = shadowRect; michael@0: nscoord blurRadius = shadowItem->mRadius; michael@0: shadowRectPlusBlur.Inflate( michael@0: nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, twipsPerPixel)); michael@0: michael@0: gfxRect shadowGfxRectPlusBlur = michael@0: nsLayoutUtils::RectToGfxRect(shadowRectPlusBlur, twipsPerPixel); michael@0: shadowGfxRectPlusBlur.RoundOut(); michael@0: michael@0: // Set the shadow color; if not specified, use the foreground color michael@0: nscolor shadowColor; michael@0: if (shadowItem->mHasColor) michael@0: shadowColor = shadowItem->mColor; michael@0: else michael@0: shadowColor = aForFrame->StyleColor()->mColor; michael@0: michael@0: gfxRGBA gfxShadowColor(shadowColor); michael@0: gfxShadowColor.a *= aOpacity; michael@0: michael@0: gfxContext* renderContext = aRenderingContext.ThebesContext(); michael@0: if (nativeTheme) { michael@0: nsContextBoxBlur blurringArea; michael@0: michael@0: // When getting the widget shape from the native theme, we're going michael@0: // to draw the widget into the shadow surface to create a mask. michael@0: // We need to ensure that there actually *is* a shadow surface michael@0: // and that we're not going to draw directly into renderContext. michael@0: gfxContext* shadowContext = michael@0: blurringArea.Init(shadowRect, shadowItem->mSpread, michael@0: blurRadius, twipsPerPixel, renderContext, aDirtyRect, michael@0: useSkipGfxRect ? &skipGfxRect : nullptr, michael@0: nsContextBoxBlur::FORCE_MASK); michael@0: if (!shadowContext) michael@0: continue; michael@0: michael@0: // shadowContext is owned by either blurringArea or aRenderingContext. michael@0: MOZ_ASSERT(shadowContext == blurringArea.GetContext()); michael@0: michael@0: renderContext->Save(); michael@0: renderContext->SetColor(gfxShadowColor); michael@0: michael@0: // Draw the shape of the frame so it can be blurred. Recall how nsContextBoxBlur michael@0: // doesn't make any temporary surfaces if blur is 0 and it just returns the original michael@0: // surface? If we have no blur, we're painting this fill on the actual content surface michael@0: // (renderContext == shadowContext) which is why we set up the color and clip michael@0: // before doing this. michael@0: michael@0: // We don't clip the border-box from the shadow, nor any other box. michael@0: // We assume that the native theme is going to paint over the shadow. michael@0: michael@0: // Draw the widget shape michael@0: gfxContextMatrixAutoSaveRestore save(shadowContext); michael@0: nsRefPtr wrapperCtx = new nsRenderingContext(); michael@0: wrapperCtx->Init(aPresContext->DeviceContext(), shadowContext); michael@0: wrapperCtx->Translate(nsPoint(shadowItem->mXOffset, michael@0: shadowItem->mYOffset)); michael@0: michael@0: nsRect nativeRect; michael@0: nativeRect.IntersectRect(frameRect, aDirtyRect); michael@0: aPresContext->GetTheme()->DrawWidgetBackground(wrapperCtx, aForFrame, michael@0: styleDisplay->mAppearance, aFrameArea, nativeRect); michael@0: michael@0: blurringArea.DoPaint(); michael@0: renderContext->Restore(); michael@0: } else { michael@0: renderContext->Save(); michael@0: // Clip out the area of the actual frame so the shadow is not shown within michael@0: // the frame michael@0: renderContext->NewPath(); michael@0: renderContext->Rectangle(shadowGfxRectPlusBlur); michael@0: if (hasBorderRadius) { michael@0: renderContext->RoundedRectangle(frameGfxRect, borderRadii); michael@0: } else { michael@0: renderContext->Rectangle(frameGfxRect); michael@0: } michael@0: michael@0: renderContext->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); michael@0: renderContext->Clip(); michael@0: michael@0: gfxCornerSizes clipRectRadii; michael@0: if (hasBorderRadius) { michael@0: gfxFloat spreadDistance = shadowItem->mSpread / twipsPerPixel; michael@0: michael@0: gfxFloat borderSizes[4]; michael@0: michael@0: borderSizes[NS_SIDE_LEFT] = spreadDistance; michael@0: borderSizes[NS_SIDE_TOP] = spreadDistance; michael@0: borderSizes[NS_SIDE_RIGHT] = spreadDistance; michael@0: borderSizes[NS_SIDE_BOTTOM] = spreadDistance; michael@0: michael@0: nsCSSBorderRenderer::ComputeOuterRadii(borderRadii, borderSizes, michael@0: &clipRectRadii); michael@0: michael@0: } michael@0: nsContextBoxBlur::BlurRectangle(renderContext, michael@0: shadowRect, michael@0: twipsPerPixel, michael@0: hasBorderRadius ? &clipRectRadii : nullptr, michael@0: blurRadius, michael@0: gfxShadowColor, michael@0: aDirtyRect, michael@0: skipGfxRect); michael@0: renderContext->Restore(); michael@0: } michael@0: michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsIFrame* aForFrame, michael@0: const nsRect& aFrameArea, michael@0: const nsRect& aDirtyRect) michael@0: { michael@0: const nsStyleBorder* styleBorder = aForFrame->StyleBorder(); michael@0: nsCSSShadowArray* shadows = styleBorder->mBoxShadow; michael@0: if (!shadows) michael@0: return; michael@0: if (aForFrame->IsThemed() && aForFrame->GetContent() && michael@0: !nsContentUtils::IsChromeDoc(aForFrame->GetContent()->GetCurrentDoc())) { michael@0: // There's no way of getting hold of a shape corresponding to a michael@0: // "padding-box" for native-themed widgets, so just don't draw michael@0: // inner box-shadows for them. But we allow chrome to paint inner michael@0: // box shadows since chrome can be aware of the platform theme. michael@0: return; michael@0: } michael@0: michael@0: // Get any border radius, since box-shadow must also have rounded corners michael@0: // if the frame does. michael@0: nscoord twipsRadii[8]; michael@0: NS_ASSERTION(aForFrame->GetType() == nsGkAtoms::fieldSetFrame || michael@0: aFrameArea.Size() == aForFrame->GetSize(), "unexpected size"); michael@0: bool hasBorderRadius = aForFrame->GetBorderRadii(twipsRadii); michael@0: nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1); michael@0: michael@0: nsRect paddingRect = aFrameArea; michael@0: nsMargin border = aForFrame->GetUsedBorder(); michael@0: aForFrame->ApplySkipSides(border); michael@0: paddingRect.Deflate(border); michael@0: michael@0: gfxCornerSizes innerRadii; michael@0: if (hasBorderRadius) { michael@0: gfxCornerSizes borderRadii; michael@0: michael@0: ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii); michael@0: gfxFloat borderSizes[4] = { michael@0: gfxFloat(border.top / twipsPerPixel), michael@0: gfxFloat(border.right / twipsPerPixel), michael@0: gfxFloat(border.bottom / twipsPerPixel), michael@0: gfxFloat(border.left / twipsPerPixel) michael@0: }; michael@0: nsCSSBorderRenderer::ComputeInnerRadii(borderRadii, borderSizes, michael@0: &innerRadii); michael@0: } michael@0: michael@0: for (uint32_t i = shadows->Length(); i > 0; --i) { michael@0: nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1); michael@0: if (!shadowItem->mInset) michael@0: continue; michael@0: michael@0: /* michael@0: * shadowRect: the frame's padding rect michael@0: * shadowPaintRect: the area to paint on the temp surface, larger than shadowRect michael@0: * so that blurs still happen properly near the edges michael@0: * shadowClipRect: the area on the temporary surface within shadowPaintRect michael@0: * that we will NOT paint in michael@0: */ michael@0: nscoord blurRadius = shadowItem->mRadius; michael@0: nsMargin blurMargin = michael@0: nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, twipsPerPixel); michael@0: nsRect shadowPaintRect = paddingRect; michael@0: shadowPaintRect.Inflate(blurMargin); michael@0: michael@0: nsRect shadowClipRect = paddingRect; michael@0: shadowClipRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset); michael@0: shadowClipRect.Deflate(shadowItem->mSpread, shadowItem->mSpread); michael@0: michael@0: gfxCornerSizes clipRectRadii; michael@0: if (hasBorderRadius) { michael@0: // Calculate the radii the inner clipping rect will have michael@0: gfxFloat spreadDistance = shadowItem->mSpread / twipsPerPixel; michael@0: gfxFloat borderSizes[4] = {0, 0, 0, 0}; michael@0: michael@0: // See PaintBoxShadowOuter and bug 514670 michael@0: if (innerRadii[C_TL].width > 0 || innerRadii[C_BL].width > 0) { michael@0: borderSizes[NS_SIDE_LEFT] = spreadDistance; michael@0: } michael@0: michael@0: if (innerRadii[C_TL].height > 0 || innerRadii[C_TR].height > 0) { michael@0: borderSizes[NS_SIDE_TOP] = spreadDistance; michael@0: } michael@0: michael@0: if (innerRadii[C_TR].width > 0 || innerRadii[C_BR].width > 0) { michael@0: borderSizes[NS_SIDE_RIGHT] = spreadDistance; michael@0: } michael@0: michael@0: if (innerRadii[C_BL].height > 0 || innerRadii[C_BR].height > 0) { michael@0: borderSizes[NS_SIDE_BOTTOM] = spreadDistance; michael@0: } michael@0: michael@0: nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes, michael@0: &clipRectRadii); michael@0: } michael@0: michael@0: // Set the "skip rect" to the area within the frame that we don't paint in, michael@0: // including after blurring. michael@0: nsRect skipRect = shadowClipRect; michael@0: skipRect.Deflate(blurMargin); michael@0: gfxRect skipGfxRect = nsLayoutUtils::RectToGfxRect(skipRect, twipsPerPixel); michael@0: if (hasBorderRadius) { michael@0: skipGfxRect.Deflate(gfxMargin( michael@0: std::max(clipRectRadii[C_TL].height, clipRectRadii[C_TR].height), 0, michael@0: std::max(clipRectRadii[C_BL].height, clipRectRadii[C_BR].height), 0)); michael@0: } michael@0: michael@0: // When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area michael@0: // unchanged. And by construction the gfxSkipRect is not touched by the michael@0: // rendered shadow (even after blurring), so those pixels must be completely michael@0: // transparent in the shadow, so drawing them changes nothing. michael@0: gfxContext* renderContext = aRenderingContext.ThebesContext(); michael@0: nsContextBoxBlur blurringArea; michael@0: gfxContext* shadowContext = michael@0: blurringArea.Init(shadowPaintRect, 0, blurRadius, twipsPerPixel, michael@0: renderContext, aDirtyRect, &skipGfxRect); michael@0: if (!shadowContext) michael@0: continue; michael@0: michael@0: // shadowContext is owned by either blurringArea or aRenderingContext. michael@0: MOZ_ASSERT(shadowContext == renderContext || michael@0: shadowContext == blurringArea.GetContext()); michael@0: michael@0: // Set the shadow color; if not specified, use the foreground color michael@0: nscolor shadowColor; michael@0: if (shadowItem->mHasColor) michael@0: shadowColor = shadowItem->mColor; michael@0: else michael@0: shadowColor = aForFrame->StyleColor()->mColor; michael@0: michael@0: renderContext->Save(); michael@0: renderContext->SetColor(gfxRGBA(shadowColor)); michael@0: michael@0: // Clip the context to the area of the frame's padding rect, so no part of the michael@0: // shadow is painted outside. Also cut out anything beyond where the inset shadow michael@0: // will be. michael@0: gfxRect shadowGfxRect = michael@0: nsLayoutUtils::RectToGfxRect(paddingRect, twipsPerPixel); michael@0: shadowGfxRect.Round(); michael@0: renderContext->NewPath(); michael@0: if (hasBorderRadius) michael@0: renderContext->RoundedRectangle(shadowGfxRect, innerRadii, false); michael@0: else michael@0: renderContext->Rectangle(shadowGfxRect); michael@0: renderContext->Clip(); michael@0: michael@0: // Fill the surface minus the area within the frame that we should michael@0: // not paint in, and blur and apply it. michael@0: gfxRect shadowPaintGfxRect = michael@0: nsLayoutUtils::RectToGfxRect(shadowPaintRect, twipsPerPixel); michael@0: shadowPaintGfxRect.RoundOut(); michael@0: gfxRect shadowClipGfxRect = michael@0: nsLayoutUtils::RectToGfxRect(shadowClipRect, twipsPerPixel); michael@0: shadowClipGfxRect.Round(); michael@0: shadowContext->NewPath(); michael@0: shadowContext->Rectangle(shadowPaintGfxRect); michael@0: if (hasBorderRadius) michael@0: shadowContext->RoundedRectangle(shadowClipGfxRect, clipRectRadii, false); michael@0: else michael@0: shadowContext->Rectangle(shadowClipGfxRect); michael@0: shadowContext->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); michael@0: shadowContext->Fill(); michael@0: michael@0: blurringArea.DoPaint(); michael@0: renderContext->Restore(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCSSRendering::PaintBackground(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsIFrame* aForFrame, michael@0: const nsRect& aDirtyRect, michael@0: const nsRect& aBorderArea, michael@0: uint32_t aFlags, michael@0: nsRect* aBGClipRect, michael@0: int32_t aLayer) michael@0: { michael@0: PROFILER_LABEL("nsCSSRendering", "PaintBackground"); michael@0: NS_PRECONDITION(aForFrame, michael@0: "Frame is expected to be provided to PaintBackground"); michael@0: michael@0: nsStyleContext *sc; michael@0: if (!FindBackground(aForFrame, &sc)) { michael@0: // We don't want to bail out if moz-appearance is set on a root michael@0: // node. If it has a parent content node, bail because it's not michael@0: // a root, otherwise keep going in order to let the theme stuff michael@0: // draw the background. The canvas really should be drawing the michael@0: // bg, but there's no way to hook that up via css. michael@0: if (!aForFrame->StyleDisplay()->mAppearance) { michael@0: return; michael@0: } michael@0: michael@0: nsIContent* content = aForFrame->GetContent(); michael@0: if (!content || content->GetParent()) { michael@0: return; michael@0: } michael@0: michael@0: sc = aForFrame->StyleContext(); michael@0: } michael@0: michael@0: PaintBackgroundWithSC(aPresContext, aRenderingContext, aForFrame, michael@0: aDirtyRect, aBorderArea, sc, michael@0: *aForFrame->StyleBorder(), aFlags, michael@0: aBGClipRect, aLayer); michael@0: } michael@0: michael@0: void michael@0: nsCSSRendering::PaintBackgroundColor(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsIFrame* aForFrame, michael@0: const nsRect& aDirtyRect, michael@0: const nsRect& aBorderArea, michael@0: uint32_t aFlags) michael@0: { michael@0: PROFILER_LABEL("nsCSSRendering", "PaintBackgroundColor"); michael@0: NS_PRECONDITION(aForFrame, michael@0: "Frame is expected to be provided to PaintBackground"); michael@0: michael@0: nsStyleContext *sc; michael@0: if (!FindBackground(aForFrame, &sc)) { michael@0: // We don't want to bail out if moz-appearance is set on a root michael@0: // node. If it has a parent content node, bail because it's not michael@0: // a root, other wise keep going in order to let the theme stuff michael@0: // draw the background. The canvas really should be drawing the michael@0: // bg, but there's no way to hook that up via css. michael@0: if (!aForFrame->StyleDisplay()->mAppearance) { michael@0: return; michael@0: } michael@0: michael@0: nsIContent* content = aForFrame->GetContent(); michael@0: if (!content || content->GetParent()) { michael@0: return; michael@0: } michael@0: michael@0: sc = aForFrame->StyleContext(); michael@0: } michael@0: michael@0: PaintBackgroundColorWithSC(aPresContext, aRenderingContext, aForFrame, michael@0: aDirtyRect, aBorderArea, sc, michael@0: *aForFrame->StyleBorder(), aFlags); michael@0: } michael@0: michael@0: static bool michael@0: IsOpaqueBorderEdge(const nsStyleBorder& aBorder, mozilla::css::Side aSide) michael@0: { michael@0: if (aBorder.GetComputedBorder().Side(aSide) == 0) michael@0: return true; michael@0: switch (aBorder.GetBorderStyle(aSide)) { michael@0: case NS_STYLE_BORDER_STYLE_SOLID: michael@0: case NS_STYLE_BORDER_STYLE_GROOVE: michael@0: case NS_STYLE_BORDER_STYLE_RIDGE: michael@0: case NS_STYLE_BORDER_STYLE_INSET: michael@0: case NS_STYLE_BORDER_STYLE_OUTSET: michael@0: break; michael@0: default: michael@0: return false; michael@0: } michael@0: michael@0: // If we're using a border image, assume it's not fully opaque, michael@0: // because we may not even have the image loaded at this point, and michael@0: // even if we did, checking whether the relevant tile is fully michael@0: // opaque would be too much work. michael@0: if (aBorder.mBorderImageSource.GetType() != eStyleImageType_Null) michael@0: return false; michael@0: michael@0: nscolor color; michael@0: bool isForeground; michael@0: aBorder.GetBorderColor(aSide, color, isForeground); michael@0: michael@0: // We don't know the foreground color here, so if it's being used michael@0: // we must assume it might be transparent. michael@0: if (isForeground) michael@0: return false; michael@0: michael@0: return NS_GET_A(color) == 255; michael@0: } michael@0: michael@0: /** michael@0: * Returns true if all border edges are either missing or opaque. michael@0: */ michael@0: static bool michael@0: IsOpaqueBorder(const nsStyleBorder& aBorder) michael@0: { michael@0: if (aBorder.mBorderColors) michael@0: return false; michael@0: NS_FOR_CSS_SIDES(i) { michael@0: if (!IsOpaqueBorderEdge(aBorder, i)) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static inline void michael@0: SetupDirtyRects(const nsRect& aBGClipArea, const nsRect& aCallerDirtyRect, michael@0: nscoord aAppUnitsPerPixel, michael@0: /* OUT: */ michael@0: nsRect* aDirtyRect, gfxRect* aDirtyRectGfx) michael@0: { michael@0: aDirtyRect->IntersectRect(aBGClipArea, aCallerDirtyRect); michael@0: michael@0: // Compute the Thebes equivalent of the dirtyRect. michael@0: *aDirtyRectGfx = nsLayoutUtils::RectToGfxRect(*aDirtyRect, aAppUnitsPerPixel); michael@0: NS_WARN_IF_FALSE(aDirtyRect->IsEmpty() || !aDirtyRectGfx->IsEmpty(), michael@0: "converted dirty rect should not be empty"); michael@0: NS_ABORT_IF_FALSE(!aDirtyRect->IsEmpty() || aDirtyRectGfx->IsEmpty(), michael@0: "second should be empty if first is"); michael@0: } michael@0: michael@0: struct BackgroundClipState { michael@0: nsRect mBGClipArea; // Affected by mClippedRadii michael@0: nsRect mAdditionalBGClipArea; // Not affected by mClippedRadii michael@0: nsRect mDirtyRect; michael@0: gfxRect mDirtyRectGfx; michael@0: michael@0: gfxCornerSizes mClippedRadii; michael@0: bool mRadiiAreOuter; michael@0: bool mHasAdditionalBGClipArea; michael@0: michael@0: // Whether we are being asked to draw with a caller provided background michael@0: // clipping area. If this is true we also disable rounded corners. michael@0: bool mCustomClip; michael@0: }; michael@0: michael@0: static void michael@0: GetBackgroundClip(gfxContext *aCtx, uint8_t aBackgroundClip, michael@0: uint8_t aBackgroundAttachment, michael@0: nsIFrame* aForFrame, const nsRect& aBorderArea, michael@0: const nsRect& aCallerDirtyRect, bool aHaveRoundedCorners, michael@0: const gfxCornerSizes& aBGRadii, nscoord aAppUnitsPerPixel, michael@0: /* out */ BackgroundClipState* aClipState) michael@0: { michael@0: aClipState->mBGClipArea = aBorderArea; michael@0: aClipState->mHasAdditionalBGClipArea = false; michael@0: aClipState->mCustomClip = false; michael@0: aClipState->mRadiiAreOuter = true; michael@0: aClipState->mClippedRadii = aBGRadii; michael@0: if (aForFrame->GetType() == nsGkAtoms::scrollFrame && michael@0: NS_STYLE_BG_ATTACHMENT_LOCAL == aBackgroundAttachment) { michael@0: // As of this writing, this is still in discussion in the CSS Working Group michael@0: // http://lists.w3.org/Archives/Public/www-style/2013Jul/0250.html michael@0: michael@0: // The rectangle for 'background-clip' scrolls with the content, michael@0: // but the background is also clipped at a non-scrolling 'padding-box' michael@0: // like the content. (See below.) michael@0: // Therefore, only 'content-box' makes a difference here. michael@0: if (aBackgroundClip == NS_STYLE_BG_CLIP_CONTENT) { michael@0: nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame); michael@0: // Clip at a rectangle attached to the scrolled content. michael@0: aClipState->mHasAdditionalBGClipArea = true; michael@0: aClipState->mAdditionalBGClipArea = nsRect( michael@0: aClipState->mBGClipArea.TopLeft() michael@0: + scrollableFrame->GetScrolledFrame()->GetPosition() michael@0: // For the dir=rtl case: michael@0: + scrollableFrame->GetScrollRange().TopLeft(), michael@0: scrollableFrame->GetScrolledRect().Size()); michael@0: nsMargin padding = aForFrame->GetUsedPadding(); michael@0: // padding-bottom is ignored on scrollable frames: michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=748518 michael@0: padding.bottom = 0; michael@0: aForFrame->ApplySkipSides(padding); michael@0: aClipState->mAdditionalBGClipArea.Deflate(padding); michael@0: } michael@0: michael@0: // Also clip at a non-scrolling, rounded-corner 'padding-box', michael@0: // same as the scrolled content because of the 'overflow' property. michael@0: aBackgroundClip = NS_STYLE_BG_CLIP_PADDING; michael@0: } michael@0: michael@0: if (aBackgroundClip != NS_STYLE_BG_CLIP_BORDER) { michael@0: nsMargin border = aForFrame->GetUsedBorder(); michael@0: if (aBackgroundClip == NS_STYLE_BG_CLIP_MOZ_ALMOST_PADDING) { michael@0: // Reduce |border| by 1px (device pixels) on all sides, if michael@0: // possible, so that we don't get antialiasing seams between the michael@0: // background and border. michael@0: border.top = std::max(0, border.top - aAppUnitsPerPixel); michael@0: border.right = std::max(0, border.right - aAppUnitsPerPixel); michael@0: border.bottom = std::max(0, border.bottom - aAppUnitsPerPixel); michael@0: border.left = std::max(0, border.left - aAppUnitsPerPixel); michael@0: } else if (aBackgroundClip != NS_STYLE_BG_CLIP_PADDING) { michael@0: NS_ASSERTION(aBackgroundClip == NS_STYLE_BG_CLIP_CONTENT, michael@0: "unexpected background-clip"); michael@0: border += aForFrame->GetUsedPadding(); michael@0: } michael@0: aForFrame->ApplySkipSides(border); michael@0: aClipState->mBGClipArea.Deflate(border); michael@0: michael@0: if (aHaveRoundedCorners) { michael@0: gfxFloat borderSizes[4] = { michael@0: gfxFloat(border.top / aAppUnitsPerPixel), michael@0: gfxFloat(border.right / aAppUnitsPerPixel), michael@0: gfxFloat(border.bottom / aAppUnitsPerPixel), michael@0: gfxFloat(border.left / aAppUnitsPerPixel) michael@0: }; michael@0: nsCSSBorderRenderer::ComputeInnerRadii(aBGRadii, borderSizes, michael@0: &aClipState->mClippedRadii); michael@0: aClipState->mRadiiAreOuter = false; michael@0: } michael@0: } michael@0: michael@0: if (!aHaveRoundedCorners && aClipState->mHasAdditionalBGClipArea) { michael@0: // Do the intersection here to account for the fast path(?) below. michael@0: aClipState->mBGClipArea = michael@0: aClipState->mBGClipArea.Intersect(aClipState->mAdditionalBGClipArea); michael@0: aClipState->mHasAdditionalBGClipArea = false; michael@0: } michael@0: michael@0: SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, aAppUnitsPerPixel, michael@0: &aClipState->mDirtyRect, &aClipState->mDirtyRectGfx); michael@0: } michael@0: michael@0: static void michael@0: SetupBackgroundClip(BackgroundClipState& aClipState, gfxContext *aCtx, michael@0: bool aHaveRoundedCorners, nscoord aAppUnitsPerPixel, michael@0: gfxContextAutoSaveRestore* aAutoSR) michael@0: { michael@0: if (aClipState.mDirtyRectGfx.IsEmpty()) { michael@0: // Our caller won't draw anything under this condition, so no need michael@0: // to set more up. michael@0: return; michael@0: } michael@0: michael@0: if (aClipState.mCustomClip) { michael@0: // We don't support custom clips and rounded corners, arguably a bug, but michael@0: // table painting seems to depend on it. michael@0: return; michael@0: } michael@0: michael@0: // If we have rounded corners, clip all subsequent drawing to the michael@0: // rounded rectangle defined by bgArea and bgRadii (we don't know michael@0: // whether the rounded corners intrude on the dirtyRect or not). michael@0: // Do not do this if we have a caller-provided clip rect -- michael@0: // as above with bgArea, arguably a bug, but table painting seems michael@0: // to depend on it. michael@0: michael@0: if (aHaveRoundedCorners || aClipState.mHasAdditionalBGClipArea) { michael@0: aAutoSR->Reset(aCtx); michael@0: } michael@0: michael@0: if (aClipState.mHasAdditionalBGClipArea) { michael@0: gfxRect bgAreaGfx = nsLayoutUtils::RectToGfxRect( michael@0: aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel); michael@0: bgAreaGfx.Round(); michael@0: bgAreaGfx.Condition(); michael@0: aCtx->NewPath(); michael@0: aCtx->Rectangle(bgAreaGfx, true); michael@0: aCtx->Clip(); michael@0: } michael@0: michael@0: if (aHaveRoundedCorners) { michael@0: gfxRect bgAreaGfx = michael@0: nsLayoutUtils::RectToGfxRect(aClipState.mBGClipArea, aAppUnitsPerPixel); michael@0: bgAreaGfx.Round(); michael@0: bgAreaGfx.Condition(); michael@0: michael@0: if (bgAreaGfx.IsEmpty()) { michael@0: // I think it's become possible to hit this since michael@0: // http://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed. michael@0: NS_WARNING("converted background area should not be empty"); michael@0: // Make our caller not do anything. michael@0: aClipState.mDirtyRectGfx.SizeTo(gfxSize(0.0, 0.0)); michael@0: return; michael@0: } michael@0: michael@0: aCtx->NewPath(); michael@0: aCtx->RoundedRectangle(bgAreaGfx, aClipState.mClippedRadii, aClipState.mRadiiAreOuter); michael@0: aCtx->Clip(); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: DrawBackgroundColor(BackgroundClipState& aClipState, gfxContext *aCtx, michael@0: bool aHaveRoundedCorners, nscoord aAppUnitsPerPixel) michael@0: { michael@0: if (aClipState.mDirtyRectGfx.IsEmpty()) { michael@0: // Our caller won't draw anything under this condition, so no need michael@0: // to set more up. michael@0: return; michael@0: } michael@0: michael@0: // We don't support custom clips and rounded corners, arguably a bug, but michael@0: // table painting seems to depend on it. michael@0: if (!aHaveRoundedCorners || aClipState.mCustomClip) { michael@0: aCtx->NewPath(); michael@0: aCtx->Rectangle(aClipState.mDirtyRectGfx, true); michael@0: aCtx->Fill(); michael@0: return; michael@0: } michael@0: michael@0: gfxRect bgAreaGfx = michael@0: nsLayoutUtils::RectToGfxRect(aClipState.mBGClipArea, aAppUnitsPerPixel); michael@0: bgAreaGfx.Round(); michael@0: bgAreaGfx.Condition(); michael@0: michael@0: if (bgAreaGfx.IsEmpty()) { michael@0: // I think it's become possible to hit this since michael@0: // http://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed. michael@0: NS_WARNING("converted background area should not be empty"); michael@0: // Make our caller not do anything. michael@0: aClipState.mDirtyRectGfx.SizeTo(gfxSize(0.0, 0.0)); michael@0: return; michael@0: } michael@0: michael@0: aCtx->Save(); michael@0: gfxRect dirty = bgAreaGfx.Intersect(aClipState.mDirtyRectGfx); michael@0: michael@0: aCtx->NewPath(); michael@0: aCtx->Rectangle(dirty, true); michael@0: aCtx->Clip(); michael@0: michael@0: if (aClipState.mHasAdditionalBGClipArea) { michael@0: gfxRect bgAdditionalAreaGfx = nsLayoutUtils::RectToGfxRect( michael@0: aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel); michael@0: bgAdditionalAreaGfx.Round(); michael@0: bgAdditionalAreaGfx.Condition(); michael@0: aCtx->NewPath(); michael@0: aCtx->Rectangle(bgAdditionalAreaGfx, true); michael@0: aCtx->Clip(); michael@0: } michael@0: michael@0: aCtx->NewPath(); michael@0: aCtx->RoundedRectangle(bgAreaGfx, aClipState.mClippedRadii, michael@0: aClipState.mRadiiAreOuter); michael@0: michael@0: aCtx->Fill(); michael@0: aCtx->Restore(); michael@0: } michael@0: michael@0: nscolor michael@0: nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext, michael@0: nsStyleContext* aStyleContext, michael@0: nsIFrame* aFrame, michael@0: bool& aDrawBackgroundImage, michael@0: bool& aDrawBackgroundColor) michael@0: { michael@0: aDrawBackgroundImage = true; michael@0: aDrawBackgroundColor = true; michael@0: michael@0: if (aFrame->HonorPrintBackgroundSettings()) { michael@0: aDrawBackgroundImage = aPresContext->GetBackgroundImageDraw(); michael@0: aDrawBackgroundColor = aPresContext->GetBackgroundColorDraw(); michael@0: } michael@0: michael@0: const nsStyleBackground *bg = aStyleContext->StyleBackground(); michael@0: nscolor bgColor; michael@0: if (aDrawBackgroundColor) { michael@0: bgColor = michael@0: aStyleContext->GetVisitedDependentColor(eCSSProperty_background_color); michael@0: if (NS_GET_A(bgColor) == 0) { michael@0: aDrawBackgroundColor = false; michael@0: } michael@0: } else { michael@0: // If GetBackgroundColorDraw() is false, we are still expected to michael@0: // draw color in the background of any frame that's not completely michael@0: // transparent, but we are expected to use white instead of whatever michael@0: // color was specified. michael@0: bgColor = NS_RGB(255, 255, 255); michael@0: if (aDrawBackgroundImage || !bg->IsTransparent()) { michael@0: aDrawBackgroundColor = true; michael@0: } else { michael@0: bgColor = NS_RGBA(0,0,0,0); michael@0: } michael@0: } michael@0: michael@0: // We can skip painting the background color if a background image is opaque. michael@0: if (aDrawBackgroundColor && michael@0: bg->BottomLayer().mRepeat.mXRepeat == NS_STYLE_BG_REPEAT_REPEAT && michael@0: bg->BottomLayer().mRepeat.mYRepeat == NS_STYLE_BG_REPEAT_REPEAT && michael@0: bg->BottomLayer().mImage.IsOpaque() && michael@0: bg->BottomLayer().mBlendMode == NS_STYLE_BLEND_NORMAL) { michael@0: aDrawBackgroundColor = false; michael@0: } michael@0: michael@0: return bgColor; michael@0: } michael@0: michael@0: static gfxFloat michael@0: ConvertGradientValueToPixels(const nsStyleCoord& aCoord, michael@0: gfxFloat aFillLength, michael@0: int32_t aAppUnitsPerPixel) michael@0: { michael@0: switch (aCoord.GetUnit()) { michael@0: case eStyleUnit_Percent: michael@0: return aCoord.GetPercentValue() * aFillLength; michael@0: case eStyleUnit_Coord: michael@0: return NSAppUnitsToFloatPixels(aCoord.GetCoordValue(), aAppUnitsPerPixel); michael@0: case eStyleUnit_Calc: { michael@0: const nsStyleCoord::Calc *calc = aCoord.GetCalcValue(); michael@0: return calc->mPercent * aFillLength + michael@0: NSAppUnitsToFloatPixels(calc->mLength, aAppUnitsPerPixel); michael@0: } michael@0: default: michael@0: NS_WARNING("Unexpected coord unit"); michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: // Given a box with size aBoxSize and origin (0,0), and an angle aAngle, michael@0: // and a starting point for the gradient line aStart, find the endpoint of michael@0: // the gradient line --- the intersection of the gradient line with a line michael@0: // perpendicular to aAngle that passes through the farthest corner in the michael@0: // direction aAngle. michael@0: static gfxPoint michael@0: ComputeGradientLineEndFromAngle(const gfxPoint& aStart, michael@0: double aAngle, michael@0: const gfxSize& aBoxSize) michael@0: { michael@0: double dx = cos(-aAngle); michael@0: double dy = sin(-aAngle); michael@0: gfxPoint farthestCorner(dx > 0 ? aBoxSize.width : 0, michael@0: dy > 0 ? aBoxSize.height : 0); michael@0: gfxPoint delta = farthestCorner - aStart; michael@0: double u = delta.x*dy - delta.y*dx; michael@0: return farthestCorner + gfxPoint(-u*dy, u*dx); michael@0: } michael@0: michael@0: // Compute the start and end points of the gradient line for a linear gradient. michael@0: static void michael@0: ComputeLinearGradientLine(nsPresContext* aPresContext, michael@0: nsStyleGradient* aGradient, michael@0: const gfxSize& aBoxSize, michael@0: gfxPoint* aLineStart, michael@0: gfxPoint* aLineEnd) michael@0: { michael@0: if (aGradient->mBgPosX.GetUnit() == eStyleUnit_None) { michael@0: double angle; michael@0: if (aGradient->mAngle.IsAngleValue()) { michael@0: angle = aGradient->mAngle.GetAngleValueInRadians(); michael@0: if (!aGradient->mLegacySyntax) { michael@0: angle = M_PI_2 - angle; michael@0: } michael@0: } else { michael@0: angle = -M_PI_2; // defaults to vertical gradient starting from top michael@0: } michael@0: gfxPoint center(aBoxSize.width/2, aBoxSize.height/2); michael@0: *aLineEnd = ComputeGradientLineEndFromAngle(center, angle, aBoxSize); michael@0: *aLineStart = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineEnd; michael@0: } else if (!aGradient->mLegacySyntax) { michael@0: float xSign = aGradient->mBgPosX.GetPercentValue() * 2 - 1; michael@0: float ySign = 1 - aGradient->mBgPosY.GetPercentValue() * 2; michael@0: double angle = atan2(ySign * aBoxSize.width, xSign * aBoxSize.height); michael@0: gfxPoint center(aBoxSize.width/2, aBoxSize.height/2); michael@0: *aLineEnd = ComputeGradientLineEndFromAngle(center, angle, aBoxSize); michael@0: *aLineStart = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineEnd; michael@0: } else { michael@0: int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel(); michael@0: *aLineStart = gfxPoint( michael@0: ConvertGradientValueToPixels(aGradient->mBgPosX, aBoxSize.width, michael@0: appUnitsPerPixel), michael@0: ConvertGradientValueToPixels(aGradient->mBgPosY, aBoxSize.height, michael@0: appUnitsPerPixel)); michael@0: if (aGradient->mAngle.IsAngleValue()) { michael@0: MOZ_ASSERT(aGradient->mLegacySyntax); michael@0: double angle = aGradient->mAngle.GetAngleValueInRadians(); michael@0: *aLineEnd = ComputeGradientLineEndFromAngle(*aLineStart, angle, aBoxSize); michael@0: } else { michael@0: // No angle, the line end is just the reflection of the start point michael@0: // through the center of the box michael@0: *aLineEnd = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineStart; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Compute the start and end points of the gradient line for a radial gradient. michael@0: // Also returns the horizontal and vertical radii defining the circle or michael@0: // ellipse to use. michael@0: static void michael@0: ComputeRadialGradientLine(nsPresContext* aPresContext, michael@0: nsStyleGradient* aGradient, michael@0: const gfxSize& aBoxSize, michael@0: gfxPoint* aLineStart, michael@0: gfxPoint* aLineEnd, michael@0: double* aRadiusX, michael@0: double* aRadiusY) michael@0: { michael@0: if (aGradient->mBgPosX.GetUnit() == eStyleUnit_None) { michael@0: // Default line start point is the center of the box michael@0: *aLineStart = gfxPoint(aBoxSize.width/2, aBoxSize.height/2); michael@0: } else { michael@0: int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel(); michael@0: *aLineStart = gfxPoint( michael@0: ConvertGradientValueToPixels(aGradient->mBgPosX, aBoxSize.width, michael@0: appUnitsPerPixel), michael@0: ConvertGradientValueToPixels(aGradient->mBgPosY, aBoxSize.height, michael@0: appUnitsPerPixel)); michael@0: } michael@0: michael@0: // Compute gradient shape: the x and y radii of an ellipse. michael@0: double radiusX, radiusY; michael@0: double leftDistance = Abs(aLineStart->x); michael@0: double rightDistance = Abs(aBoxSize.width - aLineStart->x); michael@0: double topDistance = Abs(aLineStart->y); michael@0: double bottomDistance = Abs(aBoxSize.height - aLineStart->y); michael@0: switch (aGradient->mSize) { michael@0: case NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE: michael@0: radiusX = std::min(leftDistance, rightDistance); michael@0: radiusY = std::min(topDistance, bottomDistance); michael@0: if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) { michael@0: radiusX = radiusY = std::min(radiusX, radiusY); michael@0: } michael@0: break; michael@0: case NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER: { michael@0: // Compute x and y distances to nearest corner michael@0: double offsetX = std::min(leftDistance, rightDistance); michael@0: double offsetY = std::min(topDistance, bottomDistance); michael@0: if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) { michael@0: radiusX = radiusY = NS_hypot(offsetX, offsetY); michael@0: } else { michael@0: // maintain aspect ratio michael@0: radiusX = offsetX*M_SQRT2; michael@0: radiusY = offsetY*M_SQRT2; michael@0: } michael@0: break; michael@0: } michael@0: case NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE: michael@0: radiusX = std::max(leftDistance, rightDistance); michael@0: radiusY = std::max(topDistance, bottomDistance); michael@0: if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) { michael@0: radiusX = radiusY = std::max(radiusX, radiusY); michael@0: } michael@0: break; michael@0: case NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER: { michael@0: // Compute x and y distances to nearest corner michael@0: double offsetX = std::max(leftDistance, rightDistance); michael@0: double offsetY = std::max(topDistance, bottomDistance); michael@0: if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) { michael@0: radiusX = radiusY = NS_hypot(offsetX, offsetY); michael@0: } else { michael@0: // maintain aspect ratio michael@0: radiusX = offsetX*M_SQRT2; michael@0: radiusY = offsetY*M_SQRT2; michael@0: } michael@0: break; michael@0: } michael@0: case NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE: { michael@0: int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel(); michael@0: radiusX = ConvertGradientValueToPixels(aGradient->mRadiusX, michael@0: aBoxSize.width, appUnitsPerPixel); michael@0: radiusY = ConvertGradientValueToPixels(aGradient->mRadiusY, michael@0: aBoxSize.height, appUnitsPerPixel); michael@0: break; michael@0: } michael@0: default: michael@0: radiusX = radiusY = 0; michael@0: NS_ABORT_IF_FALSE(false, "unknown radial gradient sizing method"); michael@0: } michael@0: *aRadiusX = radiusX; michael@0: *aRadiusY = radiusY; michael@0: michael@0: double angle; michael@0: if (aGradient->mAngle.IsAngleValue()) { michael@0: angle = aGradient->mAngle.GetAngleValueInRadians(); michael@0: } else { michael@0: // Default angle is 0deg michael@0: angle = 0.0; michael@0: } michael@0: michael@0: // The gradient line end point is where the gradient line intersects michael@0: // the ellipse. michael@0: *aLineEnd = *aLineStart + gfxPoint(radiusX*cos(-angle), radiusY*sin(-angle)); michael@0: } michael@0: michael@0: // Returns aFrac*aC2 + (1 - aFrac)*C1. The interpolation is done michael@0: // in unpremultiplied space, which is what SVG gradients and cairo michael@0: // gradients expect. michael@0: static gfxRGBA michael@0: InterpolateColor(const gfxRGBA& aC1, const gfxRGBA& aC2, double aFrac) michael@0: { michael@0: double other = 1 - aFrac; michael@0: return gfxRGBA(aC2.r*aFrac + aC1.r*other, michael@0: aC2.g*aFrac + aC1.g*other, michael@0: aC2.b*aFrac + aC1.b*other, michael@0: aC2.a*aFrac + aC1.a*other); michael@0: } michael@0: michael@0: static nscoord michael@0: FindTileStart(nscoord aDirtyCoord, nscoord aTilePos, nscoord aTileDim) michael@0: { michael@0: NS_ASSERTION(aTileDim > 0, "Non-positive tile dimension"); michael@0: double multiples = floor(double(aDirtyCoord - aTilePos)/aTileDim); michael@0: return NSToCoordRound(multiples*aTileDim + aTilePos); michael@0: } michael@0: michael@0: void michael@0: nsCSSRendering::PaintGradient(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsStyleGradient* aGradient, michael@0: const nsRect& aDirtyRect, michael@0: const nsRect& aDest, michael@0: const nsRect& aFillArea, michael@0: const CSSIntRect& aSrc, michael@0: const nsSize& aIntrinsicSize) michael@0: { michael@0: PROFILER_LABEL("nsCSSRendering", "PaintGradient"); michael@0: Telemetry::AutoTimer gradientTimer; michael@0: if (aDest.IsEmpty() || aFillArea.IsEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: gfxContext *ctx = aRenderingContext.ThebesContext(); michael@0: nscoord appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel(); michael@0: gfxSize srcSize = gfxSize(gfxFloat(aIntrinsicSize.width)/appUnitsPerDevPixel, michael@0: gfxFloat(aIntrinsicSize.height)/appUnitsPerDevPixel); michael@0: michael@0: bool cellContainsFill = aDest.Contains(aFillArea); michael@0: michael@0: // Compute "gradient line" start and end relative to the intrinsic size of michael@0: // the gradient. michael@0: gfxPoint lineStart, lineEnd; michael@0: double radiusX = 0, radiusY = 0; // for radial gradients only michael@0: if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) { michael@0: ComputeLinearGradientLine(aPresContext, aGradient, srcSize, michael@0: &lineStart, &lineEnd); michael@0: } else { michael@0: ComputeRadialGradientLine(aPresContext, aGradient, srcSize, michael@0: &lineStart, &lineEnd, &radiusX, &radiusY); michael@0: } michael@0: gfxFloat lineLength = NS_hypot(lineEnd.x - lineStart.x, michael@0: lineEnd.y - lineStart.y); michael@0: michael@0: NS_ABORT_IF_FALSE(aGradient->mStops.Length() >= 2, michael@0: "The parser should reject gradients with less than two stops"); michael@0: michael@0: // Build color stop array and compute stop positions michael@0: nsTArray stops; michael@0: // If there is a run of stops before stop i that did not have specified michael@0: // positions, then this is the index of the first stop in that run, otherwise michael@0: // it's -1. michael@0: int32_t firstUnsetPosition = -1; michael@0: for (uint32_t i = 0; i < aGradient->mStops.Length(); ++i) { michael@0: const nsStyleGradientStop& stop = aGradient->mStops[i]; michael@0: double position; michael@0: switch (stop.mLocation.GetUnit()) { michael@0: case eStyleUnit_None: michael@0: if (i == 0) { michael@0: // First stop defaults to position 0.0 michael@0: position = 0.0; michael@0: } else if (i == aGradient->mStops.Length() - 1) { michael@0: // Last stop defaults to position 1.0 michael@0: position = 1.0; michael@0: } else { michael@0: // Other stops with no specified position get their position assigned michael@0: // later by interpolation, see below. michael@0: // Remeber where the run of stops with no specified position starts, michael@0: // if it starts here. michael@0: if (firstUnsetPosition < 0) { michael@0: firstUnsetPosition = i; michael@0: } michael@0: stops.AppendElement(ColorStop(0, stop.mColor)); michael@0: continue; michael@0: } michael@0: break; michael@0: case eStyleUnit_Percent: michael@0: position = stop.mLocation.GetPercentValue(); michael@0: break; michael@0: case eStyleUnit_Coord: michael@0: position = lineLength < 1e-6 ? 0.0 : michael@0: stop.mLocation.GetCoordValue() / appUnitsPerDevPixel / lineLength; michael@0: break; michael@0: case eStyleUnit_Calc: michael@0: nsStyleCoord::Calc *calc; michael@0: calc = stop.mLocation.GetCalcValue(); michael@0: position = calc->mPercent + michael@0: ((lineLength < 1e-6) ? 0.0 : michael@0: (NSAppUnitsToFloatPixels(calc->mLength, appUnitsPerDevPixel) / lineLength)); michael@0: break; michael@0: default: michael@0: NS_ABORT_IF_FALSE(false, "Unknown stop position type"); michael@0: } michael@0: michael@0: if (i > 0) { michael@0: // Prevent decreasing stop positions by advancing this position michael@0: // to the previous stop position, if necessary michael@0: position = std::max(position, stops[i - 1].mPosition); michael@0: } michael@0: stops.AppendElement(ColorStop(position, stop.mColor)); michael@0: if (firstUnsetPosition > 0) { michael@0: // Interpolate positions for all stops that didn't have a specified position michael@0: double p = stops[firstUnsetPosition - 1].mPosition; michael@0: double d = (stops[i].mPosition - p)/(i - firstUnsetPosition + 1); michael@0: for (uint32_t j = firstUnsetPosition; j < i; ++j) { michael@0: p += d; michael@0: stops[j].mPosition = p; michael@0: } michael@0: firstUnsetPosition = -1; michael@0: } michael@0: } michael@0: michael@0: // Eliminate negative-position stops if the gradient is radial. michael@0: double firstStop = stops[0].mPosition; michael@0: if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && firstStop < 0.0) { michael@0: if (aGradient->mRepeating) { michael@0: // Choose an instance of the repeated pattern that gives us all positive michael@0: // stop-offsets. michael@0: double lastStop = stops[stops.Length() - 1].mPosition; michael@0: double stopDelta = lastStop - firstStop; michael@0: // If all the stops are in approximately the same place then logic below michael@0: // will kick in that makes us draw just the last stop color, so don't michael@0: // try to do anything in that case. We certainly need to avoid michael@0: // dividing by zero. michael@0: if (stopDelta >= 1e-6) { michael@0: double instanceCount = ceil(-firstStop/stopDelta); michael@0: // Advance stops by instanceCount multiples of the period of the michael@0: // repeating gradient. michael@0: double offset = instanceCount*stopDelta; michael@0: for (uint32_t i = 0; i < stops.Length(); i++) { michael@0: stops[i].mPosition += offset; michael@0: } michael@0: } michael@0: } else { michael@0: // Move negative-position stops to position 0.0. We may also need michael@0: // to set the color of the stop to the color the gradient should have michael@0: // at the center of the ellipse. michael@0: for (uint32_t i = 0; i < stops.Length(); i++) { michael@0: double pos = stops[i].mPosition; michael@0: if (pos < 0.0) { michael@0: stops[i].mPosition = 0.0; michael@0: // If this is the last stop, we don't need to adjust the color, michael@0: // it will fill the entire area. michael@0: if (i < stops.Length() - 1) { michael@0: double nextPos = stops[i + 1].mPosition; michael@0: // If nextPos is approximately equal to pos, then we don't michael@0: // need to adjust the color of this stop because it's michael@0: // not going to be displayed. michael@0: // If nextPos is negative, we don't need to adjust the color of michael@0: // this stop since it's not going to be displayed because michael@0: // nextPos will also be moved to 0.0. michael@0: if (nextPos >= 0.0 && nextPos - pos >= 1e-6) { michael@0: // Compute how far the new position 0.0 is along the interval michael@0: // between pos and nextPos. michael@0: // XXX Color interpolation (in cairo, too) should use the michael@0: // CSS 'color-interpolation' property! michael@0: double frac = (0.0 - pos)/(nextPos - pos); michael@0: stops[i].mColor = michael@0: InterpolateColor(stops[i].mColor, stops[i + 1].mColor, frac); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: firstStop = stops[0].mPosition; michael@0: NS_ABORT_IF_FALSE(firstStop >= 0.0, "Failed to fix stop offsets"); michael@0: } michael@0: michael@0: if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && !aGradient->mRepeating) { michael@0: // Direct2D can only handle a particular class of radial gradients because michael@0: // of the way the it specifies gradients. Setting firstStop to 0, when we michael@0: // can, will help us stay on the fast path. Currently we don't do this michael@0: // for repeating gradients but we could by adjusting the stop collection michael@0: // to start at 0 michael@0: firstStop = 0; michael@0: } michael@0: michael@0: double lastStop = stops[stops.Length() - 1].mPosition; michael@0: // Cairo gradients must have stop positions in the range [0, 1]. So, michael@0: // stop positions will be normalized below by subtracting firstStop and then michael@0: // multiplying by stopScale. michael@0: double stopScale; michael@0: double stopOrigin = firstStop; michael@0: double stopEnd = lastStop; michael@0: double stopDelta = lastStop - firstStop; michael@0: bool zeroRadius = aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && michael@0: (radiusX < 1e-6 || radiusY < 1e-6); michael@0: if (stopDelta < 1e-6 || lineLength < 1e-6 || zeroRadius) { michael@0: // Stops are all at the same place. Map all stops to 0.0. michael@0: // For repeating radial gradients, or for any radial gradients with michael@0: // a zero radius, we need to fill with the last stop color, so just set michael@0: // both radii to 0. michael@0: if (aGradient->mRepeating || zeroRadius) { michael@0: radiusX = radiusY = 0.0; michael@0: } michael@0: stopDelta = 0.0; michael@0: lastStop = firstStop; michael@0: } michael@0: michael@0: // Don't normalize non-repeating or degenerate gradients below 0..1 michael@0: // This keeps the gradient line as large as the box and doesn't michael@0: // lets us avoiding having to get padding correct for stops michael@0: // at 0 and 1 michael@0: if (!aGradient->mRepeating || stopDelta == 0.0) { michael@0: stopOrigin = std::min(stopOrigin, 0.0); michael@0: stopEnd = std::max(stopEnd, 1.0); michael@0: } michael@0: stopScale = 1.0/(stopEnd - stopOrigin); michael@0: michael@0: // Create the gradient pattern. michael@0: nsRefPtr gradientPattern; michael@0: bool forceRepeatToCoverTiles = false; michael@0: gfxMatrix matrix; michael@0: if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) { michael@0: // Compute the actual gradient line ends we need to pass to cairo after michael@0: // stops have been normalized. michael@0: gfxPoint gradientStart = lineStart + (lineEnd - lineStart)*stopOrigin; michael@0: gfxPoint gradientEnd = lineStart + (lineEnd - lineStart)*stopEnd; michael@0: gfxPoint gradientStopStart = lineStart + (lineEnd - lineStart)*firstStop; michael@0: gfxPoint gradientStopEnd = lineStart + (lineEnd - lineStart)*lastStop; michael@0: michael@0: if (stopDelta == 0.0) { michael@0: // Stops are all at the same place. For repeating gradients, this will michael@0: // just paint the last stop color. We don't need to do anything. michael@0: // For non-repeating gradients, this should render as two colors, one michael@0: // on each "side" of the gradient line segment, which is a point. All michael@0: // our stops will be at 0.0; we just need to set the direction vector michael@0: // correctly. michael@0: gradientEnd = gradientStart + (lineEnd - lineStart); michael@0: gradientStopEnd = gradientStopStart + (lineEnd - lineStart); michael@0: } michael@0: michael@0: gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y, michael@0: gradientEnd.x, gradientEnd.y); michael@0: michael@0: // When the gradient line is parallel to the x axis from the left edge michael@0: // to the right edge of a tile, then we can repeat by just repeating the michael@0: // gradient. michael@0: if (!cellContainsFill && michael@0: ((gradientStopStart.y == gradientStopEnd.y && gradientStopStart.x == 0 && michael@0: gradientStopEnd.x == srcSize.width) || michael@0: (gradientStopStart.x == gradientStopEnd.x && gradientStopStart.y == 0 && michael@0: gradientStopEnd.y == srcSize.height))) { michael@0: forceRepeatToCoverTiles = true; michael@0: } michael@0: } else { michael@0: NS_ASSERTION(firstStop >= 0.0, michael@0: "Negative stops not allowed for radial gradients"); michael@0: michael@0: // To form an ellipse, we'll stretch a circle vertically, if necessary. michael@0: // So our radii are based on radiusX. michael@0: double innerRadius = radiusX*stopOrigin; michael@0: double outerRadius = radiusX*stopEnd; michael@0: if (stopDelta == 0.0) { michael@0: // Stops are all at the same place. See above (except we now have michael@0: // the inside vs. outside of an ellipse). michael@0: outerRadius = innerRadius + 1; michael@0: } michael@0: gradientPattern = new gfxPattern(lineStart.x, lineStart.y, innerRadius, michael@0: lineStart.x, lineStart.y, outerRadius); michael@0: if (radiusX != radiusY) { michael@0: // Stretch the circles into ellipses vertically by setting a transform michael@0: // in the pattern. michael@0: // Recall that this is the transform from user space to pattern space. michael@0: // So to stretch the ellipse by factor of P vertically, we scale michael@0: // user coordinates by 1/P. michael@0: matrix.Translate(lineStart); michael@0: matrix.Scale(1.0, radiusX/radiusY); michael@0: matrix.Translate(-lineStart); michael@0: } michael@0: } michael@0: // Use a pattern transform to take account of source and dest rects michael@0: matrix.Translate(gfxPoint(aPresContext->CSSPixelsToDevPixels(aSrc.x), michael@0: aPresContext->CSSPixelsToDevPixels(aSrc.y))); michael@0: matrix.Scale(gfxFloat(aPresContext->CSSPixelsToAppUnits(aSrc.width))/aDest.width, michael@0: gfxFloat(aPresContext->CSSPixelsToAppUnits(aSrc.height))/aDest.height); michael@0: gradientPattern->SetMatrix(matrix); michael@0: michael@0: if (gradientPattern->CairoStatus()) michael@0: return; michael@0: michael@0: if (stopDelta == 0.0) { michael@0: // Non-repeating gradient with all stops in same place -> just add michael@0: // first stop and last stop, both at position 0. michael@0: // Repeating gradient with all stops in the same place, or radial michael@0: // gradient with radius of 0 -> just paint the last stop color. michael@0: // We use firstStop offset to keep |stops| with same units (will later normalize to 0). michael@0: gfxRGBA firstColor(stops[0].mColor); michael@0: gfxRGBA lastColor(stops.LastElement().mColor); michael@0: stops.Clear(); michael@0: michael@0: if (!aGradient->mRepeating && !zeroRadius) { michael@0: stops.AppendElement(ColorStop(firstStop, firstColor)); michael@0: } michael@0: stops.AppendElement(ColorStop(firstStop, lastColor)); michael@0: } michael@0: michael@0: bool isRepeat = aGradient->mRepeating || forceRepeatToCoverTiles; michael@0: michael@0: // Now set normalized color stops in pattern. michael@0: if (!ctx->IsCairo()) { michael@0: // Offscreen gradient surface cache (not a tile): michael@0: // On some backends (e.g. D2D), the GradientStops object holds an offscreen surface michael@0: // which is a lookup table used to evaluate the gradient. This surface can use michael@0: // much memory (ram and/or GPU ram) and can be expensive to create. So we cache it. michael@0: // The cache key correlates 1:1 with the arguments for CreateGradientStops (also the implied backend type) michael@0: // Note that GradientStop is a simple struct with a stop value (while GradientStops has the surface). michael@0: nsTArray rawStops(stops.Length()); michael@0: rawStops.SetLength(stops.Length()); michael@0: for(uint32_t i = 0; i < stops.Length(); i++) { michael@0: rawStops[i].color = gfx::Color(stops[i].mColor.r, stops[i].mColor.g, stops[i].mColor.b, stops[i].mColor.a); michael@0: rawStops[i].offset = stopScale * (stops[i].mPosition - stopOrigin); michael@0: } michael@0: mozilla::RefPtr gs = michael@0: gfxGradientCache::GetOrCreateGradientStops(ctx->GetDrawTarget(), michael@0: rawStops, michael@0: isRepeat ? gfx::ExtendMode::REPEAT : gfx::ExtendMode::CLAMP); michael@0: gradientPattern->SetColorStops(gs); michael@0: } else { michael@0: for (uint32_t i = 0; i < stops.Length(); i++) { michael@0: double pos = stopScale*(stops[i].mPosition - stopOrigin); michael@0: gradientPattern->AddColorStop(pos, stops[i].mColor); michael@0: } michael@0: // Set repeat mode. Default cairo extend mode is PAD. michael@0: if (isRepeat) { michael@0: gradientPattern->SetExtend(gfxPattern::EXTEND_REPEAT); michael@0: } michael@0: } michael@0: michael@0: // Paint gradient tiles. This isn't terribly efficient, but doing it this michael@0: // way is simple and sure to get pixel-snapping right. We could speed things michael@0: // up by drawing tiles into temporary surfaces and copying those to the michael@0: // destination, but after pixel-snapping tiles may not all be the same size. michael@0: nsRect dirty; michael@0: if (!dirty.IntersectRect(aDirtyRect, aFillArea)) michael@0: return; michael@0: michael@0: gfxRect areaToFill = michael@0: nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel); michael@0: gfxMatrix ctm = ctx->CurrentMatrix(); michael@0: bool isCTMPreservingAxisAlignedRectangles = ctm.PreservesAxisAlignedRectangles(); michael@0: michael@0: // xStart/yStart are the top-left corner of the top-left tile. michael@0: nscoord xStart = FindTileStart(dirty.x, aDest.x, aDest.width); michael@0: nscoord yStart = FindTileStart(dirty.y, aDest.y, aDest.height); michael@0: nscoord xEnd = forceRepeatToCoverTiles ? xStart + aDest.width : dirty.XMost(); michael@0: nscoord yEnd = forceRepeatToCoverTiles ? yStart + aDest.height : dirty.YMost(); michael@0: michael@0: // x and y are the top-left corner of the tile to draw michael@0: for (nscoord y = yStart; y < yEnd; y += aDest.height) { michael@0: for (nscoord x = xStart; x < xEnd; x += aDest.width) { michael@0: // The coordinates of the tile michael@0: gfxRect tileRect = nsLayoutUtils::RectToGfxRect( michael@0: nsRect(x, y, aDest.width, aDest.height), michael@0: appUnitsPerDevPixel); michael@0: // The actual area to fill with this tile is the intersection of this michael@0: // tile with the overall area we're supposed to be filling michael@0: gfxRect fillRect = michael@0: forceRepeatToCoverTiles ? areaToFill : tileRect.Intersect(areaToFill); michael@0: // Try snapping the fill rect. Snap its top-left and bottom-right michael@0: // independently to preserve the orientation. michael@0: gfxPoint snappedFillRectTopLeft = fillRect.TopLeft(); michael@0: gfxPoint snappedFillRectTopRight = fillRect.TopRight(); michael@0: gfxPoint snappedFillRectBottomRight = fillRect.BottomRight(); michael@0: // Snap three points instead of just two to ensure we choose the michael@0: // correct orientation if there's a reflection. michael@0: if (isCTMPreservingAxisAlignedRectangles && michael@0: ctx->UserToDevicePixelSnapped(snappedFillRectTopLeft, true) && michael@0: ctx->UserToDevicePixelSnapped(snappedFillRectBottomRight, true) && michael@0: ctx->UserToDevicePixelSnapped(snappedFillRectTopRight, true)) { michael@0: if (snappedFillRectTopLeft.x == snappedFillRectBottomRight.x || michael@0: snappedFillRectTopLeft.y == snappedFillRectBottomRight.y) { michael@0: // Nothing to draw; avoid scaling by zero and other weirdness that michael@0: // could put the context in an error state. michael@0: continue; michael@0: } michael@0: // Set the context's transform to the transform that maps fillRect to michael@0: // snappedFillRect. The part of the gradient that was going to michael@0: // exactly fill fillRect will fill snappedFillRect instead. michael@0: gfxMatrix transform = gfxUtils::TransformRectToRect(fillRect, michael@0: snappedFillRectTopLeft, snappedFillRectTopRight, michael@0: snappedFillRectBottomRight); michael@0: ctx->SetMatrix(transform); michael@0: } michael@0: ctx->NewPath(); michael@0: ctx->Rectangle(fillRect); michael@0: ctx->Translate(tileRect.TopLeft()); michael@0: ctx->SetPattern(gradientPattern); michael@0: ctx->Fill(); michael@0: ctx->SetMatrix(ctm); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsIFrame* aForFrame, michael@0: const nsRect& aDirtyRect, michael@0: const nsRect& aBorderArea, michael@0: nsStyleContext* aBackgroundSC, michael@0: const nsStyleBorder& aBorder, michael@0: uint32_t aFlags, michael@0: nsRect* aBGClipRect, michael@0: int32_t aLayer) michael@0: { michael@0: NS_PRECONDITION(aForFrame, michael@0: "Frame is expected to be provided to PaintBackground"); michael@0: michael@0: // Check to see if we have an appearance defined. If so, we let the theme michael@0: // renderer draw the background and bail out. michael@0: // XXXzw this ignores aBGClipRect. michael@0: const nsStyleDisplay* displayData = aForFrame->StyleDisplay(); michael@0: if (displayData->mAppearance) { michael@0: nsITheme *theme = aPresContext->GetTheme(); michael@0: if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame, michael@0: displayData->mAppearance)) { michael@0: nsRect drawing(aBorderArea); michael@0: theme->GetWidgetOverflow(aPresContext->DeviceContext(), michael@0: aForFrame, displayData->mAppearance, &drawing); michael@0: drawing.IntersectRect(drawing, aDirtyRect); michael@0: theme->DrawWidgetBackground(&aRenderingContext, aForFrame, michael@0: displayData->mAppearance, aBorderArea, michael@0: drawing); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // For canvas frames (in the CSS sense) we draw the background color using michael@0: // a solid color item that gets added in nsLayoutUtils::PaintFrame, michael@0: // or nsSubDocumentFrame::BuildDisplayList (bug 488242). (The solid michael@0: // color may be moved into nsDisplayCanvasBackground by michael@0: // nsPresShell::AddCanvasBackgroundColorItem, and painted by michael@0: // nsDisplayCanvasBackground directly.) Either way we don't need to michael@0: // paint the background color here. michael@0: bool isCanvasFrame = IsCanvasFrame(aForFrame); michael@0: michael@0: // Determine whether we are drawing background images and/or michael@0: // background colors. michael@0: bool drawBackgroundImage; michael@0: bool drawBackgroundColor; michael@0: michael@0: nscolor bgColor = DetermineBackgroundColor(aPresContext, michael@0: aBackgroundSC, michael@0: aForFrame, michael@0: drawBackgroundImage, michael@0: drawBackgroundColor); michael@0: michael@0: // If we're drawing a specific layer, we don't want to draw the michael@0: // background color. michael@0: const nsStyleBackground *bg = aBackgroundSC->StyleBackground(); michael@0: if (drawBackgroundColor && aLayer >= 0) { michael@0: drawBackgroundColor = false; michael@0: } michael@0: michael@0: // At this point, drawBackgroundImage and drawBackgroundColor are michael@0: // true if and only if we are actually supposed to paint an image or michael@0: // color into aDirtyRect, respectively. michael@0: if (!drawBackgroundImage && !drawBackgroundColor) michael@0: return; michael@0: michael@0: // Compute the outermost boundary of the area that might be painted. michael@0: gfxContext *ctx = aRenderingContext.ThebesContext(); michael@0: nscoord appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel(); michael@0: michael@0: // Same coordinate space as aBorderArea & aBGClipRect michael@0: gfxCornerSizes bgRadii; michael@0: bool haveRoundedCorners; michael@0: { michael@0: nscoord radii[8]; michael@0: nsSize frameSize = aForFrame->GetSize(); michael@0: if (&aBorder == aForFrame->StyleBorder() && michael@0: frameSize == aBorderArea.Size()) { michael@0: haveRoundedCorners = aForFrame->GetBorderRadii(radii); michael@0: } else { michael@0: haveRoundedCorners = nsIFrame::ComputeBorderRadii(aBorder.mBorderRadius, michael@0: frameSize, aBorderArea.Size(), michael@0: aForFrame->GetSkipSides(), radii); michael@0: } michael@0: if (haveRoundedCorners) michael@0: ComputePixelRadii(radii, appUnitsPerPixel, &bgRadii); michael@0: } michael@0: michael@0: // The 'bgClipArea' (used only by the image tiling logic, far below) michael@0: // is the caller-provided aBGClipRect if any, or else the area michael@0: // determined by the value of 'background-clip' in michael@0: // SetupCurrentBackgroundClip. (Arguably it should be the michael@0: // intersection, but that breaks the table painter -- in particular, michael@0: // taking the intersection breaks reftests/bugs/403249-1[ab].) michael@0: BackgroundClipState clipState; michael@0: uint8_t currentBackgroundClip; michael@0: bool isSolidBorder; michael@0: if (aBGClipRect) { michael@0: clipState.mBGClipArea = *aBGClipRect; michael@0: clipState.mCustomClip = true; michael@0: SetupDirtyRects(clipState.mBGClipArea, aDirtyRect, appUnitsPerPixel, michael@0: &clipState.mDirtyRect, &clipState.mDirtyRectGfx); michael@0: } else { michael@0: // The background is rendered over the 'background-clip' area, michael@0: // which is normally equal to the border area but may be reduced michael@0: // to the padding area by CSS. Also, if the border is solid, we michael@0: // don't need to draw outside the padding area. In either case, michael@0: // if the borders are rounded, make sure we use the same inner michael@0: // radii as the border code will. michael@0: // The background-color is drawn based on the bottom michael@0: // background-clip. michael@0: currentBackgroundClip = bg->BottomLayer().mClip; michael@0: isSolidBorder = michael@0: (aFlags & PAINTBG_WILL_PAINT_BORDER) && IsOpaqueBorder(aBorder); michael@0: if (isSolidBorder && currentBackgroundClip == NS_STYLE_BG_CLIP_BORDER) { michael@0: // If we have rounded corners, we need to inflate the background michael@0: // drawing area a bit to avoid seams between the border and michael@0: // background. michael@0: currentBackgroundClip = haveRoundedCorners ? michael@0: NS_STYLE_BG_CLIP_MOZ_ALMOST_PADDING : NS_STYLE_BG_CLIP_PADDING; michael@0: } michael@0: michael@0: GetBackgroundClip(ctx, currentBackgroundClip, bg->BottomLayer().mAttachment, michael@0: aForFrame, aBorderArea, michael@0: aDirtyRect, haveRoundedCorners, bgRadii, appUnitsPerPixel, michael@0: &clipState); michael@0: } michael@0: michael@0: // If we might be using a background color, go ahead and set it now. michael@0: if (drawBackgroundColor && !isCanvasFrame) michael@0: ctx->SetColor(gfxRGBA(bgColor)); michael@0: michael@0: gfxContextAutoSaveRestore autoSR; michael@0: michael@0: // If there is no background image, draw a color. (If there is michael@0: // neither a background image nor a color, we wouldn't have gotten michael@0: // this far.) michael@0: if (!drawBackgroundImage) { michael@0: if (!isCanvasFrame) { michael@0: DrawBackgroundColor(clipState, ctx, haveRoundedCorners, appUnitsPerPixel); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (bg->mImageCount < 1) { michael@0: // Return if there are no background layers, all work from this point michael@0: // onwards happens iteratively on these. michael@0: return; michael@0: } michael@0: michael@0: // Validate the layer range before we start iterating. michael@0: int32_t startLayer = aLayer; michael@0: int32_t nLayers = 1; michael@0: if (startLayer < 0) { michael@0: startLayer = (int32_t)bg->mImageCount - 1; michael@0: nLayers = bg->mImageCount; michael@0: } michael@0: michael@0: // Ensure we get invalidated for loads of the image. We need to do michael@0: // this here because this might be the only code that knows about the michael@0: // association of the style data with the frame. michael@0: if (aBackgroundSC != aForFrame->StyleContext()) { michael@0: NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT_WITH_RANGE(i, bg, startLayer, nLayers) { michael@0: aForFrame->AssociateImage(bg->mLayers[i].mImage, aPresContext); michael@0: } michael@0: } michael@0: michael@0: // The background color is rendered over the entire dirty area, michael@0: // even if the image isn't. michael@0: if (drawBackgroundColor && !isCanvasFrame) { michael@0: DrawBackgroundColor(clipState, ctx, haveRoundedCorners, appUnitsPerPixel); michael@0: } michael@0: michael@0: if (drawBackgroundImage) { michael@0: bool clipSet = false; michael@0: NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT_WITH_RANGE(i, bg, bg->mImageCount - 1, michael@0: nLayers + (bg->mImageCount - michael@0: startLayer - 1)) { michael@0: const nsStyleBackground::Layer &layer = bg->mLayers[i]; michael@0: if (!aBGClipRect) { michael@0: uint8_t newBackgroundClip = layer.mClip; michael@0: if (isSolidBorder && newBackgroundClip == NS_STYLE_BG_CLIP_BORDER) { michael@0: newBackgroundClip = haveRoundedCorners ? michael@0: NS_STYLE_BG_CLIP_MOZ_ALMOST_PADDING : NS_STYLE_BG_CLIP_PADDING; michael@0: } michael@0: if (currentBackgroundClip != newBackgroundClip || !clipSet) { michael@0: currentBackgroundClip = newBackgroundClip; michael@0: // If clipSet is false that means this is the bottom layer and we michael@0: // already called GetBackgroundClip above and it stored its results michael@0: // in clipState. michael@0: if (clipSet) { michael@0: GetBackgroundClip(ctx, currentBackgroundClip, layer.mAttachment, aForFrame, michael@0: aBorderArea, aDirtyRect, haveRoundedCorners, michael@0: bgRadii, appUnitsPerPixel, &clipState); michael@0: } michael@0: SetupBackgroundClip(clipState, ctx, haveRoundedCorners, michael@0: appUnitsPerPixel, &autoSR); michael@0: clipSet = true; michael@0: } michael@0: } michael@0: if ((aLayer < 0 || i == (uint32_t)startLayer) && michael@0: !clipState.mDirtyRectGfx.IsEmpty()) { michael@0: nsBackgroundLayerState state = PrepareBackgroundLayer(aPresContext, aForFrame, michael@0: aFlags, aBorderArea, clipState.mBGClipArea, *bg, layer); michael@0: if (!state.mFillArea.IsEmpty()) { michael@0: if (state.mCompositingOp != gfxContext::OPERATOR_OVER) { michael@0: NS_ASSERTION(ctx->CurrentOperator() == gfxContext::OPERATOR_OVER, michael@0: "It is assumed the initial operator is OPERATOR_OVER, when it is restored later"); michael@0: ctx->SetOperator(state.mCompositingOp); michael@0: } michael@0: state.mImageRenderer.DrawBackground(aPresContext, aRenderingContext, michael@0: state.mDestArea, state.mFillArea, michael@0: state.mAnchor + aBorderArea.TopLeft(), michael@0: clipState.mDirtyRect); michael@0: if (state.mCompositingOp != gfxContext::OPERATOR_OVER) { michael@0: ctx->SetOperator(gfxContext::OPERATOR_OVER); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCSSRendering::PaintBackgroundColorWithSC(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsIFrame* aForFrame, michael@0: const nsRect& aDirtyRect, michael@0: const nsRect& aBorderArea, michael@0: nsStyleContext* aBackgroundSC, michael@0: const nsStyleBorder& aBorder, michael@0: uint32_t aFlags) michael@0: { michael@0: NS_PRECONDITION(aForFrame, michael@0: "Frame is expected to be provided to PaintBackground"); michael@0: michael@0: // Check to see if we have an appearance defined. If so, we let the theme michael@0: // renderer draw the background and bail out. michael@0: const nsStyleDisplay* displayData = aForFrame->StyleDisplay(); michael@0: if (displayData->mAppearance) { michael@0: nsITheme *theme = aPresContext->GetTheme(); michael@0: if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame, michael@0: displayData->mAppearance)) { michael@0: NS_ERROR("Shouldn't be trying to paint a background color if we are themed!"); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(!IsCanvasFrame(aForFrame), "Should not be trying to paint a background color for canvas frames!"); michael@0: michael@0: // Determine whether we are drawing background images and/or michael@0: // background colors. michael@0: bool drawBackgroundImage; michael@0: bool drawBackgroundColor; michael@0: nscolor bgColor = DetermineBackgroundColor(aPresContext, michael@0: aBackgroundSC, michael@0: aForFrame, michael@0: drawBackgroundImage, michael@0: drawBackgroundColor); michael@0: michael@0: NS_ASSERTION(drawBackgroundImage || drawBackgroundColor, michael@0: "Should not be trying to paint a background if we don't have one"); michael@0: if (!drawBackgroundColor) { michael@0: return; michael@0: } michael@0: michael@0: // Compute the outermost boundary of the area that might be painted. michael@0: gfxContext *ctx = aRenderingContext.ThebesContext(); michael@0: nscoord appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel(); michael@0: michael@0: // Same coordinate space as aBorderArea michael@0: gfxCornerSizes bgRadii; michael@0: bool haveRoundedCorners; michael@0: { michael@0: nscoord radii[8]; michael@0: nsSize frameSize = aForFrame->GetSize(); michael@0: if (&aBorder == aForFrame->StyleBorder() && michael@0: frameSize == aBorderArea.Size()) { michael@0: haveRoundedCorners = aForFrame->GetBorderRadii(radii); michael@0: } else { michael@0: haveRoundedCorners = nsIFrame::ComputeBorderRadii(aBorder.mBorderRadius, michael@0: frameSize, aBorderArea.Size(), michael@0: aForFrame->GetSkipSides(), radii); michael@0: } michael@0: if (haveRoundedCorners) michael@0: ComputePixelRadii(radii, appUnitsPerPixel, &bgRadii); michael@0: } michael@0: michael@0: // The background is rendered over the 'background-clip' area, michael@0: // which is normally equal to the border area but may be reduced michael@0: // to the padding area by CSS. Also, if the border is solid, we michael@0: // don't need to draw outside the padding area. In either case, michael@0: // if the borders are rounded, make sure we use the same inner michael@0: // radii as the border code will. michael@0: // The background-color is drawn based on the bottom michael@0: // background-clip. michael@0: const nsStyleBackground *bg = aBackgroundSC->StyleBackground(); michael@0: uint8_t currentBackgroundClip = bg->BottomLayer().mClip; michael@0: bool isSolidBorder = michael@0: (aFlags & PAINTBG_WILL_PAINT_BORDER) && IsOpaqueBorder(aBorder); michael@0: if (isSolidBorder && currentBackgroundClip == NS_STYLE_BG_CLIP_BORDER) { michael@0: // If we have rounded corners, we need to inflate the background michael@0: // drawing area a bit to avoid seams between the border and michael@0: // background. michael@0: currentBackgroundClip = haveRoundedCorners ? michael@0: NS_STYLE_BG_CLIP_MOZ_ALMOST_PADDING : NS_STYLE_BG_CLIP_PADDING; michael@0: } michael@0: michael@0: BackgroundClipState clipState; michael@0: GetBackgroundClip(ctx, currentBackgroundClip, bg->BottomLayer().mAttachment, michael@0: aForFrame, aBorderArea, michael@0: aDirtyRect, haveRoundedCorners, bgRadii, appUnitsPerPixel, michael@0: &clipState); michael@0: michael@0: ctx->SetColor(gfxRGBA(bgColor)); michael@0: michael@0: gfxContextAutoSaveRestore autoSR; michael@0: DrawBackgroundColor(clipState, ctx, haveRoundedCorners, appUnitsPerPixel); michael@0: } michael@0: michael@0: static inline bool michael@0: IsTransformed(nsIFrame* aForFrame, nsIFrame* aTopFrame) michael@0: { michael@0: for (nsIFrame* f = aForFrame; f != aTopFrame; f = f->GetParent()) { michael@0: if (f->IsTransformed()) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: nsRect michael@0: nsCSSRendering::ComputeBackgroundPositioningArea(nsPresContext* aPresContext, michael@0: nsIFrame* aForFrame, michael@0: const nsRect& aBorderArea, michael@0: const nsStyleBackground& aBackground, michael@0: const nsStyleBackground::Layer& aLayer, michael@0: nsIFrame** aAttachedToFrame) michael@0: { michael@0: // Compute background origin area relative to aBorderArea now as we may need michael@0: // it to compute the effective image size for a CSS gradient. michael@0: nsRect bgPositioningArea(0, 0, 0, 0); michael@0: michael@0: nsIAtom* frameType = aForFrame->GetType(); michael@0: nsIFrame* geometryFrame = aForFrame; michael@0: if (frameType == nsGkAtoms::inlineFrame) { michael@0: // XXXjwalden Strictly speaking this is not quite faithful to how michael@0: // background-break is supposed to interact with background-origin values, michael@0: // but it's a non-trivial amount of work to make it fully conformant, and michael@0: // until the specification is more finalized (and assuming background-break michael@0: // even makes the cut) it doesn't make sense to hammer out exact behavior. michael@0: switch (aBackground.mBackgroundInlinePolicy) { michael@0: case NS_STYLE_BG_INLINE_POLICY_EACH_BOX: michael@0: bgPositioningArea = nsRect(nsPoint(0,0), aBorderArea.Size()); michael@0: break; michael@0: case NS_STYLE_BG_INLINE_POLICY_BOUNDING_BOX: michael@0: bgPositioningArea = gInlineBGData->GetBoundingRect(aForFrame); michael@0: break; michael@0: default: michael@0: NS_ERROR("Unknown background-inline-policy value! " michael@0: "Please, teach me what to do."); michael@0: case NS_STYLE_BG_INLINE_POLICY_CONTINUOUS: michael@0: bgPositioningArea = gInlineBGData->GetContinuousRect(aForFrame); michael@0: break; michael@0: } michael@0: } else if (frameType == nsGkAtoms::canvasFrame) { michael@0: geometryFrame = aForFrame->GetFirstPrincipalChild(); michael@0: // geometryFrame might be null if this canvas is a page created michael@0: // as an overflow container (e.g. the in-flow content has already michael@0: // finished and this page only displays the continuations of michael@0: // absolutely positioned content). michael@0: if (geometryFrame) { michael@0: bgPositioningArea = geometryFrame->GetRect(); michael@0: } michael@0: } else if (frameType == nsGkAtoms::scrollFrame && michael@0: NS_STYLE_BG_ATTACHMENT_LOCAL == aLayer.mAttachment) { michael@0: nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame); michael@0: bgPositioningArea = nsRect( michael@0: scrollableFrame->GetScrolledFrame()->GetPosition() michael@0: // For the dir=rtl case: michael@0: + scrollableFrame->GetScrollRange().TopLeft(), michael@0: scrollableFrame->GetScrolledRect().Size()); michael@0: // The ScrolledRect’s size does not include the borders or scrollbars, michael@0: // reverse the handling of background-origin michael@0: // compared to the common case below. michael@0: if (aLayer.mOrigin == NS_STYLE_BG_ORIGIN_BORDER) { michael@0: nsMargin border = geometryFrame->GetUsedBorder(); michael@0: geometryFrame->ApplySkipSides(border); michael@0: bgPositioningArea.Inflate(border); michael@0: bgPositioningArea.Inflate(scrollableFrame->GetActualScrollbarSizes()); michael@0: } else if (aLayer.mOrigin != NS_STYLE_BG_ORIGIN_PADDING) { michael@0: nsMargin padding = geometryFrame->GetUsedPadding(); michael@0: geometryFrame->ApplySkipSides(padding); michael@0: bgPositioningArea.Deflate(padding); michael@0: NS_ASSERTION(aLayer.mOrigin == NS_STYLE_BG_ORIGIN_CONTENT, michael@0: "unknown background-origin value"); michael@0: } michael@0: *aAttachedToFrame = aForFrame; michael@0: return bgPositioningArea; michael@0: } else { michael@0: bgPositioningArea = nsRect(nsPoint(0,0), aBorderArea.Size()); michael@0: } michael@0: michael@0: // Background images are tiled over the 'background-clip' area michael@0: // but the origin of the tiling is based on the 'background-origin' area michael@0: if (aLayer.mOrigin != NS_STYLE_BG_ORIGIN_BORDER && geometryFrame) { michael@0: nsMargin border = geometryFrame->GetUsedBorder(); michael@0: if (aLayer.mOrigin != NS_STYLE_BG_ORIGIN_PADDING) { michael@0: border += geometryFrame->GetUsedPadding(); michael@0: NS_ASSERTION(aLayer.mOrigin == NS_STYLE_BG_ORIGIN_CONTENT, michael@0: "unknown background-origin value"); michael@0: } michael@0: geometryFrame->ApplySkipSides(border); michael@0: bgPositioningArea.Deflate(border); michael@0: } michael@0: michael@0: nsIFrame* attachedToFrame = aForFrame; michael@0: if (NS_STYLE_BG_ATTACHMENT_FIXED == aLayer.mAttachment) { michael@0: // If it's a fixed background attachment, then the image is placed michael@0: // relative to the viewport, which is the area of the root frame michael@0: // in a screen context or the page content frame in a print context. michael@0: attachedToFrame = aPresContext->PresShell()->FrameManager()->GetRootFrame(); michael@0: NS_ASSERTION(attachedToFrame, "no root frame"); michael@0: nsIFrame* pageContentFrame = nullptr; michael@0: if (aPresContext->IsPaginated()) { michael@0: pageContentFrame = michael@0: nsLayoutUtils::GetClosestFrameOfType(aForFrame, nsGkAtoms::pageContentFrame); michael@0: if (pageContentFrame) { michael@0: attachedToFrame = pageContentFrame; michael@0: } michael@0: // else this is an embedded shell and its root frame is what we want michael@0: } michael@0: michael@0: // Set the background positioning area to the viewport's area michael@0: // (relative to aForFrame) michael@0: bgPositioningArea = michael@0: nsRect(-aForFrame->GetOffsetTo(attachedToFrame), attachedToFrame->GetSize()); michael@0: michael@0: if (!pageContentFrame) { michael@0: // Subtract the size of scrollbars. michael@0: nsIScrollableFrame* scrollableFrame = michael@0: aPresContext->PresShell()->GetRootScrollFrameAsScrollable(); michael@0: if (scrollableFrame) { michael@0: nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes(); michael@0: bgPositioningArea.Deflate(scrollbars); michael@0: } michael@0: } michael@0: } michael@0: *aAttachedToFrame = attachedToFrame; michael@0: michael@0: return bgPositioningArea; michael@0: } michael@0: michael@0: // Apply the CSS image sizing algorithm as it applies to background images. michael@0: // See http://www.w3.org/TR/css3-background/#the-background-size . michael@0: // aIntrinsicSize is the size that the background image 'would like to be'. michael@0: // It can be found by calling nsImageRenderer::ComputeIntrinsicSize. michael@0: static nsSize michael@0: ComputeDrawnSizeForBackground(const CSSSizeOrRatio& aIntrinsicSize, michael@0: const nsSize& aBgPositioningArea, michael@0: const nsStyleBackground::Size& aLayerSize) michael@0: { michael@0: // Size is dictated by cover or contain rules. michael@0: if (aLayerSize.mWidthType == nsStyleBackground::Size::eContain || michael@0: aLayerSize.mWidthType == nsStyleBackground::Size::eCover) { michael@0: nsImageRenderer::FitType fitType = michael@0: aLayerSize.mWidthType == nsStyleBackground::Size::eCover michael@0: ? nsImageRenderer::COVER michael@0: : nsImageRenderer::CONTAIN; michael@0: return nsImageRenderer::ComputeConstrainedSize(aBgPositioningArea, michael@0: aIntrinsicSize.mRatio, michael@0: fitType); michael@0: } michael@0: michael@0: // No cover/contain constraint, use default algorithm. michael@0: CSSSizeOrRatio specifiedSize; michael@0: if (aLayerSize.mWidthType == nsStyleBackground::Size::eLengthPercentage) { michael@0: specifiedSize.SetWidth( michael@0: aLayerSize.ResolveWidthLengthPercentage(aBgPositioningArea)); michael@0: } michael@0: if (aLayerSize.mHeightType == nsStyleBackground::Size::eLengthPercentage) { michael@0: specifiedSize.SetHeight( michael@0: aLayerSize.ResolveHeightLengthPercentage(aBgPositioningArea)); michael@0: } michael@0: michael@0: return nsImageRenderer::ComputeConcreteSize(specifiedSize, michael@0: aIntrinsicSize, michael@0: aBgPositioningArea); michael@0: } michael@0: michael@0: nsBackgroundLayerState michael@0: nsCSSRendering::PrepareBackgroundLayer(nsPresContext* aPresContext, michael@0: nsIFrame* aForFrame, michael@0: uint32_t aFlags, michael@0: const nsRect& aBorderArea, michael@0: const nsRect& aBGClipRect, michael@0: const nsStyleBackground& aBackground, michael@0: const nsStyleBackground::Layer& aLayer) michael@0: { michael@0: /* michael@0: * The background properties we need to keep in mind when drawing background michael@0: * layers are: michael@0: * michael@0: * background-image michael@0: * background-repeat michael@0: * background-attachment michael@0: * background-position michael@0: * background-clip michael@0: * background-origin michael@0: * background-size michael@0: * background-break (-moz-background-inline-policy) michael@0: * background-blend-mode michael@0: * michael@0: * (background-color applies to the entire element and not to individual michael@0: * layers, so it is irrelevant to this method.) michael@0: * michael@0: * These properties have the following dependencies upon each other when michael@0: * determining rendering: michael@0: * michael@0: * background-image michael@0: * no dependencies michael@0: * background-repeat michael@0: * no dependencies michael@0: * background-attachment michael@0: * no dependencies michael@0: * background-position michael@0: * depends upon background-size (for the image's scaled size) and michael@0: * background-break (for the background positioning area) michael@0: * background-clip michael@0: * no dependencies michael@0: * background-origin michael@0: * depends upon background-attachment (only in the case where that value michael@0: * is 'fixed') michael@0: * background-size michael@0: * depends upon background-break (for the background positioning area for michael@0: * resolving percentages), background-image (for the image's intrinsic michael@0: * size), background-repeat (if that value is 'round'), and michael@0: * background-origin (for the background painting area, when michael@0: * background-repeat is 'round') michael@0: * background-break michael@0: * depends upon background-origin (specifying how the boxes making up the michael@0: * background positioning area are determined) michael@0: * michael@0: * As a result of only-if dependencies we don't strictly do a topological michael@0: * sort of the above properties when processing, but it's pretty close to one: michael@0: * michael@0: * background-clip (by caller) michael@0: * background-image michael@0: * background-break, background-origin michael@0: * background-attachment (postfix for background-{origin,break} if 'fixed') michael@0: * background-size michael@0: * background-position michael@0: * background-repeat michael@0: */ michael@0: michael@0: uint32_t irFlags = 0; michael@0: if (aFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) { michael@0: irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES; michael@0: } michael@0: if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) { michael@0: irFlags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW; michael@0: } michael@0: michael@0: nsBackgroundLayerState state(aForFrame, &aLayer.mImage, irFlags); michael@0: if (!state.mImageRenderer.PrepareImage()) { michael@0: // There's no image or it's not ready to be painted. michael@0: return state; michael@0: } michael@0: michael@0: // The frame to which the background is attached michael@0: nsIFrame* attachedToFrame = aForFrame; michael@0: // Compute background origin area relative to aBorderArea now as we may need michael@0: // it to compute the effective image size for a CSS gradient. michael@0: nsRect bgPositioningArea = michael@0: ComputeBackgroundPositioningArea(aPresContext, aForFrame, aBorderArea, michael@0: aBackground, aLayer, &attachedToFrame); michael@0: michael@0: // For background-attachment:fixed backgrounds, we'll limit the area michael@0: // where the background can be drawn to the viewport. michael@0: nsRect bgClipRect = aBGClipRect; michael@0: michael@0: // Compute the anchor point. michael@0: // michael@0: // relative to aBorderArea.TopLeft() (which is where the top-left michael@0: // of aForFrame's border-box will be rendered) michael@0: nsPoint imageTopLeft; michael@0: if (NS_STYLE_BG_ATTACHMENT_FIXED == aLayer.mAttachment) { michael@0: if ((aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) && michael@0: !IsTransformed(aForFrame, attachedToFrame)) { michael@0: // Clip background-attachment:fixed backgrounds to the viewport, if we're michael@0: // painting to the screen and not transformed. This avoids triggering michael@0: // tiling in common cases, without affecting output since drawing is michael@0: // always clipped to the viewport when we draw to the screen. (But it's michael@0: // not a pure optimization since it can affect the values of pixels at the michael@0: // edge of the viewport --- whether they're sampled from a putative "next michael@0: // tile" or not.) michael@0: bgClipRect.IntersectRect(bgClipRect, bgPositioningArea + aBorderArea.TopLeft()); michael@0: } michael@0: } michael@0: michael@0: // Scale the image as specified for background-size and as required for michael@0: // proper background positioning when background-position is defined with michael@0: // percentages. michael@0: CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize(); michael@0: nsSize bgPositionSize = bgPositioningArea.Size(); michael@0: nsSize imageSize = ComputeDrawnSizeForBackground(intrinsicSize, michael@0: bgPositionSize, michael@0: aLayer.mSize); michael@0: if (imageSize.width <= 0 || imageSize.height <= 0) michael@0: return state; michael@0: michael@0: state.mImageRenderer.SetPreferredSize(intrinsicSize, michael@0: imageSize); michael@0: michael@0: // Compute the position of the background now that the background's size is michael@0: // determined. michael@0: ComputeBackgroundAnchorPoint(aLayer, bgPositionSize, imageSize, michael@0: &imageTopLeft, &state.mAnchor); michael@0: imageTopLeft += bgPositioningArea.TopLeft(); michael@0: state.mAnchor += bgPositioningArea.TopLeft(); michael@0: michael@0: state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize); michael@0: state.mFillArea = state.mDestArea; michael@0: int repeatX = aLayer.mRepeat.mXRepeat; michael@0: int repeatY = aLayer.mRepeat.mYRepeat; michael@0: if (repeatX == NS_STYLE_BG_REPEAT_REPEAT) { michael@0: state.mFillArea.x = bgClipRect.x; michael@0: state.mFillArea.width = bgClipRect.width; michael@0: } michael@0: if (repeatY == NS_STYLE_BG_REPEAT_REPEAT) { michael@0: state.mFillArea.y = bgClipRect.y; michael@0: state.mFillArea.height = bgClipRect.height; michael@0: } michael@0: state.mFillArea.IntersectRect(state.mFillArea, bgClipRect); michael@0: michael@0: state.mCompositingOp = GetGFXBlendMode(aLayer.mBlendMode); michael@0: michael@0: return state; michael@0: } michael@0: michael@0: nsRect michael@0: nsCSSRendering::GetBackgroundLayerRect(nsPresContext* aPresContext, michael@0: nsIFrame* aForFrame, michael@0: const nsRect& aBorderArea, michael@0: const nsRect& aClipRect, michael@0: const nsStyleBackground& aBackground, michael@0: const nsStyleBackground::Layer& aLayer, michael@0: uint32_t aFlags) michael@0: { michael@0: nsBackgroundLayerState state = michael@0: PrepareBackgroundLayer(aPresContext, aForFrame, aFlags, aBorderArea, michael@0: aClipRect, aBackground, aLayer); michael@0: return state.mFillArea; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsCSSRendering::IsBackgroundImageDecodedForStyleContextAndLayer( michael@0: const nsStyleBackground *aBackground, uint32_t aLayer) michael@0: { michael@0: const nsStyleImage* image = &aBackground->mLayers[aLayer].mImage; michael@0: if (image->GetType() == eStyleImageType_Image) { michael@0: nsCOMPtr img; michael@0: if (NS_SUCCEEDED(image->GetImageData()->GetImage(getter_AddRefs(img)))) { michael@0: if (!img->IsDecoded()) { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsCSSRendering::AreAllBackgroundImagesDecodedForFrame(nsIFrame* aFrame) michael@0: { michael@0: const nsStyleBackground *bg = aFrame->StyleContext()->StyleBackground(); michael@0: NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(i, bg) { michael@0: if (!IsBackgroundImageDecodedForStyleContextAndLayer(bg, i)) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: DrawBorderImage(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsIFrame* aForFrame, michael@0: const nsRect& aBorderArea, michael@0: const nsStyleBorder& aStyleBorder, michael@0: const nsRect& aDirtyRect) michael@0: { michael@0: NS_PRECONDITION(aStyleBorder.IsBorderImageLoaded(), michael@0: "drawing border image that isn't successfully loaded"); michael@0: michael@0: if (aDirtyRect.IsEmpty()) michael@0: return; michael@0: michael@0: nsImageRenderer renderer(aForFrame, &aStyleBorder.mBorderImageSource, 0); michael@0: michael@0: // Ensure we get invalidated for loads and animations of the image. michael@0: // We need to do this here because this might be the only code that michael@0: // knows about the association of the style data with the frame. michael@0: // XXX We shouldn't really... since if anybody is passing in a michael@0: // different style, they'll potentially have the wrong size for the michael@0: // border too. michael@0: aForFrame->AssociateImage(aStyleBorder.mBorderImageSource, aPresContext); michael@0: michael@0: if (!renderer.PrepareImage()) { michael@0: return; michael@0: } michael@0: michael@0: // Determine the border image area, which by default corresponds to the michael@0: // border box but can be modified by 'border-image-outset'. michael@0: nsRect borderImgArea(aBorderArea); michael@0: borderImgArea.Inflate(aStyleBorder.GetImageOutset()); michael@0: michael@0: // Calculate the image size used to compute slice points. michael@0: CSSSizeOrRatio intrinsicSize = renderer.ComputeIntrinsicSize(); michael@0: nsSize imageSize = nsImageRenderer::ComputeConcreteSize(CSSSizeOrRatio(), michael@0: intrinsicSize, michael@0: borderImgArea.Size()); michael@0: renderer.SetPreferredSize(intrinsicSize, imageSize); michael@0: michael@0: // Compute the used values of 'border-image-slice' and 'border-image-width'; michael@0: // we do them together because the latter can depend on the former. michael@0: nsMargin slice; michael@0: nsMargin border; michael@0: NS_FOR_CSS_SIDES(s) { michael@0: nsStyleCoord coord = aStyleBorder.mBorderImageSlice.Get(s); michael@0: int32_t imgDimension = NS_SIDE_IS_VERTICAL(s) michael@0: ? imageSize.width : imageSize.height; michael@0: nscoord borderDimension = NS_SIDE_IS_VERTICAL(s) michael@0: ? borderImgArea.width : borderImgArea.height; michael@0: double value; michael@0: switch (coord.GetUnit()) { michael@0: case eStyleUnit_Percent: michael@0: value = coord.GetPercentValue() * imgDimension; michael@0: break; michael@0: case eStyleUnit_Factor: michael@0: value = nsPresContext::CSSPixelsToAppUnits( michael@0: NS_lround(coord.GetFactorValue())); michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("unexpected CSS unit for image slice"); michael@0: value = 0; michael@0: break; michael@0: } michael@0: if (value < 0) michael@0: value = 0; michael@0: if (value > imgDimension) michael@0: value = imgDimension; michael@0: slice.Side(s) = value; michael@0: michael@0: nsMargin borderWidths(aStyleBorder.GetComputedBorder()); michael@0: coord = aStyleBorder.mBorderImageWidth.Get(s); michael@0: switch (coord.GetUnit()) { michael@0: case eStyleUnit_Coord: // absolute dimension michael@0: value = coord.GetCoordValue(); michael@0: break; michael@0: case eStyleUnit_Percent: michael@0: value = coord.GetPercentValue() * borderDimension; michael@0: break; michael@0: case eStyleUnit_Factor: michael@0: value = coord.GetFactorValue() * borderWidths.Side(s); michael@0: break; michael@0: case eStyleUnit_Auto: // same as the slice value, in CSS pixels michael@0: value = slice.Side(s); michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("unexpected CSS unit for border image area division"); michael@0: value = 0; michael@0: break; michael@0: } michael@0: // NSToCoordRoundWithClamp rounds towards infinity, but that's OK michael@0: // because we expect value to be non-negative. michael@0: MOZ_ASSERT(value >= 0); michael@0: border.Side(s) = NSToCoordRoundWithClamp(value); michael@0: MOZ_ASSERT(border.Side(s) >= 0); michael@0: } michael@0: michael@0: // "If two opposite border-image-width offsets are large enough that they michael@0: // overlap, their used values are proportionately reduced until they no michael@0: // longer overlap." michael@0: uint32_t combinedBorderWidth = uint32_t(border.left) + michael@0: uint32_t(border.right); michael@0: double scaleX = combinedBorderWidth > uint32_t(borderImgArea.width) michael@0: ? borderImgArea.width / double(combinedBorderWidth) michael@0: : 1.0; michael@0: uint32_t combinedBorderHeight = uint32_t(border.top) + michael@0: uint32_t(border.bottom); michael@0: double scaleY = combinedBorderHeight > uint32_t(borderImgArea.height) michael@0: ? borderImgArea.height / double(combinedBorderHeight) michael@0: : 1.0; michael@0: double scale = std::min(scaleX, scaleY); michael@0: if (scale < 1.0) { michael@0: border.left *= scale; michael@0: border.right *= scale; michael@0: border.top *= scale; michael@0: border.bottom *= scale; michael@0: NS_ASSERTION(border.left + border.right <= borderImgArea.width && michael@0: border.top + border.bottom <= borderImgArea.height, michael@0: "rounding error in width reduction???"); michael@0: } michael@0: michael@0: // These helper tables recharacterize the 'slice' and 'width' margins michael@0: // in a more convenient form: they are the x/y/width/height coords michael@0: // required for various bands of the border, and they have been transformed michael@0: // to be relative to the innerRect (for 'slice') or the page (for 'border'). michael@0: enum { michael@0: LEFT, MIDDLE, RIGHT, michael@0: TOP = LEFT, BOTTOM = RIGHT michael@0: }; michael@0: const nscoord borderX[3] = { michael@0: borderImgArea.x + 0, michael@0: borderImgArea.x + border.left, michael@0: borderImgArea.x + borderImgArea.width - border.right, michael@0: }; michael@0: const nscoord borderY[3] = { michael@0: borderImgArea.y + 0, michael@0: borderImgArea.y + border.top, michael@0: borderImgArea.y + borderImgArea.height - border.bottom, michael@0: }; michael@0: const nscoord borderWidth[3] = { michael@0: border.left, michael@0: borderImgArea.width - border.left - border.right, michael@0: border.right, michael@0: }; michael@0: const nscoord borderHeight[3] = { michael@0: border.top, michael@0: borderImgArea.height - border.top - border.bottom, michael@0: border.bottom, michael@0: }; michael@0: const int32_t sliceX[3] = { michael@0: 0, michael@0: slice.left, michael@0: imageSize.width - slice.right, michael@0: }; michael@0: const int32_t sliceY[3] = { michael@0: 0, michael@0: slice.top, michael@0: imageSize.height - slice.bottom, michael@0: }; michael@0: const int32_t sliceWidth[3] = { michael@0: slice.left, michael@0: std::max(imageSize.width - slice.left - slice.right, 0), michael@0: slice.right, michael@0: }; michael@0: const int32_t sliceHeight[3] = { michael@0: slice.top, michael@0: std::max(imageSize.height - slice.top - slice.bottom, 0), michael@0: slice.bottom, michael@0: }; michael@0: michael@0: for (int i = LEFT; i <= RIGHT; i++) { michael@0: for (int j = TOP; j <= BOTTOM; j++) { michael@0: uint8_t fillStyleH, fillStyleV; michael@0: nsSize unitSize; michael@0: michael@0: if (i == MIDDLE && j == MIDDLE) { michael@0: // Discard the middle portion unless set to fill. michael@0: if (NS_STYLE_BORDER_IMAGE_SLICE_NOFILL == michael@0: aStyleBorder.mBorderImageFill) { michael@0: continue; michael@0: } michael@0: michael@0: NS_ASSERTION(NS_STYLE_BORDER_IMAGE_SLICE_FILL == michael@0: aStyleBorder.mBorderImageFill, michael@0: "Unexpected border image fill"); michael@0: michael@0: // css-background: michael@0: // The middle image's width is scaled by the same factor as the michael@0: // top image unless that factor is zero or infinity, in which michael@0: // case the scaling factor of the bottom is substituted, and michael@0: // failing that, the width is not scaled. The height of the michael@0: // middle image is scaled by the same factor as the left image michael@0: // unless that factor is zero or infinity, in which case the michael@0: // scaling factor of the right image is substituted, and failing michael@0: // that, the height is not scaled. michael@0: gfxFloat hFactor, vFactor; michael@0: michael@0: if (0 < border.left && 0 < slice.left) michael@0: vFactor = gfxFloat(border.left)/slice.left; michael@0: else if (0 < border.right && 0 < slice.right) michael@0: vFactor = gfxFloat(border.right)/slice.right; michael@0: else michael@0: vFactor = 1; michael@0: michael@0: if (0 < border.top && 0 < slice.top) michael@0: hFactor = gfxFloat(border.top)/slice.top; michael@0: else if (0 < border.bottom && 0 < slice.bottom) michael@0: hFactor = gfxFloat(border.bottom)/slice.bottom; michael@0: else michael@0: hFactor = 1; michael@0: michael@0: unitSize.width = sliceWidth[i]*hFactor; michael@0: unitSize.height = sliceHeight[j]*vFactor; michael@0: fillStyleH = aStyleBorder.mBorderImageRepeatH; michael@0: fillStyleV = aStyleBorder.mBorderImageRepeatV; michael@0: michael@0: } else if (i == MIDDLE) { // top, bottom michael@0: // Sides are always stretched to the thickness of their border, michael@0: // and stretched proportionately on the other axis. michael@0: gfxFloat factor; michael@0: if (0 < borderHeight[j] && 0 < sliceHeight[j]) michael@0: factor = gfxFloat(borderHeight[j])/sliceHeight[j]; michael@0: else michael@0: factor = 1; michael@0: michael@0: unitSize.width = sliceWidth[i]*factor; michael@0: unitSize.height = borderHeight[j]; michael@0: fillStyleH = aStyleBorder.mBorderImageRepeatH; michael@0: fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH; michael@0: michael@0: } else if (j == MIDDLE) { // left, right michael@0: gfxFloat factor; michael@0: if (0 < borderWidth[i] && 0 < sliceWidth[i]) michael@0: factor = gfxFloat(borderWidth[i])/sliceWidth[i]; michael@0: else michael@0: factor = 1; michael@0: michael@0: unitSize.width = borderWidth[i]; michael@0: unitSize.height = sliceHeight[j]*factor; michael@0: fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH; michael@0: fillStyleV = aStyleBorder.mBorderImageRepeatV; michael@0: michael@0: } else { michael@0: // Corners are always stretched to fit the corner. michael@0: unitSize.width = borderWidth[i]; michael@0: unitSize.height = borderHeight[j]; michael@0: fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH; michael@0: fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH; michael@0: } michael@0: michael@0: nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]); michael@0: nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]); michael@0: nsIntRect intSubArea = subArea.ToOutsidePixels(nsPresContext::AppUnitsPerCSSPixel()); michael@0: michael@0: renderer.DrawBorderImageComponent(aPresContext, michael@0: aRenderingContext, aDirtyRect, michael@0: destArea, CSSIntRect(intSubArea.x, michael@0: intSubArea.y, michael@0: intSubArea.width, michael@0: intSubArea.height), michael@0: fillStyleH, fillStyleV, michael@0: unitSize, j * (RIGHT + 1) + i); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Begin table border-collapsing section michael@0: // These functions were written to not disrupt the normal ones and yet satisfy some additional requirements michael@0: // At some point, all functions should be unified to include the additional functionality that these provide michael@0: michael@0: static nscoord michael@0: RoundIntToPixel(nscoord aValue, michael@0: nscoord aTwipsPerPixel, michael@0: bool aRoundDown = false) michael@0: { michael@0: if (aTwipsPerPixel <= 0) michael@0: // We must be rendering to a device that has a resolution greater than Twips! michael@0: // In that case, aValue is as accurate as it's going to get. michael@0: return aValue; michael@0: michael@0: nscoord halfPixel = NSToCoordRound(aTwipsPerPixel / 2.0f); michael@0: nscoord extra = aValue % aTwipsPerPixel; michael@0: nscoord finalValue = (!aRoundDown && (extra >= halfPixel)) ? aValue + (aTwipsPerPixel - extra) : aValue - extra; michael@0: return finalValue; michael@0: } michael@0: michael@0: static nscoord michael@0: RoundFloatToPixel(float aValue, michael@0: nscoord aTwipsPerPixel, michael@0: bool aRoundDown = false) michael@0: { michael@0: return RoundIntToPixel(NSToCoordRound(aValue), aTwipsPerPixel, aRoundDown); michael@0: } michael@0: michael@0: static void michael@0: SetPoly(const nsRect& aRect, michael@0: nsPoint* poly) michael@0: { michael@0: poly[0].x = aRect.x; michael@0: poly[0].y = aRect.y; michael@0: poly[1].x = aRect.x + aRect.width; michael@0: poly[1].y = aRect.y; michael@0: poly[2].x = aRect.x + aRect.width; michael@0: poly[2].y = aRect.y + aRect.height; michael@0: poly[3].x = aRect.x; michael@0: poly[3].y = aRect.y + aRect.height; michael@0: poly[4].x = aRect.x; michael@0: poly[4].y = aRect.y; michael@0: } michael@0: michael@0: static void michael@0: DrawSolidBorderSegment(nsRenderingContext& aContext, michael@0: nsRect aRect, michael@0: nscoord aTwipsPerPixel, michael@0: uint8_t aStartBevelSide = 0, michael@0: nscoord aStartBevelOffset = 0, michael@0: uint8_t aEndBevelSide = 0, michael@0: nscoord aEndBevelOffset = 0) michael@0: { michael@0: michael@0: if ((aRect.width == aTwipsPerPixel) || (aRect.height == aTwipsPerPixel) || michael@0: ((0 == aStartBevelOffset) && (0 == aEndBevelOffset))) { michael@0: // simple line or rectangle michael@0: if ((NS_SIDE_TOP == aStartBevelSide) || (NS_SIDE_BOTTOM == aStartBevelSide)) { michael@0: if (1 == aRect.height) michael@0: aContext.DrawLine(aRect.TopLeft(), aRect.BottomLeft()); michael@0: else michael@0: aContext.FillRect(aRect); michael@0: } michael@0: else { michael@0: if (1 == aRect.width) michael@0: aContext.DrawLine(aRect.TopLeft(), aRect.TopRight()); michael@0: else michael@0: aContext.FillRect(aRect); michael@0: } michael@0: } michael@0: else { michael@0: // polygon with beveling michael@0: nsPoint poly[5]; michael@0: SetPoly(aRect, poly); michael@0: switch(aStartBevelSide) { michael@0: case NS_SIDE_TOP: michael@0: poly[0].x += aStartBevelOffset; michael@0: poly[4].x = poly[0].x; michael@0: break; michael@0: case NS_SIDE_BOTTOM: michael@0: poly[3].x += aStartBevelOffset; michael@0: break; michael@0: case NS_SIDE_RIGHT: michael@0: poly[1].y += aStartBevelOffset; michael@0: break; michael@0: case NS_SIDE_LEFT: michael@0: poly[0].y += aStartBevelOffset; michael@0: poly[4].y = poly[0].y; michael@0: } michael@0: michael@0: switch(aEndBevelSide) { michael@0: case NS_SIDE_TOP: michael@0: poly[1].x -= aEndBevelOffset; michael@0: break; michael@0: case NS_SIDE_BOTTOM: michael@0: poly[2].x -= aEndBevelOffset; michael@0: break; michael@0: case NS_SIDE_RIGHT: michael@0: poly[2].y -= aEndBevelOffset; michael@0: break; michael@0: case NS_SIDE_LEFT: michael@0: poly[3].y -= aEndBevelOffset; michael@0: } michael@0: michael@0: aContext.FillPolygon(poly, 5); michael@0: } michael@0: michael@0: michael@0: } michael@0: michael@0: static void michael@0: GetDashInfo(nscoord aBorderLength, michael@0: nscoord aDashLength, michael@0: nscoord aTwipsPerPixel, michael@0: int32_t& aNumDashSpaces, michael@0: nscoord& aStartDashLength, michael@0: nscoord& aEndDashLength) michael@0: { michael@0: aNumDashSpaces = 0; michael@0: if (aStartDashLength + aDashLength + aEndDashLength >= aBorderLength) { michael@0: aStartDashLength = aBorderLength; michael@0: aEndDashLength = 0; michael@0: } michael@0: else { michael@0: aNumDashSpaces = (aBorderLength - aDashLength)/ (2 * aDashLength); // round down michael@0: nscoord extra = aBorderLength - aStartDashLength - aEndDashLength - (((2 * aNumDashSpaces) - 1) * aDashLength); michael@0: if (extra > 0) { michael@0: nscoord half = RoundIntToPixel(extra / 2, aTwipsPerPixel); michael@0: aStartDashLength += half; michael@0: aEndDashLength += (extra - half); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCSSRendering::DrawTableBorderSegment(nsRenderingContext& aContext, michael@0: uint8_t aBorderStyle, michael@0: nscolor aBorderColor, michael@0: const nsStyleBackground* aBGColor, michael@0: const nsRect& aBorder, michael@0: int32_t aAppUnitsPerCSSPixel, michael@0: uint8_t aStartBevelSide, michael@0: nscoord aStartBevelOffset, michael@0: uint8_t aEndBevelSide, michael@0: nscoord aEndBevelOffset) michael@0: { michael@0: aContext.SetColor (aBorderColor); michael@0: michael@0: bool horizontal = ((NS_SIDE_TOP == aStartBevelSide) || (NS_SIDE_BOTTOM == aStartBevelSide)); michael@0: nscoord twipsPerPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerCSSPixel); michael@0: uint8_t ridgeGroove = NS_STYLE_BORDER_STYLE_RIDGE; michael@0: michael@0: if ((twipsPerPixel >= aBorder.width) || (twipsPerPixel >= aBorder.height) || michael@0: (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle) || (NS_STYLE_BORDER_STYLE_DOTTED == aBorderStyle)) { michael@0: // no beveling for 1 pixel border, dash or dot michael@0: aStartBevelOffset = 0; michael@0: aEndBevelOffset = 0; michael@0: } michael@0: michael@0: gfxContext *ctx = aContext.ThebesContext(); michael@0: gfxContext::AntialiasMode oldMode = ctx->CurrentAntialiasMode(); michael@0: ctx->SetAntialiasMode(gfxContext::MODE_ALIASED); michael@0: michael@0: switch (aBorderStyle) { michael@0: case NS_STYLE_BORDER_STYLE_NONE: michael@0: case NS_STYLE_BORDER_STYLE_HIDDEN: michael@0: //NS_ASSERTION(false, "style of none or hidden"); michael@0: break; michael@0: case NS_STYLE_BORDER_STYLE_DOTTED: michael@0: case NS_STYLE_BORDER_STYLE_DASHED: michael@0: { michael@0: nscoord dashLength = (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle) ? DASH_LENGTH : DOT_LENGTH; michael@0: // make the dash length proportional to the border thickness michael@0: dashLength *= (horizontal) ? aBorder.height : aBorder.width; michael@0: // make the min dash length for the ends 1/2 the dash length michael@0: nscoord minDashLength = (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle) michael@0: ? RoundFloatToPixel(((float)dashLength) / 2.0f, twipsPerPixel) : dashLength; michael@0: minDashLength = std::max(minDashLength, twipsPerPixel); michael@0: nscoord numDashSpaces = 0; michael@0: nscoord startDashLength = minDashLength; michael@0: nscoord endDashLength = minDashLength; michael@0: if (horizontal) { michael@0: GetDashInfo(aBorder.width, dashLength, twipsPerPixel, numDashSpaces, startDashLength, endDashLength); michael@0: nsRect rect(aBorder.x, aBorder.y, startDashLength, aBorder.height); michael@0: DrawSolidBorderSegment(aContext, rect, twipsPerPixel); michael@0: for (int32_t spaceX = 0; spaceX < numDashSpaces; spaceX++) { michael@0: rect.x += rect.width + dashLength; michael@0: rect.width = (spaceX == (numDashSpaces - 1)) ? endDashLength : dashLength; michael@0: DrawSolidBorderSegment(aContext, rect, twipsPerPixel); michael@0: } michael@0: } michael@0: else { michael@0: GetDashInfo(aBorder.height, dashLength, twipsPerPixel, numDashSpaces, startDashLength, endDashLength); michael@0: nsRect rect(aBorder.x, aBorder.y, aBorder.width, startDashLength); michael@0: DrawSolidBorderSegment(aContext, rect, twipsPerPixel); michael@0: for (int32_t spaceY = 0; spaceY < numDashSpaces; spaceY++) { michael@0: rect.y += rect.height + dashLength; michael@0: rect.height = (spaceY == (numDashSpaces - 1)) ? endDashLength : dashLength; michael@0: DrawSolidBorderSegment(aContext, rect, twipsPerPixel); michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: case NS_STYLE_BORDER_STYLE_GROOVE: michael@0: ridgeGroove = NS_STYLE_BORDER_STYLE_GROOVE; // and fall through to ridge michael@0: case NS_STYLE_BORDER_STYLE_RIDGE: michael@0: if ((horizontal && (twipsPerPixel >= aBorder.height)) || michael@0: (!horizontal && (twipsPerPixel >= aBorder.width))) { michael@0: // a one pixel border michael@0: DrawSolidBorderSegment(aContext, aBorder, twipsPerPixel, aStartBevelSide, aStartBevelOffset, michael@0: aEndBevelSide, aEndBevelOffset); michael@0: } michael@0: else { michael@0: nscoord startBevel = (aStartBevelOffset > 0) michael@0: ? RoundFloatToPixel(0.5f * (float)aStartBevelOffset, twipsPerPixel, true) : 0; michael@0: nscoord endBevel = (aEndBevelOffset > 0) michael@0: ? RoundFloatToPixel(0.5f * (float)aEndBevelOffset, twipsPerPixel, true) : 0; michael@0: mozilla::css::Side ridgeGrooveSide = (horizontal) ? NS_SIDE_TOP : NS_SIDE_LEFT; michael@0: // FIXME: In theory, this should use the visited-dependent michael@0: // background color, but I don't care. michael@0: aContext.SetColor ( michael@0: MakeBevelColor(ridgeGrooveSide, ridgeGroove, aBGColor->mBackgroundColor, aBorderColor)); michael@0: nsRect rect(aBorder); michael@0: nscoord half; michael@0: if (horizontal) { // top, bottom michael@0: half = RoundFloatToPixel(0.5f * (float)aBorder.height, twipsPerPixel); michael@0: rect.height = half; michael@0: if (NS_SIDE_TOP == aStartBevelSide) { michael@0: rect.x += startBevel; michael@0: rect.width -= startBevel; michael@0: } michael@0: if (NS_SIDE_TOP == aEndBevelSide) { michael@0: rect.width -= endBevel; michael@0: } michael@0: DrawSolidBorderSegment(aContext, rect, twipsPerPixel, aStartBevelSide, michael@0: startBevel, aEndBevelSide, endBevel); michael@0: } michael@0: else { // left, right michael@0: half = RoundFloatToPixel(0.5f * (float)aBorder.width, twipsPerPixel); michael@0: rect.width = half; michael@0: if (NS_SIDE_LEFT == aStartBevelSide) { michael@0: rect.y += startBevel; michael@0: rect.height -= startBevel; michael@0: } michael@0: if (NS_SIDE_LEFT == aEndBevelSide) { michael@0: rect.height -= endBevel; michael@0: } michael@0: DrawSolidBorderSegment(aContext, rect, twipsPerPixel, aStartBevelSide, michael@0: startBevel, aEndBevelSide, endBevel); michael@0: } michael@0: michael@0: rect = aBorder; michael@0: ridgeGrooveSide = (NS_SIDE_TOP == ridgeGrooveSide) ? NS_SIDE_BOTTOM : NS_SIDE_RIGHT; michael@0: // FIXME: In theory, this should use the visited-dependent michael@0: // background color, but I don't care. michael@0: aContext.SetColor ( michael@0: MakeBevelColor(ridgeGrooveSide, ridgeGroove, aBGColor->mBackgroundColor, aBorderColor)); michael@0: if (horizontal) { michael@0: rect.y = rect.y + half; michael@0: rect.height = aBorder.height - half; michael@0: if (NS_SIDE_BOTTOM == aStartBevelSide) { michael@0: rect.x += startBevel; michael@0: rect.width -= startBevel; michael@0: } michael@0: if (NS_SIDE_BOTTOM == aEndBevelSide) { michael@0: rect.width -= endBevel; michael@0: } michael@0: DrawSolidBorderSegment(aContext, rect, twipsPerPixel, aStartBevelSide, michael@0: startBevel, aEndBevelSide, endBevel); michael@0: } michael@0: else { michael@0: rect.x = rect.x + half; michael@0: rect.width = aBorder.width - half; michael@0: if (NS_SIDE_RIGHT == aStartBevelSide) { michael@0: rect.y += aStartBevelOffset - startBevel; michael@0: rect.height -= startBevel; michael@0: } michael@0: if (NS_SIDE_RIGHT == aEndBevelSide) { michael@0: rect.height -= endBevel; michael@0: } michael@0: DrawSolidBorderSegment(aContext, rect, twipsPerPixel, aStartBevelSide, michael@0: startBevel, aEndBevelSide, endBevel); michael@0: } michael@0: } michael@0: break; michael@0: case NS_STYLE_BORDER_STYLE_DOUBLE: michael@0: // We can only do "double" borders if the thickness of the border michael@0: // is more than 2px. Otherwise, we fall through to painting a michael@0: // solid border. michael@0: if ((aBorder.width > 2*twipsPerPixel || horizontal) && michael@0: (aBorder.height > 2*twipsPerPixel || !horizontal)) { michael@0: nscoord startBevel = (aStartBevelOffset > 0) michael@0: ? RoundFloatToPixel(0.333333f * (float)aStartBevelOffset, twipsPerPixel) : 0; michael@0: nscoord endBevel = (aEndBevelOffset > 0) michael@0: ? RoundFloatToPixel(0.333333f * (float)aEndBevelOffset, twipsPerPixel) : 0; michael@0: if (horizontal) { // top, bottom michael@0: nscoord thirdHeight = RoundFloatToPixel(0.333333f * (float)aBorder.height, twipsPerPixel); michael@0: michael@0: // draw the top line or rect michael@0: nsRect topRect(aBorder.x, aBorder.y, aBorder.width, thirdHeight); michael@0: if (NS_SIDE_TOP == aStartBevelSide) { michael@0: topRect.x += aStartBevelOffset - startBevel; michael@0: topRect.width -= aStartBevelOffset - startBevel; michael@0: } michael@0: if (NS_SIDE_TOP == aEndBevelSide) { michael@0: topRect.width -= aEndBevelOffset - endBevel; michael@0: } michael@0: DrawSolidBorderSegment(aContext, topRect, twipsPerPixel, aStartBevelSide, michael@0: startBevel, aEndBevelSide, endBevel); michael@0: michael@0: // draw the botom line or rect michael@0: nscoord heightOffset = aBorder.height - thirdHeight; michael@0: nsRect bottomRect(aBorder.x, aBorder.y + heightOffset, aBorder.width, aBorder.height - heightOffset); michael@0: if (NS_SIDE_BOTTOM == aStartBevelSide) { michael@0: bottomRect.x += aStartBevelOffset - startBevel; michael@0: bottomRect.width -= aStartBevelOffset - startBevel; michael@0: } michael@0: if (NS_SIDE_BOTTOM == aEndBevelSide) { michael@0: bottomRect.width -= aEndBevelOffset - endBevel; michael@0: } michael@0: DrawSolidBorderSegment(aContext, bottomRect, twipsPerPixel, aStartBevelSide, michael@0: startBevel, aEndBevelSide, endBevel); michael@0: } michael@0: else { // left, right michael@0: nscoord thirdWidth = RoundFloatToPixel(0.333333f * (float)aBorder.width, twipsPerPixel); michael@0: michael@0: nsRect leftRect(aBorder.x, aBorder.y, thirdWidth, aBorder.height); michael@0: if (NS_SIDE_LEFT == aStartBevelSide) { michael@0: leftRect.y += aStartBevelOffset - startBevel; michael@0: leftRect.height -= aStartBevelOffset - startBevel; michael@0: } michael@0: if (NS_SIDE_LEFT == aEndBevelSide) { michael@0: leftRect.height -= aEndBevelOffset - endBevel; michael@0: } michael@0: DrawSolidBorderSegment(aContext, leftRect, twipsPerPixel, aStartBevelSide, michael@0: startBevel, aEndBevelSide, endBevel); michael@0: michael@0: nscoord widthOffset = aBorder.width - thirdWidth; michael@0: nsRect rightRect(aBorder.x + widthOffset, aBorder.y, aBorder.width - widthOffset, aBorder.height); michael@0: if (NS_SIDE_RIGHT == aStartBevelSide) { michael@0: rightRect.y += aStartBevelOffset - startBevel; michael@0: rightRect.height -= aStartBevelOffset - startBevel; michael@0: } michael@0: if (NS_SIDE_RIGHT == aEndBevelSide) { michael@0: rightRect.height -= aEndBevelOffset - endBevel; michael@0: } michael@0: DrawSolidBorderSegment(aContext, rightRect, twipsPerPixel, aStartBevelSide, michael@0: startBevel, aEndBevelSide, endBevel); michael@0: } michael@0: break; michael@0: } michael@0: // else fall through to solid michael@0: case NS_STYLE_BORDER_STYLE_SOLID: michael@0: DrawSolidBorderSegment(aContext, aBorder, twipsPerPixel, aStartBevelSide, michael@0: aStartBevelOffset, aEndBevelSide, aEndBevelOffset); michael@0: break; michael@0: case NS_STYLE_BORDER_STYLE_OUTSET: michael@0: case NS_STYLE_BORDER_STYLE_INSET: michael@0: NS_ASSERTION(false, "inset, outset should have been converted to groove, ridge"); michael@0: break; michael@0: case NS_STYLE_BORDER_STYLE_AUTO: michael@0: NS_ASSERTION(false, "Unexpected 'auto' table border"); michael@0: break; michael@0: } michael@0: michael@0: ctx->SetAntialiasMode(oldMode); michael@0: } michael@0: michael@0: // End table border-collapsing section michael@0: michael@0: gfxRect michael@0: nsCSSRendering::ExpandPaintingRectForDecorationLine(nsIFrame* aFrame, michael@0: const uint8_t aStyle, michael@0: const gfxRect& aClippedRect, michael@0: const gfxFloat aXInFrame, michael@0: const gfxFloat aCycleLength) michael@0: { michael@0: switch (aStyle) { michael@0: case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED: michael@0: case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: michael@0: case NS_STYLE_TEXT_DECORATION_STYLE_WAVY: michael@0: break; michael@0: default: michael@0: NS_ERROR("Invalid style was specified"); michael@0: return aClippedRect; michael@0: } michael@0: michael@0: nsBlockFrame* block = nullptr; michael@0: // Note that when we paint the decoration lines in relative positioned michael@0: // box, we should paint them like all of the boxes are positioned as static. michael@0: nscoord frameXInBlockAppUnits = 0; michael@0: for (nsIFrame* f = aFrame; f; f = f->GetParent()) { michael@0: block = do_QueryFrame(f); michael@0: if (block) { michael@0: break; michael@0: } michael@0: frameXInBlockAppUnits += f->GetNormalPosition().x; michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(block, aClippedRect); michael@0: michael@0: nsPresContext *pc = aFrame->PresContext(); michael@0: gfxFloat frameXInBlock = pc->AppUnitsToGfxUnits(frameXInBlockAppUnits); michael@0: int32_t rectXInBlock = int32_t(NS_round(frameXInBlock + aXInFrame)); michael@0: int32_t extraLeft = michael@0: rectXInBlock - (rectXInBlock / int32_t(aCycleLength) * aCycleLength); michael@0: gfxRect rect(aClippedRect); michael@0: rect.x -= extraLeft; michael@0: rect.width += extraLeft; michael@0: return rect; michael@0: } michael@0: michael@0: void michael@0: nsCSSRendering::PaintDecorationLine(nsIFrame* aFrame, michael@0: gfxContext* aGfxContext, michael@0: const gfxRect& aDirtyRect, michael@0: const nscolor aColor, michael@0: const gfxPoint& aPt, michael@0: const gfxFloat aXInFrame, michael@0: const gfxSize& aLineSize, michael@0: const gfxFloat aAscent, michael@0: const gfxFloat aOffset, michael@0: const uint8_t aDecoration, michael@0: const uint8_t aStyle, michael@0: const gfxFloat aDescentLimit) michael@0: { michael@0: NS_ASSERTION(aStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE, "aStyle is none"); michael@0: michael@0: gfxRect rect = michael@0: GetTextDecorationRectInternal(aPt, aLineSize, aAscent, aOffset, michael@0: aDecoration, aStyle, aDescentLimit); michael@0: if (rect.IsEmpty() || !rect.Intersects(aDirtyRect)) { michael@0: return; michael@0: } michael@0: michael@0: if (aDecoration != NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE && michael@0: aDecoration != NS_STYLE_TEXT_DECORATION_LINE_OVERLINE && michael@0: aDecoration != NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) { michael@0: NS_ERROR("Invalid decoration value!"); michael@0: return; michael@0: } michael@0: michael@0: gfxFloat lineHeight = std::max(NS_round(aLineSize.height), 1.0); michael@0: bool contextIsSaved = false; michael@0: michael@0: gfxFloat oldLineWidth; michael@0: nsRefPtr oldPattern; michael@0: michael@0: switch (aStyle) { michael@0: case NS_STYLE_TEXT_DECORATION_STYLE_SOLID: michael@0: case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE: michael@0: oldLineWidth = aGfxContext->CurrentLineWidth(); michael@0: oldPattern = aGfxContext->GetPattern(); michael@0: break; michael@0: case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: { michael@0: aGfxContext->Save(); michael@0: contextIsSaved = true; michael@0: aGfxContext->Clip(rect); michael@0: gfxFloat dashWidth = lineHeight * DOT_LENGTH * DASH_LENGTH; michael@0: gfxFloat dash[2] = { dashWidth, dashWidth }; michael@0: aGfxContext->SetLineCap(gfxContext::LINE_CAP_BUTT); michael@0: aGfxContext->SetDash(dash, 2, 0.0); michael@0: rect = ExpandPaintingRectForDecorationLine(aFrame, aStyle, rect, michael@0: aXInFrame, dashWidth * 2); michael@0: // We should continue to draw the last dash even if it is not in the rect. michael@0: rect.width += dashWidth; michael@0: break; michael@0: } michael@0: case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED: { michael@0: aGfxContext->Save(); michael@0: contextIsSaved = true; michael@0: aGfxContext->Clip(rect); michael@0: gfxFloat dashWidth = lineHeight * DOT_LENGTH; michael@0: gfxFloat dash[2]; michael@0: if (lineHeight > 2.0) { michael@0: dash[0] = 0.0; michael@0: dash[1] = dashWidth * 2.0; michael@0: aGfxContext->SetLineCap(gfxContext::LINE_CAP_ROUND); michael@0: } else { michael@0: dash[0] = dashWidth; michael@0: dash[1] = dashWidth; michael@0: } michael@0: aGfxContext->SetDash(dash, 2, 0.0); michael@0: rect = ExpandPaintingRectForDecorationLine(aFrame, aStyle, rect, michael@0: aXInFrame, dashWidth * 2); michael@0: // We should continue to draw the last dot even if it is not in the rect. michael@0: rect.width += dashWidth; michael@0: break; michael@0: } michael@0: case NS_STYLE_TEXT_DECORATION_STYLE_WAVY: michael@0: aGfxContext->Save(); michael@0: contextIsSaved = true; michael@0: aGfxContext->Clip(rect); michael@0: if (lineHeight > 2.0) { michael@0: aGfxContext->SetAntialiasMode(gfxContext::MODE_COVERAGE); michael@0: } else { michael@0: // Don't use anti-aliasing here. Because looks like lighter color wavy michael@0: // line at this case. And probably, users don't think the michael@0: // non-anti-aliased wavy line is not pretty. michael@0: aGfxContext->SetAntialiasMode(gfxContext::MODE_ALIASED); michael@0: } michael@0: break; michael@0: default: michael@0: NS_ERROR("Invalid style value!"); michael@0: return; michael@0: } michael@0: michael@0: // The y position should be set to the middle of the line. michael@0: rect.y += lineHeight / 2; michael@0: michael@0: aGfxContext->SetColor(gfxRGBA(aColor)); michael@0: aGfxContext->SetLineWidth(lineHeight); michael@0: switch (aStyle) { michael@0: case NS_STYLE_TEXT_DECORATION_STYLE_SOLID: michael@0: aGfxContext->NewPath(); michael@0: aGfxContext->MoveTo(rect.TopLeft()); michael@0: aGfxContext->LineTo(rect.TopRight()); michael@0: aGfxContext->Stroke(); michael@0: break; michael@0: case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE: michael@0: /** michael@0: * We are drawing double line as: michael@0: * michael@0: * +-------------------------------------------+ michael@0: * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^ michael@0: * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineHeight michael@0: * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v michael@0: * | | michael@0: * | | michael@0: * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^ michael@0: * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineHeight michael@0: * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v michael@0: * +-------------------------------------------+ michael@0: */ michael@0: aGfxContext->NewPath(); michael@0: aGfxContext->MoveTo(rect.TopLeft()); michael@0: aGfxContext->LineTo(rect.TopRight()); michael@0: rect.height -= lineHeight; michael@0: aGfxContext->MoveTo(rect.BottomLeft()); michael@0: aGfxContext->LineTo(rect.BottomRight()); michael@0: aGfxContext->Stroke(); michael@0: break; michael@0: case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED: michael@0: case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: michael@0: aGfxContext->NewPath(); michael@0: aGfxContext->MoveTo(rect.TopLeft()); michael@0: aGfxContext->LineTo(rect.TopRight()); michael@0: aGfxContext->Stroke(); michael@0: break; michael@0: case NS_STYLE_TEXT_DECORATION_STYLE_WAVY: { michael@0: /** michael@0: * We are drawing wavy line as: michael@0: * michael@0: * P: Path, X: Painted pixel michael@0: * michael@0: * +---------------------------------------+ michael@0: * XX|X XXXXXX XXXXXX | michael@0: * PP|PX XPPPPPPX XPPPPPPX | ^ michael@0: * XX|XPX XPXXXXXXPX XPXXXXXXPX| | michael@0: * | XPX XPX XPX XPX XP|X |adv michael@0: * | XPXXXXXXPX XPXXXXXXPX X|PX | michael@0: * | XPPPPPPX XPPPPPPX |XPX v michael@0: * | XXXXXX XXXXXX | XX michael@0: * +---------------------------------------+ michael@0: * <---><---> ^ michael@0: * adv flatLengthAtVertex rightMost michael@0: * michael@0: * 1. Always starts from top-left of the drawing area, however, we need michael@0: * to draw the line from outside of the rect. Because the start michael@0: * point of the line is not good style if we draw from inside it. michael@0: * 2. First, draw horizontal line from outside the rect to top-left of michael@0: * the rect; michael@0: * 3. Goes down to bottom of the area at 45 degrees. michael@0: * 4. Slides to right horizontaly, see |flatLengthAtVertex|. michael@0: * 5. Goes up to top of the area at 45 degrees. michael@0: * 6. Slides to right horizontaly. michael@0: * 7. Repeat from 2 until reached to right-most edge of the area. michael@0: */ michael@0: michael@0: gfxFloat adv = rect.Height() - lineHeight; michael@0: gfxFloat flatLengthAtVertex = std::max((lineHeight - 1.0) * 2.0, 1.0); michael@0: michael@0: // Align the start of wavy lines to the nearest ancestor block. michael@0: gfxFloat cycleLength = 2 * (adv + flatLengthAtVertex); michael@0: rect = ExpandPaintingRectForDecorationLine(aFrame, aStyle, rect, michael@0: aXInFrame, cycleLength); michael@0: // figure out if we can trim whole cycles from the left and right edges michael@0: // of the line, to try and avoid creating an unnecessarily long and michael@0: // complex path michael@0: int32_t skipCycles = floor((aDirtyRect.x - rect.x) / cycleLength); michael@0: if (skipCycles > 0) { michael@0: rect.x += skipCycles * cycleLength; michael@0: rect.width -= skipCycles * cycleLength; michael@0: } michael@0: michael@0: rect.x += lineHeight / 2.0; michael@0: gfxPoint pt(rect.TopLeft()); michael@0: gfxFloat rightMost = pt.x + rect.Width() + lineHeight; michael@0: michael@0: skipCycles = floor((rightMost - aDirtyRect.XMost()) / cycleLength); michael@0: if (skipCycles > 0) { michael@0: rightMost -= skipCycles * cycleLength; michael@0: } michael@0: michael@0: aGfxContext->NewPath(); michael@0: michael@0: pt.x -= lineHeight; michael@0: aGfxContext->MoveTo(pt); // 1 michael@0: michael@0: pt.x = rect.X(); michael@0: aGfxContext->LineTo(pt); // 2 michael@0: michael@0: bool goDown = true; michael@0: uint32_t iter = 0; michael@0: while (pt.x < rightMost) { michael@0: if (++iter > 1000) { michael@0: // stroke the current path and start again, to avoid pathological michael@0: // behavior in cairo with huge numbers of path segments michael@0: aGfxContext->Stroke(); michael@0: aGfxContext->NewPath(); michael@0: aGfxContext->MoveTo(pt); michael@0: iter = 0; michael@0: } michael@0: pt.x += adv; michael@0: pt.y += goDown ? adv : -adv; michael@0: michael@0: aGfxContext->LineTo(pt); // 3 and 5 michael@0: michael@0: pt.x += flatLengthAtVertex; michael@0: aGfxContext->LineTo(pt); // 4 and 6 michael@0: michael@0: goDown = !goDown; michael@0: } michael@0: aGfxContext->Stroke(); michael@0: break; michael@0: } michael@0: default: michael@0: NS_ERROR("Invalid style value!"); michael@0: break; michael@0: } michael@0: michael@0: if (contextIsSaved) { michael@0: aGfxContext->Restore(); michael@0: } else { michael@0: aGfxContext->SetPattern(oldPattern); michael@0: aGfxContext->SetLineWidth(oldLineWidth); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCSSRendering::DecorationLineToPath(nsIFrame* aFrame, michael@0: gfxContext* aGfxContext, michael@0: const gfxRect& aDirtyRect, michael@0: const nscolor aColor, michael@0: const gfxPoint& aPt, michael@0: const gfxFloat aXInFrame, michael@0: const gfxSize& aLineSize, michael@0: const gfxFloat aAscent, michael@0: const gfxFloat aOffset, michael@0: const uint8_t aDecoration, michael@0: const uint8_t aStyle, michael@0: const gfxFloat aDescentLimit) michael@0: { michael@0: NS_ASSERTION(aStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE, "aStyle is none"); michael@0: michael@0: aGfxContext->NewPath(); michael@0: michael@0: gfxRect rect = michael@0: GetTextDecorationRectInternal(aPt, aLineSize, aAscent, aOffset, michael@0: aDecoration, aStyle, aDescentLimit); michael@0: if (rect.IsEmpty() || !rect.Intersects(aDirtyRect)) { michael@0: return; michael@0: } michael@0: michael@0: if (aDecoration != NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE && michael@0: aDecoration != NS_STYLE_TEXT_DECORATION_LINE_OVERLINE && michael@0: aDecoration != NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) { michael@0: NS_ERROR("Invalid decoration value!"); michael@0: return; michael@0: } michael@0: michael@0: if (aStyle != NS_STYLE_TEXT_DECORATION_STYLE_SOLID) { michael@0: // For the moment, we support only solid text decorations. michael@0: return; michael@0: } michael@0: michael@0: gfxFloat lineHeight = std::max(NS_round(aLineSize.height), 1.0); michael@0: michael@0: // The y position should be set to the middle of the line. michael@0: rect.y += lineHeight / 2; michael@0: michael@0: aGfxContext->Rectangle michael@0: (gfxRect(gfxPoint(rect.TopLeft() - gfxPoint(0.0, lineHeight / 2)), michael@0: gfxSize(rect.Width(), lineHeight))); michael@0: } michael@0: michael@0: nsRect michael@0: nsCSSRendering::GetTextDecorationRect(nsPresContext* aPresContext, michael@0: const gfxSize& aLineSize, michael@0: const gfxFloat aAscent, michael@0: const gfxFloat aOffset, michael@0: const uint8_t aDecoration, michael@0: const uint8_t aStyle, michael@0: const gfxFloat aDescentLimit) michael@0: { michael@0: NS_ASSERTION(aPresContext, "aPresContext is null"); michael@0: NS_ASSERTION(aStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE, "aStyle is none"); michael@0: michael@0: gfxRect rect = michael@0: GetTextDecorationRectInternal(gfxPoint(0, 0), aLineSize, aAscent, aOffset, michael@0: aDecoration, aStyle, aDescentLimit); michael@0: // The rect values are already rounded to nearest device pixels. michael@0: nsRect r; michael@0: r.x = aPresContext->GfxUnitsToAppUnits(rect.X()); michael@0: r.y = aPresContext->GfxUnitsToAppUnits(rect.Y()); michael@0: r.width = aPresContext->GfxUnitsToAppUnits(rect.Width()); michael@0: r.height = aPresContext->GfxUnitsToAppUnits(rect.Height()); michael@0: return r; michael@0: } michael@0: michael@0: gfxRect michael@0: nsCSSRendering::GetTextDecorationRectInternal(const gfxPoint& aPt, michael@0: const gfxSize& aLineSize, michael@0: const gfxFloat aAscent, michael@0: const gfxFloat aOffset, michael@0: const uint8_t aDecoration, michael@0: const uint8_t aStyle, michael@0: const gfxFloat aDescentLimit) michael@0: { michael@0: NS_ASSERTION(aStyle <= NS_STYLE_TEXT_DECORATION_STYLE_WAVY, michael@0: "Invalid aStyle value"); michael@0: michael@0: if (aStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) michael@0: return gfxRect(0, 0, 0, 0); michael@0: michael@0: bool canLiftUnderline = aDescentLimit >= 0.0; michael@0: michael@0: const gfxFloat left = floor(aPt.x + 0.5), michael@0: right = floor(aPt.x + aLineSize.width + 0.5); michael@0: gfxRect r(left, 0, right - left, 0); michael@0: michael@0: gfxFloat lineHeight = NS_round(aLineSize.height); michael@0: lineHeight = std::max(lineHeight, 1.0); michael@0: michael@0: gfxFloat ascent = NS_round(aAscent); michael@0: gfxFloat descentLimit = floor(aDescentLimit); michael@0: michael@0: gfxFloat suggestedMaxRectHeight = std::max(std::min(ascent, descentLimit), 1.0); michael@0: r.height = lineHeight; michael@0: if (aStyle == NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE) { michael@0: /** michael@0: * We will draw double line as: michael@0: * michael@0: * +-------------------------------------------+ michael@0: * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^ michael@0: * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineHeight michael@0: * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v michael@0: * | | ^ michael@0: * | | | gap michael@0: * | | v michael@0: * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^ michael@0: * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineHeight michael@0: * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v michael@0: * +-------------------------------------------+ michael@0: */ michael@0: gfxFloat gap = NS_round(lineHeight / 2.0); michael@0: gap = std::max(gap, 1.0); michael@0: r.height = lineHeight * 2.0 + gap; michael@0: if (canLiftUnderline) { michael@0: if (r.Height() > suggestedMaxRectHeight) { michael@0: // Don't shrink the line height, because the thickness has some meaning. michael@0: // We can just shrink the gap at this time. michael@0: r.height = std::max(suggestedMaxRectHeight, lineHeight * 2.0 + 1.0); michael@0: } michael@0: } michael@0: } else if (aStyle == NS_STYLE_TEXT_DECORATION_STYLE_WAVY) { michael@0: /** michael@0: * We will draw wavy line as: michael@0: * michael@0: * +-------------------------------------------+ michael@0: * |XXXXX XXXXXX XXXXXX | ^ michael@0: * |XXXXXX XXXXXXXX XXXXXXXX | | lineHeight michael@0: * |XXXXXXX XXXXXXXXXX XXXXXXXXXX| v michael@0: * | XXX XXX XXX XXX XX| michael@0: * | XXXXXXXXXX XXXXXXXXXX X| michael@0: * | XXXXXXXX XXXXXXXX | michael@0: * | XXXXXX XXXXXX | michael@0: * +-------------------------------------------+ michael@0: */ michael@0: r.height = lineHeight > 2.0 ? lineHeight * 4.0 : lineHeight * 3.0; michael@0: if (canLiftUnderline) { michael@0: if (r.Height() > suggestedMaxRectHeight) { michael@0: // Don't shrink the line height even if there is not enough space, michael@0: // because the thickness has some meaning. E.g., the 1px wavy line and michael@0: // 2px wavy line can be used for different meaning in IME selections michael@0: // at same time. michael@0: r.height = std::max(suggestedMaxRectHeight, lineHeight * 2.0); michael@0: } michael@0: } michael@0: } michael@0: michael@0: gfxFloat baseline = floor(aPt.y + aAscent + 0.5); michael@0: gfxFloat offset = 0.0; michael@0: switch (aDecoration) { michael@0: case NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE: michael@0: offset = aOffset; michael@0: if (canLiftUnderline) { michael@0: if (descentLimit < -offset + r.Height()) { michael@0: // If we can ignore the offset and the decoration line is overflowing, michael@0: // we should align the bottom edge of the decoration line rect if it's michael@0: // possible. Otherwise, we should lift up the top edge of the rect as michael@0: // far as possible. michael@0: gfxFloat offsetBottomAligned = -descentLimit + r.Height(); michael@0: gfxFloat offsetTopAligned = 0.0; michael@0: offset = std::min(offsetBottomAligned, offsetTopAligned); michael@0: } michael@0: } michael@0: break; michael@0: case NS_STYLE_TEXT_DECORATION_LINE_OVERLINE: michael@0: offset = aOffset - lineHeight + r.Height(); michael@0: break; michael@0: case NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH: { michael@0: gfxFloat extra = floor(r.Height() / 2.0 + 0.5); michael@0: extra = std::max(extra, lineHeight); michael@0: offset = aOffset - lineHeight + extra; michael@0: break; michael@0: } michael@0: default: michael@0: NS_ERROR("Invalid decoration value!"); michael@0: } michael@0: r.y = baseline - floor(offset + 0.5); michael@0: return r; michael@0: } michael@0: michael@0: // ------------------ michael@0: // ImageRenderer michael@0: // ------------------ michael@0: nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame, michael@0: const nsStyleImage* aImage, michael@0: uint32_t aFlags) michael@0: : mForFrame(aForFrame) michael@0: , mImage(aImage) michael@0: , mType(aImage->GetType()) michael@0: , mImageContainer(nullptr) michael@0: , mGradientData(nullptr) michael@0: , mPaintServerFrame(nullptr) michael@0: , mIsReady(false) michael@0: , mSize(0, 0) michael@0: , mFlags(aFlags) michael@0: { michael@0: } michael@0: michael@0: nsImageRenderer::~nsImageRenderer() michael@0: { michael@0: } michael@0: michael@0: bool michael@0: nsImageRenderer::PrepareImage() michael@0: { michael@0: if (mImage->IsEmpty()) michael@0: return false; michael@0: michael@0: if (!mImage->IsComplete()) { michael@0: // Make sure the image is actually decoding michael@0: mImage->StartDecoding(); michael@0: michael@0: // check again to see if we finished michael@0: if (!mImage->IsComplete()) { michael@0: // We can not prepare the image for rendering if it is not fully loaded. michael@0: // michael@0: // Special case: If we requested a sync decode and we have an image, push michael@0: // on through because the Draw() will do a sync decode then michael@0: nsCOMPtr img; michael@0: if (!((mFlags & FLAG_SYNC_DECODE_IMAGES) && michael@0: (mType == eStyleImageType_Image) && michael@0: (NS_SUCCEEDED(mImage->GetImageData()->GetImage(getter_AddRefs(img)))))) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: switch (mType) { michael@0: case eStyleImageType_Image: michael@0: { michael@0: nsCOMPtr srcImage; michael@0: DebugOnly rv = michael@0: mImage->GetImageData()->GetImage(getter_AddRefs(srcImage)); michael@0: NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv) && srcImage, michael@0: "If GetImage() is failing, mImage->IsComplete() " michael@0: "should have returned false"); michael@0: michael@0: if (!mImage->GetCropRect()) { michael@0: mImageContainer.swap(srcImage); michael@0: } else { michael@0: nsIntRect actualCropRect; michael@0: bool isEntireImage; michael@0: bool success = michael@0: mImage->ComputeActualCropRect(actualCropRect, &isEntireImage); michael@0: NS_ASSERTION(success, "ComputeActualCropRect() should not fail here"); michael@0: if (!success || actualCropRect.IsEmpty()) { michael@0: // The cropped image has zero size michael@0: return false; michael@0: } michael@0: if (isEntireImage) { michael@0: // The cropped image is identical to the source image michael@0: mImageContainer.swap(srcImage); michael@0: } else { michael@0: nsCOMPtr subImage = ImageOps::Clip(srcImage, actualCropRect); michael@0: mImageContainer.swap(subImage); michael@0: } michael@0: } michael@0: mIsReady = true; michael@0: break; michael@0: } michael@0: case eStyleImageType_Gradient: michael@0: mGradientData = mImage->GetGradientData(); michael@0: mIsReady = true; michael@0: break; michael@0: case eStyleImageType_Element: michael@0: { michael@0: nsAutoString elementId = michael@0: NS_LITERAL_STRING("#") + nsDependentString(mImage->GetElementId()); michael@0: nsCOMPtr targetURI; michael@0: nsCOMPtr base = mForFrame->GetContent()->GetBaseURI(); michael@0: nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), elementId, michael@0: mForFrame->GetContent()->GetCurrentDoc(), base); michael@0: nsSVGPaintingProperty* property = nsSVGEffects::GetPaintingPropertyForURI( michael@0: targetURI, mForFrame->FirstContinuation(), michael@0: nsSVGEffects::BackgroundImageProperty()); michael@0: if (!property) michael@0: return false; michael@0: mPaintServerFrame = property->GetReferencedFrame(); michael@0: michael@0: // If the referenced element doesn't have a frame we might still be able michael@0: // to paint it if it's an , , or