cui/source/inc/paragrph.hxx | 4 - cui/source/tabpages/paragrph.cxx | 45 +-------------- cui/uiconfig/ui/paragalignpage.ui | 37 ++---------- offapi/com/sun/star/style/ParagraphProperties.idl | 15 +++++ sw/qa/extras/layout/data/tdf167648_minimum.fodt | 56 ++++++++++++++++++ sw/qa/extras/layout/layout3.cxx | 42 +++++++++++++- sw/source/core/text/guess.cxx | 12 ++++ sw/source/core/text/inftxt.cxx | 2 sw/source/core/text/itradj.cxx | 4 - sw/source/core/text/portxt.cxx | 66 +++++++++++----------- sw/source/core/text/portxt.hxx | 2 11 files changed, 171 insertions(+), 114 deletions(-)
New commits: commit 3c53797210bf0a4e3ffb36ed2beac4d5ce229ff2 Author: László Németh <[email protected]> AuthorDate: Thu Aug 28 11:27:07 2025 +0200 Commit: László Németh <[email protected]> CommitDate: Wed Sep 3 15:21:58 2025 +0200 tdf#167648 sw letter spacing: implement minimum letter spacing Implement new paragraph justification option "Minimum letter spacing" allowing to shrink text lines more, choosing better paragraph layout instead of hyphenation or rivers of space. * Implement visual layout of minimum letter spacing. * Enable Minimum letter spacing spin box on Alignment pane of the paragraph formatting dialog window * Remove also desired letter spacing spin box from the alignment pane. It will be better to visualize CharKerning with percentage value instead of adding a new user interface to the same setting. * Set minimum and maximum spin box ranges to [-100, 0] and [0, 500], and remove unnecessary handlers. * offapi: extend API description with ranges of letter spacing [-100, 500] and word spacing ([0, 1000]). * Add ODF unit test. Note: hyphenated lines, lines with multiple portions haven't been using custom letter spacing, yet. Note: resolution of the custom letter spacing is only 1/20 point (1 twip) yet. Note: adjust testTdf163149 failing on a test machine, maybe because the DOCX test document contains the not supported Arial font, resulting small differences based on the font replacement. Follow-up to commit f83a04c51056445bbf947a31c8c1866a5c30bef1 "tdf#167648 cui offapi xmloff sw: add DTP-feature maximum letter spacing". Change-Id: I0d686ccbdaa5324eaf6c4c7f6da0f37e76f60611 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/190533 Tested-by: Jenkins Reviewed-by: László Németh <[email protected]> diff --git a/cui/source/inc/paragrph.hxx b/cui/source/inc/paragrph.hxx index 51d49765ff15..4218f3091e3b 100644 --- a/cui/source/inc/paragrph.hxx +++ b/cui/source/inc/paragrph.hxx @@ -161,7 +161,6 @@ class SvxParaAlignTabPage : public SfxTabPage /// letter spacing std::unique_ptr<weld::Label> m_xLabelLetterSpacing; - std::unique_ptr<weld::MetricSpinButton> m_xLetterSpacing; std::unique_ptr<weld::MetricSpinButton> m_xLetterSpacingMinimum; std::unique_ptr<weld::MetricSpinButton> m_xLetterSpacingMaximum; @@ -171,9 +170,6 @@ class SvxParaAlignTabPage : public SfxTabPage DECL_LINK(WordSpacingHdl_Impl, weld::MetricSpinButton&, void); DECL_LINK(WordSpacingMinimumHdl_Impl, weld::MetricSpinButton&, void); DECL_LINK(WordSpacingMaximumHdl_Impl, weld::MetricSpinButton&, void); - DECL_LINK(LetterSpacingHdl_Impl, weld::MetricSpinButton&, void); - DECL_LINK(LetterSpacingMinimumHdl_Impl, weld::MetricSpinButton&, void); - DECL_LINK(LetterSpacingMaximumHdl_Impl, weld::MetricSpinButton&, void); void UpdateExample_Impl(); diff --git a/cui/source/tabpages/paragrph.cxx b/cui/source/tabpages/paragrph.cxx index b55863973c2a..5feaf5420286 100644 --- a/cui/source/tabpages/paragrph.cxx +++ b/cui/source/tabpages/paragrph.cxx @@ -1288,7 +1288,6 @@ SvxParaAlignTabPage::SvxParaAlignTabPage(weld::Container* pPage, weld::DialogCon , m_xWordSpacingMinimum(m_xBuilder->weld_metric_spin_button(u"spin_WORD_SPACING_MIN"_ustr, FieldUnit::PERCENT)) , m_xWordSpacingMaximum(m_xBuilder->weld_metric_spin_button(u"spin_WORD_SPACING_MAX"_ustr, FieldUnit::PERCENT)) , m_xLabelLetterSpacing(m_xBuilder->weld_label(u"labelLetterSpacing"_ustr)) - , m_xLetterSpacing(m_xBuilder->weld_metric_spin_button(u"spin_LETTER_SPACING"_ustr, FieldUnit::PERCENT)) , m_xLetterSpacingMinimum(m_xBuilder->weld_metric_spin_button(u"spin_LETTER_SPACING_MIN"_ustr, FieldUnit::PERCENT)) , m_xLetterSpacingMaximum(m_xBuilder->weld_metric_spin_button(u"spin_LETTER_SPACING_MAX"_ustr, FieldUnit::PERCENT)) { @@ -1334,12 +1333,6 @@ SvxParaAlignTabPage::SvxParaAlignTabPage(weld::Container* pPage, weld::DialogCon m_xWordSpacing->connect_value_changed(LINK(this, SvxParaAlignTabPage, WordSpacingHdl_Impl)); m_xWordSpacingMinimum->connect_value_changed(LINK(this, SvxParaAlignTabPage, WordSpacingMinimumHdl_Impl)); m_xWordSpacingMaximum->connect_value_changed(LINK(this, SvxParaAlignTabPage, WordSpacingMaximumHdl_Impl)); - - // Minimum <= Desired <= Maximum letter spacing - // apply these modifying the other values, if needed - m_xLetterSpacing->connect_value_changed(LINK(this, SvxParaAlignTabPage, LetterSpacingHdl_Impl)); - m_xLetterSpacingMinimum->connect_value_changed(LINK(this, SvxParaAlignTabPage, LetterSpacingMinimumHdl_Impl)); - m_xLetterSpacingMaximum->connect_value_changed(LINK(this, SvxParaAlignTabPage, LetterSpacingMaximumHdl_Impl)); } SvxParaAlignTabPage::~SvxParaAlignTabPage() @@ -1389,7 +1382,6 @@ bool SvxParaAlignTabPage::FillItemSet( SfxItemSet* rOutSet ) m_xWordSpacing->get_value_changed_from_saved() || m_xWordSpacingMinimum->get_value_changed_from_saved() || m_xWordSpacingMaximum->get_value_changed_from_saved() || - m_xLetterSpacing->get_value_changed_from_saved() || m_xLetterSpacingMinimum->get_value_changed_from_saved() || m_xLetterSpacingMaximum->get_value_changed_from_saved(); } @@ -1505,6 +1497,7 @@ void SvxParaAlignTabPage::Reset( const SfxItemSet* rSet ) m_xLabelLetterSpacing->set_sensitive(true); // TODO add LetterSpacing (CharKern) and LetterSpacingMinimum m_xLetterSpacingMaximum->set_sensitive(true); + m_xLetterSpacingMinimum->set_sensitive(true); m_xLetterSpacingMinimum->set_value(rAdj.GetPropLetterSpacingMinimum(), FieldUnit::PERCENT); m_xLetterSpacingMaximum->set_value(rAdj.GetPropLetterSpacingMaximum(), FieldUnit::PERCENT); } @@ -1518,7 +1511,6 @@ void SvxParaAlignTabPage::Reset( const SfxItemSet* rSet ) m_xWordSpacingMinimum->set_sensitive(false); m_xWordSpacingMaximum->set_sensitive(false); m_xLabelLetterSpacing->set_sensitive(false); - m_xLetterSpacing->set_sensitive(false); m_xLetterSpacingMinimum->set_sensitive(false); m_xLetterSpacingMaximum->set_sensitive(false); } @@ -1537,7 +1529,6 @@ void SvxParaAlignTabPage::Reset( const SfxItemSet* rSet ) m_xWordSpacingMinimum->set_sensitive(false); m_xWordSpacingMaximum->set_sensitive(false); m_xLabelLetterSpacing->set_sensitive(false); - m_xLetterSpacing->set_sensitive(false); m_xLetterSpacingMinimum->set_sensitive(false); m_xLetterSpacingMaximum->set_sensitive(false); } @@ -1600,7 +1591,6 @@ void SvxParaAlignTabPage::Reset( const SfxItemSet* rSet ) m_xWordSpacing->save_value(); m_xWordSpacingMinimum->save_value(); m_xWordSpacingMaximum->save_value(); - m_xLetterSpacing->save_value(); m_xLetterSpacingMinimum->save_value(); m_xLetterSpacingMaximum->save_value(); @@ -1621,7 +1611,6 @@ void SvxParaAlignTabPage::ChangesApplied() m_xWordSpacing->save_value(); m_xWordSpacingMinimum->save_value(); m_xWordSpacingMaximum->save_value(); - m_xLetterSpacing->save_value(); m_xLetterSpacingMinimum->save_value(); m_xLetterSpacingMaximum->save_value(); } @@ -1639,9 +1628,8 @@ IMPL_LINK_NOARG(SvxParaAlignTabPage, AlignHdl_Impl, weld::Toggleable&, void) m_xWordSpacingMinimum->set_sensitive(bJustify); m_xWordSpacingMaximum->set_sensitive(bJustify); m_xLabelLetterSpacing->set_sensitive(bJustify); - // TODO implement LetterSpacing and LetterSpaceMinimum - m_xLetterSpacing->set_sensitive(false); - m_xLetterSpacingMinimum->set_sensitive(false); + // TODO visualize CharKerning with percentage + m_xLetterSpacingMinimum->set_sensitive(bJustify); m_xLetterSpacingMaximum->set_sensitive(bJustify); bool bLastLineIsBlock = m_xLastLineLB->get_active() == 2; @@ -1698,33 +1686,6 @@ IMPL_LINK_NOARG(SvxParaAlignTabPage, WordSpacingMaximumHdl_Impl, weld::MetricSpi m_xWordSpacing->set_value(nMaximum, FieldUnit::PERCENT); } -IMPL_LINK_NOARG(SvxParaAlignTabPage, LetterSpacingHdl_Impl, weld::MetricSpinButton&, void) -{ - sal_Int16 nDesired = m_xLetterSpacing->get_value(FieldUnit::PERCENT); - if (nDesired < m_xLetterSpacingMinimum->get_value(FieldUnit::PERCENT)) - m_xLetterSpacingMinimum->set_value(nDesired, FieldUnit::PERCENT); - if (nDesired > m_xLetterSpacingMaximum->get_value(FieldUnit::PERCENT)) - m_xLetterSpacingMaximum->set_value(nDesired, FieldUnit::PERCENT); -} - -IMPL_LINK_NOARG(SvxParaAlignTabPage, LetterSpacingMinimumHdl_Impl, weld::MetricSpinButton&, void) -{ - sal_Int16 nMinimum = m_xLetterSpacingMinimum->get_value(FieldUnit::PERCENT); - if (nMinimum > m_xLetterSpacing->get_value(FieldUnit::PERCENT)) - m_xLetterSpacing->set_value(nMinimum, FieldUnit::PERCENT); - if (nMinimum > m_xLetterSpacingMaximum->get_value(FieldUnit::PERCENT)) - m_xLetterSpacingMaximum->set_value(nMinimum, FieldUnit::PERCENT); -} - -IMPL_LINK_NOARG(SvxParaAlignTabPage, LetterSpacingMaximumHdl_Impl, weld::MetricSpinButton&, void) -{ - sal_Int16 nMaximum = m_xLetterSpacingMaximum->get_value(FieldUnit::PERCENT); - if (nMaximum < m_xLetterSpacingMinimum->get_value(FieldUnit::PERCENT)) - m_xLetterSpacingMinimum->set_value(nMaximum, FieldUnit::PERCENT); - if (nMaximum < m_xLetterSpacing->get_value(FieldUnit::PERCENT)) - m_xLetterSpacing->set_value(nMaximum, FieldUnit::PERCENT); -} - void SvxParaAlignTabPage::UpdateExample_Impl() { if (m_xLeft->get_active()) diff --git a/cui/uiconfig/ui/paragalignpage.ui b/cui/uiconfig/ui/paragalignpage.ui index 36bf63e7c379..451f6b6765cc 100644 --- a/cui/uiconfig/ui/paragalignpage.ui +++ b/cui/uiconfig/ui/paragalignpage.ui @@ -21,7 +21,7 @@ <property name="page-increment">10</property> </object> <object class="GtkAdjustment" id="adjustmentPercent4"> - <property name="upper">500</property> + <property name="upper">0</property> <property name="lower">-100</property> <property name="value">0</property> <property name="step-increment">1</property> @@ -29,14 +29,7 @@ </object> <object class="GtkAdjustment" id="adjustmentPercent5"> <property name="upper">500</property> - <property name="lower">-100</property> - <property name="value">0</property> - <property name="step-increment">1</property> - <property name="page-increment">10</property> - </object> - <object class="GtkAdjustment" id="adjustmentPercent6"> - <property name="upper">500</property> - <property name="lower">-100</property> + <property name="lower">0</property> <property name="value">0</property> <property name="step-increment">1</property> <property name="page-increment">10</property> @@ -556,7 +549,7 @@ <object class="GtkLabel" id="labelLetterSpacing"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="label" translatable="yes" context="paragalignpage|labelWordSpacing">_Letter spacing:</property> + <property name="label" translatable="yes" context="paragalignpage|labelLetterSpacing">_Letter spacing:</property> <property name="use-underline">True</property> <property name="xalign">0</property> </object> @@ -574,7 +567,7 @@ <property name="adjustment">adjustmentPercent4</property> <child internal-child="accessible"> <object class="AtkObject" id="spin_LETTER_SPACING_MIN-atkobject"> - <property name="AtkObject::accessible-description" translatable="yes" context="paralignpage|extended_tip|LETTER-JUSTIFICATION-MIN">Adjusts the minimum letter spacing. Enter a number between -100% (no letter spacing) and 250% (two and a half times the width of the normal letter spacing).</property> + <property name="AtkObject::accessible-description" translatable="yes" context="paralignpage|extended_tip|LETTER-JUSTIFICATION-MIN">Adjusts the minimum letter spacing. Enter a number between -100% and 0% (original letter spacing).</property> </object> </child> </object> @@ -583,34 +576,16 @@ <property name="top-attach">2</property> </packing> </child> - <child> - <object class="GtkSpinButton" id="spin_LETTER_SPACING"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="activates-default">True</property> - <property name="truncate-multiline">True</property> - <property name="adjustment">adjustmentPercent5</property> - <child internal-child="accessible"> - <object class="AtkObject" id="spin_LETTER_SPACING-atkobject"> - <property name="AtkObject::accessible-description" translatable="yes" context="paralignpage|extended_tip|JUSTIFICATION">Adjusts the desired letter spacing. Enter a number between -100% (no letter spacing) and 250% (two and a half times the width of the normal letter spacing).</property> - </object> - </child> - </object> - <packing> - <property name="left-attach">2</property> - <property name="top-attach">2</property> - </packing> - </child> <child> <object class="GtkSpinButton" id="spin_LETTER_SPACING_MAX"> <property name="visible">True</property> <property name="can-focus">True</property> <property name="activates-default">True</property> <property name="truncate-multiline">True</property> - <property name="adjustment">adjustmentPercent6</property> + <property name="adjustment">adjustmentPercent5</property> <child internal-child="accessible"> <object class="AtkObject" id="spin_LETTER_SPACING_MAX-atkobject"> - <property name="AtkObject::accessible-description" translatable="yes" context="paralignpage|extended_tip|JUSTIFICATION-MAX">Adjusts the maximum letter spacing. Enter a number between -100% (no letter spacing) and 250% (two and a half times the width of the normal letter spacing).</property> + <property name="AtkObject::accessible-description" translatable="yes" context="paralignpage|extended_tip|LETTER-JUSTIFICATION-MAX">Adjusts the maximum letter spacing. Enter a number between 0% (original letter spacing) and 500% (letter spacing is five times the width of the space character).</property> </object> </child> </object> diff --git a/offapi/com/sun/star/style/ParagraphProperties.idl b/offapi/com/sun/star/style/ParagraphProperties.idl index 387aa569ae89..45bc14c6508e 100644 --- a/offapi/com/sun/star/style/ParagraphProperties.idl +++ b/offapi/com/sun/star/style/ParagraphProperties.idl @@ -514,6 +514,9 @@ published service ParagraphProperties /** specifies the desired word spacing as percentage value relative to the width of the space character. + <p>It takes a percent value between [0, 1000], where the original + word spacing is denoted by 100.</p> + @see ParaWordSpacingMininum @see ParaWordSpacingMaximum @@ -525,6 +528,9 @@ published service ParagraphProperties /** specifies the minimum word spacing as percentage value relative to the width of the space character. + <p>It takes a percent value between [0, 1000], where the original + word spacing is denoted by 100.</p> + @see ParaWordSpacing @see ParaWordSpacingMaximum @@ -536,6 +542,9 @@ published service ParagraphProperties /** specifies the maximum word spacing as percentage value relative to the width of the space character. + <p>It takes a percent value between [0, 1000], where the original + word spacing is denoted by 100.</p> + @see ParaWordSpacing @see ParaWordSpacingMininum @@ -547,6 +556,9 @@ published service ParagraphProperties /** specifies the minimum letter spacing as percentage value relative to the width of the space character. + <p>It takes a percent value between [-100, 0], where the original + letter spacing is denoted by 0, and negative values mean its shrinking.</p> + @see CharKerning @see ParaLetterSpacingMaximum @@ -558,6 +570,9 @@ published service ParagraphProperties /** specifies the maximum letter spacing as percentage value relative to the width of the space character. + <p>It takes a percent value between [0, 500], where the original + letter spacing is denoted by 0, and negative values mean its shrinking.</p> + @see CharKerning @see ParaLetterSpacingMininum diff --git a/sw/qa/extras/layout/data/tdf167648_minimum.fodt b/sw/qa/extras/layout/data/tdf167648_minimum.fodt new file mode 100644 index 000000000000..7e81d76bd543 --- /dev/null +++ b/sw/qa/extras/layout/data/tdf167648_minimum.fodt @@ -0,0 +1,56 @@ +<?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: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">Lorem 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/layout3.cxx b/sw/qa/extras/layout/layout3.cxx index cfc53c6eca62..6b259aa69bc4 100644 --- a/sw/qa/extras/layout/layout3.cxx +++ b/sw/qa/extras/layout/layout3.cxx @@ -545,7 +545,7 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf163149) // Assert we are using the expected position for the last char // This was 4673, now 4163, according to the fixed space shrinking - CPPUNIT_ASSERT_LESS(sal_Int32(4200), sal_Int32(pDXArray[45])); + CPPUNIT_ASSERT_LESS(sal_Int32(4250), sal_Int32(pDXArray[45])); break; } } @@ -573,8 +573,7 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf167648) auto pTextArrayAction = static_cast<MetaTextArrayAction*>(pAction); auto pDXArray = pTextArrayAction->GetDXArray(); - // There should be 27 chars on the first line - // (tdf#164499 no space shrinking in lines with tabulation) + // There should be 27 characters on the first line CPPUNIT_ASSERT_EQUAL(size_t(27), pDXArray.size()); // Assert we are using the expected position for the @@ -593,6 +592,43 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf167648) } } +CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf167648_minimum) +{ + createSwDoc("tdf167648_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 39 characters on the first line + // This was 27 characters, but setting minimum letter spacing + // to -25% allows more words in the line + CPPUNIT_ASSERT_EQUAL(size_t(39), 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, testTdf164499) { createSwDoc("tdf164499.docx"); diff --git a/sw/source/core/text/guess.cxx b/sw/source/core/text/guess.cxx index 694a74361553..384c98baba8d 100644 --- a/sw/source/core/text/guess.cxx +++ b/sw/source/core/text/guess.cxx @@ -499,6 +499,18 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, { m_nCutPos = rInf.GetTextBreak( nLineWidth, nMaxLen, nMaxComp, rInf.GetCachedVclData().get() ); + // tdf#167648 minimum letter spacing allows more text in the line + // TODO don't be greedy, allow only an extra word or word part + if ( const sal_Int16 nLetterSpacingMinimum = aAdjustItem.GetPropLetterSpacingMinimum() ) + { + SwTwips nExtraSpace = sal_Int32(m_nCutPos - rInf.GetIdx()) * + nSpaceWidth / 10.0 * nLetterSpacingMinimum / 100.0; + nLineWidth -= nExtraSpace; + // sum minimum word spacing and letter spacing + rInf.SetExtraSpace( rInf.GetExtraSpace() + nExtraSpace ); + m_nCutPos = rInf.GetTextBreak( nLineWidth, nMaxLen, nMaxComp, rInf.GetCachedVclData().get() ); + } + #if OSL_DEBUG_LEVEL > 1 if ( TextFrameIndex(COMPLETE_STRING) != m_nCutPos ) { diff --git a/sw/source/core/text/inftxt.cxx b/sw/source/core/text/inftxt.cxx index 9c0f15d62451..8c297d4e300d 100644 --- a/sw/source/core/text/inftxt.cxx +++ b/sw/source/core/text/inftxt.cxx @@ -780,7 +780,7 @@ 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 ) + if ( rPor.GetLetterSpacing() != 0 ) aDrawInf.SetLetterSpacing( rPor.GetLetterSpacing() / sal_Int32(nLength) ); m_pFnt->DrawText_( aDrawInf ); diff --git a/sw/source/core/text/itradj.cxx b/sw/source/core/text/itradj.cxx index 3a1a956f4059..c6230b18f5db 100644 --- a/sw/source/core/text/itradj.cxx +++ b/sw/source/core/text/itradj.cxx @@ -370,11 +370,11 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent, : 0; SwLinePortion *pPortion = pCurrent->GetFirstPortion(); - tools::Long nSpaceKern = pPortion->GetLetterSpacing() > 0 && pPortion->GetSpaceCount() + tools::Long nSpaceKern = pPortion->GetSpaceCount() ? tools::Long(pPortion->GetLetterSpacing()) / sal_Int32(pPortion->GetSpaceCount()) * 100 : 0; // set expansion in 1/100 twips/space - pCurrent->SetLLSpaceAdd( nSpaceSub ? nSpaceSub : (nSpaceAdd > nSpaceKern ? nSpaceAdd - nSpaceKern : 0), nSpaceIdx ); + pCurrent->SetLLSpaceAdd( nSpaceSub ? nSpaceSub + nSpaceKern : (nSpaceAdd > nSpaceKern ? nSpaceAdd - nSpaceKern : 0), nSpaceIdx ); pPos->Width( static_cast<SwGluePortion*>(pPos)->GetFixWidth() ); } else if (IsOneBlock() && nCharCnt > TextFrameIndex(1)) diff --git a/sw/source/core/text/portxt.cxx b/sw/source/core/text/portxt.cxx index a95ec986ef7b..a8c4fac62a03 100644 --- a/sw/source/core/text/portxt.cxx +++ b/sw/source/core/text/portxt.cxx @@ -337,6 +337,33 @@ sal_uInt16 SwTextPortion::GetMaxComp(const SwTextFormatInfo& rInf) const : 0; } +void SwTextPortion::SetSpacing( SwTextFormatInfo &rInf, const TextFrameIndex nBreakPos, + const sal_Int32 nSpaces, const sal_Int16 nWidthOf10Spaces ) +{ + SvxAdjustItem aAdjustItem = + rInf.GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust(); + // width of a single expanded space without letter spacing and glyph scaling + float fSpaceNormal = + (rInf.GetLineWidth() - (rInf.GetBreakWidth() - 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(nBreakPos) - sal_Int32(rInf.GetIdx()); + // letter spacing/character to be added or substracted to get the desired word spacing + float fLetterSpacingForDesiredWordSpacing = + nLetterCount > 0 ? (((fSpaceNormal - nWidthOf10Spaces/10.0) * nSpaces) / nLetterCount) : 0; + // letter spacing/character allowed by maximum letter spacing + float fMaximumLetterSpacing = + nWidthOf10Spaces / 10.0 * aAdjustItem.GetPropLetterSpacingMaximum() / 100.0; + // final letter spacing/character based on the desired word spacing and maximum letter spacing + // TODO fix resolution applying 1/100 twips instead of 1 twip + SwTwips nLetterSpacing = std::min( fLetterSpacingForDesiredWordSpacing, fMaximumLetterSpacing ); + // full width of the extra (rounded) letter spacing within the line + SetLetterSpacing( SwTwips(nLetterSpacing * nLetterCount) ); + SetSpaceCount( TextFrameIndex(nSpaces) ); +} + bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) { // 5744: If only the hyphen does not fit anymore, we still need to wrap @@ -379,6 +406,7 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) bool bNoWordSpacing = aAdjustItem.GetPropWordSpacing() == 100 && aAdjustItem.GetPropWordSpacingMinimum() == 100 && aAdjustItem.GetPropWordSpacingMaximum() == 100 && + aAdjustItem.GetPropLetterSpacingMinimum() == 0 && aAdjustItem.GetPropLetterSpacingMaximum() == 0; // support old ODT documents, where only JustifyLinesWithShrinking was set bool bOldInterop = bInteropSmartJustify && bNoWordSpacing; @@ -430,20 +458,9 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) // calculate available word spacing for letter spacing, and for the word spacing indicator // for non-hyphenated single portion lines - // TODO: enable letter spacing for multiportion lines + // TODO: enable letter spacing for multiportion, also for hyphenated lines if ( !bOrigHyphenated && rInf.GetLineStart() == rInf.GetIdx() ) - { - // TODO calculate correct value for letter spacing of hyphenated lines - float fExpansionOverMax = fSpaceNormal - nSpaceWidth/10.0 * aAdjustItem.GetPropWordSpacingMaximum()/100.0; - ExtraSpaceSize( rInf.GetBreakWidth() > rInf.GetLineWidth()/2 && fExpansionOverMax > 0 ? fExpansionOverMax : 0); - int nLetterCount = sal_Int32(pGuess->BreakPos()) - sal_Int32(rInf.GetIdx()); - float fAvailableLetterSpacing = ((fSpaceNormal - nSpaceWidth/10.0) * nRealSpaces) / nLetterCount; - float fCustomLetterSpacing = nSpaceWidth/10.0 * aAdjustItem.GetPropLetterSpacingMaximum() / 100.0; - // TODO fix resolution applying 1/100 twips instead of 1 twip - SwTwips nLetterSpacing = std::min(fAvailableLetterSpacing, fCustomLetterSpacing); - SetLetterSpacing(SwTwips(nLetterSpacing * nLetterCount)); - SetSpaceCount(TextFrameIndex(nRealSpaces)); - } + SetSpacing(rInf, pGuess->BreakPos(), nRealSpaces, nSpaceWidth); // calculate line breaking with desired word spacing, also // if the desired word spacing is 100%, but there is a greater @@ -454,23 +471,12 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) pGuess.emplace(); bFull = !pGuess->Guess( *this, rInf, Height(), nSpacesInLine, aAdjustItem.GetPropWordSpacing(), nSpaceWidth ); sal_Int32 nSpacesInLine2 = rInf.GetLineSpaceCount( pGuess->BreakPos() ); - - if ( rInf.GetBreakWidth() <= rInf.GetLineWidth() ) + if ( aAdjustItem.GetPropLetterSpacingMinimum() < 0 || rInf.GetBreakWidth() <= rInf.GetLineWidth() ) { fSpaceNormal = (rInf.GetLineWidth() - (rInf.GetBreakWidth() - nSpacesInLine2 * nSpaceWidth/10.0))/nSpacesInLine2; - // TODO: enable letter spacing for multiportion lines + // TODO: enable letter spacing for multiportion, also for hyphenated lines if ( !bOrigHyphenated && rInf.GetLineStart() == rInf.GetIdx() ) - { - float fExpansionOverMax = fSpaceNormal - nSpaceWidth/10.0 * aAdjustItem.GetPropWordSpacingMaximum()/100.0; - ExtraSpaceSize( rInf.GetBreakWidth() > rInf.GetLineWidth()/2 && fExpansionOverMax > 0 ? fExpansionOverMax : 0); - int nLetterCount = sal_Int32(pGuess->BreakPos()) - sal_Int32(rInf.GetIdx()); - float fAvailableLetterSpacing = ((fSpaceNormal - nSpaceWidth/10.0) * nRealSpaces) / nLetterCount; - float fCustomLetterSpacing = nSpaceWidth/10.0 * aAdjustItem.GetPropLetterSpacingMaximum() / 100.0; - // TODO fix resolution applying 1/100 twips instead of 1 twip - SwTwips nLetterSpacing = std::min(fAvailableLetterSpacing, fCustomLetterSpacing); - SetLetterSpacing(SwTwips(nLetterSpacing * nLetterCount)); - SetSpaceCount(TextFrameIndex(nRealSpaces)); - } + SetSpacing(rInf, pGuess->BreakPos(), nSpacesInLine2, nSpaceWidth); } } @@ -539,16 +545,15 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) if ( z1 >= z0 || bIsPortion ) { pGuess = std::move(pGuess2); - ExtraSpaceSize(0); - SetLetterSpacing(0); + SetSpacing(rInf, pGuess->BreakPos(), nSpacesInLineShrink, nSpaceWidth); bFull = bFull2; } } else if ( bOldInterop ) { pGuess = std::move(pGuess2); + SetSpaceCount( TextFrameIndex(nSpacesInLineShrink)); ExtraSpaceSize(0); - SetLetterSpacing(0); bFull = bFull2; } } @@ -561,7 +566,6 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) { ExtraShrunkWidth( pGuess->BreakWidth() ); ExtraSpaceSize( 0 ); - SetLetterSpacing(0); } } } diff --git a/sw/source/core/text/portxt.hxx b/sw/source/core/text/portxt.hxx index 132efe86dd20..aff2680731f4 100644 --- a/sw/source/core/text/portxt.hxx +++ b/sw/source/core/text/portxt.hxx @@ -27,6 +27,8 @@ class SwTextPortion : public SwLinePortion { void BreakCut( SwTextFormatInfo &rInf, const SwTextGuess &rGuess ); void BreakUnderflow( SwTextFormatInfo &rInf ); + void SetSpacing( SwTextFormatInfo &rInf, const TextFrameIndex nBreakPos, + const sal_Int32 nSpaces, const sal_Int16 nWidthOf10Spaces ); bool Format_( SwTextFormatInfo &rInf ); public:
