[LyX/master] Fix use of light inset background colors in dark mode (#13312)
Juergen Spitzmueller
spitz at lyx.org
Sat May 9 06:46:32 UTC 2026
commit d4ef3b0045da048d4834284c31fb133526dc4fba
Author: Juergen Spitzmueller <spitz at lyx.org>
Date: Sat May 9 08:42:13 2026 +0200
Fix use of light inset background colors in dark mode (#13312)
We cannot use the default (light) font color in this case, as text is
not readable (or even invisible with white background).
Hence we define a new color "foreground_inverted" (black by default)
that is used if the inset background color is light (determined by
the W3C relative luminosity threshold)
We do not need an analogouos setting in light mode I think as this
conforms with the output font/paper colors
---
src/Color.cpp | 1 +
src/ColorCode.h | 2 ++
src/FontInfo.cpp | 4 ++--
src/FontInfo.h | 2 +-
src/RowPainter.cpp | 45 ++++++++++++++++++++++++++++++++++++++---
src/frontends/NullPainter.h | 4 ++--
src/frontends/Painter.h | 8 +++++---
src/frontends/qt/GuiPainter.cpp | 19 ++++++++---------
src/frontends/qt/GuiPainter.h | 13 +++++++-----
9 files changed, 73 insertions(+), 25 deletions(-)
diff --git a/src/Color.cpp b/src/Color.cpp
index ecb72eca87..6744221f5c 100644
--- a/src/Color.cpp
+++ b/src/Color.cpp
@@ -263,6 +263,7 @@ ColorSet::ColorSet()
{ Color_cursor, N_("cursor"), "cursor", black, Linen, "cursor" },
{ Color_background, N_("background"), "background", Linen, black, "background" },
{ Color_foreground, N_("text"), "foreground", black, Linen, "foreground" },
+ { Color_foreground_inverted, N_("text"), "foreground_inverted", Linen, black, "foreground_inverted" },
{ Color_selection, N_("selection"), "selection", "#add8e6", "#add8e6", "selection" },
{ Color_selectionmath, N_("selected math"), "selectionmath", "#00008B", "#00008B", "selectionmath" },
{ Color_selectiontext, N_("selected text"), "selectiontext", black, black, "selectiontext" },
diff --git a/src/ColorCode.h b/src/ColorCode.h
index c0cdaaadee..60837a3c2c 100644
--- a/src/ColorCode.h
+++ b/src/ColorCode.h
@@ -63,6 +63,8 @@ enum ColorCode {
Color_background,
/// Foreground color
Color_foreground,
+ /// Foreground color in inverted background
+ Color_foreground_inverted,
/// Background color of selected text
Color_selection,
/// Foreground color of selected math
diff --git a/src/FontInfo.cpp b/src/FontInfo.cpp
index 867b1e66e0..7133836949 100644
--- a/src/FontInfo.cpp
+++ b/src/FontInfo.cpp
@@ -450,12 +450,12 @@ bool FontInfo::resolved() const
}
-Color FontInfo::realColor() const
+Color FontInfo::realColor(bool const invert) const
{
if (paint_color_ != Color_none)
return paint_color_;
if (color_ == Color_none)
- return Color_foreground;
+ return invert ? Color_foreground_inverted : Color_foreground;
return color_;
}
diff --git a/src/FontInfo.h b/src/FontInfo.h
index 008504653e..bcbe596e98 100644
--- a/src/FontInfo.h
+++ b/src/FontInfo.h
@@ -112,7 +112,7 @@ public:
/// The real color of the font. This can be the color that is
/// set for painting, the color of the font or a default color.
- Color realColor() const;
+ Color realColor(bool const invert = false) const;
/// Sets the color which is used during painting
void setPaintColor(Color c) { paint_color_ = c; }
diff --git a/src/RowPainter.cpp b/src/RowPainter.cpp
index 3ee26b6096..cced976d6f 100644
--- a/src/RowPainter.cpp
+++ b/src/RowPainter.cpp
@@ -14,6 +14,7 @@
#include "RowPainter.h"
#include "Buffer.h"
+#include "ColorSet.h"
#include "CoordCache.h"
#include "Cursor.h"
#include "BufferParams.h"
@@ -32,6 +33,7 @@
#include "TextMetrics.h"
#include "frontends/FontMetrics.h"
+#include "frontends/Application.h"
#include "frontends/Painter.h"
#include "support/debug.h"
@@ -39,6 +41,7 @@
#include "support/lassert.h"
#include <algorithm>
+#include <cmath>
using namespace std;
@@ -245,6 +248,37 @@ void RowPainter::paintMisspelledMark(Row::Element const & e) const
}
+namespace {
+
+// check whether a color is relatively light (i.e., needs
+// a dark one as contrast) depending on relative luminance
+// (cf. https://www.w3.org/TR/WCAG20/#relativeluminancedef)
+bool isLightColor(RGBColor const & rgb)
+{
+ // RGB to sRGB
+ double r = rgb.r / 255.0;
+ if (r <= 0.04045)
+ r = r / 12.92;
+ else
+ r = pow((r + 0.055) / 1.055, 2.4);
+ double g = rgb.g / 255.0;
+ if (g <= 0.04045)
+ g = g / 12.92;
+ else
+ g = pow((g + 0.055) / 1.055, 2.4);
+ double b = rgb.b / 255.0;
+ if (b <= 0.04045)
+ b = b / 12.92;
+ else
+ b = pow((b + 0.055) / 1.055, 2.4);
+ // Calculate luminance
+ double L = 0.2126 * r + 0.7152 * g + 0.0722 * b;
+
+ return (L + 0.05) / (0.0 + 0.05) > (1.0 + 0.05) / (L + 0.05);
+}
+} // namespace anon
+
+
void RowPainter::paintStringAndSel(Row::Element const & e) const
{
// at least part of text selected?
@@ -254,6 +288,11 @@ void RowPainter::paintStringAndSel(Row::Element const & e) const
bool const all_sel = (e.pos >= row_.sel_beg && e.endpos < row_.sel_end)
|| pi_.selected;
+ bool invert = false;
+ if (theApp() && theApp()->isInDarkMode()
+ && isLightColor(rgbFromHexName(lcolor.getX11HexName(pi_.background_color, true))))
+ invert = true;
+
if (e.type == Row::PREEDIT) {
// the case of the preedit element
pi_.pain.text(int(x_), yo_, e.str, e.im, e.char_format_index);
@@ -262,14 +301,14 @@ void RowPainter::paintStringAndSel(Row::Element const & e) const
Color const col = e.change.changed() ? e.change.color()
: Color_selectiontext;
copy.fontInfo().setPaintColor(col);
- pi_.pain.text(int(x_), yo_, e.str, copy, e.extra, e.full_width());
+ pi_.pain.text(int(x_), yo_, e.str, copy, e.extra, e.full_width(), invert);
} else if (!some_sel) {
- pi_.pain.text(int(x_), yo_, e.str, e.font, e.extra, e.full_width());
+ pi_.pain.text(int(x_), yo_, e.str, e.font, e.extra, e.full_width(), invert);
} else {
pi_.pain.text(int(x_), yo_, e.str, e.font, Color_selectiontext,
max(row_.sel_beg, e.pos) - e.pos,
min(row_.sel_end, e.endpos) - e.pos,
- e.extra, e.full_width());
+ e.extra, e.full_width(), invert);
}
}
diff --git a/src/frontends/NullPainter.h b/src/frontends/NullPainter.h
index 05716322ee..f3113eb65f 100644
--- a/src/frontends/NullPainter.h
+++ b/src/frontends/NullPainter.h
@@ -71,11 +71,11 @@ public:
void text(int, int, char_type, FontInfo const &, Direction const = Auto) override {}
/// draw a string
- void text(int, int, docstring const &, Font const &, double, double) override {}
+ void text(int, int, docstring const &, Font const &, double, double, bool) override {}
///
void text(int, int, docstring const &, Font const &,
- Color, size_type, size_type, double, double) override {}
+ Color, size_type, size_type, double, double, bool) override {}
/// draw a char with input method, FontInfo is used in mathed
void text(int, int, char_type, InputMethod const *, pos_type const,
diff --git a/src/frontends/Painter.h b/src/frontends/Painter.h
index f6054068cf..c9f90ecb90 100644
--- a/src/frontends/Painter.h
+++ b/src/frontends/Painter.h
@@ -150,15 +150,17 @@ public:
* text direction is enforced by the \c Font.
*/
virtual void text(int x, int y, docstring const & str, Font const & f,
- double wordspacing, double textwidth) = 0;
+ double wordspacing, double textwidth,
+ bool const invert = false) = 0;
/** draw a string at position x, y (y is the baseline), but
* make sure that the part between \c from and \c to is in
* \c other color. The text direction is enforced by the \c Font.
*/
virtual void text(int x, int y, docstring const & str, Font const & f,
- Color other, size_type from, size_type to,
- double wordspacing, double textwidth) = 0;
+ Color other, size_type from, size_type to,
+ double wordspacing, double textwidth,
+ bool const invert = false) = 0;
/// draw a char at position x, y (y is the baseline) using input method
/// f is used to get the font size in the text mode of mathed
diff --git a/src/frontends/qt/GuiPainter.cpp b/src/frontends/qt/GuiPainter.cpp
index 8920e95425..cf00f0257e 100644
--- a/src/frontends/qt/GuiPainter.cpp
+++ b/src/frontends/qt/GuiPainter.cpp
@@ -296,7 +296,8 @@ void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f, Dir
void GuiPainter::text(int x, int y, docstring const & s,
FontInfo const & f, Direction const dir,
- double const wordspacing, double const tw)
+ double const wordspacing, double const tw,
+ bool const invert)
{
//LYXERR0("text: x=" << x << ", s=" << s);
if (s.empty())
@@ -329,7 +330,7 @@ void GuiPainter::text(int x, int y, docstring const & s,
textDecoration(f, x, y, textwidth);
- setQPainterPen(computeColor(f.realColor()));
+ setQPainterPen(computeColor(f.realColor(invert)));
if (dir != Auto) {
auto ptl = fm.getTextLayout(s, dir == RtL, wordspacing);
QTextLine const & tline = ptl->lineForTextPosition(0);
@@ -391,16 +392,16 @@ void GuiPainter::text(int x, int y, docstring const & s,
}
void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
- double const wordspacing, double const tw)
+ double const wordspacing, double const tw, bool const invert)
{
text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
- wordspacing, tw);
+ wordspacing, tw, invert);
}
void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
Color other, size_type const from, size_type const to,
- double const wordspacing, double const tw)
+ double const wordspacing, double const tw, bool const invert)
{
GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
FontInfo fi = f.fontInfo();
@@ -413,17 +414,17 @@ void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
// Avoid this case, since it would make the `other' text spill in some cases
if (xmin == xmax) {
- text(x, y, str, fi, dir, wordspacing, tw);
+ text(x, y, str, fi, dir, wordspacing, tw, invert);
return;
} else if (xmin > xmax)
swap(xmin, xmax);
// First the part in other color
- Color const orig = fi.realColor();
+ Color const orig = fi.realColor(invert);
fi.setPaintColor(other);
QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
setClipRegion(clip);
- text(x, y, str, fi, dir, wordspacing, tw);
+ text(x, y, str, fi, dir, wordspacing, tw, invert);
// Then the part in normal color
// Note that in Qt5, it is not possible to use Qt::UniteClip,
@@ -431,7 +432,7 @@ void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
fi.setPaintColor(orig);
QRegion region(viewport());
setClipRegion(region - clip);
- text(x, y, str, fi, dir, wordspacing, tw);
+ text(x, y, str, fi, dir, wordspacing, tw, invert);
setClipping(false);
}
diff --git a/src/frontends/qt/GuiPainter.h b/src/frontends/qt/GuiPainter.h
index 9988e43569..b53e4392b5 100644
--- a/src/frontends/qt/GuiPainter.h
+++ b/src/frontends/qt/GuiPainter.h
@@ -126,15 +126,17 @@ public:
* text direction is enforced by the \c Font.
*/
void text(int x, int y, docstring const & str, Font const & f,
- double wordspacing, double textwidth) override;
+ double wordspacing, double textwidth,
+ bool const invert = false) override;
/** draw a string at position x, y (y is the baseline), but
* make sure that the part between \c from and \c to is in
* \c other color. The text direction is enforced by the \c Font.
*/
void text(int x, int y, docstring const & str, Font const & f,
- Color other, size_type from, size_type to,
- double wordspacing, double textwidth) override;
+ Color other, size_type from, size_type to,
+ double wordspacing, double textwidth,
+ bool const invert = false) override;
/// draw a char at position x, y (y is the baseline) using input method
/// char_format_index specifies preedit format
@@ -200,8 +202,9 @@ private:
// Real text() method
void text(int x, int y, docstring const & s,
- FontInfo const & f, Direction const dir,
- double const wordspacing, double tw);
+ FontInfo const & f, Direction const dir,
+ double const wordspacing, double tw,
+ bool const invert = false);
QColor current_color_;
Painter::line_style current_ls_;
More information about the lyx-cvs
mailing list