diff options
Diffstat (limited to 'layout/generic/nsTextPaintStyle.cpp')
-rw-r--r-- | layout/generic/nsTextPaintStyle.cpp | 575 |
1 files changed, 575 insertions, 0 deletions
diff --git a/layout/generic/nsTextPaintStyle.cpp b/layout/generic/nsTextPaintStyle.cpp new file mode 100644 index 0000000000..497bbb7a37 --- /dev/null +++ b/layout/generic/nsTextPaintStyle.cpp @@ -0,0 +1,575 @@ +/* -*- 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)) { + for (uint32_t i = 0; i < ArrayLength(mSelectionStyle); i++) + mSelectionStyle[i].mInit = false; +} + +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::GetCustomHighlightColors(const nsAtom* aHighlightName, + nscolor* aForeColor, + nscolor* aBackColor) { + NS_ASSERTION(aForeColor, "aForeColor is null"); + 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; + } + // this is just copied from here: + // `InitSelectionColorsAndShadow()`. + *aBackColor = highlightStyle->GetVisitedDependentColor( + &nsStyleBackground::mBackgroundColor); + *aForeColor = highlightStyle->GetVisitedDependentColor( + &nsStyleText::mWebkitTextFillColor); + return true; +} + +void nsTextPaintStyle::GetURLSecondaryColor(nscolor* aForeColor) { + NS_ASSERTION(aForeColor, "aForeColor is null"); + + nscolor textColor = GetTextColor(); + textColor = NS_RGBA(NS_GET_R(textColor), NS_GET_G(textColor), + NS_GET_B(textColor), (uint8_t)(255 * 0.5f)); + // Don't use true alpha color for readability. + InitCommonColors(); + *aForeColor = NS_ComposeColors(mFrameBackgroundColor, textColor); +} + +void nsTextPaintStyle::GetIMESelectionColors(int32_t aIndex, + nscolor* aForeColor, + nscolor* aBackColor) { + NS_ASSERTION(aForeColor, "aForeColor is null"); + NS_ASSERTION(aBackColor, "aBackColor is null"); + NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range"); + + nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex); + *aForeColor = selectionStyle->mTextColor; + *aBackColor = selectionStyle->mBGColor; +} + +bool nsTextPaintStyle::GetSelectionUnderlineForPaint( + int32_t aIndex, nscolor* aLineColor, float* aRelativeSize, + StyleTextDecorationStyle* aStyle) { + NS_ASSERTION(aLineColor, "aLineColor is null"); + NS_ASSERTION(aRelativeSize, "aRelativeSize is null"); + NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range"); + + nsSelectionStyle* selectionStyle = GetSelectionStyle(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::GetSelectionStyle( + int32_t aIndex) { + InitSelectionStyle(aIndex); + return &mSelectionStyle[aIndex]; +} + +struct StyleIDs { + LookAndFeel::ColorID mForeground, mBackground, mLine; + LookAndFeel::IntID mLineStyle; + LookAndFeel::FloatID mLineRelativeSize; +}; +static StyleIDs SelectionStyleIDs[] = { + {LookAndFeel::ColorID::IMERawInputForeground, + LookAndFeel::ColorID::IMERawInputBackground, + LookAndFeel::ColorID::IMERawInputUnderline, + LookAndFeel::IntID::IMERawInputUnderlineStyle, + LookAndFeel::FloatID::IMEUnderlineRelativeSize}, + {LookAndFeel::ColorID::IMESelectedRawTextForeground, + LookAndFeel::ColorID::IMESelectedRawTextBackground, + LookAndFeel::ColorID::IMESelectedRawTextUnderline, + LookAndFeel::IntID::IMESelectedRawTextUnderlineStyle, + LookAndFeel::FloatID::IMEUnderlineRelativeSize}, + {LookAndFeel::ColorID::IMEConvertedTextForeground, + LookAndFeel::ColorID::IMEConvertedTextBackground, + LookAndFeel::ColorID::IMEConvertedTextUnderline, + LookAndFeel::IntID::IMEConvertedTextUnderlineStyle, + LookAndFeel::FloatID::IMEUnderlineRelativeSize}, + {LookAndFeel::ColorID::IMESelectedConvertedTextForeground, + LookAndFeel::ColorID::IMESelectedConvertedTextBackground, + LookAndFeel::ColorID::IMESelectedConvertedTextUnderline, + LookAndFeel::IntID::IMESelectedConvertedTextUnderline, + LookAndFeel::FloatID::IMEUnderlineRelativeSize}, + {LookAndFeel::ColorID::End, LookAndFeel::ColorID::End, + LookAndFeel::ColorID::SpellCheckerUnderline, + LookAndFeel::IntID::SpellCheckerUnderlineStyle, + LookAndFeel::FloatID::SpellCheckerUnderlineRelativeSize}}; + +void nsTextPaintStyle::InitSelectionStyle(int32_t aIndex) { + NS_ASSERTION(aIndex >= 0 && aIndex < 5, "aIndex is invalid"); + nsSelectionStyle* selectionStyle = &mSelectionStyle[aIndex]; + if (selectionStyle->mInit) { + return; + } + + 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); + } + + selectionStyle->mTextColor = foreColor; + selectionStyle->mBGColor = backColor; + selectionStyle->mUnderlineColor = lineColor; + selectionStyle->mUnderlineStyle = lineStyle; + selectionStyle->mUnderlineRelativeSize = relativeSize; + selectionStyle->mInit = true; +} + +/* static */ +bool nsTextPaintStyle::GetSelectionUnderline(nsIFrame* aFrame, int32_t 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"); + NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range"); + + StyleIDs& styleID = SelectionStyleIDs[aIndex]; + + nscolor color = LookAndFeel::Color(styleID.mLine, aFrame); + const int32_t lineStyle = LookAndFeel::GetInt(styleID.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(styleID.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); +} |