sw/qa/extras/layout/data/tdf168351.fodt         |    2 
 sw/qa/extras/layout/data/tdf169168.fodt         |   59 ++++++
 sw/qa/extras/layout/data/tdf169168_hyphen.fodt  |   45 +++++
 sw/qa/extras/layout/data/tdf169168_minimum.fodt |   59 ++++++
 sw/qa/extras/layout/data/tdf169168_scaling.fodt |   59 ++++++
 sw/qa/extras/layout/layout3.cxx                 |  215 +++++++++++++++++++++++-
 sw/source/core/text/guess.cxx                   |   10 +
 sw/source/core/text/inftxt.cxx                  |   12 +
 sw/source/core/text/inftxt.hxx                  |    9 +
 sw/source/core/text/itradj.cxx                  |   25 ++
 sw/source/core/text/itrcrsr.cxx                 |    5 
 sw/source/core/text/itrform2.cxx                |    2 
 sw/source/core/text/itrpaint.cxx                |   11 -
 sw/source/core/text/porlay.hxx                  |   17 +
 sw/source/core/text/porlin.hxx                  |   27 +--
 sw/source/core/text/portxt.cxx                  |  184 +++++++++++++++++---
 16 files changed, 674 insertions(+), 67 deletions(-)

New commits:
commit 97f97ba44e6af2be2e94ae26054b060b0303933a
Author:     László Németh <[email protected]>
AuthorDate: Fri Oct 31 12:01:28 2025 +0100
Commit:     László Németh <[email protected]>
CommitDate: Sat Nov 1 09:46:53 2025 +0100

    tdf#169168 sw letter spacing: extend it for multiportion lines
    
    Enable custom letter spacing and glyph scaling for multiportion
    lines, i.e. lines with emphasized and underlined words, or
    lines hyphenated automatically or with soft hyphen etc.
    
    For this, width of the text portions are modified temporarily
    according to the actual available blank space of the lines
    at processing the last text portion. Depending on the filling
    of the text line with text portions during line breaking, portion
    width data are changed back and forth.
    
    Note: Letter spacing and scaling data are stored in SwLineLayout
    instead of SwTextPortion.
    
    Other fixes and changes:
    
    – itrcrsr.cxx: text cursor position follows the custom letter
      spacing and custom glyph scaling applied in the line;
    
    – itradj.cxx: at letter spacing, the enlarged blank space is
      removed after the last letter in the line, fixing jittering
      of the right side of the lines.
    
    – guess.cxx: fix crashing during typing before a terminating
      text portion;
    
    – portxt.cxx: fix bad letter spacing, when the line contains
      zero width characters (soft hyphen, zero-width space etc.);
    
    – testTdf168351: extend test document, because compression is not
      applied in the last line any more. Note: this is likely not a
      problem (in fact, it's the opposite, because filling the last
      line completely can destroy the clear separation of the paragraphs,
      especially without first line indent).
    
    – testTdf168251 and testTdf168448: adjust the tests according to
      the fixed line breaking.
    
    Follow-up to commit 45ec7bd76196dcc60b4c2db2f6f00623ecbaf5a4
    "tdf#168251 cui offapi xmloff sw glyph scaling: extend UNO/UX/ODF",
    commit 3c53797210bf0a4e3ffb36ed2beac4d5ce229ff2
    "tdf#167648 sw letter spacing: implement minimum letter spacing"
    and commit f83a04c51056445bbf947a31c8c1866a5c30bef1
    "tdf#167648 cui offapi xmloff sw: add DTP-feature maximum letter
    spacing".
    
    Change-Id: I7fc144d499b466ca42f5e1d81e9618fc3fb1f487
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193274
    Tested-by: Jenkins
    Reviewed-by: László Németh <[email protected]>

diff --git a/sw/qa/extras/layout/data/tdf168351.fodt 
b/sw/qa/extras/layout/data/tdf168351.fodt
index 62c0813d67d4..2fcd084e6ab1 100644
--- a/sw/qa/extras/layout/data/tdf168351.fodt
+++ b/sw/qa/extras/layout/data/tdf168351.fodt
@@ -39,7 +39,7 @@
  </office:master-styles>
  <office:body>
   <office:text>
-   <text:p text:style-name="P1"><text:span text:style-name="T1">vehicula 
vestibulum est vel ultricies.</text:span></text:p>
+   <text:p text:style-name="P1"><text:span text:style-name="T1">vehicula 
vestibulum est vel ultricies. Lorem.</text:span></text:p>
   </office:text>
  </office:body>
 </office:document>
diff --git a/sw/qa/extras/layout/data/tdf169168.fodt 
b/sw/qa/extras/layout/data/tdf169168.fodt
new file mode 100644
index 000000000000..94b858551d4e
--- /dev/null
+++ b/sw/qa/extras/layout/data/tdf169168.fodt
@@ -0,0 +1,59 @@
+<?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:font-face-decls>
+  <style:font-face style:name="Liberation Serif" 
svg:font-family="&apos;Liberation Serif&apos;" 
style:font-family-generic="modern" style:font-pitch="fixed"/>
+ </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.3cm" 
draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" 
draw:start-line-spacing-vertical="0.283cm" 
draw:end-line-spacing-horizontal="0.283cm" 
draw:end-line-spacing-vertical="0.283cm" 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="0cm" 
style:writing-mode="lr-tb" 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="Lohit Devanagari1" 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: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="1.251cm" 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: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="Lohit Devanagari1" 
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="no-limit" 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:style style:name="Text_20_body" style:display-name="Text body" 
style:family="paragraph" style:parent-style-name="Standard" style:class="text" 
style:master-page-name="">
+   <style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" 
fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" 
fo:line-height="100%" fo:text-align="start" style:justify-single-word="false" 
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" fo:text-indent="0.423cm" 
style:auto-text-indent="false" style:page-number="auto"/>
+   <style:text-properties fo:hyphenate="true" 
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="no-limit" loext:hyphenation-zone="no-limit"/>
+  </style:style>
+ </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="P1" style:family="paragraph" 
style:parent-style-name="Text_20_body" style:master-page-name="">
+   <style:paragraph-properties fo:text-align="justify" 
style:justify-single-word="false" loext:word-spacing-maximum="101%" 
loext:letter-spacing-maximum="25%" fo:hyphenation-ladder-count="no-limit" 
fo:hyphenation-keep="auto" loext:hyphenation-keep-type="column" 
loext:hyphenation-keep-line="true" style:page-number="auto" 
style:writing-mode="lr-tb"/>
+   <style:text-properties style:font-name="Linux Libertine G:litt=0" 
fo:letter-spacing="0.018cm" officeooo:paragraph-rsid="001f2a0b" 
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="no-limit" loext:hyphenation-zone="no-limit"/>
+  </style:style>
+  <style:style style:name="T1" style:family="text">
+   <style:text-properties style:text-underline-style="solid" 
style:text-underline-width="auto" style:text-underline-color="font-color"/>
+  </style:style>
+  <style:page-layout style:name="pm1">
+   <style:page-layout-properties fo:page-width="594.99pt" 
fo:page-height="842pt" style:num-format="1" style:print-orientation="portrait" 
fo:margin-top="72pt" fo:margin-bottom="72pt" fo:margin-left="195pt" 
fo:margin-right="195pt" style:writing-mode="lr-tb" 
style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" 
style:layout-grid-base-height="20.01pt" style:layout-grid-ruby-height="10.01pt" 
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="0pt" loext:margin-gutter="0pt">
+    <style:footnote-sep style:width="0.018cm" 
style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" 
style:line-style="solid" style:adjustment="left" style:rel-width="25%" 
style:color="#000000"/>
+   </style:page-layout-properties>
+  </style:page-layout>
+ </office:automatic-styles>
+ <office:master-styles>
+  <style:master-page style:name="Standard" style:page-layout-name="pm1" 
draw:style-name="dp1">
+   <style:header>
+    <text:p text:style-name="Header"/>
+   </style:header>
+  </style:master-page>
+  <style:master-page style:name="Footnote" style:page-layout-name="pm2" 
draw:style-name="dp1"/>
+  <style:master-page style:name="Endnote" style:page-layout-name="pm2" 
draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+  <office:text>
+   <text:p text:style-name="P1"><text:span text:style-name="T1">Lorem 
ipsum</text:span> dolor <text:span text:style-name="T1">sit</text:span> amet, 
consectetur adipiscing elit. Vestibulum consequat mi quis pretium semper. Proin 
luctus orci ac neque venenatis, quis commodo dolor posuere. Curabitur dignissim 
sapien quis cursus egestas. Donec blandit auctor arcu, nec pellentesque eros 
molestie eget. In consectetur aliquam hendrerit.</text:p>
+  </office:text>
+ </office:body>
+</office:document>
diff --git a/sw/qa/extras/layout/data/tdf169168_hyphen.fodt 
b/sw/qa/extras/layout/data/tdf169168_hyphen.fodt
new file mode 100644
index 000000000000..6c37ce770219
--- /dev/null
+++ b/sw/qa/extras/layout/data/tdf169168_hyphen.fodt
@@ -0,0 +1,45 @@
+<?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:font-face-decls>
+  <style:font-face style:name="Liberation Serif" 
svg:font-family="&apos;Liberation Serif&apos;" 
style:font-family-generic="modern" style:font-pitch="fixed"/>
+ </office:font-face-decls>
+ <office:styles>
+  <style:default-style style:family="paragraph">
+   <style:paragraph-properties fo:hyphenation-ladder-count="no-limit" 
fo:hyphenation-keep="auto" 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="35.46pt" 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" 
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="no-limit" loext:hyphenation-zone="no-limit"/>
+  </style:default-style>
+  <style:style style:name="Standard" style:family="paragraph" 
style:class="text"/>
+  <style:style style:name="Text_20_body" style:display-name="Text body" 
style:family="paragraph" style:parent-style-name="Standard" style:class="text" 
style:master-page-name="">
+   <style:paragraph-properties fo:margin-left="0pt" fo:margin-right="0pt" 
fo:margin-top="0pt" fo:margin-bottom="0pt" style:contextual-spacing="false" 
fo:line-height="100%" fo:text-align="start" style:justify-single-word="false" 
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" fo:text-indent="11.99pt" 
style:auto-text-indent="false" style:page-number="auto"/>
+   <style:text-properties fo:hyphenate="true" 
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="no-limit" loext:hyphenation-zone="no-limit"/>
+  </style:style>
+ </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="P1" style:family="paragraph" 
style:parent-style-name="Text_20_body" style:master-page-name="">
+   <style:paragraph-properties fo:text-align="justify" 
style:justify-single-word="false" fo:hyphenation-ladder-count="no-limit" 
fo:hyphenation-keep="auto" loext:hyphenation-keep-type="column" 
loext:hyphenation-keep-line="true" fo:text-indent="0pt" 
style:auto-text-indent="false" style:page-number="auto" 
style:writing-mode="lr-tb" loext:letter-spacing-minimum="-25%" 
loext:letter-spacing-maximum="50%"/>
+   <style:text-properties officeooo:paragraph-rsid="00110e77" 
fo:hyphenate="true" 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="4" 
loext:hyphenation-zone="no-limit" 
loext:hyphenation-compound-remain-char-count="2" fo:letter-spacing="0.51pt"/>
+  </style:style>
+  <style:style style:name="T1" style:family="text">
+   <style:text-properties style:text-underline-style="solid" 
style:text-underline-width="auto" style:text-underline-color="font-color"/>
+  </style:style>
+  <style:page-layout style:name="pm1">
+   <style:page-layout-properties fo:page-width="595.3pt" 
fo:page-height="841.89pt" style:num-format="1" 
style:print-orientation="portrait" fo:margin-top="72pt" fo:margin-bottom="72pt" 
fo:margin-left="28.35pt" fo:margin-right="398.89pt" style:writing-mode="lr-tb" 
style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" 
style:layout-grid-base-height="20.01pt" style:layout-grid-ruby-height="10.01pt" 
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="0pt" loext:margin-gutter="0pt">
+    <style:footnote-sep style:width="0.51pt" 
style:distance-before-sep="2.86pt" style:distance-after-sep="2.86pt" 
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:p text:style-name="P1"><text:span text:style-name="T1">Lorem 
ipsum</text:span> <text:span text:style-name="T1">dolor</text:span> sit 
consectetur ad.</text:p>
+  </office:text>
+ </office:body>
+</office:document>
diff --git a/sw/qa/extras/layout/data/tdf169168_minimum.fodt 
b/sw/qa/extras/layout/data/tdf169168_minimum.fodt
new file mode 100644
index 000000000000..59ce1e8afd6d
--- /dev/null
+++ b/sw/qa/extras/layout/data/tdf169168_minimum.fodt
@@ -0,0 +1,59 @@
+<?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:font-face-decls>
+  <style:font-face style:name="Liberation Serif" 
svg:font-family="&apos;Liberation Serif&apos;" 
style:font-family-generic="modern" style:font-pitch="fixed"/>
+ </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.3cm" 
draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" 
draw:start-line-spacing-vertical="0.283cm" 
draw:end-line-spacing-horizontal="0.283cm" 
draw:end-line-spacing-vertical="0.283cm" 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="0cm" 
style:writing-mode="lr-tb" 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="Lohit Devanagari1" 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: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="1.251cm" 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: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="Lohit Devanagari1" 
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="no-limit" 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:style style:name="Text_20_body" style:display-name="Text body" 
style:family="paragraph" style:parent-style-name="Standard" style:class="text" 
style:master-page-name="">
+   <style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" 
fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" 
fo:line-height="100%" fo:text-align="start" style:justify-single-word="false" 
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" fo:text-indent="0.423cm" 
style:auto-text-indent="false" style:page-number="auto"/>
+   <style:text-properties fo:hyphenate="true" 
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="no-limit" loext:hyphenation-zone="no-limit"/>
+  </style:style>
+ </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="P1" style:family="paragraph" 
style:parent-style-name="Text_20_body" style:master-page-name="">
+   <style:paragraph-properties fo:text-align="justify" 
style:justify-single-word="false" loext:word-spacing-maximum="101%" 
loext:letter-spacing-minimum="-25%" loext:letter-spacing-maximum="25%" 
fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" 
loext:hyphenation-keep-type="column" loext:hyphenation-keep-line="true" 
style:page-number="auto" style:writing-mode="lr-tb"/>
+   <style:text-properties style:font-name="Linux Libertine G:litt=0" 
fo:letter-spacing="0.018cm" officeooo:paragraph-rsid="001f2a0b" 
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="no-limit" loext:hyphenation-zone="no-limit"/>
+  </style:style>
+  <style:style style:name="T1" style:family="text">
+   <style:text-properties style:text-underline-style="solid" 
style:text-underline-width="auto" style:text-underline-color="font-color"/>
+  </style:style>
+  <style:page-layout style:name="pm1">
+   <style:page-layout-properties fo:page-width="594.99pt" 
fo:page-height="842pt" style:num-format="1" style:print-orientation="portrait" 
fo:margin-top="72pt" fo:margin-bottom="72pt" fo:margin-left="195pt" 
fo:margin-right="195pt" style:writing-mode="lr-tb" 
style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" 
style:layout-grid-base-height="20.01pt" style:layout-grid-ruby-height="10.01pt" 
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="0pt" loext:margin-gutter="0pt">
+    <style:footnote-sep style:width="0.018cm" 
style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" 
style:line-style="solid" style:adjustment="left" style:rel-width="25%" 
style:color="#000000"/>
+   </style:page-layout-properties>
+  </style:page-layout>
+ </office:automatic-styles>
+ <office:master-styles>
+  <style:master-page style:name="Standard" style:page-layout-name="pm1" 
draw:style-name="dp1">
+   <style:header>
+    <text:p text:style-name="Header"/>
+   </style:header>
+  </style:master-page>
+  <style:master-page style:name="Footnote" style:page-layout-name="pm2" 
draw:style-name="dp1"/>
+  <style:master-page style:name="Endnote" style:page-layout-name="pm2" 
draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+  <office:text>
+   <text:p text:style-name="P1"><text:span 
text:style-name="T1">Lorem</text:span> ipsum dolor sit amet, consectetur 
adipiscing elit. Vestibulum consequat mi quis pretium semper. Proin luctus orci 
ac neque venenatis, quis commodo dolor posuere. Curabitur dignissim sapien quis 
cursus egestas. Donec blandit auctor arcu, nec pellentesque eros molestie eget. 
In consectetur aliquam hendrerit.</text:p>
+  </office:text>
+ </office:body>
+</office:document>
diff --git a/sw/qa/extras/layout/data/tdf169168_scaling.fodt 
b/sw/qa/extras/layout/data/tdf169168_scaling.fodt
new file mode 100644
index 000000000000..7840612ac30a
--- /dev/null
+++ b/sw/qa/extras/layout/data/tdf169168_scaling.fodt
@@ -0,0 +1,59 @@
+<?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:font-face-decls>
+  <style:font-face style:name="Liberation Serif" 
svg:font-family="&apos;Liberation Serif&apos;" 
style:font-family-generic="modern" style:font-pitch="fixed"/>
+ </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.3cm" 
draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" 
draw:start-line-spacing-vertical="0.283cm" 
draw:end-line-spacing-horizontal="0.283cm" 
draw:end-line-spacing-vertical="0.283cm" 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="0cm" 
style:writing-mode="lr-tb" 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="Lohit Devanagari1" 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: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="1.251cm" 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: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="Lohit Devanagari1" 
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="no-limit" 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:style style:name="Text_20_body" style:display-name="Text body" 
style:family="paragraph" style:parent-style-name="Standard" style:class="text" 
style:master-page-name="">
+   <style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" 
fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" 
fo:line-height="100%" fo:text-align="start" style:justify-single-word="false" 
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" fo:text-indent="0.423cm" 
style:auto-text-indent="false" style:page-number="auto"/>
+   <style:text-properties fo:hyphenate="true" 
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="no-limit" loext:hyphenation-zone="no-limit"/>
+  </style:style>
+ </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="P1" style:family="paragraph" 
style:parent-style-name="Text_20_body" style:master-page-name="">
+   <style:paragraph-properties fo:text-align="justify" 
style:justify-single-word="false" fo:hyphenation-ladder-count="no-limit" 
fo:hyphenation-keep="auto" loext:hyphenation-keep-type="column" 
loext:hyphenation-keep-line="false" style:page-number="auto" 
loext:letter-spacing-maximum="10%" loext:text-scale-minimum="99%" 
loext:text-scale-maximum="110%"/>
+   <style:text-properties officeooo:paragraph-rsid="001f2a0b" 
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="no-limit" loext:hyphenation-zone="no-limit"/>
+  </style:style>
+  <style:style style:name="T1" style:family="text">
+   <style:text-properties style:text-underline-style="solid" 
style:text-underline-width="auto" style:text-underline-color="font-color"/>
+  </style:style>
+  <style:page-layout style:name="pm1">
+   <style:page-layout-properties fo:page-width="594.99pt" 
fo:page-height="842pt" style:num-format="1" style:print-orientation="portrait" 
fo:margin-top="72pt" fo:margin-bottom="72pt" fo:margin-left="195pt" 
fo:margin-right="195pt" style:writing-mode="lr-tb" 
style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" 
style:layout-grid-base-height="20.01pt" style:layout-grid-ruby-height="10.01pt" 
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="0pt" loext:margin-gutter="0pt">
+    <style:footnote-sep style:width="0.018cm" 
style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" 
style:line-style="solid" style:adjustment="left" style:rel-width="25%" 
style:color="#000000"/>
+   </style:page-layout-properties>
+  </style:page-layout>
+ </office:automatic-styles>
+ <office:master-styles>
+  <style:master-page style:name="Standard" style:page-layout-name="pm1" 
draw:style-name="dp1">
+   <style:header>
+    <text:p text:style-name="Header"/>
+   </style:header>
+  </style:master-page>
+  <style:master-page style:name="Footnote" style:page-layout-name="pm2" 
draw:style-name="dp1"/>
+  <style:master-page style:name="Endnote" style:page-layout-name="pm2" 
draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+  <office:text>
+   <text:p text:style-name="P1"><text:span text:style-name="T1">Lorem 
ipsum</text:span> <text:span text:style-name="T1">dolor</text:span> sit amet, 
consectetur adipiscing elit. Vestibulum consequat mi quis pretium semper. Proin 
luctus orci ac neque venenatis, quis commodo dolor posuere. Curabitur dignissim 
sapien quis cursus egestas. Donec blandit auctor arcu, nec pellentesque eros 
molestie eget. In consectetur aliquam hendrerit.</text:p>
+  </office:text>
+ </office:body>
+</office:document>
diff --git a/sw/qa/extras/layout/layout3.cxx b/sw/qa/extras/layout/layout3.cxx
index a53ed0ada4ee..94a522def904 100644
--- a/sw/qa/extras/layout/layout3.cxx
+++ b/sw/qa/extras/layout/layout3.cxx
@@ -710,6 +710,82 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, 
testTdf167648_minimum)
     }
 }
 
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf169168)
+{
+    createSwDoc("tdf169168.fodt");
+    // Ensure that all text portions are calculated before testing.
+    SwDocShell* pShell = getSwDocShell();
+
+    // Dump the rendering of the first page as an XML file.
+    std::shared_ptr<GDIMetaFile> xMetaFile = pShell->GetPreviewMetaFile();
+    MetafileXmlDump dumper;
+
+    xmlDocUniquePtr pXmlDoc = dumpAndParse(dumper, *xMetaFile);
+    CPPUNIT_ASSERT(pXmlDoc);
+
+    // Find the first text array action
+    for (size_t nAction = 0; nAction < xMetaFile->GetActionSize(); nAction++)
+    {
+        auto pAction = xMetaFile->GetAction(nAction);
+        if (pAction->GetType() == MetaActionType::TEXTARRAY)
+        {
+            auto pTextArrayAction = static_cast<MetaTextArrayAction*>(pAction);
+            auto pDXArray = pTextArrayAction->GetDXArray();
+
+            // There should be 11 characters in the first portion on the first 
line
+            CPPUNIT_ASSERT_EQUAL(size_t(11), pDXArray.size());
+
+            // Assert we are using the expected position for the
+            // second character of the first word with enlarged letter-spacing
+            // This was 286, now 320, according to the 25% maximum letter 
spacing
+            CPPUNIT_ASSERT_GREATER(sal_Int32(315), sal_Int32(pDXArray[1]));
+            CPPUNIT_ASSERT_LESS(sal_Int32(325), sal_Int32(pDXArray[1]));
+
+            // first character of the second word nearer to the left side
+            // because of the narrower spaces
+            // This was 977, now 965, according to the 25% maximum letter 
spacing
+            CPPUNIT_ASSERT_LESS(sal_Int32(970), sal_Int32(pDXArray[5]));
+            CPPUNIT_ASSERT_GREATER(sal_Int32(960), sal_Int32(pDXArray[5]));
+            break;
+        }
+    }
+}
+
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf169168_minimum)
+{
+    createSwDoc("tdf169168_minimum.fodt");
+    // Ensure that all text portions are calculated before testing.
+    SwDocShell* pShell = getSwDocShell();
+
+    // Dump the rendering of the first page as an XML file.
+    std::shared_ptr<GDIMetaFile> xMetaFile = pShell->GetPreviewMetaFile();
+    MetafileXmlDump dumper;
+
+    xmlDocUniquePtr pXmlDoc = dumpAndParse(dumper, *xMetaFile);
+    CPPUNIT_ASSERT(pXmlDoc);
+
+    // Find the first text array action
+    for (size_t nAction = 0; nAction < xMetaFile->GetActionSize(); nAction++)
+    {
+        auto pAction = xMetaFile->GetAction(nAction);
+        if (pAction->GetType() == MetaActionType::TEXTARRAY)
+        {
+            auto pTextArrayAction = static_cast<MetaTextArrayAction*>(pAction);
+            auto pDXArray = pTextArrayAction->GetDXArray();
+
+            // There should be 5 characters in the first portion on the first 
line
+            CPPUNIT_ASSERT_EQUAL(size_t(5), pDXArray.size());
+
+            // Assert we are using the expected position for the
+            // second character of the first word with enlarged letter-spacing
+            // This was 286, now 266, according to the -25% minimum letter 
spacing
+            CPPUNIT_ASSERT_LESS(sal_Int32(270), sal_Int32(pDXArray[1]));
+
+            break;
+        }
+    }
+}
+
 CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf168251)
 {
     createSwDoc("tdf168251.fodt");
@@ -770,8 +846,74 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf168251)
 
             // Assert we are using the expected position for the
             // first character of the last word with enlarged glyph width
-            // This was 3689, now 3659, according to the 110% maximum glyph 
scaling
-            CPPUNIT_ASSERT_LESS(sal_Int32(3665), sal_Int32(pDXArray[30]));
+            // This was 3689, now 3667, according to the 110% maximum glyph 
scaling
+            CPPUNIT_ASSERT_LESS(sal_Int32(3675), sal_Int32(pDXArray[30]));
+
+            break;
+        }
+    }
+}
+
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf169168_scaling)
+{
+    createSwDoc("tdf169168_scaling.fodt");
+    // Ensure that all text portions are calculated before testing.
+    SwDocShell* pShell = getSwDocShell();
+
+    // Dump the rendering of the first page as an XML file.
+    std::shared_ptr<GDIMetaFile> xMetaFile = pShell->GetPreviewMetaFile();
+    MetafileXmlDump dumper;
+
+    xmlDocUniquePtr pXmlDoc = dumpAndParse(dumper, *xMetaFile);
+    CPPUNIT_ASSERT(pXmlDoc);
+
+    // Find the first text array action
+    for (size_t nAction = 0; nAction < xMetaFile->GetActionSize(); nAction++)
+    {
+        auto pAction = xMetaFile->GetAction(nAction);
+        if (pAction->GetType() == MetaActionType::TEXTARRAY)
+        {
+            auto pTextArrayAction = static_cast<MetaTextArrayAction*>(pAction);
+            auto pDXArray = pTextArrayAction->GetDXArray();
+
+            // There should be 11 characters in the first portion on the first 
line
+            CPPUNIT_ASSERT_EQUAL(size_t(11), pDXArray.size());
+
+            // Assert we are using the expected position for the
+            // second character of the first word with enlarged letter-spacing
+            // This was 286, now 266, according to the -25% minimum letter 
spacing
+            CPPUNIT_ASSERT_LESS(sal_Int32(270), sal_Int32(pDXArray[1]));
+
+            break;
+        }
+    }
+
+    // Find the fourth text array action
+    int nLine = 0;
+    for (size_t nAction = 0; nAction < xMetaFile->GetActionSize(); nAction++)
+    {
+        auto pAction = xMetaFile->GetAction(nAction);
+        if (pAction->GetType() == MetaActionType::TEXTARRAY)
+        {
+            if (++nLine < 9)
+                continue;
+
+            auto pTextArrayAction = static_cast<MetaTextArrayAction*>(pAction);
+            auto pDXArray = pTextArrayAction->GetDXArray();
+
+            // There should be 35 characters on the first line
+            CPPUNIT_ASSERT_EQUAL(size_t(35), pDXArray.size());
+
+            // Assert we are using the expected position for the
+            // second character of the first word with enlarged glyph width
+            // This was 238, now 251, according to the 110% maximum glyph 
scaling
+            // (and no changes in letter spacing)
+            CPPUNIT_ASSERT_GREATER(sal_Int32(245), sal_Int32(pDXArray[1]));
+
+            // Assert we are using the expected position for the
+            // first character of the last word with enlarged glyph width
+            // This was 3689, now 3667, according to the 110% maximum glyph 
scaling
+            CPPUNIT_ASSERT_LESS(sal_Int32(3675), sal_Int32(pDXArray[30]));
 
             break;
         }
@@ -853,10 +995,8 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf168448)
             // This was 750, now 786, according to the enabled maximum letter 
spacing
             CPPUNIT_ASSERT_GREATER(sal_Int32(770), sal_Int32(pDXArray[4]));
 
-            // Assert we are using the expected position for the
-            // first character of the second word with enlarged letter-spacing
-            // This was 881, now 877, according to the enabled maximum letter 
spacing
-            CPPUNIT_ASSERT_LESS(sal_Int32(880), sal_Int32(pDXArray[5]));
+            // first character of the second word is there after a space
+            CPPUNIT_ASSERT_GREATER(sal_Int32(877), sal_Int32(pDXArray[5]));
 
             bFirstArray = false;
             continue;
@@ -881,6 +1021,69 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf168448)
     }
 }
 
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf169168_hyphen)
+{
+    uno::Reference<linguistic2::XHyphenator> xHyphenator = 
LinguMgr::GetHyphenator();
+    if (!xHyphenator->hasLocale(lang::Locale(u"en"_ustr, u"US"_ustr, 
OUString())))
+        return;
+
+    createSwDoc("tdf169168_hyphen.fodt");
+    // Ensure that all text portions are calculated before testing.
+    SwDocShell* pShell = getSwDocShell();
+
+    // Dump the rendering of the first page as an XML file.
+    std::shared_ptr<GDIMetaFile> xMetaFile = pShell->GetPreviewMetaFile();
+    MetafileXmlDump dumper;
+
+    xmlDocUniquePtr pXmlDoc = dumpAndParse(dumper, *xMetaFile);
+    CPPUNIT_ASSERT(pXmlDoc);
+
+    // Find the first two text array actions (associated to the first text 
line)
+    int nArray = 0;
+    for (size_t nAction = 0; nAction < xMetaFile->GetActionSize(); nAction++)
+    {
+        auto pAction = xMetaFile->GetAction(nAction);
+
+        // check letter spacing in the first line (in the first text array)
+        if (nArray == 0 && pAction->GetType() == MetaActionType::TEXTARRAY)
+        {
+            auto pTextArrayAction = static_cast<MetaTextArrayAction*>(pAction);
+            auto pDXArray = pTextArrayAction->GetDXArray();
+
+            // There should be 11 characters in the first portion on the first 
line
+            CPPUNIT_ASSERT_EQUAL(size_t(11), pDXArray.size());
+
+            // Assert we are using the expected position for the
+            // last character of the first word with enlarged letter-spacing
+            // This was 750, now 786, according to the enabled maximum letter 
spacing
+            CPPUNIT_ASSERT_GREATER(sal_Int32(770), sal_Int32(pDXArray[4]));
+
+            // first character of the second word is there after a space
+            CPPUNIT_ASSERT_GREATER(sal_Int32(877), sal_Int32(pDXArray[5]));
+        }
+
+        // check hyphen position of the first line (in the forth text array)
+        if (pAction->GetType() == MetaActionType::TEXTARRAY)
+        {
+            if (++nArray < 7)
+                continue;
+
+            auto pTextArrayAction = static_cast<MetaTextArrayAction*>(pAction);
+            auto pDXArray = pTextArrayAction->GetDXArray();
+
+            // There should be 1 character, the hyphen of the first line
+            CPPUNIT_ASSERT_EQUAL(size_t(1), pDXArray.size());
+
+            // This was 3662 (at enabled letter spacing for the hyphenated 
line),
+            // now 4149, according to the fixed hyphen position
+            auto nX = pTextArrayAction->GetPoint().X();
+            CPPUNIT_ASSERT_GREATER(sal_Int32(4100), sal_Int32(nX));
+
+            break;
+        }
+    }
+}
+
 CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf164499)
 {
     createSwDoc("tdf164499.docx");
diff --git a/sw/source/core/text/guess.cxx b/sw/source/core/text/guess.cxx
index 1088f5e949a3..a7db9c2c13bb 100644
--- a/sw/source/core/text/guess.cxx
+++ b/sw/source/core/text/guess.cxx
@@ -509,7 +509,12 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, 
SwTextFormatInfo &rInf,
             SwTwips nExtraSpace = nLineWidth / (nScaleWidthMinimum / 100.0) - 
nLineWidth;
             nLineWidth += nExtraSpace;
             rInf.SetExtraSpace( rInf.GetExtraSpace() + nExtraSpace );
+            TextFrameIndex nOrigCutPos = m_nCutPos;
             m_nCutPos = rInf.GetTextBreak( nLineWidth, nMaxLen, nMaxComp, 
rInf.GetCachedVclData().get() );
+            // restore the cut position, where the last portion fit the line 
exactly
+            // to avoid of infinite loop
+            if ( TextFrameIndex(COMPLETE_STRING) == m_nCutPos )
+                m_nCutPos = nOrigCutPos;
         }
 
         // tdf#167648 minimum letter spacing allows more text in the line
@@ -524,7 +529,12 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, 
SwTextFormatInfo &rInf,
             nLineWidth -= nExtraSpace;
             // sum minimum word spacing and letter spacing
             rInf.SetExtraSpace( rInf.GetExtraSpace() + nExtraSpace );
+            TextFrameIndex nOrigCutPos = m_nCutPos;
             m_nCutPos = rInf.GetTextBreak( nLineWidth, nMaxLen, nMaxComp, 
rInf.GetCachedVclData().get() );
+            // restore the cut position, where the last portion fit the line 
exactly
+            // to avoid of infinite loop
+            if ( TextFrameIndex(COMPLETE_STRING) == m_nCutPos )
+                m_nCutPos = nOrigCutPos;
         }
 
 #if OSL_DEBUG_LEVEL > 1
diff --git a/sw/source/core/text/inftxt.cxx b/sw/source/core/text/inftxt.cxx
index d6936a0ec996..8110a64b95e0 100644
--- a/sw/source/core/text/inftxt.cxx
+++ b/sw/source/core/text/inftxt.cxx
@@ -519,6 +519,8 @@ void SwTextPaintInfo::CtorInitTextPaintInfo( OutputDevice* 
pRenderContext, SwTex
     m_aPaintRect = rPaint;
     m_nSpaceIdx = 0;
     m_pSpaceAdd = nullptr;
+    m_nLetterSpacing = 0;
+    m_nScaleWidth = 0;
     m_pWrongList = nullptr;
     m_pGrammarCheckList = nullptr;
     m_pSmartTags = nullptr;
@@ -531,6 +533,8 @@ SwTextPaintInfo::SwTextPaintInfo( const SwTextPaintInfo 
&rInf, const OUString* p
     , m_pGrammarCheckList( rInf.GetGrammarCheckList() )
     , m_pSmartTags( rInf.GetSmartTags() )
     , m_pSpaceAdd( rInf.GetpSpaceAdd() ),
+      m_nLetterSpacing( rInf.GetLetterSpacing() ),
+      m_nScaleWidth( rInf.GetScaleWidth() ),
       m_pBrushItem( rInf.GetBrushItem() ),
       m_aTextFly( rInf.GetTextFly() ),
       m_aPos( rInf.GetPos() ),
@@ -545,6 +549,8 @@ SwTextPaintInfo::SwTextPaintInfo( const SwTextPaintInfo 
&rInf )
     , m_pGrammarCheckList( rInf.GetGrammarCheckList() )
     , m_pSmartTags( rInf.GetSmartTags() )
     , m_pSpaceAdd( rInf.GetpSpaceAdd() ),
+      m_nLetterSpacing( rInf.GetLetterSpacing() ),
+      m_nScaleWidth( rInf.GetScaleWidth() ),
       m_pBrushItem( rInf.GetBrushItem() ),
       m_aTextFly( rInf.GetTextFly() ),
       m_aPos( rInf.GetPos() ),
@@ -788,12 +794,12 @@ void SwTextPaintInfo::DrawText_( const OUString &rText, 
const SwLinePortion &rPo
             aDrawInf.SetSmartTags( bTmpSmart ? m_pSmartTags : nullptr );
 
             // set custom letter spacing (hyphenation hasn't been supported 
yet)
-            if ( rPor.GetLetterSpacing() != 0 )
-                aDrawInf.SetLetterSpacing( rPor.GetLetterSpacing() / 
sal_Int32(nLength) );
+            if ( GetLetterSpacing() != 0 )
+                aDrawInf.SetLetterSpacing( GetLetterSpacing() );
 
             // set custom glyph scaling (hyphenation hasn't been supported yet)
             // Note: set 100 percent, too (to reset the setting of the 
previous line)
-            aDrawInf.SetScaleWidth( rPor.GetScaleWidth() );
+            aDrawInf.SetScaleWidth( GetScaleWidth() );
 
             m_pFnt->DrawText_( aDrawInf );
         }
diff --git a/sw/source/core/text/inftxt.hxx b/sw/source/core/text/inftxt.hxx
index fb6b2435df52..d99d93dc248a 100644
--- a/sw/source/core/text/inftxt.hxx
+++ b/sw/source/core/text/inftxt.hxx
@@ -360,6 +360,9 @@ class SwTextPaintInfo : public SwTextSizeInfo
     sw::WrongListIterator *m_pGrammarCheckList;
     sw::WrongListIterator *m_pSmartTags;
     std::vector<tools::Long>* m_pSpaceAdd;
+    SwTwips m_nLetterSpacing = 0; // custom letter spacing
+    SwTwips m_nScaleWidth = 100;  // glyph scaling (grain resolution to limit 
font generation)
+
     const SvxBrushItem *m_pBrushItem; // For the background
     SwTextFly    m_aTextFly;    // Calculate the FlyFrame
     Point       m_aPos;       // Paint position
@@ -476,6 +479,12 @@ public:
     void SetpSpaceAdd( std::vector<tools::Long>* pNew ){ m_pSpaceAdd = pNew; }
     std::vector<tools::Long>* GetpSpaceAdd() const { return m_pSpaceAdd; }
 
+    SwTwips GetLetterSpacing() const { return m_nLetterSpacing; }
+    void SetLetterSpacing(SwTwips nNew) { m_nLetterSpacing = nNew; }
+
+    SwTwips GetScaleWidth() const { return m_nScaleWidth; }
+    void SetScaleWidth(SwTwips nNew) { m_nScaleWidth = nNew; }
+
     void SetWrongList(sw::WrongListIterator *const pNew) { m_pWrongList = 
pNew; }
     sw::WrongListIterator* GetpWrongList() const { return m_pWrongList; }
 
diff --git a/sw/source/core/text/itradj.cxx b/sw/source/core/text/itradj.cxx
index 64bd6e7dd582..c3bdc9d98e47 100644
--- a/sw/source/core/text/itradj.cxx
+++ b/sw/source/core/text/itradj.cxx
@@ -369,13 +369,28 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent,
                             ? -nSpaceAdd + LONG_MAX/2
                             : 0;
 
-                    SwLinePortion *pPortion = pCurrent->GetFirstPortion();
                     // word spacing filled by letter spacing and glyph scaling 
(at expansion) or
                     // word spacing shrunk by them (at shrinking)
-                    tools::Long nSpaceKernAndScale = pPortion->GetSpaceCount()
-                        ? ( pPortion->GetLetterSpacing() + 
pPortion->GetScaleWidthSpacing() ) * 100.0 /
-                                                                            
sal_Int32(pPortion->GetSpaceCount())
-                        : 0;
+                    tools::Long nSpaceKernAndScale = 0;
+                    if ( pCurrent->GetSpaceCount() )
+                    {
+                        // note: soft hyphen, zero-width spaces are not normal 
text,
+                        // i.e. GetLen() is OK for checking multiportion lines
+                        // (where the width of the portions are adjusted for 
custom
+                        // letter spacing and glyph scaling)
+                        bool bSinglePortion =
+                                pCurrent->GetLetterCount() == 
pCurrent->GetFirstPortion()->GetLen();
+                        nSpaceKernAndScale = bSinglePortion
+                            ? 100.0 * ( pCurrent->GetLetterSpacing() *
+                                    // -1: remove letter spacing after the 
last letter
+                                    ( sal_Int32(pCurrent->GetLetterCount()) - 
1 ) +
+                                    pCurrent->GetScaleWidthSpacing() ) / 
pCurrent->GetSpaceCount()
+                            // multiportion lines: width of the portions are 
already adjusted, so
+                            // remove letter spacing only after the last 
letter, and adjust scaling
+                            : 100.0 * ( -pCurrent->GetLetterSpacing() +
+                                    pCurrent->GetScaleWidthSpacing() ) / 
pCurrent->GetSpaceCount();
+                    }
+
                     // set expansion in 1/100 twips/space
                     pCurrent->SetLLSpaceAdd( nSpaceSub
                         ? ( nSpaceSub + nSpaceKernAndScale <= LONG_MAX/2 ? 0 : 
nSpaceSub + nSpaceKernAndScale )
diff --git a/sw/source/core/text/itrcrsr.cxx b/sw/source/core/text/itrcrsr.cxx
index bff160bffba8..b2ea1062dc65 100644
--- a/sw/source/core/text/itrcrsr.cxx
+++ b/sw/source/core/text/itrcrsr.cxx
@@ -970,7 +970,10 @@ void SwTextCursor::GetCharRect_( SwRect* pOrig, 
TextFrameIndex const nOfst,
                             SwTwips nTmp = nX;
                             aInf.SetKanaComp( pKanaComp );
                             aInf.SetKanaIdx( nKanaIdx );
-                            nX += pPor->GetTextSize( aInf ).Width();
+                            // fix cursor position, when the line contains 
custom letter spacing
+                            // and custom glyph scaling for better 
justification
+                            nX += pPor->GetTextSize( aInf ).Width() * 
m_pCurr->GetScaleWidth() / 100.0 +
+                                    sal_Int32(pPor->GetLen()) * 
m_pCurr->GetLetterSpacing();
                             aInf.SetOnWin( bOldOnWin );
                             if ( pPor->InSpaceGrp() && nSpaceAdd )
                                 nX += pPor->CalcSpacing( nSpaceAdd, aInf );
diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx
index a5363e06544a..eaa6eb6c3e17 100644
--- a/sw/source/core/text/itrform2.cxx
+++ b/sw/source/core/text/itrform2.cxx
@@ -2635,6 +2635,8 @@ void SwTextFormatter::UpdatePos( SwLineLayout *pCurrent, 
Point aStart,
     aTmpInf.ResetSpaceIdx();
     aTmpInf.SetKanaComp( pCurrent->GetpKanaComp() );
     aTmpInf.ResetKanaIdx();
+    aTmpInf.SetLetterSpacing( 0 );
+    aTmpInf.SetScaleWidth( 100 );
 
     // The frame's size
     aTmpInf.SetIdx( nStartIdx );
diff --git a/sw/source/core/text/itrpaint.cxx b/sw/source/core/text/itrpaint.cxx
index 293bca3a9747..14693858cda8 100644
--- a/sw/source/core/text/itrpaint.cxx
+++ b/sw/source/core/text/itrpaint.cxx
@@ -136,6 +136,8 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, 
SwSaveClip &rClip,
     GetAdjusted();
     AddExtraBlankWidth();
     GetInfo().SetpSpaceAdd( m_pCurr->GetpLLSpaceAdd() );
+    GetInfo().SetLetterSpacing( m_pCurr->GetLetterSpacing() );
+    GetInfo().SetScaleWidth( m_pCurr->GetScaleWidth() );
     GetInfo().ResetSpaceIdx();
     GetInfo().SetKanaComp( m_pCurr->GetpKanaComp() );
     GetInfo().ResetKanaIdx();
@@ -430,12 +432,13 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, 
SwSaveClip &rClip,
             else
             {
                 // adjust the hyphen at custom spacing
-                if ( pPor->InHyphGrp() && ( 
m_pCurr->GetFirstPortion()->GetLetterSpacing() > 0 ||
-                            m_pCurr->GetFirstPortion()->GetScaleWidthSpacing() 
> 0 ) )
+                if ( pPor->Width() > 0 && pPor->InHyphGrp() && ( 
m_pCurr->GetLetterSpacing() > 0 ||
+                            m_pCurr->GetScaleWidthSpacing() > 0 ) &&
+                                m_pCurr->GetFirstPortion()->GetLen() == 
m_pCurr-> GetLetterCount() )
                 {
                     GetInfo().X( GetInfo().X() +
-                            m_pCurr->GetFirstPortion()->GetLetterSpacing() +
-                            m_pCurr->GetFirstPortion()->GetScaleWidthSpacing() 
);
+                            m_pCurr->GetLetterSpacing() * 
(sal_Int32(m_pCurr->GetLetterCount())) +
+                            m_pCurr->GetScaleWidthSpacing() );
                 }
                 pPor->Paint( GetInfo() );
             }
diff --git a/sw/source/core/text/porlay.hxx b/sw/source/core/text/porlay.hxx
index afb8534de477..76c14ede874a 100644
--- a/sw/source/core/text/porlay.hxx
+++ b/sw/source/core/text/porlay.hxx
@@ -86,6 +86,11 @@ private:
     SwTwips m_nTextHeight;             // The max height of all non-FlyCnt 
portions in this Line
     SwTwips m_nExtraAscent = 0;
     SwTwips m_nExtraDescent = 0;
+    sal_Int32 m_nSpaceCount = 0;       // space count in the line
+    SwTwips m_nLetterSpacing = 0;      // custom letter spacing
+    SwTwips m_nScaleWidth = 100;       // glyph scaling (grain resolution to 
limit font generation)
+    float m_fScaleWidthSpacing = 0.0;  // extra space filled by glyph scaling
+
     bool m_bFormatAdj : 1;
     bool m_bDummy     : 1;
     bool m_bEndHyph   : 1;
@@ -179,6 +184,18 @@ public:
     void SetExtraDescent(SwTwips nNew) { m_nExtraDescent = nNew; }
     SwTwips GetExtraDescent() const { return m_nExtraDescent; }
 
+    void SetSpaceCount(sal_Int32 nNew) { m_nSpaceCount = nNew; }
+    sal_Int32 GetSpaceCount() const { return m_nSpaceCount; }
+
+    void SetLetterSpacing(SwTwips nNew) { m_nLetterSpacing = nNew; }
+    SwTwips GetLetterSpacing() const { return m_nLetterSpacing; }
+
+    void SetScaleWidth(SwTwips nNew) { m_nScaleWidth = nNew; }
+    SwTwips GetScaleWidth() const { return m_nScaleWidth; }
+
+    void SetScaleWidthSpacing(SwTwips nNew) { m_fScaleWidthSpacing = nNew; }
+    float GetScaleWidthSpacing() const { return m_fScaleWidthSpacing; }
+
     // Creates the glue chain for short lines
     SwMarginPortion *CalcLeftMargin();
 
diff --git a/sw/source/core/text/porlin.hxx b/sw/source/core/text/porlin.hxx
index 6a3a58f1aec6..0ec1be29216e 100644
--- a/sw/source/core/text/porlin.hxx
+++ b/sw/source/core/text/porlin.hxx
@@ -52,10 +52,9 @@ private:
     SwTwips m_nExtraBlankWidth = 0;    // width of spaces after the break
     SwTwips m_nExtraShrunkWidth = 0;   // width of not shrunk line
     SwTwips m_nExtraSpaceSize = 0;     // extra space over normal space width
-    SwTwips m_nLetterSpacing = 0;      // letter spacing, TODO: add better 
resolution
-    TextFrameIndex m_nSpaceCount;      // space count for letter spacing
-    SwTwips m_nScaleWidth = 100;       // glyph scaling (good resolution to 
limit font generation)
-    float m_fScaleWidthSpacing = 0.0;  // extra space filled by glyph scaling
+    bool m_bModifiedWidth = false;     // applied custom letter spacing and 
scale width
+    TextFrameIndex m_nSpaceCount = TextFrameIndex(0);  // space count for 
letter spacing
+    TextFrameIndex m_nLetterCount = TextFrameIndex(0); // letter count for 
multi-portion lines
 
     std::optional<SwLinePortionLayoutContext> m_nLayoutContext;
 
@@ -84,14 +83,12 @@ public:
     void ExtraShrunkWidth(const SwTwips nNew) { m_nExtraShrunkWidth = nNew; }
     SwTwips ExtraSpaceSize() const { return m_nExtraSpaceSize; }
     void ExtraSpaceSize(const SwTwips nNew) { m_nExtraSpaceSize = nNew; }
-    SwTwips GetLetterSpacing() const { return m_nLetterSpacing; }
-    void SetLetterSpacing(const SwTwips nNew) { m_nLetterSpacing = nNew; }
+    bool IsModifiedWidth() const { return m_bModifiedWidth; }
+    void SetModifiedWidth(const bool bNew) { m_bModifiedWidth = bNew; }
     TextFrameIndex GetSpaceCount() const { return m_nSpaceCount; }
-    SwTwips GetScaleWidth() const { return m_nScaleWidth; }
-    void SetScaleWidth(const SwTwips nNew) { m_nScaleWidth = nNew; }
-    float GetScaleWidthSpacing() const { return m_fScaleWidthSpacing; }
-    void SetScaleWidthSpacing(const float fNew) { m_fScaleWidthSpacing = fNew; 
}
+    TextFrameIndex GetLetterCount() const { return m_nLetterCount; }
     void SetSpaceCount(TextFrameIndex const nSpaceCount) { m_nSpaceCount = 
nSpaceCount; }
+    void SetLetterCount(TextFrameIndex const nLetterCount) { m_nLetterCount = 
nLetterCount; }
     SwTwips GetHangingBaseline() const { return mnHangingBaseline; }
     void SetHangingBaseline( const SwTwips nNewBaseline ) { mnHangingBaseline 
= nNewBaseline; }
     const std::optional<SwLinePortionLayoutContext> & GetLayoutContext() const 
{ return m_nLayoutContext; }
@@ -209,10 +206,9 @@ inline SwLinePortion &SwLinePortion::operator=(const 
SwLinePortion &rPortion)
     m_nExtraBlankWidth = rPortion.m_nExtraBlankWidth;
     m_nExtraShrunkWidth = rPortion.m_nExtraShrunkWidth;
     m_nExtraSpaceSize = rPortion.m_nExtraSpaceSize;
-    m_nLetterSpacing = rPortion.m_nLetterSpacing;
+    m_bModifiedWidth = rPortion.m_bModifiedWidth;
     m_nSpaceCount = rPortion.m_nSpaceCount;
-    m_nScaleWidth = rPortion.m_nScaleWidth;
-    m_fScaleWidthSpacing = rPortion.m_fScaleWidthSpacing;
+    m_nLetterCount = rPortion.m_nLetterCount;
     m_nLayoutContext = rPortion.m_nLayoutContext;
     return *this;
 }
@@ -230,10 +226,9 @@ inline SwLinePortion::SwLinePortion(const SwLinePortion 
&rPortion) :
     m_nExtraBlankWidth(rPortion.m_nExtraBlankWidth),
     m_nExtraShrunkWidth(rPortion.m_nExtraShrunkWidth),
     m_nExtraSpaceSize(rPortion.m_nExtraSpaceSize),
-    m_nLetterSpacing(rPortion.m_nLetterSpacing),
+    m_bModifiedWidth(rPortion.m_bModifiedWidth),
     m_nSpaceCount(rPortion.m_nSpaceCount),
-    m_nScaleWidth(rPortion.m_nScaleWidth),
-    m_fScaleWidthSpacing(rPortion.m_fScaleWidthSpacing),
+    m_nLetterCount(rPortion.m_nLetterCount),
     m_nLayoutContext(rPortion.m_nLayoutContext)
 {
 }
diff --git a/sw/source/core/text/portxt.cxx b/sw/source/core/text/portxt.cxx
index 9561132861ff..89c61a1c5d9f 100644
--- a/sw/source/core/text/portxt.cxx
+++ b/sw/source/core/text/portxt.cxx
@@ -309,9 +309,7 @@ void SwTextPortion::BreakCut( SwTextFormatInfo &rInf, const 
SwTextGuess &rGuess
         Width( 0 );
         ExtraShrunkWidth( 0 );
         ExtraSpaceSize( 0 );
-        SetLetterSpacing( 0 );
-        SetScaleWidth( 100 );
-        SetScaleWidthSpacing( 0 );
+        SetModifiedWidth( false );
     }
 }
 
@@ -322,9 +320,7 @@ void SwTextPortion::BreakUnderflow( SwTextFormatInfo &rInf )
     Width( 0 );
     ExtraShrunkWidth( 0 );
     ExtraSpaceSize( 0 );
-    SetLetterSpacing( 0 );
-    SetScaleWidth( 100 );
-    SetScaleWidthSpacing( 0 );
+    SetModifiedWidth( false );
     SetLen( TextFrameIndex(0) );
     SetAscent( 0 );
     rInf.SetUnderflow( this );
@@ -352,6 +348,26 @@ void SwTextPortion::SetSpacing( SwTextFormatInfo &rInf, 
SwTextGuess const &rGues
     if ( nSpaces == 0 )
         return;
 
+    // count zero width characters to calculate correct letter spacing
+    sal_Int32 nZeroWidthCharCount = 0;
+
+    // sum width of the previous portions to calculate real line width
+    SwTwips nStartWidth = 0;
+    SwLineLayout *pLay = rInf.GetRoot();
+    SwLinePortion *pPos = pLay;
+    // first portion isn't redundant, if it's the only portion before the 
actual one: don't skip it
+    if ( !pPos->GetNextPortion() && (
+            ( pLay->GetLetterSpacing() && pPos->GetLen() ) ||
+            pLay->GetScaleWidth() != 100 ) )
+    {
+        nStartWidth += pPos->PrtWidth();
+    }
+    for (pPos = pPos->GetNextPortion(); pPos; pPos = pPos->GetNextPortion())
+    {
+        if ( pPos->Width() == 0 )
+            nZeroWidthCharCount += sal_Int32(pPos->GetLen());
+        nStartWidth += pPos->PrtWidth();
+    }
     // adjust hyphen: remove hyphen width from the line width, because
     // hyphen mark and optional text of the alternative hyphenation will
     // be in an extra portion
@@ -376,14 +392,26 @@ void SwTextPortion::SetSpacing( SwTextFormatInfo &rInf, 
SwTextGuess const &rGues
 
     SvxAdjustItem aAdjustItem =
         
rInf.GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust();
-    // width of a single expanded space without letter spacing and glyph 
scaling
-    float fSpaceNormal = ( rInf.GetLineWidth() - nHyphenWidth -
-                    (rInf.GetBreakWidth() - nSpaces * nWidthOf10Spaces/10.0) ) 
/ nSpaces;
+    // width of a single expanded space without letter spacing and glyph 
scaling,
+    // When nStartWidth is non-zero, i.e. the line contains multiple text 
portions,
+    // nStartWidth contains the length of the text portions before the last 
one,
+    // and GetLineWidth() contains the remaining blank space of the line.
+    // When nStartWidth is zero, i.e. the line is a single text portion,
+    // GetLineWidth() contains the line width with the blank space, and 
GetBreakwidth()
+    // is used for calculating the remaining blank space, except when the last 
portion
+    // fills the remaining space
+    SwTwips nBreakWidth = rGuess.BreakWidth();
+    float fSpaceNormal = ( nStartWidth + rInf.GetLineWidth() - nHyphenWidth -
+                   ( nStartWidth + nBreakWidth - nSpaces * 
nWidthOf10Spaces/10.0 ) ) / nSpaces;
+
     // the part to be removed: the previous width minus the maximum allowed 
space width
     float fExpansionOverMax =
         fSpaceNormal - nWidthOf10Spaces / 10.0 * 
aAdjustItem.GetPropWordSpacingMaximum() / 100.0;
     ExtraSpaceSize( fExpansionOverMax > 0 ? fExpansionOverMax : 0 );
-    int nLetterCount = sal_Int32(rGuess.BreakPos()) - sal_Int32(rInf.GetIdx());
+
+    sal_Int32 nLetterCount = sal_Int32(rGuess.BreakPos()) -
+            sal_Int32(rInf.GetLineStart()) - nZeroWidthCharCount;
+
     // letter spacing/character to be added or subtracted to get the desired 
word spacing
     float fLetterSpacingForDesiredWordSpacing =
         nLetterCount > 0 ? (((fSpaceNormal - nWidthOf10Spaces/10.0) * nSpaces) 
/ nLetterCount) : 0;
@@ -400,26 +428,86 @@ void SwTextPortion::SetSpacing( SwTextFormatInfo &rInf, 
SwTextGuess const &rGues
             nWidthOf10Spaces / 10.0 * 
aAdjustItem.GetPropLetterSpacingMinimum() / 100.0;
         nLetterSpacing = std::max( fLetterSpacingForDesiredWordSpacing, 
fMinimumLetterSpacing );
     }
+
+    // don't set letter spacing for multiportion lines, where the last portion
+    // does not continue in the next line, because if it is not fit the line 
e.g. during typing,
+    // it could result freezing
+    if ( nBreakWidth == 0 )
+        nLetterSpacing = 0;
+
     // full width of the extra (rounded) letter spacing within the line
     // to adjust word spacing in SwTextAdjuster::CalcNewBlock()
-    SetLetterSpacing( SwTwips(nLetterSpacing * nLetterCount) );
-    SetSpaceCount( TextFrameIndex(nSpaces) );
+    if ( aAdjustItem.GetPropLetterSpacingMinimum() != 100 ||
+            aAdjustItem.GetPropLetterSpacingMaximum() != 100 )
+        pLay->SetLetterSpacing(nLetterSpacing);
+    else
+        pLay->SetLetterSpacing(0);
+
+    pLay->SetLetterCount(TextFrameIndex(nLetterCount));
+    pLay->SetSpaceCount(nSpaces);
 
     // limit glyph scaling without optical sizing:
     // apply it only after applying maximum letter spacing
     // TODO: change this after variable font support
-    if ( aAdjustItem.GetPropScaleWidthMaximum() != 100 )
-        SetScaleWidth( 100.0 * ( rInf.GetLineWidth() - nHyphenWidth ) /
-                                         ( rInf.GetBreakWidth() + 
GetLetterSpacing() ) );
-    if ( GetScaleWidth() > aAdjustItem.GetPropScaleWidthMaximum() )
-        SetScaleWidth( aAdjustItem.GetPropScaleWidthMaximum() );
+    if ( ( aAdjustItem.GetPropScaleWidthMaximum() != 100 ||
+            aAdjustItem.GetPropScaleWidthMinimum() != 100 ) && nStartWidth + 
nBreakWidth > 0 )
+    {
+        pLay->SetScaleWidth( 100.0 *
+            ( nStartWidth + rInf.GetLineWidth() - nHyphenWidth - 
nLetterSpacing * nLetterCount ) /
+                         ( nStartWidth + nBreakWidth ) );
+    }
+    else
+    {
+        pLay->SetScaleWidth(100);
+    }
+
+    // set scaling limits
+    if ( pLay->GetScaleWidth() > aAdjustItem.GetPropScaleWidthMaximum() )
+        pLay->SetScaleWidth( aAdjustItem.GetPropScaleWidthMaximum() );
+
+    if ( pLay->GetScaleWidth() < aAdjustItem.GetPropScaleWidthMinimum() )
+        pLay->SetScaleWidth( aAdjustItem.GetPropScaleWidthMinimum() );
+
+    if ( pLay->GetLetterSpacing() < 0 && 
aAdjustItem.GetPropLetterSpacingMinimum() >= 100 )
+            pLay->SetLetterSpacing(100);
+
+    // fix calculation problem, when letter spacing removed the available space
+    if ( pLay->GetScaleWidth() < 100 && pLay->GetLetterSpacing() > 0 )
+        pLay->SetScaleWidth(100);
+
     // space used by glyph scaling to adjust word spacing
-    if ( aAdjustItem.GetPropScaleWidthMaximum() != 100 )
-        SetScaleWidthSpacing( rInf.GetBreakWidth() * (GetScaleWidth() - 100.0) 
/ 100.0 );
+    if (  ( aAdjustItem.GetPropScaleWidthMaximum() != 100 ||
+            aAdjustItem.GetPropScaleWidthMinimum() != 100 ) &&
+        // no need correction for multiportion lines,
+        // where the portions have got corrected width
+        nStartWidth == 0 )
+    {
+        pLay->SetScaleWidthSpacing( nBreakWidth * (pLay->GetScaleWidth() - 
100.0) / 100.0 );
+    }
+    else
+        pLay->SetScaleWidthSpacing(0);
 }
 
 bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
 {
+    // restore original portion width without custom letter spacing and scaling
+    SwLineLayout *pLay = rInf.GetRoot();
+    for (SwLinePortion *pPos = pLay; pPos; pPos = pPos->GetNextPortion())
+    {
+        if ( pPos->Width() != 0 && pPos->IsModifiedWidth() && (
+            ( pLay->GetLetterSpacing() && pPos->GetLen() ) || 
pLay->GetScaleWidth() != 100) )
+        {
+            pPos->Width( ( pPos->Width() - pLay->GetLetterSpacing() * 
sal_Int32(pPos->GetLen()) )
+                            * 100.0 / pLay->GetScaleWidth() );
+        }
+        pPos->SetModifiedWidth(false);
+    }
+
+    SetModifiedWidth(false);
+    pLay->SetLetterSpacing(0);
+    pLay->SetScaleWidth(100);
+    pLay->SetScaleWidthSpacing(0);
+
     // 5744: If only the hyphen does not fit anymore, we still need to wrap
     // the word, or else return true!
     if( rInf.IsUnderflow() && rInf.GetSoftHyphPos() )
@@ -444,9 +532,6 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
 
     ExtraShrunkWidth( 0 );
     ExtraSpaceSize( 0 );
-    SetLetterSpacing( 0 );
-    SetScaleWidth( 100 );
-    SetScaleWidthSpacing( 0 );
     std::optional<SwTextGuess> pGuess(std::in_place);
     bool bFull = !pGuess->Guess( *this, rInf, Height() );
 
@@ -515,9 +600,7 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
                         pGuess->BreakPos() > rInf.GetLineStart();
 
             // calculate available word spacing for letter spacing, and for 
the word spacing indicator
-            // for single portion lines, TODO: enable letter spacing for 
multiportion
-            if ( rInf.GetLineStart() == rInf.GetIdx() )
-                SetSpacing(rInf, *pGuess, nRealSpaces, nSpaceWidth);
+            SetSpacing(rInf, *pGuess, nRealSpaces, nSpaceWidth);
 
             // calculate line breaking with desired word spacing, also
             // if the desired word spacing is 100%, but there is a greater
@@ -531,9 +614,7 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
                 if ( aAdjustItem.GetPropLetterSpacingMinimum() < 0 || 
rInf.GetBreakWidth() <= rInf.GetLineWidth() )
                 {
                     fSpaceNormal = (rInf.GetLineWidth() - 
(rInf.GetBreakWidth() - nSpacesInLine2 * nSpaceWidth/10.0))/nSpacesInLine2;
-                    // TODO: enable letter spacing for multiportion
-                    if ( rInf.GetLineStart() == rInf.GetIdx() )
-                        SetSpacing(rInf, *pGuess, nSpacesInLine2, nSpaceWidth);
+                    SetSpacing(rInf, *pGuess, nSpacesInLine2, nSpaceWidth);
                 }
             }
 
@@ -619,6 +700,24 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
                     rInf.SetExtraSpace(nOldExtraSpace);
             }
 
+            // modify non-zero width portions with letter spacing
+            SwLinePortion *pPos = pLay;
+            // skip redundant first portion
+            if ( pPos->GetNextPortion() )
+                pPos = static_cast<SwLinePortion*>(pLay)->GetNextPortion();
+            while( pPos )
+            {
+                // Note: GetLen() can be zero at Italics correction
+                if ( pPos->Width() && !IsModifiedWidth() &&
+                     ( pLay->GetLetterSpacing() || pLay->GetScaleWidth() != 
100 ) )
+                {
+                    pPos->Width( pPos->Width() * pLay->GetScaleWidth()/100.0 +
+                            pLay->GetLetterSpacing() * 
sal_Int32(pPos->GetLen()) );
+                    pPos->SetModifiedWidth(true);
+                }
+                pPos = pPos->GetNextPortion();
+            }
+
             if ( pGuess->BreakWidth() != nOldWidth )
             {
                 ExtraShrunkWidth( pGuess->BreakWidth() );
@@ -680,6 +779,19 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
                 rInf.GetRoot()->SetMidHyph( true );
             else
                 rInf.GetRoot()->SetEndHyph( true );
+
+            // modify width of last portion with letter spacing
+            // if it's not a zero-width portion (i.e. a soft hyphen)
+            // Note: GetLen() can be zero at Italics correction
+            pLay = rInf.GetRoot();
+            if ( Width() && !IsModifiedWidth() &&
+                            ( pLay->GetLetterSpacing() || 
pLay->GetScaleWidth() != 100 ) )
+            {
+                Width( Width() * pLay->GetScaleWidth() / 100.0 +
+                                pLay->GetLetterSpacing() * sal_Int32(GetLen()) 
);
+
+                SetModifiedWidth(true);
+            }
         }
         // case C1
         // - Footnote portions with fake line start (i.e., not at beginning of 
line)
@@ -779,6 +891,19 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
                 if (auto ch = rInf.GetChar(pGuess->BreakStart()); !ch || ch == 
CH_BREAK)
                     bFull = false; // Keep following SwBreakPortion / para 
break in the same line
             }
+
+            // modify width of last portion with letter spacing
+            // if it's not a zero-width portion (i.e. a soft hyphen)
+            // Note: GetLen() can be zero at Italics correction
+            pLay = rInf.GetRoot();
+            if ( Width() && !IsModifiedWidth() &&
+                            ( pLay->GetLetterSpacing() || 
pLay->GetScaleWidth() != 100 ) )
+            {
+                Width( Width() * pLay->GetScaleWidth() / 100.0 +
+                                        pLay->GetLetterSpacing() * 
sal_Int32(GetLen()) );
+
+                SetModifiedWidth(true);
+            }
         }
         else    // case C2, last exit
             BreakCut( rInf, *pGuess );
@@ -813,9 +938,6 @@ bool SwTextPortion::Format( SwTextFormatInfo &rInf )
         Width( 0 );
         ExtraShrunkWidth( 0 );
         ExtraSpaceSize( 0 );
-        SetLetterSpacing( 0 );
-        SetScaleWidth( 100 );
-        SetScaleWidthSpacing( 0 );
         SetLen( TextFrameIndex(0) );
         SetAscent( 0 );
         SetNextPortion( nullptr );  // ????

Reply via email to