sw/CppunitTest_sw_core_text.mk                   |    1 
 sw/qa/core/text/data/redline-number-portion.docx |binary
 sw/qa/core/text/porfld.cxx                       |   71 +++++++++++++++++++++++
 sw/source/core/text/itrform2.cxx                 |    2 
 sw/source/core/text/porfld.cxx                   |   36 +++++++++--
 sw/source/core/text/porfld.hxx                   |    4 -
 sw/source/core/text/porftn.hxx                   |    2 
 sw/source/core/text/txtfld.cxx                   |   10 ++-
 sw/source/core/txtnode/swfont.cxx                |    5 +
 9 files changed, 120 insertions(+), 11 deletions(-)

New commits:
commit e609c44293ca7ba5658459aec15b6b084cb1bfd1
Author:     Miklos Vajna <[email protected]>
AuthorDate: Thu Feb 5 15:13:12 2026 +0100
Commit:     Miklos Vajna <[email protected]>
CommitDate: Fri Feb 6 15:10:41 2026 +0100

    cool#13988 sw redline render mode: handle number portions
    
    Open the bugdoc, switch to non-standard redline render mode (e.g.
    dispatch .uno:RedlineRenderMode), the "2." number portion is still
    underlined, while this mode doesn't underline insertions otherwise.
    
    The reason this happens is because number portions don't decide redline
    coloring/markup at render time, like SwTextPainter::DrawTextLine()
    does, instead the font to be used is decided earlier at layout time in
    SwTextFormatter::NewNumberPortion().
    
    Fix the problem by creating two fonts in
    SwTextFormatter::NewNumberPortion(), one without the redline markup and
    one with it. And then later in SwNumberPortion::Paint() use the font
    that matches the current rendline render mode. This works, because
    changing the redline render mode means SwNumberPortion::Paint() will be
    invoked, but the layout's portions won't be re-created for performance
    reasons.
    
    Note that similar to table redlines, this just disables the unwanted
    standard redline render decoration. That is probably fine, since the
    surrounding "real" text is still colored gray/red/green. But we possibly
    this can be improved in the future to apply similar coloring even for
    the number portion.
    
    Change-Id: I16b4123d20bf0c14c76bb23e574eb21a719d15df
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198822
    Reviewed-by: Miklos Vajna <[email protected]>
    Tested-by: Jenkins

diff --git a/sw/CppunitTest_sw_core_text.mk b/sw/CppunitTest_sw_core_text.mk
index 42be7e727b0c..e90c77e5f38f 100644
--- a/sw/CppunitTest_sw_core_text.mk
+++ b/sw/CppunitTest_sw_core_text.mk
@@ -18,6 +18,7 @@ $(eval $(call 
gb_CppunitTest_add_exception_objects,sw_core_text, \
     sw/qa/core/text/itratr \
     sw/qa/core/text/itrpaint \
     sw/qa/core/text/itrform2 \
+    sw/qa/core/text/porfld \
     sw/qa/core/text/porlay \
     sw/qa/core/text/porrst \
     sw/qa/core/text/text \
diff --git a/sw/qa/core/text/data/redline-number-portion.docx 
b/sw/qa/core/text/data/redline-number-portion.docx
new file mode 100644
index 000000000000..26ba473e8085
Binary files /dev/null and b/sw/qa/core/text/data/redline-number-portion.docx 
differ
diff --git a/sw/qa/core/text/porfld.cxx b/sw/qa/core/text/porfld.cxx
new file mode 100644
index 000000000000..6ab3d3f3c550
--- /dev/null
+++ b/sw/qa/core/text/porfld.cxx
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <swmodeltestbase.hxx>
+
+#include <memory>
+
+#include <vcl/gdimtf.hxx>
+
+#include <docsh.hxx>
+#include <wrtsh.hxx>
+
+namespace
+{
+/// Covers sw/source/core/text/porfld.cxx fixes.
+class Test : public SwModelTestBase
+{
+public:
+    Test()
+        : SwModelTestBase(u"/sw/qa/core/text/data/"_ustr)
+    {
+    }
+};
+
+CPPUNIT_TEST_FIXTURE(Test, testNumberPortionRedlineRenderMode)
+{
+    // Given a document with redlines, the "2." number portion is inserted:
+    createSwDoc("redline-number-portion.docx");
+
+    // When redline render mode is standard:
+    SwDocShell* pDocShell = getSwDocShell();
+    std::shared_ptr<GDIMetaFile> xMetaFile = pDocShell->GetPreviewMetaFile();
+
+    // Then make sure we paint an underline:
+    MetafileXmlDump aDumper;
+    xmlDocUniquePtr pXmlDoc = dumpAndParse(aDumper, *xMetaFile);
+    OUString aContent = getXPathContent(pXmlDoc, "(//textarray)[3]/text");
+    CPPUNIT_ASSERT_EQUAL(u"2."_ustr, aContent);
+    OUString aUnderline
+        = getXPath(pXmlDoc, "(//textarray)[3]/preceding-sibling::font[1]", 
"underline");
+    CPPUNIT_ASSERT_EQUAL(u"1"_ustr, aUnderline);
+
+    // And given "omit inserts" redline render mode:
+    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
+    SwViewOption aOpt(*pWrtShell->GetViewOptions());
+    aOpt.SetRedlineRenderMode(SwRedlineRenderMode::OmitInserts);
+    pWrtShell->ApplyViewOptions(aOpt);
+
+    // When rendering:
+    xMetaFile = pDocShell->GetPreviewMetaFile();
+
+    // Then make sure we don't paint an underline:
+    pXmlDoc = dumpAndParse(aDumper, *xMetaFile);
+    aContent = getXPathContent(pXmlDoc, "(//textarray)[3]/text");
+    CPPUNIT_ASSERT_EQUAL(u"2."_ustr, aContent);
+    aUnderline = getXPath(pXmlDoc, 
"(//textarray)[3]/preceding-sibling::font[1]", "underline");
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: 0
+    // - Actual  : 1
+    // i.e. there was an unexpected underline.
+    CPPUNIT_ASSERT_EQUAL(u"0"_ustr, aUnderline);
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx
index cca47ce36a9a..52578639a83c 100644
--- a/sw/source/core/text/itrform2.cxx
+++ b/sw/source/core/text/itrform2.cxx
@@ -1268,7 +1268,7 @@ SwTextPortion *SwTextFormatter::WhichTextPor( 
SwTextFormatInfo &rInf ) const
                 }
             }
             assert(2 <= sal_Int32(nFieldLen));
-            pPor = new 
SwFieldPortion(SwFieldType::GetTypeStr(SwFieldTypesEnum::Input), nullptr, 
nFieldLen);
+            pPor = new 
SwFieldPortion(SwFieldType::GetTypeStr(SwFieldTypesEnum::Input), nullptr, 
nullptr, nFieldLen);
         }
         else
         {
diff --git a/sw/source/core/text/porfld.cxx b/sw/source/core/text/porfld.cxx
index 899db3f0a785..7eb0cf6391fd 100644
--- a/sw/source/core/text/porfld.cxx
+++ b/sw/source/core/text/porfld.cxx
@@ -79,8 +79,8 @@ void SwFieldPortion::TakeNextOffset( const SwFieldPortion* 
pField )
     m_bFollow = true;
 }
 
-SwFieldPortion::SwFieldPortion(OUString aExpand, std::unique_ptr<SwFont> 
pFont, TextFrameIndex const nFieldLen)
-    : m_aExpand(std::move(aExpand)), m_pFont(std::move(pFont)), 
m_nNextOffset(0)
+SwFieldPortion::SwFieldPortion(OUString aExpand, std::unique_ptr<SwFont> 
pFont, std::unique_ptr<SwFont> pRedlineRenderModeFont, TextFrameIndex const 
nFieldLen)
+    : m_aExpand(std::move(aExpand)), m_pFont(std::move(pFont)), 
m_pRedlineRenderModeFont(std::move(pRedlineRenderModeFont)), m_nNextOffset(0)
     , m_nNextScriptChg(COMPLETE_STRING), m_nFieldLen(nFieldLen), 
m_nViewWidth(0)
     , m_bFollow( false ), m_bLeft( false), m_bHide( false)
     , m_bCenter (false), m_bHasFollow( false )
@@ -110,6 +110,8 @@ SwFieldPortion::SwFieldPortion( const SwFieldPortion& 
rField )
 {
     if ( rField.HasFont() )
         m_pFont.reset( new SwFont( *rField.GetFont() ) );
+    if (rField.m_pRedlineRenderModeFont)
+        m_pRedlineRenderModeFont.reset(new 
SwFont(*rField.m_pRedlineRenderModeFont));
 
     SetWhichPor( PortionType::Field );
 }
@@ -520,6 +522,12 @@ void SwFieldPortion::dumpAsXml(xmlTextWriterPtr pWriter, 
const OUString& rText,
     {
         m_pFont->dumpAsXml(pWriter);
     }
+    if (m_pRedlineRenderModeFont)
+    {
+        (void)xmlTextWriterStartElement(pWriter, 
BAD_CAST("redline-render-mode-font"));
+        m_pRedlineRenderModeFont->dumpAsXml(pWriter);
+        (void)xmlTextWriterEndElement(pWriter);
+    }
 
     (void)xmlTextWriterEndElement(pWriter);
 }
@@ -557,10 +565,11 @@ bool SwHiddenPortion::GetExpText( const SwTextSizeInfo 
&rInf, OUString &rText )
 
 SwNumberPortion::SwNumberPortion( const OUString &rExpand,
                                   std::unique_ptr<SwFont> pFont,
+                                  std::unique_ptr<SwFont> 
pRedlineRenderModeFont,
                                   const bool bLft,
                                   const bool bCntr, const SwTwips nMinDst,
                                   const bool 
bLabelAlignmentPosAndSpaceModeActive )
-    : SwFieldPortion(rExpand, std::move(pFont), TextFrameIndex(0))
+    : SwFieldPortion(rExpand, std::move(pFont), 
std::move(pRedlineRenderModeFont), TextFrameIndex(0))
     , m_nFixWidth(0)
     , m_nMinDist(nMinDst)
     , 
mbLabelAlignmentPosAndSpaceModeActive(bLabelAlignmentPosAndSpaceModeActive)
@@ -581,8 +590,11 @@ SwFieldPortion *SwNumberPortion::Clone( const OUString 
&rExpand ) const
     std::unique_ptr<SwFont> pNewFnt;
     if( m_pFont )
         pNewFnt.reset(new SwFont( *m_pFont ));
+    std::unique_ptr<SwFont> pNewRedlineRenderModeFont;
+    if (m_pRedlineRenderModeFont)
+        pNewRedlineRenderModeFont.reset(new SwFont(*m_pRedlineRenderModeFont));
 
-    return new SwNumberPortion( rExpand, std::move(pNewFnt), IsLeft(), 
IsCenter(),
+    return new SwNumberPortion( rExpand, std::move(pNewFnt), 
std::move(pNewRedlineRenderModeFont), IsLeft(), IsCenter(),
                                 m_nMinDist, 
mbLabelAlignmentPosAndSpaceModeActive );
 }
 
@@ -741,7 +753,17 @@ void SwNumberPortion::Paint( const SwTextPaintInfo &rInf ) 
const
                         STRIKEOUT_NONE != m_pFont->GetStrikeout() ) &&
                         !m_pFont->IsWordLineMode();
 
-    SwFontSave aSave( rInf, m_pFont.get() );
+    const SwViewShell* pViewShell = rInf.GetVsh();
+    const SwViewOption* pViewOptions = pViewShell ? 
pViewShell->GetViewOptions() : nullptr;
+    SwRedlineRenderMode eRedlineRenderMode = pViewOptions ? 
pViewOptions->GetRedlineRenderMode() : SwRedlineRenderMode::Standard;
+    SwFont* pFont = m_pFont.get();
+    if (eRedlineRenderMode != SwRedlineRenderMode::Standard)
+    {
+        // Non-standard redline render mode: avoid using the font which is 
decorated with
+        // underline/strike-through.
+        pFont = m_pRedlineRenderModeFont.get();
+    }
+    SwFontSave aSave(rInf, pFont);
 
     if( m_nFixWidth == Width() && ! HasFollow() )
         SwExpandPortion::Paint( rInf );
@@ -809,7 +831,7 @@ SwBulletPortion::SwBulletPortion( const sal_UCS4 cBullet,
                                  const SwTwips nMinDst,
                                   const bool 
bLabelAlignmentPosAndSpaceModeActive )
     : SwNumberPortion( OUString(&cBullet, 1) + rBulletFollowedBy,
-                       std::move(pFont), bLft, bCntr, nMinDst,
+                       std::move(pFont), nullptr, bLft, bCntr, nMinDst,
                        bLabelAlignmentPosAndSpaceModeActive )
 {
     SetWhichPor( PortionType::Bullet );
@@ -823,7 +845,7 @@ SwGrfNumPortion::SwGrfNumPortion(
         const SwFormatVertOrient* pGrfOrient, const Size& rGrfSize,
         const bool bLft, const bool bCntr, const SwTwips nMinDst,
         const bool bLabelAlignmentPosAndSpaceModeActive ) :
-    SwNumberPortion( rGraphicFollowedBy, nullptr, bLft, bCntr, nMinDst,
+    SwNumberPortion( rGraphicFollowedBy, nullptr, nullptr, bLft, bCntr, 
nMinDst,
                      bLabelAlignmentPosAndSpaceModeActive ),
     m_pBrush( new SvxBrushItem(RES_BACKGROUND) ), m_nId( 0 )
 {
diff --git a/sw/source/core/text/porfld.hxx b/sw/source/core/text/porfld.hxx
index da9624ffd854..8d31c000487b 100644
--- a/sw/source/core/text/porfld.hxx
+++ b/sw/source/core/text/porfld.hxx
@@ -39,6 +39,7 @@ class SwFieldPortion : public SwExpandPortion
 protected:
     OUString  m_aExpand;          // The expanded field
     std::unique_ptr<SwFont> m_pFont;  // For multi-line fields
+    std::unique_ptr<SwFont> m_pRedlineRenderModeFont;
     TextFrameIndex m_nNextOffset;  // Offset of the follow in the original 
string
     TextFrameIndex m_nNextScriptChg;
     TextFrameIndex m_nFieldLen; //< Length of field text, 1 for normal fields, 
any number for input fields
@@ -61,7 +62,7 @@ protected:
 
 public:
     SwFieldPortion( const SwFieldPortion& rField );
-    SwFieldPortion(OUString aExpand, std::unique_ptr<SwFont> pFnt = nullptr, 
TextFrameIndex nLen = TextFrameIndex(1));
+    SwFieldPortion(OUString aExpand, std::unique_ptr<SwFont> pFnt = nullptr, 
std::unique_ptr<SwFont> pRedlineRenderModeFont = nullptr, TextFrameIndex nLen = 
TextFrameIndex(1));
     virtual ~SwFieldPortion() override;
 
     void TakeNextOffset( const SwFieldPortion* pField );
@@ -141,6 +142,7 @@ protected:
 public:
     SwNumberPortion( const OUString &rExpand,
                      std::unique_ptr<SwFont> pFnt,
+                     std::unique_ptr<SwFont> pRedlineRenderModeFont,
                      const bool bLeft,
                      const bool bCenter, const SwTwips nMinDst,
                      const bool bLabelAlignmentPosAndSpaceModeActive );
diff --git a/sw/source/core/text/porftn.hxx b/sw/source/core/text/porftn.hxx
index b22deb6ca4a2..32ae1ab4159b 100644
--- a/sw/source/core/text/porftn.hxx
+++ b/sw/source/core/text/porftn.hxx
@@ -54,7 +54,7 @@ class SwFootnoteNumPortion : public SwNumberPortion
 {
 public:
     SwFootnoteNumPortion( const OUString &rExpand, std::unique_ptr<SwFont> 
pFntL )
-         : SwNumberPortion( rExpand, std::move(pFntL), true, false, 0, false )
+         : SwNumberPortion( rExpand, std::move(pFntL), nullptr, true, false, 
0, false )
          { SetWhichPor( PortionType::FootnoteNum ); }
 };
 
diff --git a/sw/source/core/text/txtfld.cxx b/sw/source/core/text/txtfld.cxx
index dbb178c66d10..4eb0934bd979 100644
--- a/sw/source/core/text/txtfld.cxx
+++ b/sw/source/core/text/txtfld.cxx
@@ -692,13 +692,21 @@ SwNumberPortion *SwTextFormatter::NewNumberPortion( 
SwTextFormatInfo &rInf ) con
 
                     checkApplyParagraphMarkFormatToNumbering(pNumFnt.get(), 
rInf, pIDSA, pFormat);
 
+                    auto pNumRedlineRenderModeFont = 
std::make_unique<SwFont>(*pNumFnt);
+
                     if ( !lcl_setRedlineAttr( rInf, *pTextNd, pNumFnt ) && 
bHasHiddenNum )
+                    {
                         
pNumFnt->SetColor(SwViewOption::GetCurrentViewOptions().GetNonPrintingCharacterColor());
+                        pNumRedlineRenderModeFont->SetColor(
+                            
SwViewOption::GetCurrentViewOptions().GetNonPrintingCharacterColor());
+                    }
 
                     // we do not allow a vertical font
                     pNumFnt->SetVertical( pNumFnt->GetOrientation(), 
m_pFrame->IsVertical() );
+                    pNumRedlineRenderModeFont->SetVertical(
+                        pNumRedlineRenderModeFont->GetOrientation(), 
m_pFrame->IsVertical());
 
-                    pRet = new SwNumberPortion( aTextNow, std::move(pNumFnt),
+                    pRet = new SwNumberPortion( aTextNow, std::move(pNumFnt), 
std::move(pNumRedlineRenderModeFont),
                                                 bLeft, bCenter, nMinDist,
                                                 
bLabelAlignmentPosAndSpaceModeActive );
                 }
diff --git a/sw/source/core/txtnode/swfont.cxx 
b/sw/source/core/txtnode/swfont.cxx
index 07dfef5d43c7..ef33a4e6bae8 100644
--- a/sw/source/core/txtnode/swfont.cxx
+++ b/sw/source/core/txtnode/swfont.cxx
@@ -365,6 +365,11 @@ void SwFont::dumpAsXml(xmlTextWriterPtr writer) const
         ss << GetWeight();
         (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("weight"), 
BAD_CAST(ss.str().c_str()));
     }
+    {
+        std::stringstream ss;
+        ss << GetUnderline();
+        (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("underline"), 
BAD_CAST(ss.str().c_str()));
+    }
     (void)xmlTextWriterEndElement(writer);
 }
 

Reply via email to