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="'Liberation Serif'" 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="'Liberation Serif'" 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="'Liberation Serif'" 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="'Liberation Serif'" 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 ); // ????
