1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsTextPaintStyle.h"
9 #include "nsCSSColorUtils.h"
10 #include "nsCSSRendering.h"
11 #include "nsFrameSelection.h"
12 #include "nsLayoutUtils.h"
13 #include "nsTextFrame.h"
14 #include "nsStyleConsts.h"
16 #include "mozilla/LookAndFeel.h"
18 using namespace mozilla
;
19 using namespace mozilla::dom
;
21 static nscolor
EnsureDifferentColors(nscolor colorA
, nscolor colorB
) {
22 if (colorA
== colorB
) {
24 res
= NS_RGB(NS_GET_R(colorA
) ^ 0xff, NS_GET_G(colorA
) ^ 0xff,
25 NS_GET_B(colorA
) ^ 0xff);
31 nsTextPaintStyle::nsTextPaintStyle(nsTextFrame
* aFrame
)
33 mPresContext(aFrame
->PresContext()),
34 mInitCommonColors(false),
35 mInitSelectionColorsAndShadow(false),
37 mSelectionTextColor(NS_RGBA(0, 0, 0, 0)),
38 mSelectionBGColor(NS_RGBA(0, 0, 0, 0)),
39 mSufficientContrast(0),
40 mFrameBackgroundColor(NS_RGBA(0, 0, 0, 0)),
41 mSystemFieldForegroundColor(NS_RGBA(0, 0, 0, 0)),
42 mSystemFieldBackgroundColor(NS_RGBA(0, 0, 0, 0)) {
43 for (uint32_t i
= 0; i
< ArrayLength(mSelectionStyle
); i
++)
44 mSelectionStyle
[i
].mInit
= false;
47 bool nsTextPaintStyle::EnsureSufficientContrast(nscolor
* aForeColor
,
48 nscolor
* aBackColor
) {
51 const bool sameAsForeground
= *aForeColor
== NS_SAME_AS_FOREGROUND_COLOR
;
52 if (sameAsForeground
) {
53 *aForeColor
= GetTextColor();
56 // If the combination of selection background color and frame background color
57 // has sufficient contrast, don't exchange the selection colors.
59 // Note we use a different threshold here: mSufficientContrast is for contrast
60 // between text and background colors, but since we're diffing two
61 // backgrounds, we don't need that much contrast. We match the heuristic from
62 // NS_SUFFICIENT_LUMINOSITY_DIFFERENCE_BG and use 20% of mSufficientContrast.
63 const int32_t minLuminosityDifferenceForBackground
= mSufficientContrast
/ 5;
64 const int32_t backLuminosityDifference
=
65 NS_LUMINOSITY_DIFFERENCE(*aBackColor
, mFrameBackgroundColor
);
66 if (backLuminosityDifference
>= minLuminosityDifferenceForBackground
) {
70 // Otherwise, we should use the higher-contrast color for the selection
73 // For NS_SAME_AS_FOREGROUND_COLOR we only do this if the background is
74 // totally indistinguishable, that is, if the luminosity difference is 0.
75 if (sameAsForeground
&& backLuminosityDifference
) {
79 int32_t foreLuminosityDifference
=
80 NS_LUMINOSITY_DIFFERENCE(*aForeColor
, mFrameBackgroundColor
);
81 if (backLuminosityDifference
< foreLuminosityDifference
) {
82 std::swap(*aForeColor
, *aBackColor
);
83 // Ensure foreground color is opaque to guarantee contrast.
84 *aForeColor
= NS_RGB(NS_GET_R(*aForeColor
), NS_GET_G(*aForeColor
),
85 NS_GET_B(*aForeColor
));
91 nscolor
nsTextPaintStyle::GetTextColor() {
92 if (mFrame
->IsInSVGTextSubtree()) {
93 if (!mResolveColors
) {
94 return NS_SAME_AS_FOREGROUND_COLOR
;
97 const nsStyleSVG
* style
= mFrame
->StyleSVG();
98 switch (style
->mFill
.kind
.tag
) {
99 case StyleSVGPaintKind::Tag::None
:
100 return NS_RGBA(0, 0, 0, 0);
101 case StyleSVGPaintKind::Tag::Color
:
102 return nsLayoutUtils::GetColor(mFrame
, &nsStyleSVG::mFill
);
104 NS_ERROR("cannot resolve SVG paint to nscolor");
105 return NS_RGBA(0, 0, 0, 255);
109 return nsLayoutUtils::GetColor(mFrame
, &nsStyleText::mWebkitTextFillColor
);
112 bool nsTextPaintStyle::GetSelectionColors(nscolor
* aForeColor
,
113 nscolor
* aBackColor
) {
114 NS_ASSERTION(aForeColor
, "aForeColor is null");
115 NS_ASSERTION(aBackColor
, "aBackColor is null");
117 if (!InitSelectionColorsAndShadow()) {
121 *aForeColor
= mSelectionTextColor
;
122 *aBackColor
= mSelectionBGColor
;
126 void nsTextPaintStyle::GetHighlightColors(nscolor
* aForeColor
,
127 nscolor
* aBackColor
) {
128 NS_ASSERTION(aForeColor
, "aForeColor is null");
129 NS_ASSERTION(aBackColor
, "aBackColor is null");
131 const nsFrameSelection
* frameSelection
= mFrame
->GetConstFrameSelection();
132 const Selection
* selection
=
133 frameSelection
->GetSelection(SelectionType::eFind
);
134 const SelectionCustomColors
* customColors
= nullptr;
136 customColors
= selection
->GetCustomColors();
140 nscolor backColor
= LookAndFeel::Color(
141 LookAndFeel::ColorID::TextHighlightBackground
, mFrame
);
142 nscolor foreColor
= LookAndFeel::Color(
143 LookAndFeel::ColorID::TextHighlightForeground
, mFrame
);
144 EnsureSufficientContrast(&foreColor
, &backColor
);
145 *aForeColor
= foreColor
;
146 *aBackColor
= backColor
;
150 if (customColors
->mForegroundColor
&& customColors
->mBackgroundColor
) {
151 nscolor foreColor
= *customColors
->mForegroundColor
;
152 nscolor backColor
= *customColors
->mBackgroundColor
;
154 if (EnsureSufficientContrast(&foreColor
, &backColor
) &&
155 customColors
->mAltForegroundColor
&&
156 customColors
->mAltBackgroundColor
) {
157 foreColor
= *customColors
->mAltForegroundColor
;
158 backColor
= *customColors
->mAltBackgroundColor
;
161 *aForeColor
= foreColor
;
162 *aBackColor
= backColor
;
168 if (customColors
->mBackgroundColor
) {
169 // !mForegroundColor means "currentColor"; the current color of the text.
170 nscolor foreColor
= GetTextColor();
171 nscolor backColor
= *customColors
->mBackgroundColor
;
173 int32_t luminosityDifference
=
174 NS_LUMINOSITY_DIFFERENCE(foreColor
, backColor
);
176 if (mSufficientContrast
> luminosityDifference
&&
177 customColors
->mAltBackgroundColor
) {
178 int32_t altLuminosityDifference
= NS_LUMINOSITY_DIFFERENCE(
179 foreColor
, *customColors
->mAltBackgroundColor
);
181 if (luminosityDifference
< altLuminosityDifference
) {
182 backColor
= *customColors
->mAltBackgroundColor
;
186 *aForeColor
= foreColor
;
187 *aBackColor
= backColor
;
191 if (customColors
->mForegroundColor
) {
192 nscolor foreColor
= *customColors
->mForegroundColor
;
193 // !mBackgroundColor means "transparent"; the current color of the
196 int32_t luminosityDifference
=
197 NS_LUMINOSITY_DIFFERENCE(foreColor
, mFrameBackgroundColor
);
199 if (mSufficientContrast
> luminosityDifference
&&
200 customColors
->mAltForegroundColor
) {
201 int32_t altLuminosityDifference
= NS_LUMINOSITY_DIFFERENCE(
202 *customColors
->mForegroundColor
, mFrameBackgroundColor
);
204 if (luminosityDifference
< altLuminosityDifference
) {
205 foreColor
= *customColors
->mAltForegroundColor
;
209 *aForeColor
= foreColor
;
210 *aBackColor
= NS_TRANSPARENT
;
214 // There are neither mForegroundColor nor mBackgroundColor.
215 *aForeColor
= GetTextColor();
216 *aBackColor
= NS_TRANSPARENT
;
219 bool nsTextPaintStyle::GetCustomHighlightTextColor(nsAtom
* aHighlightName
,
220 nscolor
* aForeColor
) {
221 NS_ASSERTION(aForeColor
, "aForeColor is null");
223 // non-existing highlights will be stored as `aHighlightName->nullptr`,
224 // so subsequent calls only need a hashtable lookup and don't have
225 // to enter the style engine.
226 RefPtr
<ComputedStyle
> highlightStyle
=
227 mCustomHighlightPseudoStyles
.LookupOrInsertWith(
228 aHighlightName
, [this, &aHighlightName
] {
229 return mFrame
->ComputeHighlightSelectionStyle(aHighlightName
);
231 if (!highlightStyle
) {
232 // highlight `aHighlightName` doesn't exist or has no style rules.
236 *aForeColor
= highlightStyle
->GetVisitedDependentColor(
237 &nsStyleText::mWebkitTextFillColor
);
239 return highlightStyle
->HasAuthorSpecifiedTextColor();
242 bool nsTextPaintStyle::GetCustomHighlightBackgroundColor(nsAtom
* aHighlightName
,
243 nscolor
* aBackColor
) {
244 NS_ASSERTION(aBackColor
, "aBackColor is null");
245 // non-existing highlights will be stored as `aHighlightName->nullptr`,
246 // so subsequent calls only need a hashtable lookup and don't have
247 // to enter the style engine.
248 RefPtr
<ComputedStyle
> highlightStyle
=
249 mCustomHighlightPseudoStyles
.LookupOrInsertWith(
250 aHighlightName
, [this, &aHighlightName
] {
251 return mFrame
->ComputeHighlightSelectionStyle(aHighlightName
);
253 if (!highlightStyle
) {
254 // highlight `aHighlightName` doesn't exist or has no style rules.
258 *aBackColor
= highlightStyle
->GetVisitedDependentColor(
259 &nsStyleBackground::mBackgroundColor
);
260 return NS_GET_A(*aBackColor
) != 0;
263 void nsTextPaintStyle::GetURLSecondaryColor(nscolor
* aForeColor
) {
264 NS_ASSERTION(aForeColor
, "aForeColor is null");
266 const nscolor textColor
= GetTextColor();
267 *aForeColor
= NS_RGBA(NS_GET_R(textColor
), NS_GET_G(textColor
),
268 NS_GET_B(textColor
), 127);
271 void nsTextPaintStyle::GetIMESelectionColors(int32_t aIndex
,
273 nscolor
* aBackColor
) {
274 NS_ASSERTION(aForeColor
, "aForeColor is null");
275 NS_ASSERTION(aBackColor
, "aBackColor is null");
276 NS_ASSERTION(aIndex
>= 0 && aIndex
< 5, "Index out of range");
278 nsSelectionStyle
* selectionStyle
= GetSelectionStyle(aIndex
);
279 *aForeColor
= selectionStyle
->mTextColor
;
280 *aBackColor
= selectionStyle
->mBGColor
;
283 bool nsTextPaintStyle::GetSelectionUnderlineForPaint(
284 int32_t aIndex
, nscolor
* aLineColor
, float* aRelativeSize
,
285 StyleTextDecorationStyle
* aStyle
) {
286 NS_ASSERTION(aLineColor
, "aLineColor is null");
287 NS_ASSERTION(aRelativeSize
, "aRelativeSize is null");
288 NS_ASSERTION(aIndex
>= 0 && aIndex
< 5, "Index out of range");
290 nsSelectionStyle
* selectionStyle
= GetSelectionStyle(aIndex
);
291 if (selectionStyle
->mUnderlineStyle
== StyleTextDecorationStyle::None
||
292 selectionStyle
->mUnderlineColor
== NS_TRANSPARENT
||
293 selectionStyle
->mUnderlineRelativeSize
<= 0.0f
)
296 *aLineColor
= selectionStyle
->mUnderlineColor
;
297 *aRelativeSize
= selectionStyle
->mUnderlineRelativeSize
;
298 *aStyle
= selectionStyle
->mUnderlineStyle
;
302 void nsTextPaintStyle::InitCommonColors() {
303 if (mInitCommonColors
) {
307 auto bgColor
= nsCSSRendering::FindEffectiveBackgroundColor(mFrame
);
308 mFrameBackgroundColor
= bgColor
.mColor
;
310 mSystemFieldForegroundColor
=
311 LookAndFeel::Color(LookAndFeel::ColorID::Fieldtext
, mFrame
);
312 mSystemFieldBackgroundColor
=
313 LookAndFeel::Color(LookAndFeel::ColorID::Field
, mFrame
);
315 if (bgColor
.mIsThemed
) {
316 // Assume a native widget has sufficient contrast always
317 mSufficientContrast
= 0;
318 mInitCommonColors
= true;
322 nscolor defaultWindowBackgroundColor
=
323 LookAndFeel::Color(LookAndFeel::ColorID::Window
, mFrame
);
324 nscolor selectionTextColor
=
325 LookAndFeel::Color(LookAndFeel::ColorID::Highlighttext
, mFrame
);
326 nscolor selectionBGColor
=
327 LookAndFeel::Color(LookAndFeel::ColorID::Highlight
, mFrame
);
329 mSufficientContrast
= std::min(
330 std::min(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE
,
331 NS_LUMINOSITY_DIFFERENCE(selectionTextColor
, selectionBGColor
)),
332 NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor
, selectionBGColor
));
334 mInitCommonColors
= true;
337 nscolor
nsTextPaintStyle::GetSystemFieldForegroundColor() {
339 return mSystemFieldForegroundColor
;
342 nscolor
nsTextPaintStyle::GetSystemFieldBackgroundColor() {
344 return mSystemFieldBackgroundColor
;
347 bool nsTextPaintStyle::InitSelectionColorsAndShadow() {
348 if (mInitSelectionColorsAndShadow
) {
352 int16_t selectionFlags
;
353 const int16_t selectionStatus
= mFrame
->GetSelectionStatus(&selectionFlags
);
354 if (!(selectionFlags
& nsISelectionDisplay::DISPLAY_TEXT
) ||
355 selectionStatus
< nsISelectionController::SELECTION_ON
) {
356 // Not displaying the normal selection.
357 // We're not caching this fact, so every call to GetSelectionColors
358 // will come through here. We could avoid this, but it's not really worth
363 mInitSelectionColorsAndShadow
= true;
365 // Use ::selection pseudo class if applicable.
366 if (RefPtr
<ComputedStyle
> style
=
367 mFrame
->ComputeSelectionStyle(selectionStatus
)) {
369 style
->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor
);
370 mSelectionTextColor
=
371 style
->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor
);
372 mSelectionPseudoStyle
= std::move(style
);
376 mSelectionTextColor
=
377 LookAndFeel::Color(LookAndFeel::ColorID::Highlighttext
, mFrame
);
379 nscolor selectionBGColor
=
380 LookAndFeel::Color(LookAndFeel::ColorID::Highlight
, mFrame
);
382 switch (selectionStatus
) {
383 case nsISelectionController::SELECTION_ATTENTION
: {
384 mSelectionTextColor
= LookAndFeel::Color(
385 LookAndFeel::ColorID::TextSelectAttentionForeground
, mFrame
);
386 mSelectionBGColor
= LookAndFeel::Color(
387 LookAndFeel::ColorID::TextSelectAttentionBackground
, mFrame
);
389 EnsureDifferentColors(mSelectionBGColor
, selectionBGColor
);
392 case nsISelectionController::SELECTION_ON
: {
393 mSelectionBGColor
= selectionBGColor
;
397 mSelectionBGColor
= LookAndFeel::Color(
398 LookAndFeel::ColorID::TextSelectDisabledBackground
, mFrame
);
400 EnsureDifferentColors(mSelectionBGColor
, selectionBGColor
);
405 if (mResolveColors
) {
406 EnsureSufficientContrast(&mSelectionTextColor
, &mSelectionBGColor
);
411 nsTextPaintStyle::nsSelectionStyle
* nsTextPaintStyle::GetSelectionStyle(
413 InitSelectionStyle(aIndex
);
414 return &mSelectionStyle
[aIndex
];
418 LookAndFeel::ColorID mForeground
, mBackground
, mLine
;
419 LookAndFeel::IntID mLineStyle
;
420 LookAndFeel::FloatID mLineRelativeSize
;
422 static StyleIDs SelectionStyleIDs
[] = {
423 {LookAndFeel::ColorID::IMERawInputForeground
,
424 LookAndFeel::ColorID::IMERawInputBackground
,
425 LookAndFeel::ColorID::IMERawInputUnderline
,
426 LookAndFeel::IntID::IMERawInputUnderlineStyle
,
427 LookAndFeel::FloatID::IMEUnderlineRelativeSize
},
428 {LookAndFeel::ColorID::IMESelectedRawTextForeground
,
429 LookAndFeel::ColorID::IMESelectedRawTextBackground
,
430 LookAndFeel::ColorID::IMESelectedRawTextUnderline
,
431 LookAndFeel::IntID::IMESelectedRawTextUnderlineStyle
,
432 LookAndFeel::FloatID::IMEUnderlineRelativeSize
},
433 {LookAndFeel::ColorID::IMEConvertedTextForeground
,
434 LookAndFeel::ColorID::IMEConvertedTextBackground
,
435 LookAndFeel::ColorID::IMEConvertedTextUnderline
,
436 LookAndFeel::IntID::IMEConvertedTextUnderlineStyle
,
437 LookAndFeel::FloatID::IMEUnderlineRelativeSize
},
438 {LookAndFeel::ColorID::IMESelectedConvertedTextForeground
,
439 LookAndFeel::ColorID::IMESelectedConvertedTextBackground
,
440 LookAndFeel::ColorID::IMESelectedConvertedTextUnderline
,
441 LookAndFeel::IntID::IMESelectedConvertedTextUnderline
,
442 LookAndFeel::FloatID::IMEUnderlineRelativeSize
},
443 {LookAndFeel::ColorID::End
, LookAndFeel::ColorID::End
,
444 LookAndFeel::ColorID::SpellCheckerUnderline
,
445 LookAndFeel::IntID::SpellCheckerUnderlineStyle
,
446 LookAndFeel::FloatID::SpellCheckerUnderlineRelativeSize
}};
448 void nsTextPaintStyle::InitSelectionStyle(int32_t aIndex
) {
449 NS_ASSERTION(aIndex
>= 0 && aIndex
< 5, "aIndex is invalid");
450 nsSelectionStyle
* selectionStyle
= &mSelectionStyle
[aIndex
];
451 if (selectionStyle
->mInit
) {
455 StyleIDs
* styleIDs
= &SelectionStyleIDs
[aIndex
];
457 nscolor foreColor
, backColor
;
458 if (styleIDs
->mForeground
== LookAndFeel::ColorID::End
) {
459 foreColor
= NS_SAME_AS_FOREGROUND_COLOR
;
461 foreColor
= LookAndFeel::Color(styleIDs
->mForeground
, mFrame
);
463 if (styleIDs
->mBackground
== LookAndFeel::ColorID::End
) {
464 backColor
= NS_TRANSPARENT
;
466 backColor
= LookAndFeel::Color(styleIDs
->mBackground
, mFrame
);
469 // Convert special color to actual color
470 NS_ASSERTION(foreColor
!= NS_TRANSPARENT
,
471 "foreColor cannot be NS_TRANSPARENT");
472 NS_ASSERTION(backColor
!= NS_SAME_AS_FOREGROUND_COLOR
,
473 "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR");
474 NS_ASSERTION(backColor
!= NS_40PERCENT_FOREGROUND_COLOR
,
475 "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR");
477 if (mResolveColors
) {
478 foreColor
= GetResolvedForeColor(foreColor
, GetTextColor(), backColor
);
480 if (NS_GET_A(backColor
) > 0) {
481 EnsureSufficientContrast(&foreColor
, &backColor
);
487 StyleTextDecorationStyle lineStyle
;
488 GetSelectionUnderline(mFrame
, aIndex
, &lineColor
, &relativeSize
, &lineStyle
);
490 if (mResolveColors
) {
491 lineColor
= GetResolvedForeColor(lineColor
, foreColor
, backColor
);
494 selectionStyle
->mTextColor
= foreColor
;
495 selectionStyle
->mBGColor
= backColor
;
496 selectionStyle
->mUnderlineColor
= lineColor
;
497 selectionStyle
->mUnderlineStyle
= lineStyle
;
498 selectionStyle
->mUnderlineRelativeSize
= relativeSize
;
499 selectionStyle
->mInit
= true;
503 bool nsTextPaintStyle::GetSelectionUnderline(nsIFrame
* aFrame
, int32_t aIndex
,
505 float* aRelativeSize
,
506 StyleTextDecorationStyle
* aStyle
) {
507 NS_ASSERTION(aFrame
, "aFrame is null");
508 NS_ASSERTION(aRelativeSize
, "aRelativeSize is null");
509 NS_ASSERTION(aStyle
, "aStyle is null");
510 NS_ASSERTION(aIndex
>= 0 && aIndex
< 5, "Index out of range");
512 StyleIDs
& styleID
= SelectionStyleIDs
[aIndex
];
514 nscolor color
= LookAndFeel::Color(styleID
.mLine
, aFrame
);
515 const int32_t lineStyle
= LookAndFeel::GetInt(styleID
.mLineStyle
);
516 auto style
= static_cast<StyleTextDecorationStyle
>(lineStyle
);
517 if (lineStyle
> static_cast<int32_t>(StyleTextDecorationStyle::Sentinel
)) {
518 NS_ERROR("Invalid underline style value is specified");
519 style
= StyleTextDecorationStyle::Solid
;
521 float size
= LookAndFeel::GetFloat(styleID
.mLineRelativeSize
);
523 NS_ASSERTION(size
, "selection underline relative size must be larger than 0");
528 *aRelativeSize
= size
;
531 return style
!= StyleTextDecorationStyle::None
&& color
!= NS_TRANSPARENT
&&
535 bool nsTextPaintStyle::GetSelectionShadow(
536 Span
<const StyleSimpleShadow
>* aShadows
) {
537 if (!InitSelectionColorsAndShadow()) {
541 if (mSelectionPseudoStyle
) {
542 *aShadows
= mSelectionPseudoStyle
->StyleText()->mTextShadow
.AsSpan();
549 inline nscolor
Get40PercentColor(nscolor aForeColor
, nscolor aBackColor
) {
550 nscolor foreColor
= NS_RGBA(NS_GET_R(aForeColor
), NS_GET_G(aForeColor
),
551 NS_GET_B(aForeColor
), (uint8_t)(255 * 0.4f
));
552 // Don't use true alpha color for readability.
553 return NS_ComposeColors(aBackColor
, foreColor
);
556 nscolor
nsTextPaintStyle::GetResolvedForeColor(nscolor aColor
,
557 nscolor aDefaultForeColor
,
558 nscolor aBackColor
) {
559 if (aColor
== NS_SAME_AS_FOREGROUND_COLOR
) {
560 return aDefaultForeColor
;
563 if (aColor
!= NS_40PERCENT_FOREGROUND_COLOR
) {
567 // Get actual background color
568 nscolor actualBGColor
= aBackColor
;
569 if (actualBGColor
== NS_TRANSPARENT
) {
571 actualBGColor
= mFrameBackgroundColor
;
573 return Get40PercentColor(aDefaultForeColor
, actualBGColor
);
576 nscolor
nsTextPaintStyle::GetWebkitTextStrokeColor() {
577 if (mFrame
->IsInSVGTextSubtree()) {
580 return mFrame
->StyleText()->mWebkitTextStrokeColor
.CalcColor(mFrame
);
583 float nsTextPaintStyle::GetWebkitTextStrokeWidth() {
584 if (mFrame
->IsInSVGTextSubtree()) {
587 nscoord coord
= mFrame
->StyleText()->mWebkitTextStrokeWidth
;
588 return mFrame
->PresContext()->AppUnitsToFloatDevPixels(coord
);