diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index e81da555a4eb..9afefc5c28bf 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -226,6 +226,7 @@ struct nsTextFrame::DrawTextRunParams { float textStrokeWidth = 0.0f; bool drawSoftHyphen = false; bool hasTextShadow = false; + bool paintingShadows = false; DrawTextRunParams(gfxContext* aContext, mozilla::gfx::PaletteCache& aPaletteCache) : context(aContext), paletteCache(aPaletteCache) {} @@ -276,6 +277,7 @@ struct nsTextFrame::PaintShadowParams { Point framePt; Point textBaselinePt; gfxContext* context; + DrawPathCallbacks* callbacks = nullptr; nscolor foregroundColor = NS_RGBA(0, 0, 0, 0); const ClipEdges* clipEdges = nullptr; PropertyProvider* provider = nullptr; @@ -5459,6 +5461,7 @@ struct nsTextFrame::PaintDecorationLineParams gfxFloat baselineOffset = 0.0f; DecorationType decorationType = DecorationType::Normal; DrawPathCallbacks* callbacks = nullptr; + bool paintingShadows = false; }; void nsTextFrame::PaintDecorationLine( @@ -5473,9 +5476,11 @@ void nsTextFrame::PaintDecorationLine( if (aParams.callbacks) { Rect path = nsCSSRendering::DecorationLineToPath(params); if (aParams.decorationType == DecorationType::Normal) { - aParams.callbacks->PaintDecorationLine(path, params.color); + aParams.callbacks->PaintDecorationLine(path, aParams.paintingShadows, + params.color); } else { - aParams.callbacks->PaintSelectionDecorationLine(path, params.color); + aParams.callbacks->PaintSelectionDecorationLine( + path, aParams.paintingShadows, params.color); } } else { nsCSSRendering::PaintDecorationLine(this, *aParams.context->GetDrawTarget(), @@ -5937,6 +5942,7 @@ void nsTextFrame::PaintOneShadow(const PaintShadowParams& aParams, gfxFloat advanceWidth; nsTextPaintStyle textPaintStyle(this); DrawTextParams params(shadowContext, PresContext()->FontPaletteCache()); + params.paintingShadows = true; params.advanceWidth = &advanceWidth; params.dirtyRect = aParams.dirtyRect; params.framePt = aParams.framePt + shadowGfxOffset; @@ -5944,9 +5950,10 @@ void nsTextFrame::PaintOneShadow(const PaintShadowParams& aParams, params.textStyle = &textPaintStyle; params.textColor = aParams.context == shadowContext ? shadowColor : NS_RGB(0, 0, 0); + params.callbacks = aParams.callbacks; params.clipEdges = aParams.clipEdges; params.drawSoftHyphen = HasAnyStateBits(TEXT_HYPHEN_BREAK); - // Multi-color shadow is not allowed, so we use the same color of the text + // Multi-color shadow is not allowed, so we use the same color as the text // color. params.decorationOverrideColor = ¶ms.textColor; params.fontPalette = StyleFont()->GetFontPaletteAtom(); @@ -6252,6 +6259,7 @@ bool nsTextFrame::PaintTextWithSelectionColors( PaintShadowParams shadowParams(aParams); shadowParams.provider = aParams.provider; + shadowParams.callbacks = aParams.callbacks; shadowParams.clipEdges = &aClipEdges; // Draw text @@ -6814,6 +6822,7 @@ void nsTextFrame::PaintText(const PaintTextParams& aParams, shadowParams.textBaselinePt = textBaselinePt; shadowParams.leftSideOffset = snappedStartEdge; shadowParams.provider = &provider; + shadowParams.callbacks = aParams.callbacks; shadowParams.foregroundColor = foregroundColor; shadowParams.clipEdges = &clipEdges; PaintShadows(textStyle->mTextShadow.AsSpan(), shadowParams); @@ -6853,7 +6862,8 @@ static void DrawTextRun(const gfxTextRun* aTextRun, params.callbacks = aParams.callbacks; params.hasTextShadow = aParams.hasTextShadow; if (aParams.callbacks) { - aParams.callbacks->NotifyBeforeText(aParams.textColor); + aParams.callbacks->NotifyBeforeText(aParams.paintingShadows, + aParams.textColor); params.drawMode = DrawMode::GLYPH_PATH; aTextRun->Draw(aRange, aTextBaselinePt, params); aParams.callbacks->NotifyAfterText(); @@ -6994,6 +7004,7 @@ void nsTextFrame::DrawTextRunAndDecorations( params.callbacks = aParams.callbacks; params.glyphRange = aParams.glyphRange; params.provider = aParams.provider; + params.paintingShadows = aParams.paintingShadows; // pt is the physical point where the decoration is to be drawn, // relative to the frame; one of its coordinates will be updated below. params.pt = Point(x / app, y / app); diff --git a/layout/generic/nsTextFrame.h b/layout/generic/nsTextFrame.h index 568d3333c248..707d39dba20e 100644 --- a/layout/generic/nsTextFrame.h +++ b/layout/generic/nsTextFrame.h @@ -513,20 +513,22 @@ class nsTextFrame : public nsIFrame { * Called before (for under/over-line) or after (for line-through) the text * is drawn to have a text decoration line drawn. */ - virtual void PaintDecorationLine(Rect aPath, nscolor aColor) {} + virtual void PaintDecorationLine(Rect aPath, bool aPaintingShadows, + nscolor aColor) {} /** * Called after selected text is drawn to have a decoration line drawn over * the text. (All types of text decoration are drawn after the text when * text is selected.) */ - virtual void PaintSelectionDecorationLine(Rect aPath, nscolor aColor) {} + virtual void PaintSelectionDecorationLine(Rect aPath, bool aPaintingShadows, + nscolor aColor) {} /** * Called just before any paths have been emitted to the gfxContext * for the glyphs of the frame's text. */ - virtual void NotifyBeforeText(nscolor aColor) {} + virtual void NotifyBeforeText(bool aPaintingShadows, nscolor aColor) {} /** * Called just after all the paths have been emitted to the gfxContext diff --git a/layout/svg/SVGTextFrame.cpp b/layout/svg/SVGTextFrame.cpp index 0add35ffa3d6..8429d072e202 100644 --- a/layout/svg/SVGTextFrame.cpp +++ b/layout/svg/SVGTextFrame.cpp @@ -2412,9 +2412,11 @@ class SVGTextDrawPathCallbacks final : public nsTextFrame::DrawPathCallbacks { void NotifySelectionBackgroundNeedsFill(const Rect& aBackgroundRect, nscolor aColor, DrawTarget& aDrawTarget) override; - void PaintDecorationLine(Rect aPath, nscolor aColor) override; - void PaintSelectionDecorationLine(Rect aPath, nscolor aColor) override; - void NotifyBeforeText(nscolor aColor) override; + void PaintDecorationLine(Rect aPath, bool aPaintingShadows, + nscolor aColor) override; + void PaintSelectionDecorationLine(Rect aPath, bool aPaintingShadows, + nscolor aColor) override; + void NotifyBeforeText(bool aPaintingShadows, nscolor aColor) override; void NotifyGlyphPathEmitted() override; void NotifyAfterText() override; @@ -2453,6 +2455,12 @@ class SVGTextDrawPathCallbacks final : public nsTextFrame::DrawPathCallbacks { */ void StrokeGeometry(); + /* + * Takes a colour and modifies it to account for opacity properties. + */ + void ApplyOpacity(sRGBColor& aColor, const StyleSVGPaint& aPaint, + const StyleSVGOpacity& aOpacity) const; + SVGTextFrame* const mSVGTextFrame; gfxContext& mContext; nsTextFrame* const mFrame; @@ -2466,6 +2474,11 @@ class SVGTextDrawPathCallbacks final : public nsTextFrame::DrawPathCallbacks { * painting selections or IME decorations. */ nscolor mColor; + + /** + * Whether we're painting text shadows. + */ + bool mPaintingShadows; }; void SVGTextDrawPathCallbacks::NotifySelectionBackgroundNeedsFill( @@ -2486,8 +2499,10 @@ void SVGTextDrawPathCallbacks::NotifySelectionBackgroundNeedsFill( } } -void SVGTextDrawPathCallbacks::NotifyBeforeText(nscolor aColor) { +void SVGTextDrawPathCallbacks::NotifyBeforeText(bool aPaintingShadows, + nscolor aColor) { mColor = aColor; + mPaintingShadows = aPaintingShadows; SetupContext(); mContext.NewPath(); } @@ -2499,8 +2514,11 @@ void SVGTextDrawPathCallbacks::NotifyGlyphPathEmitted() { void SVGTextDrawPathCallbacks::NotifyAfterText() { mContext.Restore(); } -void SVGTextDrawPathCallbacks::PaintDecorationLine(Rect aPath, nscolor aColor) { +void SVGTextDrawPathCallbacks::PaintDecorationLine(Rect aPath, + bool aPaintingShadows, + nscolor aColor) { mColor = aColor; + mPaintingShadows = aPaintingShadows; AntialiasMode aaMode = SVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering); @@ -2513,14 +2531,15 @@ void SVGTextDrawPathCallbacks::PaintDecorationLine(Rect aPath, nscolor aColor) { mContext.Restore(); } -void SVGTextDrawPathCallbacks::PaintSelectionDecorationLine(Rect aPath, - nscolor aColor) { +void SVGTextDrawPathCallbacks::PaintSelectionDecorationLine( + Rect aPath, bool aPaintingShadows, nscolor aColor) { if (IsClipPathChild()) { // Don't paint selection decorations when in a clip path. return; } mColor = aColor; + mPaintingShadows = aPaintingShadows; mContext.Save(); mContext.NewPath(); @@ -2560,6 +2579,17 @@ void SVGTextDrawPathCallbacks::HandleTextGeometry() { } } +void SVGTextDrawPathCallbacks::ApplyOpacity( + sRGBColor& aColor, const StyleSVGPaint& aPaint, + const StyleSVGOpacity& aOpacity) const { + if (aPaint.kind.tag == StyleSVGPaintKind::Tag::Color) { + aColor.a *= + sRGBColor::FromABGR(aPaint.kind.AsColor().CalcColor(*mFrame->Style())) + .a; + } + aColor.a *= SVGUtils::GetOpacity(aOpacity, /*aContextPaint*/ nullptr); +} + void SVGTextDrawPathCallbacks::MakeFillPattern(GeneralPattern* aOutPattern) { if (mColor == NS_SAME_AS_FOREGROUND_COLOR || mColor == NS_40PERCENT_FOREGROUND_COLOR) { @@ -2571,7 +2601,12 @@ void SVGTextDrawPathCallbacks::MakeFillPattern(GeneralPattern* aOutPattern) { return; } - aOutPattern->InitColorPattern(ToDeviceColor(mColor)); + sRGBColor color(sRGBColor::FromABGR(mColor)); + if (mPaintingShadows) { + ApplyOpacity(color, mFrame->StyleSVG()->mFill, + mFrame->StyleSVG()->mFillOpacity); + } + aOutPattern->InitColorPattern(ToDeviceColor(color)); } void SVGTextDrawPathCallbacks::FillAndStrokeGeometry() { @@ -2606,6 +2641,9 @@ void SVGTextDrawPathCallbacks::FillAndStrokeGeometry() { } void SVGTextDrawPathCallbacks::FillGeometry() { + if (mFrame->StyleSVG()->mFill.kind.IsNone()) { + return; + } GeneralPattern fillPattern; MakeFillPattern(&fillPattern); if (fillPattern.GetPattern()) { @@ -2621,39 +2659,44 @@ void SVGTextDrawPathCallbacks::FillGeometry() { void SVGTextDrawPathCallbacks::StrokeGeometry() { // We don't paint the stroke when we are filling with a selection color. - if (mColor == NS_SAME_AS_FOREGROUND_COLOR || - mColor == NS_40PERCENT_FOREGROUND_COLOR) { - if (SVGUtils::HasStroke(mFrame, /*aContextPaint*/ nullptr)) { - GeneralPattern strokePattern; - SVGUtils::MakeStrokePatternFor(mFrame, &mContext, &strokePattern, - mImgParams, /*aContextPaint*/ nullptr); - if (strokePattern.GetPattern()) { - if (!mFrame->GetParent()->GetContent()->IsSVGElement()) { - // The cast that follows would be unsafe - MOZ_ASSERT(false, "Our nsTextFrame's parent's content should be SVG"); - return; - } - SVGElement* svgOwner = - static_cast(mFrame->GetParent()->GetContent()); + if (!(mColor == NS_SAME_AS_FOREGROUND_COLOR || + mColor == NS_40PERCENT_FOREGROUND_COLOR || mPaintingShadows)) { + return; + } - // Apply any stroke-specific transform - gfxMatrix outerSVGToUser; - if (SVGUtils::GetNonScalingStrokeTransform(mFrame, &outerSVGToUser) && - outerSVGToUser.Invert()) { - mContext.Multiply(outerSVGToUser); - } + if (!SVGUtils::HasStroke(mFrame, /*aContextPaint*/ nullptr)) { + return; + } - RefPtr path = mContext.GetPath(); - SVGContentUtils::AutoStrokeOptions strokeOptions; - SVGContentUtils::GetStrokeOptions(&strokeOptions, svgOwner, - mFrame->Style(), - /*aContextPaint*/ nullptr); - DrawOptions drawOptions; - drawOptions.mAntialiasMode = - SVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering); - mContext.GetDrawTarget()->Stroke(path, strokePattern, strokeOptions); - } + GeneralPattern strokePattern; + if (mPaintingShadows) { + sRGBColor color(sRGBColor::FromABGR(mColor)); + ApplyOpacity(color, mFrame->StyleSVG()->mStroke, + mFrame->StyleSVG()->mStrokeOpacity); + strokePattern.InitColorPattern(ToDeviceColor(color)); + } else { + SVGUtils::MakeStrokePatternFor(mFrame, &mContext, &strokePattern, + mImgParams, /*aContextPaint*/ nullptr); + } + if (strokePattern.GetPattern()) { + SVGElement* svgOwner = + SVGElement::FromNode(mFrame->GetParent()->GetContent()); + + // Apply any stroke-specific transform + gfxMatrix outerSVGToUser; + if (SVGUtils::GetNonScalingStrokeTransform(mFrame, &outerSVGToUser) && + outerSVGToUser.Invert()) { + mContext.Multiply(outerSVGToUser); } + + RefPtr path = mContext.GetPath(); + SVGContentUtils::AutoStrokeOptions strokeOptions; + SVGContentUtils::GetStrokeOptions(&strokeOptions, svgOwner, mFrame->Style(), + /*aContextPaint*/ nullptr); + DrawOptions drawOptions; + drawOptions.mAntialiasMode = + SVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering); + mContext.GetDrawTarget()->Stroke(path, strokePattern, strokeOptions); } } @@ -4910,11 +4953,20 @@ bool SVGTextFrame::ShouldRenderAsPath(nsTextFrame* aFrame, const nsStyleSVG* style = aFrame->StyleSVG(); - // Fill is a non-solid paint, has a non-default fill-rule or has - // non-1 opacity. + // Fill is a non-solid paint or is not opaque. if (!(style->mFill.kind.IsNone() || - (style->mFill.kind.IsColor() && style->mFillOpacity.IsOpacity() && - style->mFillOpacity.AsOpacity() == 1))) { + (style->mFill.kind.IsColor() && + SVGUtils::GetOpacity(style->mFillOpacity, /*aContextPaint*/ nullptr) == + 1.0f))) { + return true; + } + + // If we're going to need to draw a non-opaque shadow. + // It's possible nsTextFrame will support non-opaque shadows in the future, + // in which case this test can be removed. + if (style->mFill.kind.IsColor() && aFrame->StyleText()->HasTextShadow() && + NS_GET_A(style->mFill.kind.AsColor().CalcColor(*aFrame->Style())) != + 0xFF) { return true; } diff --git a/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-fill-none-ref.html b/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-fill-none-ref.html new file mode 100644 index 000000000000..916727217837 --- /dev/null +++ b/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-fill-none-ref.html @@ -0,0 +1,8 @@ + + + + Hello + Hello + diff --git a/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-fill-none.html b/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-fill-none.html new file mode 100644 index 000000000000..389db4a3426e --- /dev/null +++ b/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-fill-none.html @@ -0,0 +1,10 @@ + +CSS Test: 'text-shadow' respects 'fill="none"' + + + + + Hello + diff --git a/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-fill-opacity-ref.html b/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-fill-opacity-ref.html new file mode 100644 index 000000000000..73c878b89dfa --- /dev/null +++ b/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-fill-opacity-ref.html @@ -0,0 +1,7 @@ + + + + Hello + diff --git a/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-fill-opacity.html b/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-fill-opacity.html new file mode 100644 index 000000000000..1ac577bf76e4 --- /dev/null +++ b/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-fill-opacity.html @@ -0,0 +1,10 @@ + +CSS Test: 'text-shadow' respects non-opaque fill + + + + + Hello + diff --git a/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-stroke-dasharray-ref.html b/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-stroke-dasharray-ref.html new file mode 100644 index 000000000000..63e45f4e1177 --- /dev/null +++ b/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-stroke-dasharray-ref.html @@ -0,0 +1,8 @@ + + + + Hello + Hello + diff --git a/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-stroke-dasharray.html b/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-stroke-dasharray.html new file mode 100644 index 000000000000..bd77d85edf4f --- /dev/null +++ b/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-stroke-dasharray.html @@ -0,0 +1,10 @@ + +CSS Test: 'text-shadow' respects stroke-dasharray + + + + + Hello + diff --git a/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-stroke-ref.html b/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-stroke-ref.html new file mode 100644 index 000000000000..d3905fbfbc96 --- /dev/null +++ b/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-stroke-ref.html @@ -0,0 +1,8 @@ + + + + Hello + Hello + diff --git a/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-stroke.html b/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-stroke.html new file mode 100644 index 000000000000..b65348fd0f20 --- /dev/null +++ b/testing/web-platform/tests/css/css-text-decor/text-shadow/svg-stroke.html @@ -0,0 +1,10 @@ + +CSS Test: 'text-shadow' respects stroke + + + + + Hello +