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

Reply via email to