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 f83a04c510
"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 <nemeth@numbertext.org>
This commit is contained in:
László Németh
2025-09-03 15:21:58 +02:00
parent 6afd17ac39
commit 3c53797210
11 changed files with 171 additions and 114 deletions
-4
View File
@@ -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();
+3 -42
View File
@@ -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())
+6 -31
View File
@@ -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>
@@ -584,33 +577,15 @@
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spin_LETTER_SPACING">
<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">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>
<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>
@@ -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
@@ -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:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.4" office:mimetype="application/vnd.oasis.opendocument.text">
<office:font-face-decls>
<style:font-face style:name="Liberation Serif" svg:font-family="&apos;Liberation Serif&apos;" style:font-family-generic="modern" style:font-pitch="fixed"/>
</office:font-face-decls>
<office:styles>
<style:default-style style:family="graphic">
<style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="false"/>
<style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
<style:tab-stops/>
</style:paragraph-properties>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lohit Devanagari1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
</style:default-style>
<style:default-style style:family="paragraph">
<style:paragraph-properties fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" loext:hyphenation-keep-type="column" loext:hyphenation-keep-line="false" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="page"/>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lohit Devanagari1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
</style:default-style>
<style:default-style style:family="table">
<style:table-properties table:border-model="collapsing"/>
</style:default-style>
<style:default-style style:family="table-row">
<style:table-row-properties fo:keep-together="auto"/>
</style:default-style>
<style:style style:name="Standard" style:family="paragraph" style:class="text"/>
<style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text" style:master-page-name="">
<style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" fo:text-align="start" style:justify-single-word="false" fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" loext:hyphenation-keep-type="column" loext:hyphenation-keep-line="false" fo:text-indent="0.423cm" style:auto-text-indent="false" style:page-number="auto"/>
<style:text-properties fo:hyphenate="true" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
</style:style>
</office:styles>
<office:automatic-styles>
<style:style style:name="P1" style:family="paragraph" style:parent-style-name="Text_20_body" style:master-page-name="">
<style:paragraph-properties fo:text-align="justify" style:justify-single-word="false" loext:word-spacing-maximum="101%" loext:letter-spacing-minimum="-25%" loext:letter-spacing-maximum="25%" fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" loext:hyphenation-keep-type="column" loext:hyphenation-keep-line="true" style:page-number="auto" style:writing-mode="lr-tb"/>
<style:text-properties style:font-name="Linux Libertine G:litt=0" fo:letter-spacing="0.018cm" officeooo:paragraph-rsid="001f2a0b" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
</style:style>
<style: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>
+39 -3
View File
@@ -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");
+12
View File
@@ -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 )
{
+1 -1
View File
@@ -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 );
+2 -2
View File
@@ -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))
+35 -31
View File
@@ -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);
}
}
}
+2
View File
@@ -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: