[LyX/master] Fix use of light inset background colors in dark mode (#13312)

Juergen Spitzmueller <[email protected]> Sat, 09 May 2026 06:46:32 +0000
Newsgroups gmane.editors.lyx.cvs
Message-ID <[email protected]>
commit d4ef3b0045da048d4834284c31fb133526dc4fba
Author: Juergen Spitzmueller <[email protected]>
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_;
-- 
lyx-cvs mailing list
[email protected]
https://lists.lyx.org/mailman/listinfo/lyx-cvs