include/vcl/outdev.hxx                                            |    7 
 officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs |    6 
 sw/inc/IDocumentSettingAccess.hxx                                 |    2 
 sw/inc/strings.hrc                                                |    1 
 sw/inc/viewsh.hxx                                                 |    2 
 sw/qa/extras/layout/data/tdf88908.fodt                            |  122 
++++++++++
 sw/qa/extras/layout/layout5.cxx                                   |   11 
 sw/qa/extras/ooxmlexport/ooxmlexport22.cxx                        |   30 ++
 sw/qa/extras/ww8export/ww8export4.cxx                             |   29 ++
 sw/source/core/doc/DocumentSettingManager.cxx                     |   16 +
 sw/source/core/inc/DocumentSettingManager.hxx                     |    1 
 sw/source/core/inc/scriptinfo.hxx                                 |    2 
 sw/source/core/text/porlay.cxx                                    |    4 
 sw/source/core/txtnode/fntcache.cxx                               |   64 +++--
 sw/source/core/txtnode/justify.cxx                                |   46 +++
 sw/source/core/txtnode/justify.hxx                                |   10 
 sw/source/core/view/viewsh.cxx                                    |   13 +
 sw/source/filter/ww8/docxexport.cxx                               |    5 
 sw/source/filter/ww8/wrtww8.cxx                                   |    2 
 sw/source/filter/ww8/ww8par.cxx                                   |    2 
 sw/source/filter/ww8/ww8scan.cxx                                  |    9 
 sw/source/filter/ww8/ww8scan.hxx                                  |    3 
 sw/source/ui/config/optcomp.cxx                                   |    6 
 sw/source/uibase/uno/SwXDocumentSettings.cxx                      |   15 +
 sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx              |    6 
 sw/source/writerfilter/dmapper/SettingsTable.cxx                  |    9 
 sw/source/writerfilter/dmapper/SettingsTable.hxx                  |    1 
 vcl/source/outdev/text.cxx                                        |   54 +++-
 28 files changed, 436 insertions(+), 42 deletions(-)

New commits:
commit 6818bc55ff248c59f12b2e090139eff30fe949dd
Author:     Jonathan Clark <[email protected]>
AuthorDate: Wed Mar 26 14:28:55 2025 -0600
Commit:     Jonathan Clark <[email protected]>
CommitDate: Thu Mar 27 20:18:25 2025 +0100

    tdf#88908 sw: Add BalanceSpacesAndIdeographicSpaces compat option
    
    Added a new Writer compatibility option, as noted. When enabled, Writer
    adjusts the width of certain normal space characters to one half the
    width of a CJK ideographic space. This adjustment is only applied to
    leading and trailing spaces, multiple sequential spaces, and spaces
    between CJK characters.
    
    This flag is enabled automatically when importing DOCX files with the
    w:balanceSingleByteDoubleByteWidth option enabled.
    
    This flag is also enabled automatically when importing DOC files with
    the fDntBlnSbDbWid compatibility flag unset.
    
    Change-Id: I1c666a9894f2e0f302df57203b1cf488cc13000b
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/183412
    Tested-by: Jenkins
    Reviewed-by: Jonathan Clark <[email protected]>

diff --git a/include/vcl/outdev.hxx b/include/vcl/outdev.hxx
index 1fcd408b950d..9b60d79d28a4 100644
--- a/include/vcl/outdev.hxx
+++ b/include/vcl/outdev.hxx
@@ -1086,6 +1086,13 @@ public:
                                               tools::Long nCharExtra,
                                               vcl::text::TextLayoutCache 
const* = nullptr,
                                               const SalLayoutGlyphs* pGlyphs = 
nullptr) const;
+    sal_Int32 GetTextBreakArray(const OUString& rStr, tools::Long nTextWidth,
+                                std::optional<sal_Unicode> nExtraChar,
+                                std::optional<sal_Int32*> pExtraCharPos, 
sal_Int32 nIndex,
+                                sal_Int32 nLen, tools::Long nCharExtra, 
KernArraySpan aKernArray,
+                                vcl::text::TextLayoutCache const* = nullptr,
+                                const SalLayoutGlyphs* pGlyphs = nullptr) 
const;
+
     static std::shared_ptr<const vcl::text::TextLayoutCache> 
CreateTextLayoutCache(OUString const&);
 
     SAL_DLLPRIVATE SalLayoutFlags GetBiDiLayoutFlags( std::u16string_view rStr,
diff --git a/officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs 
b/officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs
index 4c96abe3a080..b667e24e5172 100644
--- a/officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs
+++ b/officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs
@@ -182,6 +182,12 @@
         </info>
         <value>false</value>
       </prop>
+      <prop oor:name="BalanceSpacesAndIdeographicSpaces" oor:type="xs:boolean" 
oor:nillable="false">
+        <info>
+          <desc>Adjust spaces to half the width of ideographic spaces, using 
Word-compatible rules</desc>
+        </info>
+        <value>false</value>
+      </prop>
     </group>
   </templates>
   <component>
diff --git a/sw/inc/IDocumentSettingAccess.hxx 
b/sw/inc/IDocumentSettingAccess.hxx
index 1314f60479bc..f523a51af65d 100644
--- a/sw/inc/IDocumentSettingAccess.hxx
+++ b/sw/inc/IDocumentSettingAccess.hxx
@@ -143,6 +143,8 @@ enum class DocumentSettingId
     // tdf#161233 pictures with wrap polygon should not be clipped
     NO_CLIPPING_WITH_WRAP_POLYGON,
     MS_WORD_UL_TRAIL_SPACE,
+    // tdf#88908 optionally adjust normal spaces in CJK context to halfwidth
+    BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES,
 };
 
 /** Provides access to settings of a document
diff --git a/sw/inc/strings.hrc b/sw/inc/strings.hrc
index e114be97f708..22187814eeee 100644
--- a/sw/inc/strings.hrc
+++ b/sw/inc/strings.hrc
@@ -1534,6 +1534,7 @@
 #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_COMPAT_OPT_BALANCESPACESANDIDEOGRAPHICSPACES 
NC_("STR_COMPAT_OPT_BALANCESPACESANDIDEOGRAPHICSPACES", "Adjust spaces to half 
the width of ideographic spaces, using Word-compatible rules")
 
 #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 a96d26721aa8..7717d8dac6f1 100644
--- a/sw/inc/viewsh.hxx
+++ b/sw/inc/viewsh.hxx
@@ -473,6 +473,8 @@ public:
 
     SW_DLLPUBLIC void SetMsWordUlTrailSpace(bool val);
 
+    SW_DLLPUBLIC void SetBalanceSpacesAndIdeographicSpaces(bool bValue);
+
     // DOCUMENT COMPATIBILITY FLAGS END
 
     // Calls Idle-formatter of Layout.
diff --git a/sw/qa/extras/layout/data/tdf88908.fodt 
b/sw/qa/extras/layout/data/tdf88908.fodt
new file mode 100644
index 000000000000..72b74c472830
--- /dev/null
+++ b/sw/qa/extras/layout/data/tdf88908.fodt
@@ -0,0 +1,122 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/"; 
xmlns:grddl="http://www.w3.org/2003/g/data-view#"; 
xmlns:xhtml="http://www.w3.org/1999/xhtml"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xmlns:xsd="http://www.w3.org/2001/XMLSchema"; 
xmlns:xforms="http://www.w3.org/2002/xforms"; 
xmlns:dom="http://www.w3.org/2001/xml-events"; 
xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" 
xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" 
xmlns:math="http://www.w3.org/1998/Math/MathML"; 
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:ooow="http://openoffice.org/2004/writer"; 
xmlns:xlink="http://www.w3.org/1999/xlink"; 
xmlns:drawooo="http://openoffice.org/2010/draw"; 
xmlns:oooc="http://openoffice.org/2004/calc"; 
xmlns:dc="http://purl.org/dc/elements/1.1/"; xmlns:c
 alcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext: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:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" 
xmlns:tableooo="http://openoffice.org/2009/table"; 
xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" 
xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" 
xmlns:rpt="http://openoffice.org/2005/report"; 
xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0"
 xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" 
xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" 
xmlns:officeooo="http://openoffice.org/2009/office"; 
xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" 
xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" 
xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" 
xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:
 meta:1.0" 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"
 office:version="1.4" office:mimetype="application/vnd.oasis.opendocument.text">
+ 
<office:meta><meta:creation-date>2025-03-27T05:01:35.794822486</meta:creation-date><meta:generator>LibreOfficeDev/25.8.0.0.alpha0$Linux_X86_64
 
LibreOffice_project/5e56979e1c1b559fede4a1175a52b9b2ca5ef843</meta:generator><dc:date>2025-03-27T05:35:06.339229728</dc:date><meta:editing-duration>PT6M59S</meta:editing-duration><meta:editing-cycles>5</meta:editing-cycles><meta:document-statistic
 meta:table-count="0" meta:image-count="0" meta:object-count="0" 
meta:page-count="1" meta:paragraph-count="1" meta:word-count="8" 
meta:character-count="80" 
meta:non-whitespace-character-count="32"/></office:meta>
+ <office:settings>
+  <config:config-item-set config:name="ooo:configuration-settings">
+   <config:config-item config:name="BalanceSpacesAndIdeographicSpaces" 
config:type="boolean">true</config:config-item>
+  </config:config-item-set>
+ </office:settings>
+ <office:font-face-decls>
+  <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation 
Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+  <style:font-face style:name="Noto Sans1" svg:font-family="'Noto Sans'" 
style:font-family-generic="system" style:font-pitch="variable"/>
+  <style:font-face style:name="Noto Sans2" svg:font-family="'Noto Sans'" 
style:font-adornments="Regular" style:font-family-generic="swiss" 
style:font-pitch="variable"/>
+  <style:font-face style:name="Noto Serif CJK SC" svg:font-family="'Noto Serif 
CJK SC'" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+  <style:default-style style:family="graphic">
+   <style:graphic-properties svg:stroke-color="#3465a4" 
draw:fill-color="#729fcf" fo:wrap-option="no-wrap" 
draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" 
draw:start-line-spacing-horizontal="0.1114in" 
draw:start-line-spacing-vertical="0.1114in" 
draw:end-line-spacing-horizontal="0.1114in" 
draw:end-line-spacing-vertical="0.1114in" style:writing-mode="lr-tb" 
style:flow-with-text="false"/>
+   <style:paragraph-properties style:text-autospace="ideograph-alpha" 
style:line-break="strict" loext:tab-stop-distance="0in" 
style:font-independent-line-spacing="false">
+    <style:tab-stops/>
+   </style:paragraph-properties>
+   <style:text-properties style:use-window-font-color="true" 
loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" 
fo:language="en" fo:country="US" style:letter-kerning="true" 
style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" 
style:language-asian="zh" style:country-asian="CN" 
style:font-name-complex="Noto Sans1" style:font-size-complex="12pt" 
style:language-complex="hi" style:country-complex="IN"/>
+  </style:default-style>
+  <style:default-style style:family="paragraph">
+   <style:paragraph-properties fo:orphans="2" fo:widows="2" 
fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" 
loext:hyphenation-keep-type="column" loext:hyphenation-keep-line="false" 
style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" 
style:line-break="strict" style:tab-stop-distance="0.4925in" 
style:writing-mode="page"/>
+   <style:text-properties style:use-window-font-color="true" 
loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" 
fo:language="en" fo:country="US" style:letter-kerning="true" 
style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" 
style:language-asian="zh" style:country-asian="CN" 
style:font-name-complex="Noto Sans1" style:font-size-complex="12pt" 
style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" 
fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" 
loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" 
loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/>
+  </style:default-style>
+  <style:default-style style:family="table">
+   <style:table-properties table:border-model="collapsing"/>
+  </style:default-style>
+  <style:default-style style:family="table-row">
+   <style:table-row-properties fo:keep-together="auto"/>
+  </style:default-style>
+  <style:style style:name="Standard" style:family="paragraph" 
style:class="text">
+   <style:text-properties style:font-name="Noto Sans2" fo:font-family="'Noto 
Sans'" style:font-style-name="Regular" style:font-family-generic="swiss" 
style:font-pitch="variable" style:font-size-asian="20pt"/>
+  </style:style>
+  <text:outline-style style:name="Outline">
+   <text:outline-level-style text:level="1" loext:num-list-format="%1%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="2" loext:num-list-format="%2%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="3" loext:num-list-format="%3%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="4" loext:num-list-format="%4%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="5" loext:num-list-format="%5%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="6" loext:num-list-format="%6%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="7" loext:num-list-format="%7%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="8" loext:num-list-format="%8%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="9" loext:num-list-format="%9%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="10" loext:num-list-format="%10%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+  </text:outline-style>
+  <text:notes-configuration text:note-class="footnote" style:num-format="1" 
text:start-value="0" text:footnotes-position="page" 
text:start-numbering-at="document"/>
+  <text:notes-configuration text:note-class="endnote" style:num-format="i" 
text:start-value="0"/>
+  <text:linenumbering-configuration text:number-lines="false" 
text:offset="0.1965in" style:num-format="1" text:number-position="left" 
text:increment="5"/>
+  </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="P1" style:family="paragraph" 
style:parent-style-name="Standard">
+   <style:text-properties/>
+  </style:style>
+  <style:page-layout style:name="pm1">
+   <style:page-layout-properties fo:page-width="8.2681in" 
fo:page-height="11.6929in" style:num-format="1" 
style:print-orientation="portrait" fo:margin-top="0.7874in" 
fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" 
fo:margin-right="0.7874in" style:writing-mode="lr-tb" 
style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" 
style:layout-grid-base-height="0.278in" style:layout-grid-ruby-height="0.139in" 
style:layout-grid-mode="none" style:layout-grid-ruby-below="false" 
style:layout-grid-print="false" style:layout-grid-display="false" 
style:footnote-max-height="0in" loext:margin-gutter="0in">
+    <style:footnote-sep style:width="0.0071in" 
style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" 
style:line-style="solid" style:adjustment="left" style:rel-width="25%" 
style:color="#000000"/>
+   </style:page-layout-properties>
+   <style:header-style/>
+   <style:footer-style/>
+  </style:page-layout>
+  <style:style style:name="dp1" style:family="drawing-page">
+   <style:drawing-page-properties draw:background-size="full"/>
+  </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+  <style:master-page style:name="Standard" style:page-layout-name="pm1" 
draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+  <office:text>
+   <text:sequence-decls>
+    <text:sequence-decl text:display-outline-level="0" 
text:name="Illustration"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+   </text:sequence-decls>
+   <text:p text:style-name="P1"><text:s text:c="34"/>Without <text:s/>the 
<text:s/>fix, <text:s/>this <text:s/>will <text:s/>use <text:s/>one 
<text:s/>line</text:p>
+  </office:text>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/sw/qa/extras/layout/layout5.cxx b/sw/qa/extras/layout/layout5.cxx
index f1f7a60e1a98..473c6f5c5d49 100644
--- a/sw/qa/extras/layout/layout5.cxx
+++ b/sw/qa/extras/layout/layout5.cxx
@@ -1568,6 +1568,17 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf165089)
     CPPUNIT_ASSERT_LESS(sal_Int32(1450), nTop);
 }
 
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf88908)
+{
+    createSwDoc("tdf88908.fodt");
+    auto pXmlDoc = parseLayoutDump();
+
+    assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[1]", 
"portion",
+                u"                                  Without  the  fix,  ");
+    assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[2]", 
"portion",
+                u"this  will  use  one  line");
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx
index e7d784419ef0..5c191018560c 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx
@@ -18,6 +18,7 @@
 #include <pam.hxx>
 #include <unotxdoc.hxx>
 #include <docsh.hxx>
+#include <IDocumentSettingAccess.hxx>
 
 namespace
 {
@@ -87,6 +88,35 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf83844Hanging)
     fnVerify();
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testTdf88908)
+{
+    createSwDoc();
+
+    {
+        SwDoc* pDoc = getSwDoc();
+        IDocumentSettingAccess& rIDSA = pDoc->getIDocumentSettingAccess();
+        
CPPUNIT_ASSERT(!rIDSA.get(DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES));
+    }
+
+    saveAndReload(mpFilter);
+
+    {
+        SwDoc* pDoc = getSwDoc();
+        IDocumentSettingAccess& rIDSA = pDoc->getIDocumentSettingAccess();
+        
CPPUNIT_ASSERT(!rIDSA.get(DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES));
+
+        rIDSA.set(DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES, 
true);
+    }
+
+    saveAndReload(mpFilter);
+
+    {
+        SwDoc* pDoc = getSwDoc();
+        IDocumentSettingAccess& rIDSA = pDoc->getIDocumentSettingAccess();
+        
CPPUNIT_ASSERT(rIDSA.get(DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES));
+    }
+}
+
 } // end of anonymous namespace
 CPPUNIT_PLUGIN_IMPLEMENT();
 
diff --git a/sw/qa/extras/ww8export/ww8export4.cxx 
b/sw/qa/extras/ww8export/ww8export4.cxx
index 612d46439b77..e2c923e5b5e7 100644
--- a/sw/qa/extras/ww8export/ww8export4.cxx
+++ b/sw/qa/extras/ww8export/ww8export4.cxx
@@ -680,6 +680,35 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf80596Hanging)
     fnVerify();
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testTdf88908)
+{
+    createSwDoc();
+
+    {
+        SwDoc* pDoc = getSwDoc();
+        IDocumentSettingAccess& rIDSA = pDoc->getIDocumentSettingAccess();
+        
CPPUNIT_ASSERT(!rIDSA.get(DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES));
+    }
+
+    saveAndReload(mpFilter);
+
+    {
+        SwDoc* pDoc = getSwDoc();
+        IDocumentSettingAccess& rIDSA = pDoc->getIDocumentSettingAccess();
+        
CPPUNIT_ASSERT(!rIDSA.get(DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES));
+
+        rIDSA.set(DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES, 
true);
+    }
+
+    saveAndReload(mpFilter);
+
+    {
+        SwDoc* pDoc = getSwDoc();
+        IDocumentSettingAccess& rIDSA = pDoc->getIDocumentSettingAccess();
+        
CPPUNIT_ASSERT(rIDSA.get(DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES));
+    }
+}
+
 } // end of anonymous namespace
 CPPUNIT_PLUGIN_IMPLEMENT();
 
diff --git a/sw/source/core/doc/DocumentSettingManager.cxx 
b/sw/source/core/doc/DocumentSettingManager.cxx
index 5106cb78ddfc..7943810e8fc9 100644
--- a/sw/source/core/doc/DocumentSettingManager.cxx
+++ b/sw/source/core/doc/DocumentSettingManager.cxx
@@ -111,7 +111,8 @@ sw::DocumentSettingManager::DocumentSettingManager(SwDoc 
&rDoc)
     mbPaintHellOverHeaderFooter(false),
     mbMinRowHeightInclBorder(false),
     mbMsWordCompGridMetrics(false), // tdf#129808
-    mbNoClippingWithWrapPolygon(false)
+    mbNoClippingWithWrapPolygon(false),
+    mbBalanceSpacesAndIdeographicSpaces(false)
 
     // COMPATIBILITY FLAGS END
 {
@@ -147,6 +148,7 @@ sw::DocumentSettingManager::DocumentSettingManager(SwDoc 
&rDoc)
         mbContinuousEndnotes                = 
aOptions.get(u"ContinuousEndnotes"_ustr);
         mbMsWordCompGridMetrics             = 
aOptions.get(u"MsWordCompGridMetrics"_ustr);
         mbIgnoreTabsAndBlanksForLineCalculation = 
aOptions.get(u"IgnoreTabsAndBlanksForLineCalculation"_ustr);
+        mbBalanceSpacesAndIdeographicSpaces = 
aOptions.get(u"BalanceSpacesAndIdeographicSpaces"_ustr);
     }
     else
     {
@@ -280,6 +282,8 @@ bool sw::DocumentSettingManager::get(/*[in]*/ 
DocumentSettingId id) const
         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;
+        case DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES:
+            return mbBalanceSpacesAndIdeographicSpaces;
         default:
             OSL_FAIL("Invalid setting id");
     }
@@ -614,6 +618,9 @@ void sw::DocumentSettingManager::set(/*[in]*/ 
DocumentSettingId id, /*[in]*/ boo
         case DocumentSettingId::MS_WORD_UL_TRAIL_SPACE:
             mbMsWordUlTrailSpace = value;
             break;
+        case DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES:
+            mbBalanceSpacesAndIdeographicSpaces = value;
+            break;
         default:
             OSL_FAIL("Invalid setting id");
     }
@@ -796,6 +803,7 @@ void 
sw::DocumentSettingManager::ReplaceCompatibilityOptions(const DocumentSetti
     mbMsWordCompGridMetrics = rSource.mbMsWordCompGridMetrics;
     mbNoClippingWithWrapPolygon = rSource.mbNoClippingWithWrapPolygon;
     mbMsWordUlTrailSpace = rSource.mbMsWordUlTrailSpace;
+    mbBalanceSpacesAndIdeographicSpaces = 
rSource.mbBalanceSpacesAndIdeographicSpaces;
 }
 
 sal_uInt32 sw::DocumentSettingManager::Getn32DummyCompatibilityOptions1() const
@@ -1197,6 +1205,12 @@ void 
sw::DocumentSettingManager::dumpAsXml(xmlTextWriterPtr pWriter) const
                                 
BAD_CAST(OString::boolean(mbMsWordUlTrailSpace).getStr()));
     (void)xmlTextWriterEndElement(pWriter);
 
+    (void)xmlTextWriterStartElement(pWriter, 
BAD_CAST("mbBalanceSpacesAndIdeographicSpaces"));
+    (void)xmlTextWriterWriteAttribute(
+        pWriter, BAD_CAST("value"),
+        
BAD_CAST(OString::boolean(mbBalanceSpacesAndIdeographicSpaces).getStr()));
+    (void)xmlTextWriterEndElement(pWriter);
+
     (void)xmlTextWriterEndElement(pWriter);
 }
 
diff --git a/sw/source/core/inc/DocumentSettingManager.hxx 
b/sw/source/core/inc/DocumentSettingManager.hxx
index 92dfe6f834e8..e91d0a095528 100644
--- a/sw/source/core/inc/DocumentSettingManager.hxx
+++ b/sw/source/core/inc/DocumentSettingManager.hxx
@@ -191,6 +191,7 @@ class DocumentSettingManager final :
     bool mbMsWordCompGridMetrics : 1; // tdf#129808
     bool mbNoClippingWithWrapPolygon : 1; // tdf#161233
     bool mbMsWordUlTrailSpace : 1 = false;
+    bool mbBalanceSpacesAndIdeographicSpaces : 1 = false; // tdf#88908
 
 public:
 
diff --git a/sw/source/core/inc/scriptinfo.hxx 
b/sw/source/core/inc/scriptinfo.hxx
index 2b249033b568..3ea3bfee6b96 100644
--- a/sw/source/core/inc/scriptinfo.hxx
+++ b/sw/source/core/inc/scriptinfo.hxx
@@ -84,6 +84,7 @@ private:
 
     TextFrameIndex m_nInvalidityPos;
     sal_uInt8 m_nDefaultDir;
+    bool m_bAdjustBlock = false;
     bool m_bParagraphContainsKashidaScript = false;
 
     // examines the range [ nStart, nStart + nEnd ] if there are kanas
@@ -140,6 +141,7 @@ public:
         return m_DirectionChanges[ nCnt ].type;
     }
 
+    bool ParagraphIsJustified() const { return m_bAdjustBlock; }
     bool ParagraphContainsKashidaScript() const { return 
m_bParagraphContainsKashidaScript; }
 
     size_t CountCompChg() const { return m_CompressionChanges.size(); };
diff --git a/sw/source/core/text/porlay.cxx b/sw/source/core/text/porlay.cxx
index 405ed7f526cc..efa47661bd29 100644
--- a/sw/source/core/text/porlay.cxx
+++ b/sw/source/core/text/porlay.cxx
@@ -1322,7 +1322,7 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode,
 
     auto const& rParaItems((pMerged ? *pMerged->pParaPropsNode : 
rNode).GetSwAttrSet());
     // justification type
-    const bool bAdjustBlock = SvxAdjust::Block == 
rParaItems.GetAdjust().GetAdjust();
+    m_bAdjustBlock = (SvxAdjust::Block == rParaItems.GetAdjust().GetAdjust());
 
     // FIND INVALID RANGES IN SCRIPT INFO ARRAYS:
 
@@ -1494,7 +1494,7 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode,
                 }
             }
         }
-        else if (bAdjustBlock && i18n::ScriptType::COMPLEX == nScript)
+        else if (m_bAdjustBlock && i18n::ScriptType::COMPLEX == nScript)
         {
             if (SwScriptInfo::IsKashidaScriptText(
                     rText, TextFrameIndex{ stChange.m_nStartIndex },
diff --git a/sw/source/core/txtnode/fntcache.cxx 
b/sw/source/core/txtnode/fntcache.cxx
index fe7330276731..c8e065707618 100644
--- a/sw/source/core/txtnode/fntcache.cxx
+++ b/sw/source/core/txtnode/fntcache.cxx
@@ -742,8 +742,8 @@ static void lcl_DrawLineForWrongListData(
         rInf.GetOut().Pop();
 }
 
-static void GetTextArray(const OutputDevice& rDevice, const OUString& rStr, 
KernArray& rDXAry,
-                         sal_Int32 nIndex, sal_Int32 nLen,
+static void GetTextArray(const SwDrawTextInfo& rExtraInf, const OutputDevice& 
rDevice,
+                         const OUString& rStr, KernArray& rDXAry, sal_Int32 
nIndex, sal_Int32 nLen,
                          std::optional<SwLinePortionLayoutContext> 
nLayoutContext,
                          SwTwips* nMaxAscent = nullptr, SwTwips* nMaxDescent = 
nullptr,
                          bool bCaret = false,
@@ -783,13 +783,30 @@ static void GetTextArray(const OutputDevice& rDevice, 
const OUString& rStr, Kern
             *nMaxDescent = 
static_cast<SwTwips>(std::ceil(stMetrics.aBounds->Bottom()));
         }
     }
+
+    // tdf#88908: Adjust qualifying spaces to half of an ideographic space.
+    // For compatibility, this must be done before all other kinds of 
justification.
+    if (auto pSh = rExtraInf.GetShell(); pSh)
+    {
+        const IDocumentSettingAccess& rIDSA = pSh->getIDocumentSettingAccess();
+        if 
(rIDSA.get(DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES))
+        {
+            const auto* pFont = rExtraInf.GetFont();
+            bool bScriptIsCJK = (SwFontScript::CJK == pFont->GetActual());
+
+            auto stFontUnitMetrics = pFont->GetFontUnitMetrics();
+            sw::Justify::BalanceCjkSpaces(rDXAry, rStr, nIndex, nLen,
+                                          stFontUnitMetrics.m_dIcTwips * 0.5, 
bScriptIsCJK);
+        }
+    }
 }
 
 static void GetTextArray(const OutputDevice& rOutputDevice, const 
SwDrawTextInfo& rInf,
                          KernArray& rDXAry, bool bCaret = false)
 {
-    GetTextArray(rOutputDevice, rInf.GetText(), rDXAry, rInf.GetIdx().get(), 
rInf.GetLen().get(),
-                 rInf.GetLayoutContext(), nullptr, nullptr, bCaret, 
rInf.GetVclCache());
+    GetTextArray(rInf, rOutputDevice, rInf.GetText(), rDXAry, 
rInf.GetIdx().get(),
+                 rInf.GetLen().get(), rInf.GetLayoutContext(), nullptr, 
nullptr, bCaret,
+                 rInf.GetVclCache());
 }
 
 static void GetTextArray(const OutputDevice& rOutputDevice, const 
SwDrawTextInfo& rInf,
@@ -797,7 +814,7 @@ static void GetTextArray(const OutputDevice& rOutputDevice, 
const SwDrawTextInfo
 {
     // Substring is fine.
     assert(nLen <= rInf.GetLen().get());
-    GetTextArray(rOutputDevice, rInf.GetText(), rDXAry, rInf.GetIdx().get(), 
nLen,
+    GetTextArray(rInf, rOutputDevice, rInf.GetText(), rDXAry, 
rInf.GetIdx().get(), nLen,
                  rInf.GetLayoutContext(), nMaxAscent, nMaxDescent, bCaret, 
rInf.GetVclCache());
 }
 
@@ -1730,7 +1747,7 @@ Size SwFntObj::GetTextSize( SwDrawTextInfo& rInf )
         if( !GetScrFont()->IsSameInstance( rInf.GetOut().GetFont() ) )
             rInf.GetOut().SetFont( *m_pScrFont );
 
-        GetTextArray(*m_pPrinter, rInf.GetText(), aKernArray, 
sal_Int32(rInf.GetIdx()),
+        GetTextArray(rInf, *m_pPrinter, rInf.GetText(), aKernArray, 
sal_Int32(rInf.GetIdx()),
                      sal_Int32(nLn), rInf.GetLayoutContext(), &nMaxAscent, 
&nMaxDescent, bCaret);
     }
     else
@@ -2080,6 +2097,8 @@ TextFrameIndex SwFont::GetTextBreak(SwDrawTextInfo const 
& rInf, tools::Long nTe
     TextFrameIndex nTextBreak(0);
     tools::Long nKern = 0;
 
+    KernArray aKernArray;
+
     TextFrameIndex nLn = rInf.GetLen() == TextFrameIndex(COMPLETE_STRING)
         ? TextFrameIndex(rInf.GetText().getLength()) : rInf.GetLen();
 
@@ -2092,8 +2111,7 @@ TextFrameIndex SwFont::GetTextBreak(SwDrawTextInfo const 
& rInf, tools::Long nTe
             const SwDoc* pDoc = rInf.GetShell()->GetDoc();
             const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, *pDoc);
 
-            KernArray aKernArray;
-            GetTextArray(rInf.GetOut(), rInf.GetText(), aKernArray, 
sal_Int32(rInf.GetIdx()),
+            GetTextArray(rInf, rInf.GetOut(), rInf.GetText(), aKernArray, 
sal_Int32(rInf.GetIdx()),
                          sal_Int32(rInf.GetLen()), rInf.GetLayoutContext());
 
             if (pGrid->IsSnapToChars())
@@ -2164,15 +2182,25 @@ TextFrameIndex SwFont::GetTextBreak(SwDrawTextInfo 
const & rInf, tools::Long nTe
             bTextReplaced = true;
         }
 
+        aKernArray.clear();
+        if (auto pSh = rInf.GetShell(); 
!rInf.GetScriptInfo()->ParagraphIsJustified() && pSh)
+        {
+            const IDocumentSettingAccess& rIDSA = 
pSh->getIDocumentSettingAccess();
+            if 
(rIDSA.get(DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES))
+            {
+                GetTextArray(rInf, rInf.GetOut(), rInf.GetText(), aKernArray, 
sal_Int32(nTmpIdx),
+                             sal_Int32(nTmpLen), rInf.GetLayoutContext(), 
nullptr, nullptr, false,
+                             rInf.GetVclCache());
+            }
+        }
+
         if( rInf.GetHyphPos() ) {
             sal_Int32 nHyphPos = sal_Int32(*rInf.GetHyphPos());
             const SalLayoutGlyphs* pGlyphs = 
SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
                 &rInf.GetOut(), *pTmpText, nTmpIdx.get(), nTmpLen.get(), 0, 
rInf.GetVclCache());
-            nTextBreak = TextFrameIndex(rInf.GetOut().GetTextBreak(
-                             *pTmpText, nTextWidth,
-                             u'-', nHyphPos,
-                             sal_Int32(nTmpIdx), sal_Int32(nTmpLen),
-                             nKern, rInf.GetVclCache(), pGlyphs));
+            nTextBreak = TextFrameIndex(rInf.GetOut().GetTextBreakArray(
+                *pTmpText, nTextWidth, u'-', &nHyphPos, sal_Int32(nTmpIdx), 
sal_Int32(nTmpLen),
+                nKern, aKernArray, rInf.GetVclCache(), pGlyphs));
             *rInf.GetHyphPos() = TextFrameIndex((nHyphPos == -1) ? 
COMPLETE_STRING : nHyphPos);
         }
         else
@@ -2181,10 +2209,9 @@ TextFrameIndex SwFont::GetTextBreak(SwDrawTextInfo const 
& rInf, tools::Long nTe
                                    &m_aSub[m_nActual], rInf.GetShell());
             const SalLayoutGlyphs* pGlyphs = 
SalLayoutGlyphsCache::self()->GetLayoutGlyphs(&rInf.GetOut(),
                 *pTmpText, nTmpIdx.get(), nTmpLen.get(), 0, 
rInf.GetVclCache());
-            nTextBreak = TextFrameIndex(rInf.GetOut().GetTextBreak(
-                             *pTmpText, nTextWidth,
-                             sal_Int32(nTmpIdx), sal_Int32(nTmpLen),
-                             nKern, rInf.GetVclCache(), pGlyphs));
+            nTextBreak = TextFrameIndex(rInf.GetOut().GetTextBreakArray(
+                *pTmpText, nTextWidth, std::nullopt, std::nullopt, 
sal_Int32(nTmpIdx),
+                sal_Int32(nTmpLen), nKern, aKernArray, rInf.GetVclCache(), 
pGlyphs));
         }
 
         if (bTextReplaced && sal_Int32(nTextBreak) != -1)
@@ -2219,8 +2246,7 @@ TextFrameIndex SwFont::GetTextBreak(SwDrawTextInfo const 
& rInf, tools::Long nTe
             nLn = TextFrameIndex(1);
         else if (nLn > nTextBreak2 + nTextBreak2)
             nLn = nTextBreak2 + nTextBreak2;
-        KernArray aKernArray;
-        GetTextArray(rInf.GetOut(), rInf.GetText(), aKernArray, 
sal_Int32(rInf.GetIdx()),
+        GetTextArray(rInf, rInf.GetOut(), rInf.GetText(), aKernArray, 
sal_Int32(rInf.GetIdx()),
                      sal_Int32(nLn), rInf.GetLayoutContext());
         if( rInf.GetScriptInfo()->Compress( aKernArray, rInf.GetIdx(), nLn,
                             rInf.GetKanaComp(), 
o3tl::narrowing<sal_uInt16>(GetHeight( m_nActual )),
diff --git a/sw/source/core/txtnode/justify.cxx 
b/sw/source/core/txtnode/justify.cxx
index 41a107303588..3c9b41e74b70 100644
--- a/sw/source/core/txtnode/justify.cxx
+++ b/sw/source/core/txtnode/justify.cxx
@@ -332,6 +332,52 @@ bool KashidaJustify(std::span<TextFrameIndex const> 
aKashPositions, KernArray& r
 
     return bHasAnyKashida;
 }
+
+void BalanceCjkSpaces(KernArray& rKernArray, std::u16string_view aText, 
sal_Int32 nStt,
+                      sal_Int32 nLen, double dSpaceWidth, bool 
bInsideCjkScript)
+{
+    assert(nStt + nLen <= sal_Int32(aText.size()));
+    assert(nLen <= sal_Int32(rKernArray.size()));
+
+    // Convert kerning array into raw advances
+    for (sal_Int32 i = nLen - 1; i > 0; --i)
+    {
+        rKernArray[i] -= rKernArray[i - 1];
+    }
+
+    // Reset the widths of spaces
+    for (sal_Int32 i = 0; i < nLen; ++i)
+    {
+        sal_Unicode nCh = aText[nStt + i];
+        if (nCh == CH_BLANK)
+        {
+            bool bPrevMatches = true;
+            if (i > 0)
+            {
+                sal_Unicode nPrevCh = aText[nStt + i - 1];
+                bPrevMatches = bInsideCjkScript || (nPrevCh == CH_BLANK);
+            }
+
+            bool bNextMatches = true;
+            if (i < (nLen - 1))
+            {
+                sal_Unicode nNextCh = aText[nStt + i + 1];
+                bNextMatches = bInsideCjkScript || (nNextCh == CH_BLANK);
+            }
+
+            if (bPrevMatches || bNextMatches)
+            {
+                rKernArray[i] = dSpaceWidth;
+            }
+        }
+    }
+
+    // Convert the kerning array back into total advance
+    for (sal_Int32 i = 1; i < nLen; ++i)
+    {
+        rKernArray[i] += rKernArray[i - 1];
+    }
+}
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/justify.hxx 
b/sw/source/core/txtnode/justify.hxx
index 059c7daa71c7..494739dfcab5 100644
--- a/sw/source/core/txtnode/justify.hxx
+++ b/sw/source/core/txtnode/justify.hxx
@@ -75,6 +75,16 @@ SW_DLLPUBLIC void SnapToGridEdge(KernArray& rKernArray, 
sal_Int32 nLen, tools::L
 SW_DLLPUBLIC bool KashidaJustify(std::span<TextFrameIndex const> 
aKashPositions,
                                  KernArray& rKernArray, sal_Bool* 
pKashidaArray, sal_Int32 nStt,
                                  sal_Int32 nLen, tools::Long nSpaceAdd);
+
+/// tdf#88908: Perform a CJK space balancing on the kerning array
+/// @param[in,out] rKernArray text positions from OutDev::GetTextArray().
+/// @param aText string used to determine where space and kern are inserted.
+/// @param nStt starting index of rText.
+/// @param nLen number of elements to process in rKernArray and rText.
+/// @param nSpaceWidth new size of qualifying spaces.
+/// @param bInsideCjkScript the containing script is CJK
+SW_DLLPUBLIC void BalanceCjkSpaces(KernArray& rKernArray, std::u16string_view 
aText, sal_Int32 nStt,
+                                   sal_Int32 nLen, double dSpaceWidth, bool 
bInsideCjkScript);
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/view/viewsh.cxx b/sw/source/core/view/viewsh.cxx
index a961c75f7be0..c32a40d89b4c 100644
--- a/sw/source/core/view/viewsh.cxx
+++ b/sw/source/core/view/viewsh.cxx
@@ -1166,6 +1166,19 @@ void SwViewShell::SetMsWordUlTrailSpace(bool val)
     }
 }
 
+void SwViewShell::SetBalanceSpacesAndIdeographicSpaces(bool bValue)
+{
+    IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess();
+    if (rIDSA.get(DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES) != 
bValue)
+    {
+        SwWait aWait(*GetDoc()->GetDocShell(), true);
+        rIDSA.set(DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES, 
bValue);
+        const SwInvalidateFlags nInv
+            = SwInvalidateFlags::Size | SwInvalidateFlags::Pos | 
SwInvalidateFlags::PrtArea;
+        lcl_InvalidateAllContent(*this, nInv);
+    }
+}
+
 void SwViewShell::Reformat()
 {
     SwWait aWait( *GetDoc()->GetDocShell(), true );
diff --git a/sw/source/filter/ww8/docxexport.cxx 
b/sw/source/filter/ww8/docxexport.cxx
index a29cd847ac1c..ebbeaf257f83 100644
--- a/sw/source/filter/ww8/docxexport.cxx
+++ b/sw/source/filter/ww8/docxexport.cxx
@@ -1019,6 +1019,11 @@ WriteCompat(SwDoc const& rDoc, 
::sax_fastparser::FSHelperPtr const& rpFS) -> voi
     {
         rpFS->singleElementNS(XML_w, XML_doNotExpandShiftReturn);
     }
+    // tdf#88908 adjust CJK-context normal spaces to half of an ideographic 
space
+    if (rIDSA.get(DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES))
+    {
+        rpFS->singleElementNS(XML_w, XML_balanceSingleByteDoubleByteWidth);
+    }
     // tdf#146515 export "Use printer metrics for document formatting"
     if (!rIDSA.get(DocumentSettingId::USE_VIRTUAL_DEVICE))
         rpFS->singleElementNS(XML_w, XML_usePrinterMetrics);
diff --git a/sw/source/filter/ww8/wrtww8.cxx b/sw/source/filter/ww8/wrtww8.cxx
index 220e66367780..eba20c9916fa 100644
--- a/sw/source/filter/ww8/wrtww8.cxx
+++ b/sw/source/filter/ww8/wrtww8.cxx
@@ -572,6 +572,8 @@ static void WriteDop( WW8Export& rWrt )
     rDop.fDontUseHTMLAutoSpacing = 
rWrt.m_rDoc.getIDocumentSettingAccess().get(DocumentSettingId::PARA_SPACE_MAX);
 
     rDop.fExpShRtn = 
!rWrt.m_rDoc.getIDocumentSettingAccess().get(DocumentSettingId::DO_NOT_JUSTIFY_LINES_WITH_MANUAL_BREAK);
 // #i56856#
+    rDop.fDntBlnSbDbWid = !rWrt.m_rDoc.getIDocumentSettingAccess().get(
+        DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES); // tdf#88908
 
     IDocumentSettingAccess& rIDSA = rWrt.m_rDoc.getIDocumentSettingAccess();
     rDop.fDontBreakWrappedTables = 
rIDSA.get(DocumentSettingId::DO_NOT_BREAK_WRAPPED_TABLES);
diff --git a/sw/source/filter/ww8/ww8par.cxx b/sw/source/filter/ww8/ww8par.cxx
index 82094f1c2d14..705103b2ce23 100644
--- a/sw/source/filter/ww8/ww8par.cxx
+++ b/sw/source/filter/ww8/ww8par.cxx
@@ -1934,6 +1934,8 @@ void SwWW8ImplReader::ImportDop()
     
m_rDoc.getIDocumentSettingAccess().set(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING,
 false); // #i47448#
     
m_rDoc.getIDocumentSettingAccess().set(DocumentSettingId::NO_GAP_AFTER_NOTE_NUMBER,
 true); // tdf#159382
     
m_rDoc.getIDocumentSettingAccess().set(DocumentSettingId::DO_NOT_JUSTIFY_LINES_WITH_MANUAL_BREAK,
 !m_xWDop->fExpShRtn); // #i49277#, #i56856#
+    
m_rDoc.getIDocumentSettingAccess().set(DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES,
+                                           !m_xWDop->fDntBlnSbDbWid); // 
tdf#88908
     
m_rDoc.getIDocumentSettingAccess().set(DocumentSettingId::DO_NOT_RESET_PARA_ATTRS_FOR_NUM_FONT,
 false);  // #i53199#
     
m_rDoc.getIDocumentSettingAccess().set(DocumentSettingId::OLD_LINE_SPACING, 
false);
 
diff --git a/sw/source/filter/ww8/ww8scan.cxx b/sw/source/filter/ww8/ww8scan.cxx
index 97af529488c7..0313030da9d0 100644
--- a/sw/source/filter/ww8/ww8scan.cxx
+++ b/sw/source/filter/ww8/ww8scan.cxx
@@ -7602,7 +7602,7 @@ WW8Dop::WW8Dop(SvStream& rSt, sal_Int16 nFib, sal_Int32 
nPos, sal_uInt32 nSize):
     fNoColumnBalance(false), fConvMailMergeEsc(false), 
fSuppressTopSpacing(false),
     fOrigWordTableRules(false), fTransparentMetafiles(false), 
fShowBreaksInFrames(false),
     fSwapBordersFacingPgs(false), fCompatibilityOptions_Unknown1_13(false), 
fExpShRtn(false),
-    fCompatibilityOptions_Unknown1_15(false), 
fCompatibilityOptions_Unknown1_16(false),
+    fCompatibilityOptions_Unknown1_15(false), fDntBlnSbDbWid(false),
     fSuppressTopSpacingMac5(false), fTruncDxaExpand(false), 
fPrintBodyBeforeHdr(false),
     fNoLeading(false), fCompatibilityOptions_Unknown1_21(false), 
fMWSmallCaps(false),
     fCompatibilityOptions_Unknown1_23(false), 
fCompatibilityOptions_Unknown1_24(false),
@@ -7716,6 +7716,7 @@ WW8Dop::WW8Dop(SvStream& rSt, sal_Int16 nFib, sal_Int32 
nPos, sal_uInt32 nSize):
         copts_fShowBreaksInFrames    = 0 != ( a8Bit  &  0x04   );
         copts_fSwapBordersFacingPgs  = 0 != ( a8Bit  &  0x08   );
         copts_fExpShRtn              = 0 != ( a8Bit  &  0x20   );  // #i56856#
+        copts_fDntBlnSbDbWid         = 0 != ( a8Bit  &  0x80   );  // tdf#88908
 
         dxaTab = Get_Short( pData );         // 10 0x0a
         wSpare = Get_UShort( pData );        // 12 0x0c
@@ -7872,7 +7873,7 @@ WW8Dop::WW8Dop():
     fNoColumnBalance(false), fConvMailMergeEsc(false), 
fSuppressTopSpacing(false),
     fOrigWordTableRules(false), fTransparentMetafiles(false), 
fShowBreaksInFrames(false),
     fSwapBordersFacingPgs(false), fCompatibilityOptions_Unknown1_13(false), 
fExpShRtn(false),
-    fCompatibilityOptions_Unknown1_15(false), 
fCompatibilityOptions_Unknown1_16(false),
+    fCompatibilityOptions_Unknown1_15(false), fDntBlnSbDbWid(false),
     fSuppressTopSpacingMac5(false), fTruncDxaExpand(false), 
fPrintBodyBeforeHdr(false),
     fNoLeading(true), fCompatibilityOptions_Unknown1_21(false), 
fMWSmallCaps(false),
     fCompatibilityOptions_Unknown1_23(false), 
fCompatibilityOptions_Unknown1_24(false),
@@ -7934,7 +7935,7 @@ void WW8Dop::SetCompatibilityOptions(sal_uInt32 a32Bit)
     fCompatibilityOptions_Unknown1_13       = ( a32Bit &  0x00001000 ) >> 12 ;
     fExpShRtn                   = ( a32Bit &  0x00002000 ) >> 13 ; // #i56856#
     fCompatibilityOptions_Unknown1_15       = ( a32Bit &  0x00004000 ) >> 14 ;
-    fCompatibilityOptions_Unknown1_16       = ( a32Bit &  0x00008000 ) >> 15 ;
+    fDntBlnSbDbWid              = ( a32Bit &  0x00008000 ) >> 15 ; // tdf#88908
     fSuppressTopSpacingMac5     = ( a32Bit &  0x00010000 ) >> 16 ;
     fTruncDxaExpand             = ( a32Bit &  0x00020000 ) >> 17 ;
     fPrintBodyBeforeHdr         = ( a32Bit &  0x00040000 ) >> 18 ;
@@ -7972,7 +7973,7 @@ sal_uInt32 WW8Dop::GetCompatibilityOptions() const
     if (fCompatibilityOptions_Unknown1_13)          a32Bit |= 0x00001000;
     if (fExpShRtn)                      a32Bit |= 0x00002000; // #i56856#
     if (fCompatibilityOptions_Unknown1_15)          a32Bit |= 0x00004000;
-    if (fCompatibilityOptions_Unknown1_16)          a32Bit |= 0x00008000;
+    if (fDntBlnSbDbWid)                 a32Bit |= 0x00008000; // tdf#88908
     if (fSuppressTopSpacingMac5)        a32Bit |= 0x00010000;
     if (fTruncDxaExpand)                a32Bit |= 0x00020000;
     if (fPrintBodyBeforeHdr)            a32Bit |= 0x00040000;
diff --git a/sw/source/filter/ww8/ww8scan.hxx b/sw/source/filter/ww8/ww8scan.hxx
index 74f2efb2bead..b2f7a311d742 100644
--- a/sw/source/filter/ww8/ww8scan.hxx
+++ b/sw/source/filter/ww8/ww8scan.hxx
@@ -1666,6 +1666,7 @@ public:
      bool       copts_fShowBreaksInFrames : 1 /*= false*/;   //    when 1, 
show hard page or column breaks in frames
      bool       copts_fSwapBordersFacingPgs : 1 /*= false*/; //    when 1, 
swap left and right pages on odd facing pages
      bool       copts_fExpShRtn : 1 /*= false*/;             //    when 1, 
expand character spaces on the line ending SHIFT+RETURN  // #i56856#
+     bool       copts_fDntBlnSbDbWid : 1 = false;            //    when 0, 
expand spaces in CJK context to half-width // tdf#88908
 
     sal_Int16  dxaTab = 0;              //      720 twips - default tab width
     sal_uInt16 wSpare = 0;
@@ -1723,7 +1724,7 @@ public:
      bool       fCompatibilityOptions_Unknown1_13 : 1 /*= false*/; // #i78591#
      bool       fExpShRtn : 1 /*= false*/;                         // #i78591# 
and #i56856#
      bool       fCompatibilityOptions_Unknown1_15 : 1 /*= false*/; // #i78591#
-     bool       fCompatibilityOptions_Unknown1_16 : 1 /*= false*/; // #i78591#
+     bool       fDntBlnSbDbWid : 1 /*= false*/;                    // tdf#88908
      bool       fSuppressTopSpacingMac5 : 1 /*= false*/;           // Suppress 
extra line spacing at top
                                                       // of page like MacWord 
5.x
      bool       fTruncDxaExpand : 1 /*= false*/;                    // 
Expand/Condense by whole number of points
diff --git a/sw/source/ui/config/optcomp.cxx b/sw/source/ui/config/optcomp.cxx
index e2834d58d81c..11ae04c2e50b 100644
--- a/sw/source/ui/config/optcomp.cxx
+++ b/sw/source/ui/config/optcomp.cxx
@@ -67,6 +67,7 @@ constexpr std::pair<OUString, TranslateId> options_list[]{
     { u"MsWordCompGridMetrics"_ustr, STR_COMPAT_OPT_MSWORDCOMPGRIDMETRICS },
     { u"IgnoreTabsAndBlanksForLineCalculation"_ustr, 
STR_COMPAT_OPT_IGNORETABSANDBLANKSFORLINECALCULATION },
     { u"MsWordUlTrailSpace"_ustr, STR_COMPAT_OPT_UNDERLINETRAILINGSPACE },
+    { u"BalanceSpacesAndIdeographicSpaces"_ustr, 
STR_COMPAT_OPT_BALANCESPACESANDIDEOGRAPHICSPACES },
 };
 
 // DocumentSettingId, negate?
@@ -97,6 +98,7 @@ std::pair<DocumentSettingId, bool> 
DocumentSettingForOption(const OUString& opti
         { 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 } },
+        { u"BalanceSpacesAndIdeographicSpaces"_ustr, { 
DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES, false } },
     };
     return map.at(option);
 }
@@ -348,6 +350,10 @@ bool SwCompatibilityOptPage::FillItemSet( SfxItemSet*  )
                         m_pWrtShell->SetMsWordUlTrailSpace(bChecked);
                         break;
 
+                    case 
DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES:
+                        
m_pWrtShell->SetBalanceSpacesAndIdeographicSpaces(bChecked);
+                        break;
+
                     default:
                         break;
                 }
diff --git a/sw/source/uibase/uno/SwXDocumentSettings.cxx 
b/sw/source/uibase/uno/SwXDocumentSettings.cxx
index f4add8c2644e..62fb62380f7d 100644
--- a/sw/source/uibase/uno/SwXDocumentSettings.cxx
+++ b/sw/source/uibase/uno/SwXDocumentSettings.cxx
@@ -168,6 +168,7 @@ enum SwDocumentSettingsPropertyHandles
     HANDLE_MS_WORD_COMP_GRID_METRICS,
     HANDLE_NO_CLIPPING_WITH_WRAP_POLYGON,
     HANDLE_MS_WORD_UL_TRAIL_SPACE,
+    HANDLE_BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES,
 };
 
 }
@@ -282,6 +283,7 @@ static rtl::Reference<MasterPropertySetInfo> 
lcl_createSettingsInfo()
         { 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 },
+        { u"BalanceSpacesAndIdeographicSpaces"_ustr, 
HANDLE_BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES, cppu::UnoType<bool>::get(), 0 },
 
 /*
  * As OS said, we don't have a view when we need to set this, so I have to
@@ -1222,6 +1224,13 @@ void SwXDocumentSettings::_setSingleValue( const 
comphelper::PropertyInfo & rInf
                                                        bTmp);
             }
             break;
+        case HANDLE_BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES:
+            if (bool bTmp; rValue >>= bTmp)
+            {
+                mpDoc->getIDocumentSettingAccess().set(
+                    DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES, 
bTmp);
+            }
+            break;
         default:
             throw UnknownPropertyException(OUString::number(rInfo.mnHandle));
     }
@@ -1839,6 +1848,12 @@ void SwXDocumentSettings::_getSingleValue( const 
comphelper::PropertyInfo & rInf
                 DocumentSettingId::MS_WORD_UL_TRAIL_SPACE);
         }
         break;
+        case HANDLE_BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES:
+        {
+            rValue <<= mpDoc->getIDocumentSettingAccess().get(
+                DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES);
+        }
+        break;
         default:
             throw UnknownPropertyException(OUString::number(rInfo.mnHandle));
     }
diff --git a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx 
b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx
index cc67b8003caa..c7543c9d4d66 100644
--- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx
+++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx
@@ -9939,6 +9939,12 @@ void DomainMapper_Impl::ApplySettingsTable()
 
         if (m_pSettingsTable->GetDoNotExpandShiftReturn())
             xSettings->setPropertyValue( 
u"DoNotJustifyLinesWithManualBreak"_ustr, uno::Any(true) );
+
+        if (m_pSettingsTable->GetBalanceSingleByteDoubleByteWidth())
+        {
+            
xSettings->setPropertyValue(u"BalanceSpacesAndIdeographicSpaces"_ustr, 
uno::Any(true));
+        }
+
         // new paragraph justification has been introduced in version 15,
         // breaking text layout interoperability: new line shrinking needs 
less space
         // i.e. it typesets the same text with less lines and pages.
diff --git a/sw/source/writerfilter/dmapper/SettingsTable.cxx 
b/sw/source/writerfilter/dmapper/SettingsTable.cxx
index 48cd2a769831..2a9aa15b87ed 100644
--- a/sw/source/writerfilter/dmapper/SettingsTable.cxx
+++ b/sw/source/writerfilter/dmapper/SettingsTable.cxx
@@ -105,6 +105,7 @@ struct SettingsTable_Impl
     bool                m_bSplitPgBreakAndParaMark;
     bool                m_bMirrorMargin;
     bool                m_bDoNotExpandShiftReturn;
+    bool                m_bBalanceSingleByteDoubleByteWidth = false;
     bool                m_bDisplayBackgroundShape;
     bool                m_bNoLeading = false;
     OUString            m_sDecimalSymbol;
@@ -439,6 +440,9 @@ void SettingsTable::lcl_sprm(Sprm& rSprm)
     case NS_ooxml::LN_CT_Compat_doNotExpandShiftReturn:
         m_pImpl->m_bDoNotExpandShiftReturn = true;
         break;
+    case NS_ooxml::LN_CT_Compat_balanceSingleByteDoubleByteWidth:
+        m_pImpl->m_bBalanceSingleByteDoubleByteWidth = true;
+        break;
     case NS_ooxml::LN_CT_Settings_displayBackgroundShape:
         m_pImpl->m_bDisplayBackgroundShape = nIntValue;
         break;
@@ -547,6 +551,11 @@ bool SettingsTable::GetDoNotExpandShiftReturn() const
     return m_pImpl->m_bDoNotExpandShiftReturn;
 }
 
+bool SettingsTable::GetBalanceSingleByteDoubleByteWidth() const
+{
+    return m_pImpl->m_bBalanceSingleByteDoubleByteWidth;
+}
+
 bool SettingsTable::GetProtectForm() const
 {
     return m_pImpl->m_pDocumentProtection->getProtectForm()
diff --git a/sw/source/writerfilter/dmapper/SettingsTable.hxx 
b/sw/source/writerfilter/dmapper/SettingsTable.hxx
index 1b34c92609ae..4df8f30e258e 100644
--- a/sw/source/writerfilter/dmapper/SettingsTable.hxx
+++ b/sw/source/writerfilter/dmapper/SettingsTable.hxx
@@ -72,6 +72,7 @@ public:
     bool GetMirrorMarginSettings() const;
     bool GetDisplayBackgroundShape() const;
     bool GetDoNotExpandShiftReturn() const;
+    bool GetBalanceSingleByteDoubleByteWidth() const;
     bool GetNoColumnBalance() const;
     bool GetProtectForm() const;
     bool GetReadOnly() const;
diff --git a/vcl/source/outdev/text.cxx b/vcl/source/outdev/text.cxx
index a54854403e8c..ae68a4673371 100644
--- a/vcl/source/outdev/text.cxx
+++ b/vcl/source/outdev/text.cxx
@@ -1394,10 +1394,25 @@ sal_Int32 OutputDevice::GetTextBreak( const OUString& 
rStr, tools::Long nTextWid
          vcl::text::TextLayoutCache const*const pLayoutCache,
          const SalLayoutGlyphs* pGlyphs) const
 {
-    rHyphenPos = -1;
+    return GetTextBreakArray(rStr, nTextWidth, nHyphenChar, &rHyphenPos, 
nIndex, nLen, nCharExtra,
+                             {}, pLayoutCache, pGlyphs);
+}
 
-    std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen,
-            Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pGlyphs);
+sal_Int32 OutputDevice::GetTextBreakArray(const OUString& rStr, tools::Long 
nTextWidth,
+                                          std::optional<sal_Unicode> 
nHyphenChar,
+                                          std::optional<sal_Int32*> 
pHyphenPos, sal_Int32 nIndex,
+                                          sal_Int32 nLen, tools::Long 
nCharExtra,
+                                          KernArraySpan aKernArray,
+                                          vcl::text::TextLayoutCache const* 
const pLayoutCache,
+                                          const SalLayoutGlyphs* pGlyphs) const
+{
+    if (pHyphenPos.has_value())
+    {
+        **pHyphenPos = -1;
+    }
+
+    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(
+        rStr, nIndex, nLen, Point(0, 0), 0, aKernArray, {}, eDefaultLayout, 
pLayoutCache, pGlyphs);
     sal_Int32 nRetVal = -1;
     if( pSalLayout )
     {
@@ -1418,22 +1433,31 @@ sal_Int32 OutputDevice::GetTextBreak( const OUString& 
rStr, tools::Long nTextWid
         nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, 
nSubPixelFactor );
 
         // calculate hyphenated break position
-        OUString aHyphenStr(nHyphenChar);
-        std::unique_ptr<SalLayout> pHyphenLayout = ImplLayout( aHyphenStr, 0, 
1 );
-        if( pHyphenLayout )
+        if (nHyphenChar.has_value())
         {
-            // calculate subpixel width of hyphenation character
-            double nHyphenPixelWidth = pHyphenLayout->GetTextWidth() * 
nSubPixelFactor;
+            OUString aHyphenStr(*nHyphenChar);
+            std::unique_ptr<SalLayout> pHyphenLayout = ImplLayout(aHyphenStr, 
0, 1);
+            if (pHyphenLayout)
+            {
+                // calculate subpixel width of hyphenation character
+                double nHyphenPixelWidth = pHyphenLayout->GetTextWidth() * 
nSubPixelFactor;
 
-            // calculate hyphenated break position
-            nTextPixelWidth -= nHyphenPixelWidth;
-            if( nExtraPixelWidth > 0 )
-                nTextPixelWidth -= nExtraPixelWidth;
+                // calculate hyphenated break position
+                nTextPixelWidth -= nHyphenPixelWidth;
+                if (nExtraPixelWidth > 0)
+                    nTextPixelWidth -= nExtraPixelWidth;
 
-            rHyphenPos = pSalLayout->GetTextBreak(nTextPixelWidth, 
nExtraPixelWidth, nSubPixelFactor);
+                if (pHyphenPos.has_value())
+                {
+                    **pHyphenPos = pSalLayout->GetTextBreak(nTextPixelWidth, 
nExtraPixelWidth,
+                                                            nSubPixelFactor);
 
-            if( rHyphenPos > nRetVal )
-                rHyphenPos = nRetVal;
+                    if (**pHyphenPos > nRetVal)
+                    {
+                        **pHyphenPos = nRetVal;
+                    }
+                }
+            }
         }
     }
 

Reply via email to