fune/layout/generic/nsTextPaintStyle.cpp
Stanca Serban c9bea63c35 Backed out 6 changesets (bug 1867939) for causing wpt failures in target-text-010.html. CLOSED TREE
Backed out changeset 44101c258e52 (bug 1867939)
Backed out changeset c53267e1b460 (bug 1867939)
Backed out changeset 0748839408e5 (bug 1867939)
Backed out changeset 38631fbd2f2f (bug 1867939)
Backed out changeset d77b9257c842 (bug 1867939)
Backed out changeset 0d269b891421 (bug 1867939)
2024-04-04 00:50:50 +03:00

580 lines
20 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsTextPaintStyle.h"
#include "nsCSSColorUtils.h"
#include "nsCSSRendering.h"
#include "nsFrameSelection.h"
#include "nsLayoutUtils.h"
#include "nsTextFrame.h"
#include "nsStyleConsts.h"
#include "mozilla/LookAndFeel.h"
using namespace mozilla;
using namespace mozilla::dom;
static nscolor EnsureDifferentColors(nscolor colorA, nscolor colorB) {
if (colorA == colorB) {
nscolor res;
res = NS_RGB(NS_GET_R(colorA) ^ 0xff, NS_GET_G(colorA) ^ 0xff,
NS_GET_B(colorA) ^ 0xff);
return res;
}
return colorA;
}
nsTextPaintStyle::nsTextPaintStyle(nsTextFrame* aFrame)
: mFrame(aFrame),
mPresContext(aFrame->PresContext()),
mInitCommonColors(false),
mInitSelectionColorsAndShadow(false),
mResolveColors(true),
mSelectionTextColor(NS_RGBA(0, 0, 0, 0)),
mSelectionBGColor(NS_RGBA(0, 0, 0, 0)),
mSufficientContrast(0),
mFrameBackgroundColor(NS_RGBA(0, 0, 0, 0)),
mSystemFieldForegroundColor(NS_RGBA(0, 0, 0, 0)),
mSystemFieldBackgroundColor(NS_RGBA(0, 0, 0, 0)) {}
bool nsTextPaintStyle::EnsureSufficientContrast(nscolor* aForeColor,
nscolor* aBackColor) {
InitCommonColors();
const bool sameAsForeground = *aForeColor == NS_SAME_AS_FOREGROUND_COLOR;
if (sameAsForeground) {
*aForeColor = GetTextColor();
}
// If the combination of selection background color and frame background color
// has sufficient contrast, don't exchange the selection colors.
//
// Note we use a different threshold here: mSufficientContrast is for contrast
// between text and background colors, but since we're diffing two
// backgrounds, we don't need that much contrast. We match the heuristic from
// NS_SUFFICIENT_LUMINOSITY_DIFFERENCE_BG and use 20% of mSufficientContrast.
const int32_t minLuminosityDifferenceForBackground = mSufficientContrast / 5;
const int32_t backLuminosityDifference =
NS_LUMINOSITY_DIFFERENCE(*aBackColor, mFrameBackgroundColor);
if (backLuminosityDifference >= minLuminosityDifferenceForBackground) {
return false;
}
// Otherwise, we should use the higher-contrast color for the selection
// background color.
//
// For NS_SAME_AS_FOREGROUND_COLOR we only do this if the background is
// totally indistinguishable, that is, if the luminosity difference is 0.
if (sameAsForeground && backLuminosityDifference) {
return false;
}
int32_t foreLuminosityDifference =
NS_LUMINOSITY_DIFFERENCE(*aForeColor, mFrameBackgroundColor);
if (backLuminosityDifference < foreLuminosityDifference) {
std::swap(*aForeColor, *aBackColor);
// Ensure foreground color is opaque to guarantee contrast.
*aForeColor = NS_RGB(NS_GET_R(*aForeColor), NS_GET_G(*aForeColor),
NS_GET_B(*aForeColor));
return true;
}
return false;
}
nscolor nsTextPaintStyle::GetTextColor() {
if (mFrame->IsInSVGTextSubtree()) {
if (!mResolveColors) {
return NS_SAME_AS_FOREGROUND_COLOR;
}
const nsStyleSVG* style = mFrame->StyleSVG();
switch (style->mFill.kind.tag) {
case StyleSVGPaintKind::Tag::None:
return NS_RGBA(0, 0, 0, 0);
case StyleSVGPaintKind::Tag::Color:
return nsLayoutUtils::GetColor(mFrame, &nsStyleSVG::mFill);
default:
NS_ERROR("cannot resolve SVG paint to nscolor");
return NS_RGBA(0, 0, 0, 255);
}
}
return nsLayoutUtils::GetColor(mFrame, &nsStyleText::mWebkitTextFillColor);
}
bool nsTextPaintStyle::GetSelectionColors(nscolor* aForeColor,
nscolor* aBackColor) {
NS_ASSERTION(aForeColor, "aForeColor is null");
NS_ASSERTION(aBackColor, "aBackColor is null");
if (!InitSelectionColorsAndShadow()) {
return false;
}
*aForeColor = mSelectionTextColor;
*aBackColor = mSelectionBGColor;
return true;
}
void nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor,
nscolor* aBackColor) {
NS_ASSERTION(aForeColor, "aForeColor is null");
NS_ASSERTION(aBackColor, "aBackColor is null");
const nsFrameSelection* frameSelection = mFrame->GetConstFrameSelection();
const Selection* selection =
frameSelection->GetSelection(SelectionType::eFind);
const SelectionCustomColors* customColors = nullptr;
if (selection) {
customColors = selection->GetCustomColors();
}
if (!customColors) {
nscolor backColor = LookAndFeel::Color(
LookAndFeel::ColorID::TextHighlightBackground, mFrame);
nscolor foreColor = LookAndFeel::Color(
LookAndFeel::ColorID::TextHighlightForeground, mFrame);
EnsureSufficientContrast(&foreColor, &backColor);
*aForeColor = foreColor;
*aBackColor = backColor;
return;
}
if (customColors->mForegroundColor && customColors->mBackgroundColor) {
nscolor foreColor = *customColors->mForegroundColor;
nscolor backColor = *customColors->mBackgroundColor;
if (EnsureSufficientContrast(&foreColor, &backColor) &&
customColors->mAltForegroundColor &&
customColors->mAltBackgroundColor) {
foreColor = *customColors->mAltForegroundColor;
backColor = *customColors->mAltBackgroundColor;
}
*aForeColor = foreColor;
*aBackColor = backColor;
return;
}
InitCommonColors();
if (customColors->mBackgroundColor) {
// !mForegroundColor means "currentColor"; the current color of the text.
nscolor foreColor = GetTextColor();
nscolor backColor = *customColors->mBackgroundColor;
int32_t luminosityDifference =
NS_LUMINOSITY_DIFFERENCE(foreColor, backColor);
if (mSufficientContrast > luminosityDifference &&
customColors->mAltBackgroundColor) {
int32_t altLuminosityDifference = NS_LUMINOSITY_DIFFERENCE(
foreColor, *customColors->mAltBackgroundColor);
if (luminosityDifference < altLuminosityDifference) {
backColor = *customColors->mAltBackgroundColor;
}
}
*aForeColor = foreColor;
*aBackColor = backColor;
return;
}
if (customColors->mForegroundColor) {
nscolor foreColor = *customColors->mForegroundColor;
// !mBackgroundColor means "transparent"; the current color of the
// background.
int32_t luminosityDifference =
NS_LUMINOSITY_DIFFERENCE(foreColor, mFrameBackgroundColor);
if (mSufficientContrast > luminosityDifference &&
customColors->mAltForegroundColor) {
int32_t altLuminosityDifference = NS_LUMINOSITY_DIFFERENCE(
*customColors->mForegroundColor, mFrameBackgroundColor);
if (luminosityDifference < altLuminosityDifference) {
foreColor = *customColors->mAltForegroundColor;
}
}
*aForeColor = foreColor;
*aBackColor = NS_TRANSPARENT;
return;
}
// There are neither mForegroundColor nor mBackgroundColor.
*aForeColor = GetTextColor();
*aBackColor = NS_TRANSPARENT;
}
bool nsTextPaintStyle::GetCustomHighlightTextColor(nsAtom* aHighlightName,
nscolor* aForeColor) {
NS_ASSERTION(aForeColor, "aForeColor is null");
// non-existing highlights will be stored as `aHighlightName->nullptr`,
// so subsequent calls only need a hashtable lookup and don't have
// to enter the style engine.
RefPtr<ComputedStyle> highlightStyle =
mCustomHighlightPseudoStyles.LookupOrInsertWith(
aHighlightName, [this, &aHighlightName] {
return mFrame->ComputeHighlightSelectionStyle(aHighlightName);
});
if (!highlightStyle) {
// highlight `aHighlightName` doesn't exist or has no style rules.
return false;
}
*aForeColor = highlightStyle->GetVisitedDependentColor(
&nsStyleText::mWebkitTextFillColor);
return highlightStyle->HasAuthorSpecifiedTextColor();
}
bool nsTextPaintStyle::GetCustomHighlightBackgroundColor(nsAtom* aHighlightName,
nscolor* aBackColor) {
NS_ASSERTION(aBackColor, "aBackColor is null");
// non-existing highlights will be stored as `aHighlightName->nullptr`,
// so subsequent calls only need a hashtable lookup and don't have
// to enter the style engine.
RefPtr<ComputedStyle> highlightStyle =
mCustomHighlightPseudoStyles.LookupOrInsertWith(
aHighlightName, [this, &aHighlightName] {
return mFrame->ComputeHighlightSelectionStyle(aHighlightName);
});
if (!highlightStyle) {
// highlight `aHighlightName` doesn't exist or has no style rules.
return false;
}
*aBackColor = highlightStyle->GetVisitedDependentColor(
&nsStyleBackground::mBackgroundColor);
return NS_GET_A(*aBackColor) != 0;
}
void nsTextPaintStyle::GetURLSecondaryColor(nscolor* aForeColor) {
NS_ASSERTION(aForeColor, "aForeColor is null");
const nscolor textColor = GetTextColor();
*aForeColor = NS_RGBA(NS_GET_R(textColor), NS_GET_G(textColor),
NS_GET_B(textColor), 127);
}
void nsTextPaintStyle::GetIMESelectionColors(SelectionStyleIndex aIndex,
nscolor* aForeColor,
nscolor* aBackColor) {
NS_ASSERTION(aForeColor, "aForeColor is null");
NS_ASSERTION(aBackColor, "aBackColor is null");
nsSelectionStyle* selectionStyle = SelectionStyle(aIndex);
*aForeColor = selectionStyle->mTextColor;
*aBackColor = selectionStyle->mBGColor;
}
bool nsTextPaintStyle::GetSelectionUnderlineForPaint(
SelectionStyleIndex aIndex, nscolor* aLineColor, float* aRelativeSize,
StyleTextDecorationStyle* aStyle) {
NS_ASSERTION(aLineColor, "aLineColor is null");
NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
nsSelectionStyle* selectionStyle = SelectionStyle(aIndex);
if (selectionStyle->mUnderlineStyle == StyleTextDecorationStyle::None ||
selectionStyle->mUnderlineColor == NS_TRANSPARENT ||
selectionStyle->mUnderlineRelativeSize <= 0.0f)
return false;
*aLineColor = selectionStyle->mUnderlineColor;
*aRelativeSize = selectionStyle->mUnderlineRelativeSize;
*aStyle = selectionStyle->mUnderlineStyle;
return true;
}
void nsTextPaintStyle::InitCommonColors() {
if (mInitCommonColors) {
return;
}
auto bgColor = nsCSSRendering::FindEffectiveBackgroundColor(mFrame);
mFrameBackgroundColor = bgColor.mColor;
mSystemFieldForegroundColor =
LookAndFeel::Color(LookAndFeel::ColorID::Fieldtext, mFrame);
mSystemFieldBackgroundColor =
LookAndFeel::Color(LookAndFeel::ColorID::Field, mFrame);
if (bgColor.mIsThemed) {
// Assume a native widget has sufficient contrast always
mSufficientContrast = 0;
mInitCommonColors = true;
return;
}
nscolor defaultWindowBackgroundColor =
LookAndFeel::Color(LookAndFeel::ColorID::Window, mFrame);
nscolor selectionTextColor =
LookAndFeel::Color(LookAndFeel::ColorID::Highlighttext, mFrame);
nscolor selectionBGColor =
LookAndFeel::Color(LookAndFeel::ColorID::Highlight, mFrame);
mSufficientContrast = std::min(
std::min(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE,
NS_LUMINOSITY_DIFFERENCE(selectionTextColor, selectionBGColor)),
NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor, selectionBGColor));
mInitCommonColors = true;
}
nscolor nsTextPaintStyle::GetSystemFieldForegroundColor() {
InitCommonColors();
return mSystemFieldForegroundColor;
}
nscolor nsTextPaintStyle::GetSystemFieldBackgroundColor() {
InitCommonColors();
return mSystemFieldBackgroundColor;
}
bool nsTextPaintStyle::InitSelectionColorsAndShadow() {
if (mInitSelectionColorsAndShadow) {
return true;
}
int16_t selectionFlags;
const int16_t selectionStatus = mFrame->GetSelectionStatus(&selectionFlags);
if (!(selectionFlags & nsISelectionDisplay::DISPLAY_TEXT) ||
selectionStatus < nsISelectionController::SELECTION_ON) {
// Not displaying the normal selection.
// We're not caching this fact, so every call to GetSelectionColors
// will come through here. We could avoid this, but it's not really worth
// it.
return false;
}
mInitSelectionColorsAndShadow = true;
// Use ::selection pseudo class if applicable.
if (RefPtr<ComputedStyle> style =
mFrame->ComputeSelectionStyle(selectionStatus)) {
mSelectionBGColor =
style->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
mSelectionTextColor =
style->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor);
mSelectionPseudoStyle = std::move(style);
return true;
}
mSelectionTextColor =
LookAndFeel::Color(LookAndFeel::ColorID::Highlighttext, mFrame);
nscolor selectionBGColor =
LookAndFeel::Color(LookAndFeel::ColorID::Highlight, mFrame);
switch (selectionStatus) {
case nsISelectionController::SELECTION_ATTENTION: {
mSelectionTextColor = LookAndFeel::Color(
LookAndFeel::ColorID::TextSelectAttentionForeground, mFrame);
mSelectionBGColor = LookAndFeel::Color(
LookAndFeel::ColorID::TextSelectAttentionBackground, mFrame);
mSelectionBGColor =
EnsureDifferentColors(mSelectionBGColor, selectionBGColor);
break;
}
case nsISelectionController::SELECTION_ON: {
mSelectionBGColor = selectionBGColor;
break;
}
default: {
mSelectionBGColor = LookAndFeel::Color(
LookAndFeel::ColorID::TextSelectDisabledBackground, mFrame);
mSelectionBGColor =
EnsureDifferentColors(mSelectionBGColor, selectionBGColor);
break;
}
}
if (mResolveColors) {
EnsureSufficientContrast(&mSelectionTextColor, &mSelectionBGColor);
}
return true;
}
nsTextPaintStyle::nsSelectionStyle* nsTextPaintStyle::SelectionStyle(
SelectionStyleIndex aIndex) {
Maybe<nsSelectionStyle>& selectionStyle = mSelectionStyle[aIndex];
if (!selectionStyle) {
selectionStyle.emplace(InitSelectionStyle(aIndex));
}
return selectionStyle.ptr();
}
struct StyleIDs {
LookAndFeel::ColorID mForeground, mBackground, mLine;
LookAndFeel::IntID mLineStyle;
LookAndFeel::FloatID mLineRelativeSize;
};
EnumeratedArray<nsTextPaintStyle::SelectionStyleIndex, StyleIDs,
size_t(nsTextPaintStyle::SelectionStyleIndex::Count)>
SelectionStyleIDs = {
StyleIDs{LookAndFeel::ColorID::IMERawInputForeground,
LookAndFeel::ColorID::IMERawInputBackground,
LookAndFeel::ColorID::IMERawInputUnderline,
LookAndFeel::IntID::IMERawInputUnderlineStyle,
LookAndFeel::FloatID::IMEUnderlineRelativeSize},
StyleIDs{LookAndFeel::ColorID::IMESelectedRawTextForeground,
LookAndFeel::ColorID::IMESelectedRawTextBackground,
LookAndFeel::ColorID::IMESelectedRawTextUnderline,
LookAndFeel::IntID::IMESelectedRawTextUnderlineStyle,
LookAndFeel::FloatID::IMEUnderlineRelativeSize},
StyleIDs{LookAndFeel::ColorID::IMEConvertedTextForeground,
LookAndFeel::ColorID::IMEConvertedTextBackground,
LookAndFeel::ColorID::IMEConvertedTextUnderline,
LookAndFeel::IntID::IMEConvertedTextUnderlineStyle,
LookAndFeel::FloatID::IMEUnderlineRelativeSize},
StyleIDs{LookAndFeel::ColorID::IMESelectedConvertedTextForeground,
LookAndFeel::ColorID::IMESelectedConvertedTextBackground,
LookAndFeel::ColorID::IMESelectedConvertedTextUnderline,
LookAndFeel::IntID::IMESelectedConvertedTextUnderline,
LookAndFeel::FloatID::IMEUnderlineRelativeSize},
StyleIDs{LookAndFeel::ColorID::End, LookAndFeel::ColorID::End,
LookAndFeel::ColorID::SpellCheckerUnderline,
LookAndFeel::IntID::SpellCheckerUnderlineStyle,
LookAndFeel::FloatID::SpellCheckerUnderlineRelativeSize}};
nsTextPaintStyle::nsSelectionStyle nsTextPaintStyle::InitSelectionStyle(
SelectionStyleIndex aIndex) {
const StyleIDs& styleIDs = SelectionStyleIDs[aIndex];
nscolor foreColor, backColor;
if (styleIDs.mForeground == LookAndFeel::ColorID::End) {
foreColor = NS_SAME_AS_FOREGROUND_COLOR;
} else {
foreColor = LookAndFeel::Color(styleIDs.mForeground, mFrame);
}
if (styleIDs.mBackground == LookAndFeel::ColorID::End) {
backColor = NS_TRANSPARENT;
} else {
backColor = LookAndFeel::Color(styleIDs.mBackground, mFrame);
}
// Convert special color to actual color
NS_ASSERTION(foreColor != NS_TRANSPARENT,
"foreColor cannot be NS_TRANSPARENT");
NS_ASSERTION(backColor != NS_SAME_AS_FOREGROUND_COLOR,
"backColor cannot be NS_SAME_AS_FOREGROUND_COLOR");
NS_ASSERTION(backColor != NS_40PERCENT_FOREGROUND_COLOR,
"backColor cannot be NS_40PERCENT_FOREGROUND_COLOR");
if (mResolveColors) {
foreColor = GetResolvedForeColor(foreColor, GetTextColor(), backColor);
if (NS_GET_A(backColor) > 0) {
EnsureSufficientContrast(&foreColor, &backColor);
}
}
nscolor lineColor;
float relativeSize;
StyleTextDecorationStyle lineStyle;
GetSelectionUnderline(mFrame, aIndex, &lineColor, &relativeSize, &lineStyle);
if (mResolveColors) {
lineColor = GetResolvedForeColor(lineColor, foreColor, backColor);
}
return nsSelectionStyle{foreColor, backColor, lineColor, lineStyle,
relativeSize};
}
/* static */
bool nsTextPaintStyle::GetSelectionUnderline(nsIFrame* aFrame,
SelectionStyleIndex aIndex,
nscolor* aLineColor,
float* aRelativeSize,
StyleTextDecorationStyle* aStyle) {
NS_ASSERTION(aFrame, "aFrame is null");
NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
NS_ASSERTION(aStyle, "aStyle is null");
const StyleIDs& styleIDs = SelectionStyleIDs[aIndex];
nscolor color = LookAndFeel::Color(styleIDs.mLine, aFrame);
const int32_t lineStyle = LookAndFeel::GetInt(styleIDs.mLineStyle);
auto style = static_cast<StyleTextDecorationStyle>(lineStyle);
if (lineStyle > static_cast<int32_t>(StyleTextDecorationStyle::Sentinel)) {
NS_ERROR("Invalid underline style value is specified");
style = StyleTextDecorationStyle::Solid;
}
float size = LookAndFeel::GetFloat(styleIDs.mLineRelativeSize);
NS_ASSERTION(size, "selection underline relative size must be larger than 0");
if (aLineColor) {
*aLineColor = color;
}
*aRelativeSize = size;
*aStyle = style;
return style != StyleTextDecorationStyle::None && color != NS_TRANSPARENT &&
size > 0.0f;
}
bool nsTextPaintStyle::GetSelectionShadow(
Span<const StyleSimpleShadow>* aShadows) {
if (!InitSelectionColorsAndShadow()) {
return false;
}
if (mSelectionPseudoStyle) {
*aShadows = mSelectionPseudoStyle->StyleText()->mTextShadow.AsSpan();
return true;
}
return false;
}
inline nscolor Get40PercentColor(nscolor aForeColor, nscolor aBackColor) {
nscolor foreColor = NS_RGBA(NS_GET_R(aForeColor), NS_GET_G(aForeColor),
NS_GET_B(aForeColor), (uint8_t)(255 * 0.4f));
// Don't use true alpha color for readability.
return NS_ComposeColors(aBackColor, foreColor);
}
nscolor nsTextPaintStyle::GetResolvedForeColor(nscolor aColor,
nscolor aDefaultForeColor,
nscolor aBackColor) {
if (aColor == NS_SAME_AS_FOREGROUND_COLOR) {
return aDefaultForeColor;
}
if (aColor != NS_40PERCENT_FOREGROUND_COLOR) {
return aColor;
}
// Get actual background color
nscolor actualBGColor = aBackColor;
if (actualBGColor == NS_TRANSPARENT) {
InitCommonColors();
actualBGColor = mFrameBackgroundColor;
}
return Get40PercentColor(aDefaultForeColor, actualBGColor);
}
nscolor nsTextPaintStyle::GetWebkitTextStrokeColor() {
if (mFrame->IsInSVGTextSubtree()) {
return 0;
}
return mFrame->StyleText()->mWebkitTextStrokeColor.CalcColor(mFrame);
}
float nsTextPaintStyle::GetWebkitTextStrokeWidth() {
if (mFrame->IsInSVGTextSubtree()) {
return 0.0f;
}
nscoord coord = mFrame->StyleText()->mWebkitTextStrokeWidth;
return mFrame->PresContext()->AppUnitsToFloatDevPixels(coord);
}