Bug 1950844 - Support spelling-error and grammar-error values of text-decoration-line. r=tlouw

Differential Revision: https://phabricator.services.mozilla.com/D239913
This commit is contained in:
Jonathan Kew
2025-02-28 12:56:58 +00:00
parent 19d81e0b15
commit ea08d9b07a
7 changed files with 69 additions and 49 deletions

View File

@@ -4912,8 +4912,29 @@ void nsTextFrame::GetTextDecorations(
} }
const nsStyleTextReset* const styleTextReset = context->StyleTextReset(); const nsStyleTextReset* const styleTextReset = context->StyleTextReset();
const StyleTextDecorationLine textDecorations = StyleTextDecorationLine textDecorations =
styleTextReset->mTextDecorationLine; styleTextReset->mTextDecorationLine;
bool ignoreSubproperties = false;
auto lineStyle = styleTextReset->mTextDecorationStyle;
if (textDecorations == StyleTextDecorationLine::SPELLING_ERROR ||
textDecorations == StyleTextDecorationLine::GRAMMAR_ERROR) {
nscolor lineColor;
float relativeSize;
useOverride = nsTextPaintStyle::GetSelectionUnderline(
this, nsTextPaintStyle::SelectionStyleIndex::SpellChecker, &lineColor,
&relativeSize, &lineStyle);
if (useOverride) {
// We don't currently have a SelectionStyleIndex::GrammarChecker; for
// now just use SpellChecker and change its color to green.
overrideColor =
textDecorations == StyleTextDecorationLine::SPELLING_ERROR
? lineColor
: NS_RGBA(0, 128, 0, 255);
textDecorations = StyleTextDecorationLine::UNDERLINE;
ignoreSubproperties = true;
}
}
if (!useOverride && if (!useOverride &&
(StyleTextDecorationLine::COLOR_OVERRIDE & textDecorations)) { (StyleTextDecorationLine::COLOR_OVERRIDE & textDecorations)) {
@@ -4961,7 +4982,6 @@ void nsTextFrame::GetTextDecorations(
physicalBlockStartOffset += physicalBlockStartOffset +=
vertical ? f->GetNormalPosition().x : f->GetNormalPosition().y; vertical ? f->GetNormalPosition().x : f->GetNormalPosition().y;
const auto style = styleTextReset->mTextDecorationStyle;
if (textDecorations) { if (textDecorations) {
nscolor color; nscolor color;
if (useOverride) { if (useOverride) {
@@ -4992,23 +5012,29 @@ void nsTextFrame::GetTextDecorations(
: StyleTextDecorationLine::OVERLINE; : StyleTextDecorationLine::OVERLINE;
const nsStyleText* const styleText = context->StyleText(); const nsStyleText* const styleText = context->StyleText();
const auto position = ignoreSubproperties
? StyleTextUnderlinePosition::AUTO
: styleText->mTextUnderlinePosition;
const auto offset = ignoreSubproperties ? LengthPercentageOrAuto::Auto()
: styleText->mTextUnderlineOffset;
const auto thickness = ignoreSubproperties
? StyleTextDecorationLength::Auto()
: styleTextReset->mTextDecorationThickness;
if (textDecorations & kUnderline) { if (textDecorations & kUnderline) {
aDecorations.mUnderlines.AppendElement(nsTextFrame::LineDecoration( aDecorations.mUnderlines.AppendElement(nsTextFrame::LineDecoration(
f, baselineOffset, styleText->mTextUnderlinePosition, f, baselineOffset, position, offset, thickness, color, lineStyle,
styleText->mTextUnderlineOffset, !ignoreSubproperties));
styleTextReset->mTextDecorationThickness, color, style));
} }
if (textDecorations & kOverline) { if (textDecorations & kOverline) {
aDecorations.mOverlines.AppendElement(nsTextFrame::LineDecoration( aDecorations.mOverlines.AppendElement(nsTextFrame::LineDecoration(
f, baselineOffset, styleText->mTextUnderlinePosition, f, baselineOffset, position, offset, thickness, color, lineStyle,
styleText->mTextUnderlineOffset, !ignoreSubproperties));
styleTextReset->mTextDecorationThickness, color, style));
} }
if (textDecorations & StyleTextDecorationLine::LINE_THROUGH) { if (textDecorations & StyleTextDecorationLine::LINE_THROUGH) {
aDecorations.mStrikes.AppendElement(nsTextFrame::LineDecoration( aDecorations.mStrikes.AppendElement(nsTextFrame::LineDecoration(
f, baselineOffset, styleText->mTextUnderlinePosition, f, baselineOffset, position, offset, thickness, color, lineStyle,
styleText->mTextUnderlineOffset, !ignoreSubproperties));
styleTextReset->mTextDecorationThickness, color, style));
} }
} }
@@ -5568,6 +5594,7 @@ struct nsTextFrame::PaintDecorationLineParams
DecorationType decorationType = DecorationType::Normal; DecorationType decorationType = DecorationType::Normal;
DrawPathCallbacks* callbacks = nullptr; DrawPathCallbacks* callbacks = nullptr;
bool paintingShadows = false; bool paintingShadows = false;
bool allowInkSkipping = true;
}; };
void nsTextFrame::PaintDecorationLine( void nsTextFrame::PaintDecorationLine(
@@ -5579,6 +5606,7 @@ void nsTextFrame::PaintDecorationLine(
params.color = aParams.overrideColor ? *aParams.overrideColor : aParams.color; params.color = aParams.overrideColor ? *aParams.overrideColor : aParams.color;
params.icoordInFrame = Float(aParams.icoordInFrame); params.icoordInFrame = Float(aParams.icoordInFrame);
params.baselineOffset = Float(aParams.baselineOffset); params.baselineOffset = Float(aParams.baselineOffset);
params.allowInkSkipping = aParams.allowInkSkipping;
if (aParams.callbacks) { if (aParams.callbacks) {
Rect path = nsCSSRendering::DecorationLineToPath(params); Rect path = nsCSSRendering::DecorationLineToPath(params);
if (aParams.decorationType == DecorationType::Normal) { if (aParams.decorationType == DecorationType::Normal) {
@@ -7172,6 +7200,7 @@ void nsTextFrame::DrawTextRunAndDecorations(
app, dec.mFrame, wm.IsCentralBaseline(), swapUnderline); app, dec.mFrame, wm.IsCentralBaseline(), swapUnderline);
params.style = dec.mStyle; params.style = dec.mStyle;
params.allowInkSkipping = dec.mAllowInkSkipping;
PaintDecorationLine(params); PaintDecorationLine(params);
}; };

View File

@@ -860,11 +860,11 @@ class nsTextFrame : public nsIFrame {
const PaintShadowParams& aParams); const PaintShadowParams& aParams);
struct LineDecoration { struct LineDecoration {
nsIFrame* mFrame; nsIFrame* const mFrame;
// This is represents the offset from our baseline to mFrame's baseline; // This is represents the offset from our baseline to mFrame's baseline;
// positive offsets are *above* the baseline and negative offsets below // positive offsets are *above* the baseline and negative offsets below
nscoord mBaselineOffset; const nscoord mBaselineOffset;
// This represents the offset from the initial position of the underline // This represents the offset from the initial position of the underline
const mozilla::LengthPercentageOrAuto mTextUnderlineOffset; const mozilla::LengthPercentageOrAuto mTextUnderlineOffset;
@@ -872,26 +872,30 @@ class nsTextFrame : public nsIFrame {
// for CSS property text-decoration-thickness, the width refers to the // for CSS property text-decoration-thickness, the width refers to the
// thickness of the decoration line // thickness of the decoration line
const mozilla::StyleTextDecorationLength mTextDecorationThickness; const mozilla::StyleTextDecorationLength mTextDecorationThickness;
nscolor mColor; const nscolor mColor;
mozilla::StyleTextDecorationStyle mStyle; const mozilla::StyleTextDecorationStyle mStyle;
// The text-underline-position property; affects the underline offset only // The text-underline-position property; affects the underline offset only
// if mTextUnderlineOffset is auto. // if mTextUnderlineOffset is auto.
const mozilla::StyleTextUnderlinePosition mTextUnderlinePosition; const mozilla::StyleTextUnderlinePosition mTextUnderlinePosition;
const bool mAllowInkSkipping;
LineDecoration(nsIFrame* const aFrame, const nscoord aOff, LineDecoration(nsIFrame* const aFrame, const nscoord aOff,
mozilla::StyleTextUnderlinePosition aUnderlinePosition, const mozilla::StyleTextUnderlinePosition aUnderlinePosition,
const mozilla::LengthPercentageOrAuto& aUnderlineOffset, const mozilla::LengthPercentageOrAuto& aUnderlineOffset,
const mozilla::StyleTextDecorationLength& aDecThickness, const mozilla::StyleTextDecorationLength& aDecThickness,
const nscolor aColor, const nscolor aColor,
const mozilla::StyleTextDecorationStyle aStyle) const mozilla::StyleTextDecorationStyle aStyle,
const bool aAllowInkSkipping)
: mFrame(aFrame), : mFrame(aFrame),
mBaselineOffset(aOff), mBaselineOffset(aOff),
mTextUnderlineOffset(aUnderlineOffset), mTextUnderlineOffset(aUnderlineOffset),
mTextDecorationThickness(aDecThickness), mTextDecorationThickness(aDecThickness),
mColor(aColor), mColor(aColor),
mStyle(aStyle), mStyle(aStyle),
mTextUnderlinePosition(aUnderlinePosition) {} mTextUnderlinePosition(aUnderlinePosition),
mAllowInkSkipping(aAllowInkSkipping) {}
LineDecoration(const LineDecoration& aOther) = default; LineDecoration(const LineDecoration& aOther) = default;
@@ -901,7 +905,8 @@ class nsTextFrame : public nsIFrame {
mBaselineOffset == aOther.mBaselineOffset && mBaselineOffset == aOther.mBaselineOffset &&
mTextUnderlinePosition == aOther.mTextUnderlinePosition && mTextUnderlinePosition == aOther.mTextUnderlinePosition &&
mTextUnderlineOffset == aOther.mTextUnderlineOffset && mTextUnderlineOffset == aOther.mTextUnderlineOffset &&
mTextDecorationThickness == aOther.mTextDecorationThickness; mTextDecorationThickness == aOther.mTextDecorationThickness &&
mAllowInkSkipping == aOther.mAllowInkSkipping;
} }
bool operator!=(const LineDecoration& aOther) const { bool operator!=(const LineDecoration& aOther) const {

View File

@@ -1153,9 +1153,10 @@ static nsIFrame* GetPageSequenceForCanvas(const nsIFrame* aCanvasFrame) {
return ps; return ps;
} }
auto nsCSSRendering::FindEffectiveBackgroundColor( auto nsCSSRendering::FindEffectiveBackgroundColor(nsIFrame* aFrame,
nsIFrame* aFrame, bool aStopAtThemed, bool aStopAtThemed,
bool aPreferBodyToCanvas) -> EffectiveBackgroundColor { bool aPreferBodyToCanvas)
-> EffectiveBackgroundColor {
MOZ_ASSERT(aFrame); MOZ_ASSERT(aFrame);
nsPresContext* pc = aFrame->PresContext(); nsPresContext* pc = aFrame->PresContext();
auto BgColorIfNotTransparent = [&](nsIFrame* aFrame) -> Maybe<nscolor> { auto BgColorIfNotTransparent = [&](nsIFrame* aFrame) -> Maybe<nscolor> {
@@ -4095,21 +4096,16 @@ void nsCSSRendering::PaintDecorationLine(
aFrame->StyleText()->mTextDecorationSkipInk; aFrame->StyleText()->mTextDecorationSkipInk;
bool skipInkEnabled = bool skipInkEnabled =
skipInk != mozilla::StyleTextDecorationSkipInk::None && skipInk != mozilla::StyleTextDecorationSkipInk::None &&
aParams.decoration != StyleTextDecorationLine::LINE_THROUGH; aParams.decoration != StyleTextDecorationLine::LINE_THROUGH &&
aParams.allowInkSkipping && aFrame->IsTextFrame();
if (!skipInkEnabled || aParams.glyphRange.Length() == 0) { if (!skipInkEnabled || aParams.glyphRange.Length() == 0) {
PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect); PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
return; return;
} }
// check if the frame is a text frame or not // Must be a text frame, otherwise skipInkEnabled (above) would be false.
nsTextFrame* textFrame = nullptr; nsTextFrame* textFrame = static_cast<nsTextFrame*>(aFrame);
if (aFrame->IsTextFrame()) {
textFrame = static_cast<nsTextFrame*>(aFrame);
} else {
PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
return;
}
// get text run and current text offset (for line wrapping) // get text run and current text offset (for line wrapping)
gfxTextRun* textRun = gfxTextRun* textRun =

View File

@@ -610,6 +610,8 @@ struct nsCSSRendering {
// Baseline offset being applied to this text (block-direction adjustment // Baseline offset being applied to this text (block-direction adjustment
// applied to glyph positions when computing skip-ink intercepts). // applied to glyph positions when computing skip-ink intercepts).
Float baselineOffset = 0.0f; Float baselineOffset = 0.0f;
// Whether text-decoration-skip-ink behavior is to be supported.
bool allowInkSkipping = true;
}; };
/** /**

View File

@@ -274,7 +274,7 @@ impl ToCss for TextOverflow {
ToResolvedValue, ToResolvedValue,
ToShmem, ToShmem,
)] )]
#[css(bitflags(single = "none", mixed = "underline,overline,line-through,blink"))] #[css(bitflags(single = "none,spelling-error,grammar-error", mixed = "underline,overline,line-through,blink"))]
#[repr(C)] #[repr(C)]
/// Specified keyword values for the text-decoration-line property. /// Specified keyword values for the text-decoration-line property.
pub struct TextDecorationLine(u8); pub struct TextDecorationLine(u8);
@@ -290,6 +290,10 @@ bitflags! {
const LINE_THROUGH = 1 << 2; const LINE_THROUGH = 1 << 2;
/// blink /// blink
const BLINK = 1 << 3; const BLINK = 1 << 3;
/// spelling-error
const SPELLING_ERROR = 1 << 4;
/// grammar-error
const GRAMMAR_ERROR = 1 << 5;
/// Only set by presentation attributes /// Only set by presentation attributes
/// ///
/// Setting this will mean that text-decorations use the color /// Setting this will mean that text-decorations use the color
@@ -298,7 +302,7 @@ bitflags! {
/// For example, this gives <a href=foo><font color="red">text</font></a> /// For example, this gives <a href=foo><font color="red">text</font></a>
/// a red text decoration /// a red text decoration
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
const COLOR_OVERRIDE = 0x10; const COLOR_OVERRIDE = 1 << 7;
} }
} }

View File

@@ -1,8 +0,0 @@
[text-decoration-line-computed.html]
expected:
if (os == "android") and fission: [TIMEOUT, OK]
[Property text-decoration-line value 'spelling-error']
expected: FAIL
[Property text-decoration-line value 'grammar-error']
expected: FAIL

View File

@@ -1,8 +0,0 @@
[text-decoration-line-valid.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[e.style['text-decoration-line'\] = "spelling-error" should set the property value]
expected: FAIL
[e.style['text-decoration-line'\] = "grammar-error" should set the property value]
expected: FAIL