officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs |    6 
 sw/inc/IDocumentSettingAccess.hxx                                 |    1 
 sw/inc/strings.hrc                                                |    2 
 sw/inc/viewsh.hxx                                                 |    2 
 sw/qa/extras/odfexport/data/UnderlineTrailingSpace.fodt           |   55 +++++
 sw/qa/extras/odfexport/odfexport2.cxx                             |  109 
++++++++++
 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/uibase/uno/SwXDocumentSettings.cxx                      |   15 +
 13 files changed, 295 insertions(+), 15 deletions(-)

New commits:
commit 166e169e229b605ae6f6293e7652ed876a4a2be3
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Thu Feb 20 13:27:46 2025 +0500
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Thu Feb 27 14:50:32 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.
    
    (The UI part in compatibility options is not backported to co-24.04.)
    
    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>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182270
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs 
b/officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs
index 0d79fb5340f5..09834cd73cdd 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 264860b854c4..f4a1c4223fc0 100644
--- a/sw/inc/IDocumentSettingAccess.hxx
+++ b/sw/inc/IDocumentSettingAccess.hxx
@@ -133,6 +133,7 @@ enum class DocumentSettingId
     DROP_CAP_PUNCTUATION,
     // render NBSP as standard-space-width (prettier when justified)
     USE_VARIABLE_WIDTH_NBSP,
+    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 e2a2f153ac8d..a1ee98781182 100644
--- a/sw/inc/strings.hrc
+++ b/sw/inc/strings.hrc
@@ -1470,6 +1470,8 @@
 #define STR_CHARACTER_DIRECT_FORMATTING         
NC_("STR_CHARACTER_DIRECT_FORMATTING", "Character Direct Formatting")
 #define STR_CHARACTER_DIRECT_FORMATTING_TAG     
NC_("STR_CHARACTER_DIRECT_FORMATTING_TAG", "df")
 
+#define STR_COMPAT_OPT_UNDERLINETRAILINGSPACE 
NC_("STR_COMPAT_OPT_UNDERLINETRAILINGSPACE", "Underline Word-compatible 
trailing blanks")
+
 #endif
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/inc/viewsh.hxx b/sw/inc/viewsh.hxx
index 50abcda03782..5e7ff85ffb5f 100644
--- a/sw/inc/viewsh.hxx
+++ b/sw/inc/viewsh.hxx
@@ -446,6 +446,8 @@ public:
 
     void SetEmptyDbFieldHidesPara(bool bEmptyDbFieldHidesPara);
 
+    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 71345908df4e..eb5b1dac417b 100644
--- a/sw/qa/extras/odfexport/odfexport2.cxx
+++ b/sw/qa/extras/odfexport/odfexport2.cxx
@@ -34,6 +34,8 @@
 #include <unoprnms.hxx>
 #include <unotxdoc.hxx>
 #include <docsh.hxx>
+#include <viewsh.hxx>
+#include <IDocumentLayoutAccess.hxx>
 
 class Test : public SwModelTestBase
 {
@@ -1393,6 +1395,113 @@ DECLARE_ODFEXPORT_TEST(testTdf160877, "tdf160877.odt")
     CPPUNIT_ASSERT_EQUAL(OUString("(Sign GB)Test"), 
getParagraph(1)->getString());
 }
 
+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"_ostr,
+                       "length"_ostr);
+        CPPUNIT_ASSERT_GREATEREQUAL(sal_Int32(69), val.toInt32()); // In 
truth, it should be 70
+        val = getXPath(pXmlDoc, 
"//body/txt[1]/SwParaPortion/SwLineLayout/SwHolePortion"_ostr,
+                       "show-underline"_ostr);
+        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]"_ostr,
+                       "length"_ostr);
+        CPPUNIT_ASSERT_GREATEREQUAL(sal_Int32(140), val.toInt32());
+        val = getXPath(pXmlDoc, 
"//body/txt[3]/SwParaPortion/SwLineLayout/SwHolePortion[1]"_ostr,
+                       "show-underline"_ostr);
+        CPPUNIT_ASSERT_EQUAL(u"true"_ustr, val);
+        val = getXPath(pXmlDoc, 
"//body/txt[3]/SwParaPortion/SwLineLayout/SwHolePortion[2]"_ostr,
+                       "length"_ostr);
+        CPPUNIT_ASSERT_GREATEREQUAL(sal_Int32(850), val.toInt32());
+        val = getXPath(pXmlDoc, 
"//body/txt[3]/SwParaPortion/SwLineLayout/SwHolePortion[2]"_ostr,
+                       "show-underline"_ostr);
+        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']"_ostr,
+                           u"true"_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));
+    }
+
+    // 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"_ostr,
+                       "length"_ostr);
+        CPPUNIT_ASSERT_GREATEREQUAL(sal_Int32(69), val.toInt32()); // In 
truth, it should be 70
+        val = getXPath(pXmlDoc, 
"//body/txt[1]/SwParaPortion/SwLineLayout/SwHolePortion"_ostr,
+                       "show-underline"_ostr);
+        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"_ostr,
+                       "length"_ostr);
+        CPPUNIT_ASSERT_GREATEREQUAL(sal_Int32(999), val.toInt32());
+        val = getXPath(pXmlDoc, 
"//body/txt[3]/SwParaPortion/SwLineLayout/SwHolePortion"_ostr,
+                       "show-underline"_ostr);
+        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']"_ostr,
+                           u"false"_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(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));
+    }
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/doc/DocumentSettingManager.cxx 
b/sw/source/core/doc/DocumentSettingManager.cxx
index 14cfa5fd3c24..78dc38a343a4 100644
--- a/sw/source/core/doc/DocumentSettingManager.cxx
+++ b/sw/source/core/doc/DocumentSettingManager.cxx
@@ -263,6 +263,7 @@ bool sw::DocumentSettingManager::get(/*[in]*/ 
DocumentSettingId id) const
         case DocumentSettingId::NO_NUMBERING_SHOW_FOLLOWBY: return 
mbNoNumberingShowFollowBy;
         case DocumentSettingId::DROP_CAP_PUNCTUATION: return 
mbDropCapPunctuation;
         case DocumentSettingId::USE_VARIABLE_WIDTH_NBSP: return 
mbUseVariableWidthNBSP;
+        case DocumentSettingId::MS_WORD_UL_TRAIL_SPACE: return 
mbMsWordUlTrailSpace;
         default:
             OSL_FAIL("Invalid setting id");
     }
@@ -571,6 +572,9 @@ void sw::DocumentSettingManager::set(/*[in]*/ 
DocumentSettingId id, /*[in]*/ boo
         case DocumentSettingId::FOOTNOTE_IN_COLUMN_TO_PAGEEND:
             mbFootnoteInColumnToPageEnd = value;
             break;
+        case DocumentSettingId::MS_WORD_UL_TRAIL_SPACE:
+            mbMsWordUlTrailSpace = value;
+            break;
         default:
             OSL_FAIL("Invalid setting id");
     }
@@ -750,6 +754,7 @@ void 
sw::DocumentSettingManager::ReplaceCompatibilityOptions(const DocumentSetti
     mbFootnoteInColumnToPageEnd = rSource.mbFootnoteInColumnToPageEnd;
     mbDropCapPunctuation = rSource.mbDropCapPunctuation;
     mbUseVariableWidthNBSP = rSource.mbUseVariableWidthNBSP;
+    mbMsWordUlTrailSpace = rSource.mbMsWordUlTrailSpace;
 }
 
 sal_uInt32 sw::DocumentSettingManager::Getn32DummyCompatibilityOptions1() const
@@ -1115,6 +1120,11 @@ void 
sw::DocumentSettingManager::dumpAsXml(xmlTextWriterPtr pWriter) const
                                 
BAD_CAST(OString::boolean(mbDoNotMirrorRtlDrawObjs).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 6599c9bf2ad0..5339a0982a4b 100644
--- a/sw/source/core/inc/DocumentSettingManager.hxx
+++ b/sw/source/core/inc/DocumentSettingManager.hxx
@@ -184,6 +184,7 @@ class DocumentSettingManager final :
     bool mbNoNumberingShowFollowBy;
     bool mbDropCapPunctuation; // tdf#150200, tdf#150438
     bool mbUseVariableWidthNBSP : 1; // tdf#41652
+    bool mbMsWordUlTrailSpace : 1 = false;
 
 public:
 
diff --git a/sw/source/core/text/guess.cxx b/sw/source/core/text/guess.cxx
index 0a1efe54f136..ced91e2d0782 100644
--- a/sw/source/core/text/guess.cxx
+++ b/sw/source/core/text/guess.cxx
@@ -168,13 +168,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 8a128215e940..cf28eba10da8 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>
 
@@ -208,6 +209,35 @@ static TextFrameIndex lcl_AddSpace(const SwTextSizeInfo 
&rInf,
     return nCnt;
 }
 
+static void GetLimitedStringPart(const SwTextFormatInfo& rInf, TextFrameIndex 
nIndex,
+                                 TextFrameIndex nLength, sal_uInt16 nComp,
+                                 sal_uInt16 nOriginalWidth, SwTwips nMaxWidth,
+                                 TextFrameIndex& rOutLength, sal_uInt16& 
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, nComp, rOutWidth, 
o3tl::temporary(sal_uInt16()),
+                         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);
@@ -279,6 +309,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
@@ -450,12 +489,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);
+                sal_uInt16 nTotalExtraWidth(pGuess->ExtraBlankWidth());
+                sal_uInt16 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)
@@ -801,8 +865,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() );
@@ -844,12 +909,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() );
@@ -889,6 +955,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 c826395272e1..5fe99167fc84 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
 {
     sal_uInt16 m_nBlankWidth;
+    bool m_bShowUnderline;
+
 public:
-    explicit SwHolePortion( const SwTextPortion &rPor );
+    explicit SwHolePortion(const SwTextPortion& rPor, bool bShowUnderline = 
false);
     sal_uInt16 GetBlankWidth( ) const { return m_nBlankWidth; }
     void SetBlankWidth( const sal_uInt16 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 3299ee6b7b94..cf6aa11ac751 100644
--- a/sw/source/core/view/viewsh.cxx
+++ b/sw/source/core/view/viewsh.cxx
@@ -1051,6 +1051,18 @@ void SwViewShell::SetEmptyDbFieldHidesPara(bool 
bEmptyDbFieldHidesPara)
     EndAction();
 }
 
+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/uibase/uno/SwXDocumentSettings.cxx 
b/sw/source/uibase/uno/SwXDocumentSettings.cxx
index 3c0a8bbbf2a4..a1d9a4b8a409 100644
--- a/sw/source/uibase/uno/SwXDocumentSettings.cxx
+++ b/sw/source/uibase/uno/SwXDocumentSettings.cxx
@@ -161,6 +161,7 @@ enum SwDocumentSettingsPropertyHandles
     HANDLE_USE_VARIABLE_WIDTH_NBSP,
     HANDLE_APPLY_TEXT_ATTR_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH,
     HANDLE_DO_NOT_MIRROR_RTL_DRAW_OBJS,
+    HANDLE_MS_WORD_UL_TRAIL_SPACE,
 };
 
 }
@@ -268,6 +269,7 @@ static rtl::Reference<MasterPropertySetInfo> 
lcl_createSettingsInfo()
         { OUString("UseVariableWidthNBSP"), HANDLE_USE_VARIABLE_WIDTH_NBSP, 
cppu::UnoType<bool>::get(), 0 },
         { OUString("ApplyTextAttrToEmptyLineAtEndOfParagraph"), 
HANDLE_APPLY_TEXT_ATTR_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH, 
cppu::UnoType<bool>::get(), 0 },
         { OUString("DoNotMirrorRtlDrawObjs"), 
HANDLE_DO_NOT_MIRROR_RTL_DRAW_OBJS, cppu::UnoType<bool>::get(), 0 },
+        { OUString("MsWordUlTrailSpace"), 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
@@ -1152,6 +1154,13 @@ void SwXDocumentSettings::_setSingleValue( const 
comphelper::PropertyInfo & rInf
                     DocumentSettingId::USE_VARIABLE_WIDTH_NBSP, 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));
     }
@@ -1728,6 +1737,12 @@ void SwXDocumentSettings::_getSingleValue( const 
comphelper::PropertyInfo & rInf
                 DocumentSettingId::USE_VARIABLE_WIDTH_NBSP);
         }
         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