sw/qa/core/text/data/redline-move.docx |binary
 sw/qa/core/text/itrpaint.cxx           |   44 +++++++++++++++++++++++++++++++++
 sw/source/core/text/redlnitr.cxx       |   29 ++++++++++++---------
 sw/source/core/text/redlnitr.hxx       |    4 ++-
 vcl/source/gdi/mtfxmldump.cxx          |    1 
 5 files changed, 64 insertions(+), 14 deletions(-)

New commits:
commit 40070c5cae67c01e96b70f47d9804fd3c7757eb2
Author:     Miklos Vajna <[email protected]>
AuthorDate: Mon Feb 2 13:31:04 2026 +0100
Commit:     Caolán McNamara <[email protected]>
CommitDate: Tue Feb 3 09:27:54 2026 +0100

    cool#13988 sw redline render mode: handle moves
    
    Open the bugdoc, switch to a non-standard redline render mode (omit
    inserts or omit deletes), it's expected to see red-gray or gray-green
    pairs of text (for delete and insert), but instead double underline was
    shown for some inserts.
    
    This comes from the insert part of moves, which is wanted for the
    stanard redline render mode, where an author-specific color is used,
    then green (on top of that) means a move.
    
    Fix the problem by avoiding the move-specific rendering for the
    non-standard redline render mode case, similar to what the
    officecfg::Office::Writer::Comparison::DisplayMovedTextInGreen setting
    does.
    
    This required extending the metafile dumper a bit, since underlines were
    not exposed in the XML dump.
    
    Change-Id: I717eb0925c1959f787701ef9778ddb704b28031a
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198544
    Reviewed-by: Caolán McNamara <[email protected]>
    Tested-by: Jenkins CollaboraOffice <[email protected]>

diff --git a/sw/qa/core/text/data/redline-move.docx 
b/sw/qa/core/text/data/redline-move.docx
new file mode 100644
index 000000000000..7377bed2f299
Binary files /dev/null and b/sw/qa/core/text/data/redline-move.docx differ
diff --git a/sw/qa/core/text/itrpaint.cxx b/sw/qa/core/text/itrpaint.cxx
index e84c1183468d..a7c79d9f392c 100644
--- a/sw/qa/core/text/itrpaint.cxx
+++ b/sw/qa/core/text/itrpaint.cxx
@@ -150,6 +150,50 @@ CPPUNIT_TEST_FIXTURE(Test, 
testRedlineRenderModeOmitInsertDelete)
     CPPUNIT_ASSERT_EQUAL(120, GetColorHue(aColor3));
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testMoveRedlineRenderModeOmitDelete)
+{
+    // Given a <from>move it</from>baseline<to>move it</to> bugdoc:
+    createSwDoc("redline-move.docx");
+    SwDocShell* pDocShell = getSwDocShell();
+    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
+    SwViewOption aOpt(*pWrtShell->GetViewOptions());
+    aOpt.SetRedlineRenderMode(SwRedlineRenderMode::OmitDeletes);
+    pWrtShell->ApplyViewOptions(aOpt);
+
+    // When rendering that while omitting deletes:
+    std::shared_ptr<GDIMetaFile> xMetaFile = pDocShell->GetPreviewMetaFile();
+
+    // Then make sure "from" has no strikethrough and "to" has no underline:
+    MetafileXmlDump dumper;
+    xmlDocUniquePtr pXmlDoc = dumpAndParse(dumper, *xMetaFile);
+    assertXPath(pXmlDoc, "//textarray", 3);
+    OUString aContent = getXPathContent(pXmlDoc, "(//textarray)[1]/text");
+    sal_Int32 nIndex1 = getXPath(pXmlDoc, "(//textarray)[1]", 
"index").toInt32();
+    sal_Int32 nLength1 = getXPath(pXmlDoc, "(//textarray)[1]", 
"length").toInt32();
+    CPPUNIT_ASSERT_EQUAL(u"move it"_ustr, aContent.copy(nIndex1, nLength1));
+    OUString aFontStrikeout
+        = getXPath(pXmlDoc, "(//textarray)[1]/preceding-sibling::font[1]", 
"strikeout");
+    CPPUNIT_ASSERT_EQUAL(u"0"_ustr, aFontStrikeout);
+    OUString aFontUnderline
+        = getXPath(pXmlDoc, "(//textarray)[1]/preceding-sibling::font[1]", 
"underline");
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: 0
+    // - Actual  : 2
+    // i.e. there was an unexpected underline, while only coloring is expected 
for moves when
+    // non-standard redline render mode is used.
+    CPPUNIT_ASSERT_EQUAL(u"0"_ustr, aFontUnderline);
+    sal_Int32 nIndex2 = getXPath(pXmlDoc, "(//textarray)[2]", 
"index").toInt32();
+    sal_Int32 nLength2 = getXPath(pXmlDoc, "(//textarray)[2]", 
"length").toInt32();
+    CPPUNIT_ASSERT_EQUAL(u"baseline"_ustr, aContent.copy(nIndex2, nLength2));
+    sal_Int32 nIndex3 = getXPath(pXmlDoc, "(//textarray)[3]", 
"index").toInt32();
+    sal_Int32 nLength3 = getXPath(pXmlDoc, "(//textarray)[3]", 
"length").toInt32();
+    CPPUNIT_ASSERT_EQUAL(u"move it"_ustr, aContent.copy(nIndex3, nLength3));
+    aFontStrikeout = getXPath(pXmlDoc, 
"(//textarray)[3]/preceding-sibling::font[1]", "strikeout");
+    CPPUNIT_ASSERT_EQUAL(u"0"_ustr, aFontStrikeout);
+    aFontUnderline = getXPath(pXmlDoc, 
"(//textarray)[3]/preceding-sibling::font[1]", "underline");
+    CPPUNIT_ASSERT_EQUAL(u"0"_ustr, aFontUnderline);
+}
+
 struct ImageInfo
 {
     BitmapEx m_aBitmap;
diff --git a/sw/source/core/text/redlnitr.cxx b/sw/source/core/text/redlnitr.cxx
index 5598b741d236..949ea51dafdf 100644
--- a/sw/source/core/text/redlnitr.cxx
+++ b/sw/source/core/text/redlnitr.cxx
@@ -857,6 +857,15 @@ short SwRedlineItr::Seek(SwFont& rFnt,
         const SwRedlineTable& rTable = 
m_rDoc.getIDocumentRedlineAccess().GetRedlineTable();
         ::std::optional<decltype(m_nAct)> oFirstMatch;
 
+        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();
+        }
+
         for ( ; m_nAct < rTable.size() ; ++m_nAct)
         {
             decltype(m_nStart) nStart;
@@ -896,15 +905,18 @@ short SwRedlineItr::Seek(SwFont& rFnt,
                     }
 
                     if( 1 < pRed->GetStackCount() )
-                        FillHints( pRed->GetAuthor( 1 ), pRed->GetType( 1 ) );
-                    FillHints( pRed->GetAuthor(), pRed->GetType() );
+                        FillHints(pRed->GetAuthor(1), pRed->GetType(1), 
eRenderMode);
+                    FillHints(pRed->GetAuthor(), pRed->GetType(), eRenderMode);
 
                     SfxWhichIter aIter( *m_pSet );
 
                     // moved text: dark green with double underline or 
strikethrough
                     bool bDisplayMovedTextInGreen = 
officecfg::Office::Writer::Comparison::DisplayMovedTextInGreen::get();
-                    if ( bDisplayMovedTextInGreen && pRed->IsMoved() )
+                    if (bDisplayMovedTextInGreen && pRed->IsMoved()
+                        && eRenderMode == SwRedlineRenderMode::Standard)
                     {
+                        // Standard redline render mode, so move is more than 
just insert and
+                        // delete.
                         m_pSet->Put(SvxColorItem( COL_GREEN, RES_CHRATR_COLOR 
));
                         if (SfxItemState::SET == 
m_pSet->GetItemState(RES_CHRATR_CROSSEDOUT, true))
                             m_pSet->Put(SvxCrossedOutItem( STRIKEOUT_DOUBLE, 
RES_CHRATR_CROSSEDOUT ));
@@ -976,17 +988,8 @@ short SwRedlineItr::Seek(SwFont& rFnt,
     return nRet + EnterExtend(rFnt, nNode, nNew);
 }
 
-void SwRedlineItr::FillHints( std::size_t nAuthor, RedlineType eType )
+void SwRedlineItr::FillHints( std::size_t nAuthor, RedlineType eType, 
SwRedlineRenderMode eRenderMode )
 {
-    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:
diff --git a/sw/source/core/text/redlnitr.hxx b/sw/source/core/text/redlnitr.hxx
index 5c301312640e..3132af3053c8 100644
--- a/sw/source/core/text/redlnitr.hxx
+++ b/sw/source/core/text/redlnitr.hxx
@@ -67,6 +67,8 @@ public:
     void UpdateFont(SwFont &rFont) { ActualizeFont(rFont, m_rArr[m_nPos - 
m_nStart]); }
 };
 
+enum class SwRedlineRenderMode;
+
 class SwRedlineItr
 {
     std::deque<SwTextAttr *> m_Hints;
@@ -88,7 +90,7 @@ private:
 
     void Clear_( SwFont* pFnt );
     bool ChkSpecialUnderline_() const;
-    void FillHints( std::size_t nAuthor, RedlineType eType );
+    void FillHints(std::size_t nAuthor, RedlineType eType, SwRedlineRenderMode 
eRenderMode);
     short EnterExtend(SwFont& rFnt, SwNodeOffset const nNode, sal_Int32 const 
nNew)
     {
         if (m_pExt) return m_pExt->Enter(rFnt, nNode, nNew);
diff --git a/vcl/source/gdi/mtfxmldump.cxx b/vcl/source/gdi/mtfxmldump.cxx
index a32b79da96f3..65b4cc6fbb28 100644
--- a/vcl/source/gdi/mtfxmldump.cxx
+++ b/vcl/source/gdi/mtfxmldump.cxx
@@ -1352,6 +1352,7 @@ void MetafileXmlDump::writeXml(const GDIMetaFile& 
rMetaFile, tools::XmlWriter& r
                 rWriter.attribute("wordunderline", aFont.IsWordLineMode() ? 
"true" : "false");
                 rWriter.attribute("outline", aFont.IsOutline() ? "true" : 
"false");
                 rWriter.attribute("strikeout", aFont.GetStrikeout());
+                rWriter.attribute("underline", aFont.GetUnderline());
 
                 rWriter.endElement();
             }

Reply via email to