sw/inc/swmodule.hxx               |    5 +--
 sw/qa/core/text/itrpaint.cxx      |   61 +++++++++++++++++++++++++++++++-------
 sw/source/core/text/itrpaint.cxx  |   31 -------------------
 sw/source/core/text/redlnitr.cxx  |   16 ++++++++-
 sw/source/core/text/txtfld.cxx    |    4 +-
 sw/source/core/text/txtftn.cxx    |    5 +--
 sw/source/uibase/app/swmodul1.cxx |   33 +++++++++++++++++---
 7 files changed, 101 insertions(+), 54 deletions(-)

New commits:
commit b3656a81b40bd5012d6e9a52527e241cd6586571
Author:     Miklos Vajna <[email protected]>
AuthorDate: Thu Nov 27 08:38:13 2025 +0100
Commit:     Miklos Vajna <[email protected]>
CommitDate: Sat Nov 29 08:21:02 2025 +0100

    cool#13574 sw lok: improve non-standard redline render mode colors
    
    Instead of completely omitting insertions and deletions when requested,
    just de-saturate the original colors when the content is to be
    semi-hidden.
    
    Do this by undoing the changes to SwTextPainter::DrawTextLine() and
    instead let SwModule::GetInsertAuthorAttr() /
    SwModule::GetDeletedAuthorAttr() decide if the standard rendering for
    that redline is OK or we want a custom color.
    
    If we want something custom, then halve the saturation in
    lcl_FillAuthorAttr() to paint it semi-hidden.
    
    Also adapt the testcase, to check the saturation instead of the number
    of painted strings.
    
    Change-Id: I62362463bd1b6d6968a503c802e17a56a1aea1b1
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/194774
    Tested-by: Jenkins
    Reviewed-by: Miklos Vajna <[email protected]>

diff --git a/sw/inc/swmodule.hxx b/sw/inc/swmodule.hxx
index d860b75f62af..d3a42be17ba5 100644
--- a/sw/inc/swmodule.hxx
+++ b/sw/inc/swmodule.hxx
@@ -70,6 +70,7 @@ namespace com::sun::star::linguistic2 { class 
XLanguageGuessing; }
 namespace ooo::vba { class XSinkCaller; }
 class SwLinguServiceEventListener;
 class SwTableAutoFormatTable;
+enum class SwRedlineRenderMode;
 
 class SAL_DLLPUBLIC_RTTI SwModule final : public SfxModule, public 
SfxListener, public utl::ConfigurationListener
 {
@@ -207,8 +208,8 @@ public:
     std::size_t         InsertRedlineAuthor(const OUString& rAuthor);
     SW_DLLPUBLIC void   SetRedlineAuthor(const OUString& rAuthor); // for unit 
tests
 
-    void                GetInsertAuthorAttr(std::size_t nAuthor, SfxItemSet 
&rSet);
-    void                GetDeletedAuthorAttr(std::size_t nAuthor, SfxItemSet 
&rSet);
+    void                GetInsertAuthorAttr(std::size_t nAuthor, SfxItemSet 
&rSet, SwRedlineRenderMode eMode);
+    void                GetDeletedAuthorAttr(std::size_t nAuthor, SfxItemSet 
&rSet, SwRedlineRenderMode eMode);
     void                GetFormatAuthorAttr(std::size_t nAuthor, SfxItemSet 
&rSet);
 
     sal_uInt16              GetRedlineMarkPos() const;
diff --git a/sw/qa/core/text/itrpaint.cxx b/sw/qa/core/text/itrpaint.cxx
index 6f08dfc82a89..7eaed14187c8 100644
--- a/sw/qa/core/text/itrpaint.cxx
+++ b/sw/qa/core/text/itrpaint.cxx
@@ -11,6 +11,8 @@
 
 #include <memory>
 
+#include <o3tl/string_view.hxx>
+
 #include <docsh.hxx>
 #include <wrtsh.hxx>
 #include <ndtxt.hxx>
@@ -27,9 +29,21 @@ public:
     }
 };
 
+/// #RRGGBB -> HSL saturation.
+sal_Int16 GetColorSaturation(std::u16string_view rRGB)
+{
+    Color aColor(o3tl::toInt32(rRGB.substr(1, 2), 16), 
o3tl::toInt32(rRGB.substr(3, 2), 16),
+                 o3tl::toInt32(rRGB.substr(5, 2), 16));
+    sal_uInt16 nHue;
+    sal_uInt16 nSaturation;
+    sal_uInt16 nBrightness;
+    aColor.RGBtoHSB(nHue, nSaturation, nBrightness);
+    return nSaturation;
+}
+
 CPPUNIT_TEST_FIXTURE(Test, testRedlineRenderModeOmitInsertDelete)
 {
-    // Default rendering:
+    // Default rendering: default, normal saturation, normal saturation.
     createSwDoc("redline.docx");
 
     SwDocShell* pDocShell = getSwDocShell();
@@ -42,14 +56,23 @@ CPPUNIT_TEST_FIXTURE(Test, 
testRedlineRenderModeOmitInsertDelete)
     sal_Int32 nIndex1 = getXPath(pXmlDoc, "(//textarray)[1]", 
"index").toInt32();
     sal_Int32 nLength1 = getXPath(pXmlDoc, "(//textarray)[1]", 
"length").toInt32();
     CPPUNIT_ASSERT_EQUAL(u"baseline "_ustr, aContent.copy(nIndex1, nLength1));
+    OUString aColor1
+        = getXPath(pXmlDoc, 
"(//textarray)[1]/preceding-sibling::textcolor[1]", "color");
+    CPPUNIT_ASSERT_EQUAL(u"#000000"_ustr, aColor1);
     sal_Int32 nIndex2 = getXPath(pXmlDoc, "(//textarray)[2]", 
"index").toInt32();
     sal_Int32 nLength2 = getXPath(pXmlDoc, "(//textarray)[2]", 
"length").toInt32();
     CPPUNIT_ASSERT_EQUAL(u"oldcontent"_ustr, aContent.copy(nIndex2, nLength2));
+    OUString aColor2
+        = getXPath(pXmlDoc, 
"(//textarray)[2]/preceding-sibling::textcolor[1]", "color");
+    CPPUNIT_ASSERT_GREATER(static_cast<sal_Int16>(50), 
GetColorSaturation(aColor2));
     sal_Int32 nIndex3 = getXPath(pXmlDoc, "(//textarray)[3]", 
"index").toInt32();
     sal_Int32 nLength3 = getXPath(pXmlDoc, "(//textarray)[3]", 
"length").toInt32();
     CPPUNIT_ASSERT_EQUAL(u"newcontent"_ustr, aContent.copy(nIndex3, nLength3));
+    OUString aColor3
+        = getXPath(pXmlDoc, 
"(//textarray)[3]/preceding-sibling::textcolor[1]", "color");
+    CPPUNIT_ASSERT_GREATER(static_cast<sal_Int16>(50), 
GetColorSaturation(aColor3));
 
-    // Omit inserts:
+    // Omit inserts: default, normal saturation, de-saturated.
     SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
     SwViewOption aOpt(*pWrtShell->GetViewOptions());
     aOpt.SetRedlineRenderMode(SwRedlineRenderMode::OmitInserts);
@@ -58,32 +81,50 @@ CPPUNIT_TEST_FIXTURE(Test, 
testRedlineRenderModeOmitInsertDelete)
     xMetaFile = pDocShell->GetPreviewMetaFile();
 
     pXmlDoc = dumpAndParse(dumper, *xMetaFile);
-    // Without the accompanying fix in place, this test would have failed with:
-    // - Expected: 2
-    // - Actual  : 3
-    // i.e. the inserts were not omitted.
-    assertXPath(pXmlDoc, "//textarray", 2);
+    assertXPath(pXmlDoc, "//textarray", 3);
     nIndex1 = getXPath(pXmlDoc, "(//textarray)[1]", "index").toInt32();
     nLength1 = getXPath(pXmlDoc, "(//textarray)[1]", "length").toInt32();
     CPPUNIT_ASSERT_EQUAL(u"baseline "_ustr, aContent.copy(nIndex1, nLength1));
+    aColor1 = getXPath(pXmlDoc, 
"(//textarray)[1]/preceding-sibling::textcolor[1]", "color");
+    CPPUNIT_ASSERT_EQUAL(u"#000000"_ustr, aColor1);
     nIndex2 = getXPath(pXmlDoc, "(//textarray)[2]", "index").toInt32();
     nLength2 = getXPath(pXmlDoc, "(//textarray)[2]", "length").toInt32();
     CPPUNIT_ASSERT_EQUAL(u"oldcontent"_ustr, aContent.copy(nIndex2, nLength2));
+    aColor2 = getXPath(pXmlDoc, 
"(//textarray)[2]/preceding-sibling::textcolor[1]", "color");
+    CPPUNIT_ASSERT_GREATER(static_cast<sal_Int16>(50), 
GetColorSaturation(aColor2));
+    nIndex3 = getXPath(pXmlDoc, "(//textarray)[3]", "index").toInt32();
+    nLength3 = getXPath(pXmlDoc, "(//textarray)[3]", "length").toInt32();
+    CPPUNIT_ASSERT_EQUAL(u"newcontent"_ustr, aContent.copy(nIndex3, nLength3));
+    aColor3 = getXPath(pXmlDoc, 
"(//textarray)[3]/preceding-sibling::textcolor[1]", "color");
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected less or equal than: 50
+    // - Actual  : 100
+    // i.e. the 3rd text portion was not de-saturated.
+    CPPUNIT_ASSERT_LESSEQUAL(static_cast<sal_Int16>(50), 
GetColorSaturation(aColor3));
 
-    // Omit deletes:
+    // Omit deletes: default, de-saturated, normal saturation.
     aOpt.SetRedlineRenderMode(SwRedlineRenderMode::OmitDeletes);
     pWrtShell->ApplyViewOptions(aOpt);
 
     xMetaFile = pDocShell->GetPreviewMetaFile();
 
     pXmlDoc = dumpAndParse(dumper, *xMetaFile);
-    assertXPath(pXmlDoc, "//textarray", 2);
+    assertXPath(pXmlDoc, "//textarray", 3);
     nIndex1 = getXPath(pXmlDoc, "(//textarray)[1]", "index").toInt32();
     nLength1 = getXPath(pXmlDoc, "(//textarray)[1]", "length").toInt32();
     CPPUNIT_ASSERT_EQUAL(u"baseline "_ustr, aContent.copy(nIndex1, nLength1));
+    aColor1 = getXPath(pXmlDoc, 
"(//textarray)[1]/preceding-sibling::textcolor[1]", "color");
+    CPPUNIT_ASSERT_EQUAL(u"#000000"_ustr, aColor1);
     nIndex2 = getXPath(pXmlDoc, "(//textarray)[2]", "index").toInt32();
     nLength2 = getXPath(pXmlDoc, "(//textarray)[2]", "length").toInt32();
-    CPPUNIT_ASSERT_EQUAL(u"newcontent"_ustr, aContent.copy(nIndex2, nLength2));
+    CPPUNIT_ASSERT_EQUAL(u"oldcontent"_ustr, aContent.copy(nIndex2, nLength2));
+    aColor2 = getXPath(pXmlDoc, 
"(//textarray)[2]/preceding-sibling::textcolor[1]", "color");
+    CPPUNIT_ASSERT_LESSEQUAL(static_cast<sal_Int16>(50), 
GetColorSaturation(aColor2));
+    nIndex3 = getXPath(pXmlDoc, "(//textarray)[3]", "index").toInt32();
+    nLength3 = getXPath(pXmlDoc, "(//textarray)[3]", "length").toInt32();
+    CPPUNIT_ASSERT_EQUAL(u"newcontent"_ustr, aContent.copy(nIndex3, nLength3));
+    aColor3 = getXPath(pXmlDoc, 
"(//textarray)[3]/preceding-sibling::textcolor[1]", "color");
+    CPPUNIT_ASSERT_GREATER(static_cast<sal_Int16>(50), 
GetColorSaturation(aColor3));
 }
 }
 
diff --git a/sw/source/core/text/itrpaint.cxx b/sw/source/core/text/itrpaint.cxx
index 3a030dd8d232..14693858cda8 100644
--- a/sw/source/core/text/itrpaint.cxx
+++ b/sw/source/core/text/itrpaint.cxx
@@ -42,8 +42,6 @@
 #include "pormulti.hxx"
 #include <doc.hxx>
 #include <fmturl.hxx>
-#include <IDocumentRedlineAccess.hxx>
-#include <redline.hxx>
 
 // Returns, if we have an underline breaking situation
 // Adding some more conditions here means you also have to change them
@@ -307,9 +305,6 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, 
SwSaveClip &rClip,
     // Reference portion for the paragraph end portion
     SwLinePortion* pEndTempl = m_pCurr->GetFirstPortion();
 
-    const SwDoc& rDoc = GetInfo().GetTextFrame()->GetDoc();
-    const IDocumentRedlineAccess& rIDRA = rDoc.getIDocumentRedlineAccess();
-    const SwRedlineTable& rRedlineTable = rIDRA.GetRedlineTable();
     while( pPor )
     {
         bool bSeeked = true;
@@ -427,32 +422,6 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, 
SwSaveClip &rClip,
             roTaggedLabel.emplace(nullptr, nullptr, &aPorInfo, *pOut);
         }
 
-        // See if the redline render mode requires to omit the paint of the 
text portion.
-        SwRedlineTable::size_type nRedline = SwRedlineTable::npos;
-        SwRedlineRenderMode eRedlineRenderMode = SwRedlineRenderMode::Standard;
-        if (GetRedln() && GetRedln()->IsOn())
-        {
-            nRedline = GetRedln()->GetAct();
-            eRedlineRenderMode = GetInfo().GetOpt().GetRedlineRenderMode();
-        }
-        bool bOmitPaint = false;
-        if (nRedline != SwRedlineTable::npos)
-        {
-            const SwRangeRedline* pRedline = rRedlineTable[nRedline];
-            RedlineType eType = pRedline->GetType();
-            if (eRedlineRenderMode == SwRedlineRenderMode::OmitInserts
-                && eType == RedlineType::Insert)
-            {
-                bOmitPaint = true;
-            }
-            else if (eRedlineRenderMode == SwRedlineRenderMode::OmitDeletes
-                     && eType == RedlineType::Delete)
-            {
-                bOmitPaint = true;
-            }
-        }
-
-        if (!bOmitPaint)
         {
             // #i16816# tagged pdf support
             Por_Info aPorInfo(*pPor, *this, 0);
diff --git a/sw/source/core/text/redlnitr.cxx b/sw/source/core/text/redlnitr.cxx
index d9ec1283e159..ce7988b64d84 100644
--- a/sw/source/core/text/redlnitr.cxx
+++ b/sw/source/core/text/redlnitr.cxx
@@ -55,6 +55,9 @@
 #include <editeng/formatbreakitem.hxx>
 #include <editeng/udlnitem.hxx>
 #include <officecfg/Office/Writer.hxx>
+#include <viewopt.hxx>
+#include <docsh.hxx>
+#include <wrtsh.hxx>
 
 using namespace ::com::sun::star;
 
@@ -1009,13 +1012,22 @@ short SwRedlineItr::Seek(SwFont& rFnt,
 
 void SwRedlineItr::FillHints( std::size_t nAuthor, RedlineType eType )
 {
+    const SwDocShell* pDocShell = m_rDoc.GetDocShell();
+    const SwWrtShell* pWrtShell = pDocShell ? pDocShell->GetWrtShell() : 
nullptr;
+    SwRedlineRenderMode eRenderMode = SwRedlineRenderMode::Standard;
+    if (pWrtShell)
+    {
+        const SwViewOption* pOptions = pWrtShell->GetViewOptions();
+        eRenderMode = pOptions->GetRedlineRenderMode();
+    }
+
     switch ( eType )
     {
         case RedlineType::Insert:
-            SwModule::get()->GetInsertAuthorAttr(nAuthor, *m_pSet);
+            SwModule::get()->GetInsertAuthorAttr(nAuthor, *m_pSet, 
eRenderMode);
             break;
         case RedlineType::Delete:
-            SwModule::get()->GetDeletedAuthorAttr(nAuthor, *m_pSet);
+            SwModule::get()->GetDeletedAuthorAttr(nAuthor, *m_pSet, 
eRenderMode);
             break;
         case RedlineType::Format:
         case RedlineType::FmtColl:
diff --git a/sw/source/core/text/txtfld.cxx b/sw/source/core/text/txtfld.cxx
index 13100a23ed77..dbb178c66d10 100644
--- a/sw/source/core/text/txtfld.cxx
+++ b/sw/source/core/text/txtfld.cxx
@@ -491,9 +491,9 @@ static bool lcl_setRedlineAttr( SwTextFormatInfo &rInf, 
const SwTextNode& rTextN
             : pRedlineNum->GetAuthor();
 
     if ( RedlineType::Delete == pRedlineNum->GetType() )
-        SwModule::get()->GetDeletedAuthorAttr(aAuthor, aSet);
+        SwModule::get()->GetDeletedAuthorAttr(aAuthor, aSet, 
SwRedlineRenderMode::Standard);
     else
-        SwModule::get()->GetInsertAuthorAttr(aAuthor, aSet);
+        SwModule::get()->GetInsertAuthorAttr(aAuthor, aSet, 
SwRedlineRenderMode::Standard);
 
     if (const SvxColorItem* pItem = aSet.GetItemIfSet(RES_CHRATR_COLOR))
         pNumFnt->SetColor(pItem->GetValue());
diff --git a/sw/source/core/text/txtftn.cxx b/sw/source/core/text/txtftn.cxx
index 8a6c1613a774..b1dbc84860fc 100644
--- a/sw/source/core/text/txtftn.cxx
+++ b/sw/source/core/text/txtftn.cxx
@@ -66,6 +66,7 @@
 #include <com/sun/star/beans/XPropertySet.hpp>
 #include <com/sun/star/awt/CharSet.hpp>
 #include <com/sun/star/text/XTextRange.hpp>
+#include <viewopt.hxx>
 
 using namespace ::com::sun::star;
 
@@ -1016,9 +1017,9 @@ SwNumberPortion *SwTextFormatter::NewFootnoteNumPortion( 
SwTextFormatInfo const
                     : pRedline->GetAuthor();
 
             if ( RedlineType::Delete == pRedline->GetType() )
-                SwModule::get()->GetDeletedAuthorAttr(aAuthor, aSet);
+                SwModule::get()->GetDeletedAuthorAttr(aAuthor, aSet, 
SwRedlineRenderMode::Standard);
             else
-                SwModule::get()->GetInsertAuthorAttr(aAuthor, aSet);
+                SwModule::get()->GetInsertAuthorAttr(aAuthor, aSet, 
SwRedlineRenderMode::Standard);
 
             if (const SvxColorItem* pItem = 
aSet.GetItemIfSet(RES_CHRATR_COLOR))
                 pNumFnt->SetColor(pItem->GetValue());
diff --git a/sw/source/uibase/app/swmodul1.cxx 
b/sw/source/uibase/app/swmodul1.cxx
index 35b53b37faf0..e361ed9a23ec 100644
--- a/sw/source/uibase/app/swmodul1.cxx
+++ b/sw/source/uibase/app/swmodul1.cxx
@@ -482,13 +482,26 @@ std::size_t SwModule::InsertRedlineAuthor(const OUString& 
rAuthor)
 }
 
 static void lcl_FillAuthorAttr( std::size_t nAuthor, SfxItemSet &rSet,
-                        const AuthorCharAttr &rAttr )
+                        const AuthorCharAttr &rAttr, SwRedlineRenderMode 
eRenderMode = SwRedlineRenderMode::Standard )
 {
     Color aCol( rAttr.m_nColor );
 
     if( rAttr.m_nColor == COL_TRANSPARENT )
+    {
         aCol = lcl_GetAuthorColor(nAuthor);
 
+        // See if the redline render mode requires to de-saturize the color of 
the text portion.
+        if (eRenderMode != SwRedlineRenderMode::Standard)
+        {
+            sal_uInt16 nHue;
+            sal_uInt16 nSaturation;
+            sal_uInt16 nBrightness;
+            aCol.RGBtoHSB(nHue, nSaturation, nBrightness);
+            nSaturation = nSaturation / 2;
+            aCol = Color::HSBtoRGB(nHue, nSaturation, nBrightness);
+        }
+    }
+
     bool bBackGr = rAttr.m_nColor == COL_NONE_COLOR;
 
     switch (rAttr.m_nItemId)
@@ -540,14 +553,24 @@ static void lcl_FillAuthorAttr( std::size_t nAuthor, 
SfxItemSet &rSet,
         rSet.Put( SvxColorItem( aCol, RES_CHRATR_COLOR ) );
 }
 
-void SwModule::GetInsertAuthorAttr(std::size_t nAuthor, SfxItemSet &rSet)
+void SwModule::GetInsertAuthorAttr(std::size_t nAuthor, SfxItemSet &rSet, 
SwRedlineRenderMode eRenderMode)
 {
-    lcl_FillAuthorAttr(nAuthor, rSet, m_pModuleConfig->GetInsertAuthorAttr());
+    SwRedlineRenderMode eMode = SwRedlineRenderMode::Standard;
+    if (eRenderMode == SwRedlineRenderMode::OmitInserts)
+    {
+        eMode = eRenderMode;
+    }
+    lcl_FillAuthorAttr(nAuthor, rSet, m_pModuleConfig->GetInsertAuthorAttr(), 
eMode);
 }
 
-void SwModule::GetDeletedAuthorAttr(std::size_t nAuthor, SfxItemSet &rSet)
+void SwModule::GetDeletedAuthorAttr(std::size_t nAuthor, SfxItemSet &rSet, 
SwRedlineRenderMode eRenderMode)
 {
-    lcl_FillAuthorAttr(nAuthor, rSet, m_pModuleConfig->GetDeletedAuthorAttr());
+    SwRedlineRenderMode eMode = SwRedlineRenderMode::Standard;
+    if (eRenderMode == SwRedlineRenderMode::OmitDeletes)
+    {
+        eMode = eRenderMode;
+    }
+    lcl_FillAuthorAttr(nAuthor, rSet, m_pModuleConfig->GetDeletedAuthorAttr(), 
eMode);
 }
 
 // For future extension:

Reply via email to