officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs |    6 
 sw/inc/IDocumentSettingAccess.hxx                                 |    1 
 sw/inc/strings.hrc                                                |    1 
 sw/inc/viewsh.hxx                                                 |    2 
 sw/qa/extras/odfexport/data/UnderlineTrailingSpace.fodt           |   55 +++++
 sw/qa/extras/odfexport/odfexport2.cxx                             |  105 
++++++++++
 sw/qa/extras/ooxmlexport/data/UnderlineTrailingSpace.docx         |binary
 sw/qa/extras/ooxmlexport/ooxmlexport21.cxx                        |   26 ++
 sw/source/core/doc/DocumentSettingManager.cxx                     |   10 
 sw/source/core/inc/DocumentSettingManager.hxx                     |    1 
 sw/source/core/text/guess.cxx                                     |    8 
 sw/source/core/text/portxt.cxx                                    |   83 
+++++++
 sw/source/core/text/portxt.hxx                                    |    6 
 sw/source/core/view/viewsh.cxx                                    |   12 +
 sw/source/filter/ww8/docxexport.cxx                               |    2 
 sw/source/ui/config/optcomp.cxx                                   |    6 
 sw/source/uibase/uno/SwXDocumentSettings.cxx                      |   18 +
 sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx              |    2 
 sw/source/writerfilter/dmapper/SettingsTable.cxx                  |    9 
 sw/source/writerfilter/dmapper/SettingsTable.hxx                  |    1 
 20 files changed, 337 insertions(+), 17 deletions(-)

New commits:
commit 5dee0b3478d0112c4fdbf752dba348762400d624
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Tue Feb 25 21:12:02 2025 +0500
Commit:     Mike Kaganski <mike.kagan...@collabora.com>
CommitDate: Wed Feb 26 18:48:30 2025 +0100

    tdf#164487: "Show underline" compatibility option OOXML import/export
    
    Change-Id: I8db0b3da1cdadbf4a5c279231abc96699a8761f1
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182189
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182215
    Tested-by: Mike Kaganski <mike.kagan...@collabora.com>

diff --git a/sw/qa/extras/ooxmlexport/data/UnderlineTrailingSpace.docx 
b/sw/qa/extras/ooxmlexport/data/UnderlineTrailingSpace.docx
new file mode 100644
index 000000000000..f32e9fa880a4
Binary files /dev/null and 
b/sw/qa/extras/ooxmlexport/data/UnderlineTrailingSpace.docx differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport21.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport21.cxx
index 3f806154d257..17d13a662f40 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport21.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport21.cxx
@@ -1218,6 +1218,32 @@ DECLARE_OOXMLEXPORT_TEST(testTdf164176, "tdf164176.docx")
     CPPUNIT_ASSERT_EQUAL(sal_Int32(-1), sPresentation.indexOf("_x000a_"));
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testMsWordUlTrailSpace)
+{
+    createSwDoc("UnderlineTrailingSpace.docx");
+    {
+        uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, 
uno::UNO_QUERY_THROW);
+        uno::Reference<beans::XPropertySet> xSettings(
+            xFactory->createInstance(u"com.sun.star.document.Settings"_ustr), 
uno::UNO_QUERY_THROW);
+        CPPUNIT_ASSERT_EQUAL(uno::Any(true),
+                             
xSettings->getPropertyValue(u"MsWordUlTrailSpace"_ustr));
+    }
+
+    // Test also after save-and-reload:
+    saveAndReload(u"Office Open XML Text"_ustr);
+    {
+        uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, 
uno::UNO_QUERY_THROW);
+        uno::Reference<beans::XPropertySet> xSettings(
+            xFactory->createInstance(u"com.sun.star.document.Settings"_ustr), 
uno::UNO_QUERY_THROW);
+        CPPUNIT_ASSERT_EQUAL(uno::Any(true),
+                             
xSettings->getPropertyValue(u"MsWordUlTrailSpace"_ustr));
+    }
+
+    // Check that the compat setting is exported in OOXML
+    xmlDocUniquePtr pXmlSettings = parseExport("word/settings.xml");
+    assertXPath(pXmlSettings, "/w:settings/w:compat/w:ulTrailSpace");
+}
+
 } // end of anonymous namespace
 CPPUNIT_PLUGIN_IMPLEMENT();
 
diff --git a/sw/source/filter/ww8/docxexport.cxx 
b/sw/source/filter/ww8/docxexport.cxx
index 9745f4ba71c2..1450bdb63c5d 100644
--- a/sw/source/filter/ww8/docxexport.cxx
+++ b/sw/source/filter/ww8/docxexport.cxx
@@ -1033,6 +1033,8 @@ WriteCompat(SwDoc const& rDoc, 
::sax_fastparser::FSHelperPtr const& rpFS,
         // Map the DoNotBreakWrappedTables compat flag to 
<w:doNotBreakWrappedTables>.
         rpFS->singleElementNS(XML_w, XML_doNotBreakWrappedTables);
     }
+    if (rIDSA.get(DocumentSettingId::MS_WORD_UL_TRAIL_SPACE))
+        rpFS->singleElementNS(XML_w, XML_ulTrailSpace);
 }
 
 void DocxExport::WriteSettings()
diff --git a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx 
b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx
index 1e5b365e975f..4ebc866376ff 100644
--- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx
+++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx
@@ -9864,6 +9864,8 @@ void DomainMapper_Impl::ApplySettingsTable()
                 = m_pSettingsTable->GetWriteProtectionSettings();
         if (aWriteProtection.hasElements())
                 xSettings->setPropertyValue(u"ModifyPasswordInfo"_ustr, 
uno::Any(aWriteProtection));
+        if (m_pSettingsTable->GetMsWordUlTrailSpace())
+            xSettings->setPropertyValue(u"MsWordUlTrailSpace"_ustr, 
uno::Any(true));
     }
     catch(const uno::Exception&)
     {
diff --git a/sw/source/writerfilter/dmapper/SettingsTable.cxx 
b/sw/source/writerfilter/dmapper/SettingsTable.cxx
index 7ec07453d4f6..0efd70a8b560 100644
--- a/sw/source/writerfilter/dmapper/SettingsTable.cxx
+++ b/sw/source/writerfilter/dmapper/SettingsTable.cxx
@@ -95,6 +95,7 @@ struct SettingsTable_Impl
     bool                m_bNoColumnBalance;
     bool                m_bAutoHyphenation;
     bool                m_bNoHyphenateCaps;
+    bool                m_bMsWordUlTrailSpace = false;
     sal_Int16           m_nHyphenationZone;
     sal_Int16           m_nConsecutiveHyphenLimit;
     sal_Int16           m_nUseWord2013TrackBottomHyphenation;
@@ -456,6 +457,9 @@ void SettingsTable::lcl_sprm(Sprm& rSprm)
             m_pImpl->m_bEndnoteIsCollectAtSectionEnd = true;
         }
         break;
+    case NS_ooxml::LN_CT_Compat_ulTrailSpace:
+        m_pImpl->m_bMsWordUlTrailSpace = true;
+        break;
     default:
     {
 #ifdef DBG_UTIL
@@ -561,6 +565,11 @@ bool SettingsTable::GetNoHyphenateCaps() const
     return m_pImpl->m_bNoHyphenateCaps;
 }
 
+bool SettingsTable::GetMsWordUlTrailSpace() const
+{
+    return m_pImpl->m_bMsWordUlTrailSpace;
+}
+
 sal_Int16 SettingsTable::GetHyphenationZone() const
 {
     return m_pImpl->m_nHyphenationZone;
diff --git a/sw/source/writerfilter/dmapper/SettingsTable.hxx 
b/sw/source/writerfilter/dmapper/SettingsTable.hxx
index c90f5f389abf..9a8db79bbfa7 100644
--- a/sw/source/writerfilter/dmapper/SettingsTable.hxx
+++ b/sw/source/writerfilter/dmapper/SettingsTable.hxx
@@ -78,6 +78,7 @@ public:
     bool GetLongerSpaceSequence() const;
     bool GetNoLeading() const;
     bool GetNoHyphenateCaps() const;
+    bool GetMsWordUlTrailSpace() const;
     sal_Int16 GetHyphenationZone() const;
     sal_Int16 GetConsecutiveHyphenLimit() const;
     bool GetHyphenationKeep() const;
commit 13b80d040c4ddfd821de4138196ed1034ea7690e
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Thu Feb 20 13:27:46 2025 +0500
Commit:     Mike Kaganski <mike.kagan...@collabora.com>
CommitDate: Wed Feb 26 18:48:17 2025 +0100

    tdf#164487: Introduce "Show underline" MS Word compatibility option
    
    It corresponds to ulTrailSpace element in OOXML. This commit creates
    the option in officecfg, UI, ODF, and implements its initial support
    in layout.
    
    Unresolved problems (pre-existing, not created here):
    
    1. Short lines show underline unconditionally.  In the unit test doc,
    line 2 is such.  In SwTextGuess::Guess, there is the "first check if
    everything fits to line" shortcut, that skips SwHolePortion creation,
    that causes the problem.
    
    2. The first character of the trailing whitespace is underlined.  It
    is caused by AdjustCutPos only stepping back until there is a single
    character left. But to fix that,  we need to handle the "zero-length
    portion plus some extra" case.
    
    Change-Id: Icc84fcff3ae62ced198522a80b8ce6b0e12e3a3c
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182188
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182214
    Tested-by: Mike Kaganski <mike.kagan...@collabora.com>

diff --git a/officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs 
b/officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs
index 47760b0921f9..4c96abe3a080 100644
--- a/officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs
+++ b/officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs
@@ -116,6 +116,12 @@
         </info>
         <value>false</value>
       </prop>
+      <prop oor:name="MsWordUlTrailSpace" oor:type="xs:boolean" 
oor:nillable="false">
+        <info>
+          <desc>Show underline of Word-compatible trailing blanks</desc>
+        </info>
+        <value>false</value>
+      </prop>
       <prop oor:name="SubtractFlysAnchoredAtFlys" oor:type="xs:boolean" 
oor:nillable="false">
         <info>
           <desc>Tolerate white lines of PDF page backgrounds for compatibility 
with old documents (Use LibreOffice 4.3 anchoring paint order)</desc>
diff --git a/sw/inc/IDocumentSettingAccess.hxx 
b/sw/inc/IDocumentSettingAccess.hxx
index 9a22fac5e847..1314f60479bc 100644
--- a/sw/inc/IDocumentSettingAccess.hxx
+++ b/sw/inc/IDocumentSettingAccess.hxx
@@ -142,6 +142,7 @@ enum class DocumentSettingId
     MS_WORD_COMP_GRID_METRICS,
     // tdf#161233 pictures with wrap polygon should not be clipped
     NO_CLIPPING_WITH_WRAP_POLYGON,
+    MS_WORD_UL_TRAIL_SPACE,
 };
 
 /** Provides access to settings of a document
diff --git a/sw/inc/strings.hrc b/sw/inc/strings.hrc
index a6ce4cad0a5a..aef6984053e8 100644
--- a/sw/inc/strings.hrc
+++ b/sw/inc/strings.hrc
@@ -1532,6 +1532,7 @@
 #define STR_COMPAT_OPT_CONTINUOUS_ENDNOTES 
NC_("STR_COMPAT_OPT_CONTINUOUS_ENDNOTES", "Render endnotes at the end of 
document inline, rather than on a separate page")
 #define STR_COMPAT_OPT_MSWORDCOMPGRIDMETRICS 
NC_("STR_COMPAT_OPT_MSWORDCOMPGRIDMETRICS", "Use Word-compatible font metrics 
for text grid")
 #define STR_COMPAT_OPT_IGNORETABSANDBLANKSFORLINECALCULATION 
NC_("STR_COMPAT_OPT_IGNORETABSANDBLANKSFORLINECALCULATION", "Make whitespace 
character height not affect line height")
+#define STR_COMPAT_OPT_UNDERLINETRAILINGSPACE 
NC_("STR_COMPAT_OPT_UNDERLINETRAILINGSPACE", "Underline Word-compatible 
trailing blanks")
 
 #define STR_TABLE_PANEL_ALIGN_AUTO              
NC_("sidebartableedit|alignautolabel", "Automatic")
 #define STR_TABLE_PANEL_ALIGN_LEFT              
NC_("sidebartableedit|alignleftlabel", "Left")
diff --git a/sw/inc/viewsh.hxx b/sw/inc/viewsh.hxx
index e438a891490f..633c353aaa86 100644
--- a/sw/inc/viewsh.hxx
+++ b/sw/inc/viewsh.hxx
@@ -460,6 +460,8 @@ public:
 
     SW_DLLPUBLIC void SetIgnoreTabsAndBlanksForLineCalculation(bool 
bIgnoreTabsAndBlanksForLineCalculation);
 
+    SW_DLLPUBLIC void SetMsWordUlTrailSpace(bool val);
+
     // DOCUMENT COMPATIBILITY FLAGS END
 
     // Calls Idle-formatter of Layout.
diff --git a/sw/qa/extras/odfexport/data/UnderlineTrailingSpace.fodt 
b/sw/qa/extras/odfexport/data/UnderlineTrailingSpace.fodt
new file mode 100644
index 000000000000..aeff5412cd2a
--- /dev/null
+++ b/sw/qa/extras/odfexport/data/UnderlineTrailingSpace.fodt
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document 
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" 
xmlns:ooo="http://openoffice.org/2004/office"; 
xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" 
xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" 
xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" 
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" 
xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" 
office:version="1.4" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:settings>
+  <config:config-item-set config:name="ooo:configuration-settings">
+   <config:config-item config:name="MsWordCompTrailingBlanks" 
config:type="boolean">true</config:config-item>
+   <config:config-item config:name="MsWordUlTrailSpace" 
config:type="boolean">true</config:config-item>
+  </config:config-item-set>
+ </office:settings>
+ <office:font-face-decls>
+  <style:font-face style:name="Liberation Sans" 
svg:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="swiss" 
style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+  <style:default-style style:family="paragraph">
+   <style:paragraph-properties fo:hyphenation-ladder-count="no-limit" 
fo:hyphenation-keep="auto" style:text-autospace="ideograph-alpha" 
style:punctuation-wrap="hanging" style:line-break="strict" 
style:writing-mode="page"/>
+   <style:text-properties style:use-window-font-color="true" 
style:font-name="Liberation Sans" fo:font-size="10.5pt" fo:language="zxx" 
fo:country="none" style:letter-kerning="true" style:font-name-asian="Liberation 
Sans" style:font-size-asian="10.5pt" style:language-asian="zxx" 
style:country-asian="none" fo:hyphenate="false" 
fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2"/>
+  </style:default-style>
+  <style:style style:name="Standard" style:family="paragraph" 
style:class="text">
+   <style:paragraph-properties fo:margin-top="0" fo:margin-bottom="0" 
style:contextual-spacing="false" fo:text-align="justify" 
style:justify-single-word="false" style:writing-mode="lr-tb"/>
+  </style:style>
+  <style:style style:name="MyListLabel" style:family="text">
+   <style:text-properties style:font-name="Liberation Sans"/>
+  </style:style>
+  <text:list-style style:name="MyListStyle">
+   <text:list-level-style-bullet text:level="1" text:style-name="MyListLabel" 
style:num-suffix="•" text:bullet-char="•">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" 
fo:text-indent="-7mm" fo:margin-left="7mm"/>
+    </style:list-level-properties>
+    <style:text-properties fo:font-name="Liberation Sans"/>
+   </text:list-level-style-bullet>
+  </text:list-style>
+ </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="T1" style:family="text">
+   <style:text-properties style:text-underline-style="solid" 
style:text-underline-width="auto" style:text-underline-color="font-color"/>
+  </style:style>
+  <style:page-layout style:name="pm1">
+   <style:page-layout-properties fo:page-width="210mm" fo:page-height="297mm" 
fo:margin-top="1in" fo:margin-bottom="1in" fo:margin-left="1.25in" 
fo:margin-right="1.25in" style:writing-mode="lr-tb"/>
+  </style:page-layout>
+ </office:automatic-styles>
+ <office:master-styles>
+  <style:master-page style:name="Standard" style:page-layout-name="pm1"/>
+ </office:master-styles>
+ <office:body>
+  <office:text>
+   <text:list text:style-name="MyListStyle">
+    <text:list-item>
+     <text:p>下划线 <text:span text:style-name="T1"><text:s 
text:c="70"/></text:span></text:p>
+    </text:list-item>
+   </text:list>
+   <text:p><text:span text:style-name="T1"><text:s 
text:c="10"/></text:span></text:p>
+   <text:p><text:span text:style-name="T1"><text:s 
text:c="1000"/></text:span></text:p>
+  </office:text>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/sw/qa/extras/odfexport/odfexport2.cxx 
b/sw/qa/extras/odfexport/odfexport2.cxx
index 39659ff96ef0..13e8c5bac387 100644
--- a/sw/qa/extras/odfexport/odfexport2.cxx
+++ b/sw/qa/extras/odfexport/odfexport2.cxx
@@ -42,6 +42,7 @@
 #include <unotxdoc.hxx>
 #include <docsh.hxx>
 #include <IDocumentFieldsAccess.hxx>
+#include <IDocumentLayoutAccess.hxx>
 #include <IDocumentLinksAdministration.hxx>
 #include <sfx2/linkmgr.hxx>
 
@@ -1919,6 +1920,110 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf163913)
                 "margin-right", u"6em");
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testMsWordUlTrailSpace)
+{
+    // Testing MsWordUlTrailSpace compat option
+
+    // Given a document with both MsWordCompTrailingBlanks and 
MsWordUlTrailSpace set
+    createSwDoc("UnderlineTrailingSpace.fodt");
+    // 1. Make sure that the import sets MsWordUlTrailSpace option, and 
creates correct layout
+    {
+        uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, 
uno::UNO_QUERY_THROW);
+        uno::Reference<beans::XPropertySet> xSettings(
+            xFactory->createInstance(u"com.sun.star.document.Settings"_ustr), 
uno::UNO_QUERY_THROW);
+        CPPUNIT_ASSERT_EQUAL(uno::Any(true),
+                             
xSettings->getPropertyValue(u"MsWordUlTrailSpace"_ustr));
+
+        xmlDocUniquePtr pXmlDoc = parseLayoutDump();
+        OUString val;
+        // Line 1: one SwHolePortion, showing underline
+        val = getXPath(pXmlDoc, 
"//body/txt[1]/SwParaPortion/SwLineLayout/SwHolePortion", "length");
+        CPPUNIT_ASSERT_GREATEREQUAL(sal_Int32(69), val.toInt32()); // In 
truth, it should be 70
+        val = getXPath(pXmlDoc, 
"//body/txt[1]/SwParaPortion/SwLineLayout/SwHolePortion",
+                       "show-underline");
+        CPPUNIT_ASSERT_EQUAL(u"true"_ustr, val);
+        // TODO: Line 2
+        // Line 3: two SwHolePortion, one for shown underline, one for the rest
+        val = getXPath(pXmlDoc, 
"//body/txt[3]/SwParaPortion/SwLineLayout/SwHolePortion[1]",
+                       "length");
+        CPPUNIT_ASSERT_GREATEREQUAL(sal_Int32(140), val.toInt32());
+        val = getXPath(pXmlDoc, 
"//body/txt[3]/SwParaPortion/SwLineLayout/SwHolePortion[1]",
+                       "show-underline");
+        CPPUNIT_ASSERT_EQUAL(u"true"_ustr, val);
+        val = getXPath(pXmlDoc, 
"//body/txt[3]/SwParaPortion/SwLineLayout/SwHolePortion[2]",
+                       "length");
+        CPPUNIT_ASSERT_GREATEREQUAL(sal_Int32(850), val.toInt32());
+        val = getXPath(pXmlDoc, 
"//body/txt[3]/SwParaPortion/SwLineLayout/SwHolePortion[2]",
+                       "show-underline");
+        CPPUNIT_ASSERT_EQUAL(u"false"_ustr, val);
+    }
+
+    saveAndReload(mpFilter);
+    // 2. Make sure that exported document has MsWordUlTrailSpace option set
+    {
+        xmlDocUniquePtr pXmlDoc = parseExport(u"settings.xml"_ustr);
+        assertXPathContent(pXmlDoc, 
"//config:config-item[@config:name='MsWordUlTrailSpace']",
+                           u"true");
+
+        uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, 
uno::UNO_QUERY_THROW);
+        uno::Reference<beans::XPropertySet> xSettings(
+            xFactory->createInstance(u"com.sun.star.document.Settings"_ustr), 
uno::UNO_QUERY_THROW);
+        CPPUNIT_ASSERT_EQUAL(uno::Any(true),
+                             
xSettings->getPropertyValue(u"MsWordUlTrailSpace"_ustr));
+    }
+
+    // 3. Disable the option, and check the layout
+    {
+        uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, 
uno::UNO_QUERY_THROW);
+        uno::Reference<beans::XPropertySet> xSettings(
+            xFactory->createInstance(u"com.sun.star.document.Settings"_ustr), 
uno::UNO_QUERY_THROW);
+        xSettings->setPropertyValue(u"MsWordUlTrailSpace"_ustr, 
uno::Any(false));
+        CPPUNIT_ASSERT_EQUAL(uno::Any(false),
+                             
xSettings->getPropertyValue(u"MsWordUlTrailSpace"_ustr));
+
+        
getSwDoc()->getIDocumentLayoutAccess().GetCurrentViewShell()->Reformat();
+        xmlDocUniquePtr pXmlDoc = parseLayoutDump();
+        OUString val;
+        // Line 1: one SwHolePortion, not showing underline
+        val = getXPath(pXmlDoc, 
"//body/txt[1]/SwParaPortion/SwLineLayout/SwHolePortion", "length");
+        CPPUNIT_ASSERT_GREATEREQUAL(sal_Int32(69), val.toInt32()); // In 
truth, it should be 70
+        val = getXPath(pXmlDoc, 
"//body/txt[1]/SwParaPortion/SwLineLayout/SwHolePortion",
+                       "show-underline");
+        CPPUNIT_ASSERT_EQUAL(u"false"_ustr, val);
+        // TODO: Line 2
+        // Line 3: one SwHolePortion, not showing underline
+        val = getXPath(pXmlDoc, 
"//body/txt[3]/SwParaPortion/SwLineLayout/SwHolePortion", "length");
+        CPPUNIT_ASSERT_GREATEREQUAL(sal_Int32(999), val.toInt32());
+        val = getXPath(pXmlDoc, 
"//body/txt[3]/SwParaPortion/SwLineLayout/SwHolePortion",
+                       "show-underline");
+        CPPUNIT_ASSERT_EQUAL(u"false"_ustr, val);
+    }
+
+    saveAndReload(mpFilter);
+    // 4. Make sure that exported document has MsWordUlTrailSpace option not 
set
+    {
+        xmlDocUniquePtr pXmlDoc = parseExport(u"settings.xml"_ustr);
+        assertXPathContent(pXmlDoc, 
"//config:config-item[@config:name='MsWordUlTrailSpace']",
+                           u"false");
+
+        uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, 
uno::UNO_QUERY_THROW);
+        uno::Reference<beans::XPropertySet> xSettings(
+            xFactory->createInstance(u"com.sun.star.document.Settings"_ustr), 
uno::UNO_QUERY_THROW);
+        CPPUNIT_ASSERT_EQUAL(uno::Any(false),
+                             
xSettings->getPropertyValue(u"MsWordUlTrailSpace"_ustr));
+    }
+
+    createSwDoc();
+    // 5. Make sure that a new Writer document has this setting set to false
+    {
+        uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, 
uno::UNO_QUERY_THROW);
+        uno::Reference<beans::XPropertySet> xSettings(
+            xFactory->createInstance(u"com.sun.star.document.Settings"_ustr), 
uno::UNO_QUERY_THROW);
+        CPPUNIT_ASSERT_EQUAL(uno::Any(false),
+                             
xSettings->getPropertyValue(u"MsWordUlTrailSpace"_ustr));
+    }
+}
+
 } // end of anonymous namespace
 CPPUNIT_PLUGIN_IMPLEMENT();
 
diff --git a/sw/source/core/doc/DocumentSettingManager.cxx 
b/sw/source/core/doc/DocumentSettingManager.cxx
index 1ac6e3e15332..5106cb78ddfc 100644
--- a/sw/source/core/doc/DocumentSettingManager.cxx
+++ b/sw/source/core/doc/DocumentSettingManager.cxx
@@ -279,6 +279,7 @@ bool sw::DocumentSettingManager::get(/*[in]*/ 
DocumentSettingId id) const
         case DocumentSettingId::MIN_ROW_HEIGHT_INCL_BORDER: return 
mbMinRowHeightInclBorder;
         case DocumentSettingId::MS_WORD_COMP_GRID_METRICS: return 
mbMsWordCompGridMetrics;
         case DocumentSettingId::NO_CLIPPING_WITH_WRAP_POLYGON: return 
mbNoClippingWithWrapPolygon;
+        case DocumentSettingId::MS_WORD_UL_TRAIL_SPACE: return 
mbMsWordUlTrailSpace;
         default:
             OSL_FAIL("Invalid setting id");
     }
@@ -610,6 +611,9 @@ void sw::DocumentSettingManager::set(/*[in]*/ 
DocumentSettingId id, /*[in]*/ boo
         case DocumentSettingId::NO_CLIPPING_WITH_WRAP_POLYGON:
             mbNoClippingWithWrapPolygon = value;
             break;
+        case DocumentSettingId::MS_WORD_UL_TRAIL_SPACE:
+            mbMsWordUlTrailSpace = value;
+            break;
         default:
             OSL_FAIL("Invalid setting id");
     }
@@ -791,6 +795,7 @@ void 
sw::DocumentSettingManager::ReplaceCompatibilityOptions(const DocumentSetti
     mbUseVariableWidthNBSP = rSource.mbUseVariableWidthNBSP;
     mbMsWordCompGridMetrics = rSource.mbMsWordCompGridMetrics;
     mbNoClippingWithWrapPolygon = rSource.mbNoClippingWithWrapPolygon;
+    mbMsWordUlTrailSpace = rSource.mbMsWordUlTrailSpace;
 }
 
 sal_uInt32 sw::DocumentSettingManager::Getn32DummyCompatibilityOptions1() const
@@ -1187,6 +1192,11 @@ void 
sw::DocumentSettingManager::dumpAsXml(xmlTextWriterPtr pWriter) const
                                 
BAD_CAST(OString::boolean(mbNoClippingWithWrapPolygon).getStr()));
     (void)xmlTextWriterEndElement(pWriter);
 
+    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbMsWordUlTrailSpace"));
+    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"),
+                                
BAD_CAST(OString::boolean(mbMsWordUlTrailSpace).getStr()));
+    (void)xmlTextWriterEndElement(pWriter);
+
     (void)xmlTextWriterEndElement(pWriter);
 }
 
diff --git a/sw/source/core/inc/DocumentSettingManager.hxx 
b/sw/source/core/inc/DocumentSettingManager.hxx
index f7ca013b77f1..92dfe6f834e8 100644
--- a/sw/source/core/inc/DocumentSettingManager.hxx
+++ b/sw/source/core/inc/DocumentSettingManager.hxx
@@ -190,6 +190,7 @@ class DocumentSettingManager final :
     bool mbMinRowHeightInclBorder : 1; // tdf#155229
     bool mbMsWordCompGridMetrics : 1; // tdf#129808
     bool mbNoClippingWithWrapPolygon : 1; // tdf#161233
+    bool mbMsWordUlTrailSpace : 1 = false;
 
 public:
 
diff --git a/sw/source/core/text/guess.cxx b/sw/source/core/text/guess.cxx
index 81ca87b15113..8158dec91079 100644
--- a/sw/source/core/text/guess.cxx
+++ b/sw/source/core/text/guess.cxx
@@ -176,13 +176,7 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, 
SwTextFormatInfo &rInf,
 
     const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo();
 
-    sal_uInt16 nMaxComp = ( SwFontScript::CJK == rInf.GetFont()->GetActual() ) 
&&
-                        rSI.CountCompChg() &&
-                        ! rInf.IsMulti() &&
-                        ! rPor.InFieldGrp() &&
-                        ! rPor.IsDropPortion() ?
-                        10000 :
-                            0 ;
+    const sal_uInt16 nMaxComp = rPor.GetMaxComp(rInf);
 
     SwTwips nLineWidth = rInf.GetLineWidth();
     TextFrameIndex nMaxLen = TextFrameIndex(rInf.GetText().getLength()) - 
rInf.GetIdx();
diff --git a/sw/source/core/text/portxt.cxx b/sw/source/core/text/portxt.cxx
index d72bc719a730..3a17d3ec478b 100644
--- a/sw/source/core/text/portxt.cxx
+++ b/sw/source/core/text/portxt.cxx
@@ -36,6 +36,7 @@
 #include <IMark.hxx>
 #include <pam.hxx>
 #include <doc.hxx>
+#include <o3tl/temporary.hxx>
 #include <xmloff/odffields.hxx>
 #include <viewopt.hxx>
 
@@ -227,6 +228,35 @@ static TextFrameIndex lcl_AddSpace_Latin(const 
SwTextSizeInfo& rInf, const OUStr
     return nCnt;
 }
 
+static void GetLimitedStringPart(const SwTextFormatInfo& rInf, TextFrameIndex 
nIndex,
+                                 TextFrameIndex nLength, sal_uInt16 nComp, 
SwTwips nOriginalWidth,
+                                 SwTwips nMaxWidth, TextFrameIndex& 
rOutLength, SwTwips& rOutWidth)
+{
+    assert(nMaxWidth >= 0);
+    assert(nLength >= TextFrameIndex(0));
+    const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo();
+    rOutLength = nLength;
+    rOutWidth = nOriginalWidth;
+    while (rOutWidth > nMaxWidth)
+    {
+        TextFrameIndex nNewOnLineLengthGuess(rOutLength.get() * nMaxWidth / 
rOutWidth);
+        assert(nNewOnLineLengthGuess < rOutLength);
+        if (nNewOnLineLengthGuess < (rOutLength - TextFrameIndex(1)))
+            ++nNewOnLineLengthGuess; // to avoid too aggressive decrease
+        rOutLength = nNewOnLineLengthGuess;
+        rInf.GetTextSize(&rSI, nIndex, rOutLength, std::nullopt, nComp, 
rOutWidth,
+                         o3tl::temporary(tools::Long()), 
o3tl::temporary(SwTwips()),
+                         o3tl::temporary(SwTwips()), 
rInf.GetCachedVclData().get());
+    }
+}
+
+static bool IsMsWordUlTrailSpace(const SwTextFormatInfo& rInf)
+{
+    const auto& settings = 
rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess();
+    return settings.get(DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS)
+           && settings.get(DocumentSettingId::MS_WORD_UL_TRAIL_SPACE);
+}
+
 SwTextPortion * SwTextPortion::CopyLinePortion(const SwLinePortion &rPortion)
 {
     SwTextPortion *const pNew(new SwTextPortion);
@@ -298,6 +328,15 @@ static bool lcl_HasContent( const SwFieldPortion& rField, 
SwTextFormatInfo const
     return rField.GetExpText( rInf, aText ) && !aText.isEmpty();
 }
 
+sal_uInt16 SwTextPortion::GetMaxComp(const SwTextFormatInfo& rInf) const
+{
+    const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo();
+    return (SwFontScript::CJK == rInf.GetFont()->GetActual()) && 
rSI.CountCompChg()
+                   && !rInf.IsMulti() && !InFieldGrp() && !IsDropPortion()
+               ? 10000
+               : 0;
+}
+
 bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
 {
     // 5744: If only the hyphen does not fit anymore, we still need to wrap
@@ -477,12 +516,37 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
             TextFrameIndex const nRealStart = pGuess->BreakStart() - 
pGuess->FieldDiff();
             if( pGuess->BreakPos() < nRealStart && !InExpGrp() )
             {
-                SwHolePortion *pNew = new SwHolePortion( *this );
-                pNew->SetLen( nRealStart - pGuess->BreakPos() );
-                pNew->Width(0);
-                pNew->ExtraBlankWidth( pGuess->ExtraBlankWidth() );
+                TextFrameIndex nTotalExtraLen(nRealStart - pGuess->BreakPos());
+                TextFrameIndex nExtraLen(nTotalExtraLen);
+                TextFrameIndex nExtraLenOutOfLine(0);
+                SwTwips nTotalExtraWidth(pGuess->ExtraBlankWidth());
+                SwTwips nExtraWidth(nTotalExtraWidth);
+                SwTwips nExtraWidthOutOfLine(0);
+                SwTwips nAvailableLineWidth(rInf.GetLineWidth() - Width());
+                const bool bMsWordUlTrailSpace(IsMsWordUlTrailSpace(rInf));
+                if (nExtraWidth > nAvailableLineWidth && bMsWordUlTrailSpace)
+                {
+                    GetLimitedStringPart(rInf, pGuess->BreakPos(), 
nTotalExtraLen, GetMaxComp(rInf),
+                                         nTotalExtraWidth, 
nAvailableLineWidth, nExtraLen,
+                                         nExtraWidth);
+                    nExtraLenOutOfLine = nTotalExtraLen - nExtraLen;
+                    nExtraWidthOutOfLine = nTotalExtraWidth - nExtraWidth;
+                }
+
+                SwHolePortion* pNew = new SwHolePortion(*this, 
bMsWordUlTrailSpace);
+                pNew->SetLen(nExtraLen);
+                pNew->ExtraBlankWidth(nExtraWidth);
                 Insert( pNew );
 
+                if (nExtraWidthOutOfLine)
+                {
+                    // Out-of-line hole portion - will not show underline
+                    SwHolePortion* pNewOutOfLine = new SwHolePortion(*this, 
false);
+                    pNewOutOfLine->SetLen(nExtraLenOutOfLine);
+                    pNewOutOfLine->ExtraBlankWidth(nExtraWidthOutOfLine);
+                    pNew->Insert(pNewOutOfLine);
+                }
+
                 // UAX #14 Unicode Line Breaking Algorithm Non-tailorable Line 
breaking rule LB6:
                 // https://www.unicode.org/reports/tr14/#LB6 Do not break 
before hard line breaks
                 if (auto ch = rInf.GetChar(pGuess->BreakStart()); !ch || ch == 
CH_BREAK)
@@ -828,8 +892,9 @@ SwPosSize SwTextInputFieldPortion::GetTextSize( const 
SwTextSizeInfo &rInf ) con
     return rInf.GetTextSize();
 }
 
-SwHolePortion::SwHolePortion( const SwTextPortion &rPor )
+SwHolePortion::SwHolePortion(const SwTextPortion& rPor, bool bShowUnderline)
     : m_nBlankWidth( 0 )
+    , m_bShowUnderline(bShowUnderline)
 {
     SetLen( TextFrameIndex(1) );
     Height( rPor.Height() );
@@ -871,12 +936,13 @@ void SwHolePortion::Paint( const SwTextPaintInfo &rInf ) 
const
     const SwFont* pOrigFont = rInf.GetFont();
     std::unique_ptr<SwFont> pHoleFont;
     std::optional<SwFontSave> oFontSave;
-    if( pOrigFont->GetUnderline() != LINESTYLE_NONE
+    if( (!m_bShowUnderline && pOrigFont->GetUnderline() != LINESTYLE_NONE)
     ||  pOrigFont->GetOverline() != LINESTYLE_NONE
     ||  pOrigFont->GetStrikeout() != STRIKEOUT_NONE )
     {
         pHoleFont.reset(new SwFont( *pOrigFont ));
-        pHoleFont->SetUnderline( LINESTYLE_NONE );
+        if (!m_bShowUnderline)
+            pHoleFont->SetUnderline(LINESTYLE_NONE);
         pHoleFont->SetOverline( LINESTYLE_NONE );
         pHoleFont->SetStrikeout( STRIKEOUT_NONE );
         oFontSave.emplace( rInf, pHoleFont.get() );
@@ -916,6 +982,9 @@ void SwHolePortion::dumpAsXml(xmlTextWriterPtr pWriter, 
const OUString& rText, T
     (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("blank-width"),
                                       
BAD_CAST(OString::number(m_nBlankWidth).getStr()));
 
+    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("show-underline"),
+                                      
BAD_CAST(OString::boolean(m_bShowUnderline).getStr()));
+
     (void)xmlTextWriterEndElement(pWriter);
 }
 
diff --git a/sw/source/core/text/portxt.hxx b/sw/source/core/text/portxt.hxx
index 438764e31318..f226f33bac66 100644
--- a/sw/source/core/text/portxt.hxx
+++ b/sw/source/core/text/portxt.hxx
@@ -47,6 +47,8 @@ public:
 
     // Accessibility: pass information about this portion to the PortionHandler
     virtual void HandlePortion( SwPortionHandler& rPH ) const override;
+
+    sal_uInt16 GetMaxComp(const SwTextFormatInfo &rInf) const;
 };
 
 class SwTextInputFieldPortion : public SwTextPortion
@@ -63,8 +65,10 @@ public:
 class SwHolePortion : public SwLinePortion
 {
     SwTwips m_nBlankWidth;
+    bool m_bShowUnderline;
+
 public:
-    explicit SwHolePortion( const SwTextPortion &rPor );
+    explicit SwHolePortion(const SwTextPortion& rPor, bool bShowUnderline = 
false);
     SwTwips GetBlankWidth() const { return m_nBlankWidth; }
     void SetBlankWidth(const SwTwips nNew) { m_nBlankWidth = nNew; }
     virtual SwLinePortion *Compress() override;
diff --git a/sw/source/core/view/viewsh.cxx b/sw/source/core/view/viewsh.cxx
index 0df4c3e59923..d895c9844591 100644
--- a/sw/source/core/view/viewsh.cxx
+++ b/sw/source/core/view/viewsh.cxx
@@ -1146,6 +1146,18 @@ void 
SwViewShell::SetIgnoreTabsAndBlanksForLineCalculation(bool val)
     }
 }
 
+void SwViewShell::SetMsWordUlTrailSpace(bool val)
+{
+    IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess();
+    if (rIDSA.get(DocumentSettingId::MS_WORD_UL_TRAIL_SPACE) != val)
+    {
+        SwWait aWait(*GetDoc()->GetDocShell(), true);
+        rIDSA.set(DocumentSettingId::MS_WORD_UL_TRAIL_SPACE, val);
+        const SwInvalidateFlags nInv = SwInvalidateFlags::Size;
+        lcl_InvalidateAllContent(*this, nInv);
+    }
+}
+
 void SwViewShell::Reformat()
 {
     SwWait aWait( *GetDoc()->GetDocShell(), true );
diff --git a/sw/source/ui/config/optcomp.cxx b/sw/source/ui/config/optcomp.cxx
index 30d698bf6ed6..e2834d58d81c 100644
--- a/sw/source/ui/config/optcomp.cxx
+++ b/sw/source/ui/config/optcomp.cxx
@@ -66,6 +66,7 @@ constexpr std::pair<OUString, TranslateId> options_list[]{
     { u"ContinuousEndnotes"_ustr, STR_COMPAT_OPT_CONTINUOUS_ENDNOTES },
     { u"MsWordCompGridMetrics"_ustr, STR_COMPAT_OPT_MSWORDCOMPGRIDMETRICS },
     { u"IgnoreTabsAndBlanksForLineCalculation"_ustr, 
STR_COMPAT_OPT_IGNORETABSANDBLANKSFORLINECALCULATION },
+    { u"MsWordUlTrailSpace"_ustr, STR_COMPAT_OPT_UNDERLINETRAILINGSPACE },
 };
 
 // DocumentSettingId, negate?
@@ -95,6 +96,7 @@ std::pair<DocumentSettingId, bool> 
DocumentSettingForOption(const OUString& opti
 //        { u"AddTableLineSpacing"_ustr, { 
DocumentSettingId::ADD_PARA_LINE_SPACING_TO_TABLE_CELLS, false } },
         { u"MsWordCompGridMetrics"_ustr, { 
DocumentSettingId::MS_WORD_COMP_GRID_METRICS, false } },
         { u"IgnoreTabsAndBlanksForLineCalculation"_ustr, { 
DocumentSettingId::IGNORE_TABS_AND_BLANKS_FOR_LINE_CALCULATION, false } },
+        { u"MsWordUlTrailSpace"_ustr, { 
DocumentSettingId::MS_WORD_UL_TRAIL_SPACE, false } },
     };
     return map.at(option);
 }
@@ -342,6 +344,10 @@ bool SwCompatibilityOptPage::FillItemSet( SfxItemSet*  )
                         
m_pWrtShell->SetIgnoreTabsAndBlanksForLineCalculation(bChecked);
                         break;
 
+                    case DocumentSettingId::MS_WORD_UL_TRAIL_SPACE:
+                        m_pWrtShell->SetMsWordUlTrailSpace(bChecked);
+                        break;
+
                     default:
                         break;
                 }
diff --git a/sw/source/uibase/uno/SwXDocumentSettings.cxx 
b/sw/source/uibase/uno/SwXDocumentSettings.cxx
index dccd520f5245..f4add8c2644e 100644
--- a/sw/source/uibase/uno/SwXDocumentSettings.cxx
+++ b/sw/source/uibase/uno/SwXDocumentSettings.cxx
@@ -167,6 +167,7 @@ enum SwDocumentSettingsPropertyHandles
     HANDLE_MIN_ROW_HEIGHT_INCL_BORDER,
     HANDLE_MS_WORD_COMP_GRID_METRICS,
     HANDLE_NO_CLIPPING_WITH_WRAP_POLYGON,
+    HANDLE_MS_WORD_UL_TRAIL_SPACE,
 };
 
 }
@@ -280,6 +281,7 @@ static rtl::Reference<MasterPropertySetInfo> 
lcl_createSettingsInfo()
         { u"MinRowHeightInclBorder"_ustr, HANDLE_MIN_ROW_HEIGHT_INCL_BORDER, 
cppu::UnoType<bool>::get(), 0 },
         { u"MsWordCompGridMetrics"_ustr, HANDLE_MS_WORD_COMP_GRID_METRICS, 
cppu::UnoType<bool>::get(), 0 },
         { u"NoClippingWithWrapPolygon"_ustr, 
HANDLE_NO_CLIPPING_WITH_WRAP_POLYGON, cppu::UnoType<bool>::get(), 0 },
+        { u"MsWordUlTrailSpace"_ustr, HANDLE_MS_WORD_UL_TRAIL_SPACE, 
cppu::UnoType<bool>::get(), 0 },
 
 /*
  * As OS said, we don't have a view when we need to set this, so I have to
@@ -1207,13 +1209,19 @@ void SwXDocumentSettings::_setSingleValue( const 
comphelper::PropertyInfo & rInf
         }
         break;
         case HANDLE_NO_CLIPPING_WITH_WRAP_POLYGON:
-            bool bTmp;
-            if (rValue >>= bTmp)
+            if (bool bTmp; rValue >>= bTmp)
             {
                 
mpDoc->getIDocumentSettingAccess().set(DocumentSettingId::NO_CLIPPING_WITH_WRAP_POLYGON,
                                                        bTmp);
             }
             break;
+        case HANDLE_MS_WORD_UL_TRAIL_SPACE:
+            if (bool bTmp; rValue >>= bTmp)
+            {
+                
mpDoc->getIDocumentSettingAccess().set(DocumentSettingId::MS_WORD_UL_TRAIL_SPACE,
+                                                       bTmp);
+            }
+            break;
         default:
             throw UnknownPropertyException(OUString::number(rInfo.mnHandle));
     }
@@ -1825,6 +1833,12 @@ void SwXDocumentSettings::_getSingleValue( const 
comphelper::PropertyInfo & rInf
                 DocumentSettingId::NO_CLIPPING_WITH_WRAP_POLYGON);
         }
         break;
+        case HANDLE_MS_WORD_UL_TRAIL_SPACE:
+        {
+            rValue <<= mpDoc->getIDocumentSettingAccess().get(
+                DocumentSettingId::MS_WORD_UL_TRAIL_SPACE);
+        }
+        break;
         default:
             throw UnknownPropertyException(OUString::number(rInfo.mnHandle));
     }

Reply via email to