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; + } + } + } } }
