From 3e02ffb76c484a05cdc140d8a10bc3d993fe8320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20N=C3=A9meth?= Date: Mon, 17 Feb 2025 19:20:04 +0100 Subject: [PATCH] =?UTF-8?q?tdf#i165354=20sw=20offapi=20DOCX:=20implement?= =?UTF-8?q?=20HyphenationKeepLine=20=E2=80=93=20part=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ODF 1.0 feature hyphenation-keep, i.e. disabling hyphenation across page etc. was implemented by shifting the full hyphenated line (fixing also the DOCX layout interoperability, when value of the useWord2013TrackBottomHyphenation MSO DOCX compatibility option is true). To shift only the last word, add UNO paragraph property com::sun::star::text::ParagraphHyphenationKeepLine and the associated layout in Writer core, i.e. disabling the hyphenation in the last line, instead of line shifting, if the value of ParagraphHyphenationKeepLine is true. Fix missing DOCX layout interoperability by mapping the unhandled useWord2013TrackBottomHyphenation = false case to the new layout. Note: shifting lines may be better for languages with long words, but only with 1) typesetting with line-level (greedy) line breaking instead of the desirable paragraph-level, 2) and with missing optimized hyphenation at boundaries of compound word constituents, and 3) forgotting the following: Shifting only words or word constituents are better for 1) equal line count on pages and spreads (very basic typographical requirement), 2) disabling hyphenation really (shifting doesn't guarantee this, because shifting is limited in a single line, but the previous line can be hyphenated, too) and 3) more compact typesetting. Both hyphenation-keep algorithms are important for interoperability, but the new HyphenationKeepLine can create state-of-the-art automatic typesetting with the planned improvements. Follow-up to commit 9574a62add8e4901405e12117e75c86c2d2c2f21 "tdf#132599 cui offapi sw xmloff: implement hyphenate-keep". and commit c8ee0e8f581b8a6e41b1a6b8aa4d40b442c1d463 "tdf160518 DOCX: import hyphenation-keep to fix layout". Change-Id: Iafa352ec91bbd5d1d227b8480337fa2b9acb5993 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/181954 Reviewed-by: László Németh Tested-by: Jenkins --- editeng/source/items/paraitem.cxx | 25 +++++++++++++++++- include/editeng/editrids.hrc | 2 ++ include/editeng/hyphenzoneitem.hxx | 4 +++ include/editeng/memberids.h | 1 + include/unotools/linguprops.hxx | 2 ++ .../sun/star/style/ParagraphProperties.idl | 12 +++++++++ svx/sdi/svxitems.sdi | 1 + sw/inc/inspectorproperties.hrc | 1 + sw/inc/unoprnms.hxx | 1 + sw/qa/extras/ooxmlexport/data/tdf165354.docx | Bin 0 -> 13980 bytes sw/qa/extras/ooxmlexport/ooxmlexport14.cxx | 19 ++++++++----- sw/qa/uitest/styleInspector/styleInspector.py | 20 +++++++------- sw/qa/uitest/styleInspector/tdf137513.py | 2 +- sw/source/core/inc/txtfrm.hxx | 3 +++ sw/source/core/text/guess.cxx | 6 ++++- sw/source/core/text/inftxt.cxx | 17 ++++++++---- sw/source/core/text/txtfrm.cxx | 1 + sw/source/core/text/widorp.cxx | 25 ++++++++++++++++-- sw/source/core/unocore/unomapproperties.hxx | 2 ++ .../sidebar/WriterInspectorTextPanel.cxx | 1 + .../writerfilter/dmapper/PropertyIds.cxx | 1 + .../writerfilter/dmapper/PropertyIds.hxx | 1 + .../writerfilter/dmapper/SettingsTable.cxx | 16 ++++++++--- .../writerfilter/dmapper/SettingsTable.hxx | 1 + 24 files changed, 135 insertions(+), 29 deletions(-) create mode 100644 sw/qa/extras/ooxmlexport/data/tdf165354.docx diff --git a/editeng/source/items/paraitem.cxx b/editeng/source/items/paraitem.cxx index d06255ea3e89..91c30e5b55e1 100644 --- a/editeng/source/items/paraitem.cxx +++ b/editeng/source/items/paraitem.cxx @@ -594,6 +594,7 @@ SvxHyphenZoneItem::SvxHyphenZoneItem( const bool bHyph, const sal_uInt16 nId ) : nMinWordLength(0), nTextHyphenZone(0), nKeepType(css::text::ParagraphHyphenationKeepType::COLUMN), + bKeepLine(false), nCompoundMinLead(0) { } @@ -634,6 +635,9 @@ bool SvxHyphenZoneItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) con case MID_HYPHEN_KEEP_TYPE: rVal <<= static_cast(nKeepType); break; + case MID_HYPHEN_KEEP_LINE: + rVal <<= bKeepLine; + break; case MID_HYPHEN_COMPOUND_MIN_LEAD: rVal <<= static_cast(nCompoundMinLead); break; @@ -647,7 +651,8 @@ bool SvxHyphenZoneItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) sal_Int32 nNewVal = 0; // sal_Int32 needs for MID_HYPHEN_KEEP_TYPE if( nMemberId != MID_IS_HYPHEN && nMemberId != MID_HYPHEN_NO_CAPS && - nMemberId != MID_HYPHEN_NO_LAST_WORD && nMemberId != MID_HYPHEN_KEEP ) + nMemberId != MID_HYPHEN_NO_LAST_WORD && nMemberId != MID_HYPHEN_KEEP && + nMemberId != MID_HYPHEN_KEEP_LINE ) { if(!(rVal >>= nNewVal)) return false; @@ -685,6 +690,9 @@ bool SvxHyphenZoneItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) case MID_HYPHEN_KEEP_TYPE: nKeepType = static_cast(nNewVal); break; + case MID_HYPHEN_KEEP_LINE: + bKeepLine = Any2Bool(rVal); + break; case MID_HYPHEN_COMPOUND_MIN_LEAD: nCompoundMinLead = static_cast(nNewVal); break; @@ -708,6 +716,7 @@ bool SvxHyphenZoneItem::operator==( const SfxPoolItem& rAttr ) const && rItem.nMaxHyphens == nMaxHyphens && rItem.nMinWordLength == nMinWordLength && rItem.nTextHyphenZone == nTextHyphenZone + && rItem.bKeepLine == bKeepLine && rItem.nKeepType == nKeepType ); } @@ -748,10 +757,18 @@ bool SvxHyphenZoneItem::GetPresentation rText += cpDelimTmp + EditResId(RID_SVXITEMS_HYPHEN_LAST_WORD_TRUE); if ( bKeep ) + { rText += EditResId(RID_SVXITEMS_HYPHEN_KEEP_TRUE) + cpDelimTmp + OUString::number( nKeepType ); + if ( bKeepLine ) + rText += EditResId(RID_SVXITEMS_HYPHEN_KEEP_LINE_TRUE); + else + rText += EditResId(RID_SVXITEMS_HYPHEN_KEEP_LINE_FALSE); + } else rText += EditResId(RID_SVXITEMS_HYPHEN_KEEP_FALSE); + + return true; } case SfxItemPresentation::Complete: @@ -788,6 +805,7 @@ bool SvxHyphenZoneItem::GetPresentation if ( bKeep ) { rText += EditResId(RID_SVXITEMS_HYPHEN_KEEP_TRUE) + cpDelimTmp; + switch ( nKeepType ) { case 0: @@ -806,6 +824,11 @@ bool SvxHyphenZoneItem::GetPresentation rText += EditResId(RID_SVXITEMS_HYPHEN_KEEP_ALWAYS); break; } + + if ( bKeepLine ) + rText += EditResId(RID_SVXITEMS_HYPHEN_KEEP_LINE_TRUE); + else + rText += EditResId(RID_SVXITEMS_HYPHEN_KEEP_LINE_FALSE); } else rText += EditResId(RID_SVXITEMS_HYPHEN_KEEP_FALSE); diff --git a/include/editeng/editrids.hrc b/include/editeng/editrids.hrc index 270b02707758..4cdc87bcf856 100644 --- a/include/editeng/editrids.hrc +++ b/include/editeng/editrids.hrc @@ -201,6 +201,8 @@ #define RID_SVXITEMS_HYPHEN_FALSE NC_("RID_SVXITEMS_HYPHEN_FALSE", "No hyphenation") #define RID_SVXITEMS_HYPHEN_KEEP_TRUE NC_("RID_SVXITEMS_HYPHEN_KEEP_TRUE", "No hyphenation across") #define RID_SVXITEMS_HYPHEN_KEEP_FALSE NC_("RID_SVXITEMS_HYPHEN_KEEP_FALSE", "Hyphenation across page") +#define RID_SVXITEMS_HYPHEN_KEEP_LINE_TRUE NC_("RID_SVXITEMS_HYPHEN_KEEP_LINE_TRUE", "by shifting the last hyphenated word") +#define RID_SVXITEMS_HYPHEN_KEEP_LINE_FALSE NC_("RID_SVXITEMS_HYPHEN_KEEP_LINE_FALSE", "by shifting the last hyphenated line") #define RID_SVXITEMS_SIZE_WIDTH NC_("RID_SVXITEMS_SIZE_WIDTH", "Width: ") #define RID_SVXITEMS_SIZE_HEIGHT NC_("RID_SVXITEMS_SIZE_HEIGHT", "Height: ") #define RID_SVXITEMS_LRSPACE_LEFT NC_("RID_SVXITEMS_LRSPACE_LEFT", "Indent left ") diff --git a/include/editeng/hyphenzoneitem.hxx b/include/editeng/hyphenzoneitem.hxx index 5e68ca9281ae..05d3c9145ab8 100644 --- a/include/editeng/hyphenzoneitem.hxx +++ b/include/editeng/hyphenzoneitem.hxx @@ -42,6 +42,7 @@ class EDITENG_DLLPUBLIC SvxHyphenZoneItem final : public SfxPoolItem sal_uInt8 nMinWordLength; // hyphenate only words with at least nMinWordLength characters sal_uInt16 nTextHyphenZone; // don't force hyphenation at line end, allow this extra white space sal_uInt8 nKeepType; // avoid hyphenation across page etc., see ParagraphHyphenationKeep + bool bKeepLine : 1; // if bKeep, shift the hyphenated word (true), or the full line sal_uInt8 nCompoundMinLead; // min. characters between compound word boundary and hyphenation public: @@ -94,6 +95,9 @@ public: sal_uInt8 &GetKeepType() { return nKeepType; } sal_uInt8 GetKeepType() const { return nKeepType; } + + void SetKeepLine( const bool bNew ) { bKeepLine = bNew; } + bool IsKeepLine() const { return bKeepLine; } }; #endif diff --git a/include/editeng/memberids.h b/include/editeng/memberids.h index 82a1fe65e9bf..f07d051c4825 100644 --- a/include/editeng/memberids.h +++ b/include/editeng/memberids.h @@ -54,6 +54,7 @@ #define MID_HYPHEN_KEEP 8 #define MID_HYPHEN_KEEP_TYPE 9 #define MID_HYPHEN_COMPOUND_MIN_LEAD 10 +#define MID_HYPHEN_KEEP_LINE 11 // SvxBoxInfoItem #define MID_HORIZONTAL 1 diff --git a/include/unotools/linguprops.hxx b/include/unotools/linguprops.hxx index 92767f657620..0451a0d94a27 100644 --- a/include/unotools/linguprops.hxx +++ b/include/unotools/linguprops.hxx @@ -44,6 +44,7 @@ inline constexpr OUString UPN_HYPH_NO_LAST_WORD = u"HyphNoLastWord inline constexpr OUString UPN_HYPH_ZONE = u"HyphZone"_ustr; inline constexpr OUString UPN_HYPH_KEEP = u"HyphKeep"_ustr; inline constexpr OUString UPN_HYPH_KEEP_TYPE = u"HyphKeepType"_ustr; +inline constexpr OUString UPN_HYPH_KEEP_LINE = u"HyphKeepLine"_ustr; // UNO property names for Lingu // (those not covered by the SpellChecker and Hyphenator @@ -113,6 +114,7 @@ inline constexpr OUString UPN_IS_GRAMMAR_INTERACTIVE = u"IsInteractiveG #define UPH_HYPH_KEEP 34 #define UPH_HYPH_KEEP_TYPE 35 #define UPH_HYPH_COMPOUND_MIN_LEADING 36 +#define UPH_HYPH_KEEP_LINE 37 #ifdef __GNUC__ #pragma GCC diagnostic pop diff --git a/offapi/com/sun/star/style/ParagraphProperties.idl b/offapi/com/sun/star/style/ParagraphProperties.idl index 9cb2ff598562..5b1097feae04 100644 --- a/offapi/com/sun/star/style/ParagraphProperties.idl +++ b/offapi/com/sun/star/style/ParagraphProperties.idl @@ -454,6 +454,18 @@ published service ParagraphProperties @since LibreOffice 24.8 */ [optional, property] short ParaHyphenationCompoundMinLeadingChars; + + /** This value is `TRUE` if ParaHyphenationKeep = TRUE solved by + shifting the hyphenated word, and FALSE, if it is solved by shifting + (only a single) full hyphenated line. + + @see ParaHyphenationKeep + + @see ParaHyphenationKeepType + + @since LibreOffice 25.8 + */ + [optional, property] boolean ParaHyphenationKeepLine; }; diff --git a/svx/sdi/svxitems.sdi b/svx/sdi/svxitems.sdi index 0f3da887b64c..0415b6955dd9 100644 --- a/svx/sdi/svxitems.sdi +++ b/svx/sdi/svxitems.sdi @@ -277,6 +277,7 @@ struct SvxHyphenZone INT16 HyphenZone MID_HYPHEN_ZONE; BOOL HyphenKeep MID_HYPHEN_KEEP; INT16 HyphenKeepType MID_HYPHEN_KEEP_TYPE; + BOOL HyphenKeepLine MID_HYPHEN_KEEP_LINE; INT16 MinLead MID_HYPHEN_COMPOUND_MIN_LEAD; }; item SvxHyphenZone SvxHyphenZoneItem; diff --git a/sw/inc/inspectorproperties.hrc b/sw/inc/inspectorproperties.hrc index 88f535847970..3404018a8c19 100644 --- a/sw/inc/inspectorproperties.hrc +++ b/sw/inc/inspectorproperties.hrc @@ -212,6 +212,7 @@ #define RID_PARA_HYPHENATION_ZONE NC_("RID_ATTRIBUTE_NAMES_MAP", "Para Hyphenation Zone") #define RID_PARA_HYPHENATION_KEEP NC_("RID_ATTRIBUTE_NAMES_MAP", "Para Hyphenation Keep") #define RID_PARA_HYPHENATION_KEEP_TYPE NC_("RID_ATTRIBUTE_NAMES_MAP", "Para Hyphenation Keep Type") +#define RID_PARA_HYPHENATION_KEEP_LINE NC_("RID_ATTRIBUTE_NAMES_MAP", "Para Hyphenation Keep Line") #define RID_PARA_INTEROP_GRAB_BAG NC_("RID_ATTRIBUTE_NAMES_MAP", "Para Interop Grab Bag") #define RID_PARA_IS_AUTO_FIRST_LINE_INDENT NC_("RID_ATTRIBUTE_NAMES_MAP", "Para is Auto First Line Indent") #define RID_PARA_IS_CHARACTER_DISTANCE NC_("RID_ATTRIBUTE_NAMES_MAP", "Para is Character Distance") diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx index b262018c41f6..8408c0f13fd7 100644 --- a/sw/inc/unoprnms.hxx +++ b/sw/inc/unoprnms.hxx @@ -83,6 +83,7 @@ inline constexpr OUString UNO_NAME_PARA_HYPHENATION_NO_LAST_WORD = u"ParaHyphenationNoLastWord"_ustr; inline constexpr OUString UNO_NAME_PARA_HYPHENATION_KEEP = u"ParaHyphenationKeep"_ustr; inline constexpr OUString UNO_NAME_PARA_HYPHENATION_KEEP_TYPE = u"ParaHyphenationKeepType"_ustr; +inline constexpr OUString UNO_NAME_PARA_HYPHENATION_KEEP_LINE = u"ParaHyphenationKeepLine"_ustr; inline constexpr OUString UNO_NAME_LEFT_MARGIN = u"LeftMargin"_ustr; inline constexpr OUString UNO_NAME_RIGHT_MARGIN = u"RightMargin"_ustr; inline constexpr OUString UNO_NAME_GUTTER_MARGIN = u"GutterMargin"_ustr; diff --git a/sw/qa/extras/ooxmlexport/data/tdf165354.docx b/sw/qa/extras/ooxmlexport/data/tdf165354.docx new file mode 100644 index 0000000000000000000000000000000000000000..3a90488e7f3bb1e06973a3dfbd2278ee68fae109 GIT binary patch literal 13980 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 xHyphenator = LinguMgr::GetHyphenator(); + if (!xHyphenator->hasLocale(lang::Locale(u"en"_ustr, u"US"_ustr, OUString()))) + return; + + loadAndReload("tdf165354.docx"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + // This was "except that it has an at" (hyphenation at the end of the page) + assertXPath(pXmlDoc, "//page[1]/body/txt[2]/SwParaPortion/SwLineLayout[9]", "portion", u"except that it has an "); + // This started with "mosphere" (hyphenation at the end of the previous page) + assertXPath(pXmlDoc, "//page[2]/body/txt[1]/SwParaPortion/SwLineLayout[1]", "portion", u"atmosphere. The Earth "); + // The same word is still hyphenated in the same paragraph, but not at the bottom of the page + assertXPath(pXmlDoc, "//page[2]/body/txt[1]/SwParaPortion/SwLineLayout[9]", "portion", u"except that it has an at"); } CPPUNIT_TEST_FIXTURE(Test, testHyphenationAuto) diff --git a/sw/qa/uitest/styleInspector/styleInspector.py b/sw/qa/uitest/styleInspector/styleInspector.py index e4fe8e0259c7..2dc13d93cc48 100644 --- a/sw/qa/uitest/styleInspector/styleInspector.py +++ b/sw/qa/uitest/styleInspector/styleInspector.py @@ -26,7 +26,7 @@ class styleNavigator(UITestCase): # The cursor is on text without formatting and default style self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(143, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(144, len(xListBox.getChild('0').getChild('0').getChildren())) self.assertEqual(0, len(xListBox.getChild('1').getChildren())) self.assertEqual(0, len(xListBox.getChild('2').getChildren())) self.assertEqual(0, len(xListBox.getChild('3').getChildren())) @@ -36,7 +36,7 @@ class styleNavigator(UITestCase): # The cursor is on text with direct formatting self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(143, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(144, len(xListBox.getChild('0').getChild('0').getChildren())) self.assertEqual(0, len(xListBox.getChild('1').getChildren())) self.assertEqual(0, len(xListBox.getChild('2').getChildren())) @@ -54,7 +54,7 @@ class styleNavigator(UITestCase): # The cursor is on text with paragraph direct formatting self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(143, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(144, len(xListBox.getChild('0').getChild('0').getChildren())) xParDirFormatting = xListBox.getChild('1') self.assertEqual(7, len(xParDirFormatting.getChildren())) @@ -75,7 +75,7 @@ class styleNavigator(UITestCase): xParStyle = xListBox.getChild('0') self.assertEqual(3, len(xParStyle.getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xParStyle.getChild('0'))['Text']) - self.assertEqual(143, len(xParStyle.getChild('0').getChildren())) + self.assertEqual(144, len(xParStyle.getChild('0').getChildren())) self.assertEqual("Heading\t", get_state_as_dict(xParStyle.getChild('1'))['Text']) self.assertEqual(28, len(xParStyle.getChild('1').getChildren())) @@ -109,7 +109,7 @@ class styleNavigator(UITestCase): xParStyle = xListBox.getChild('0') self.assertEqual(3, len(xParStyle.getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xParStyle.getChild('0'))['Text']) - self.assertEqual(143, len(xParStyle.getChild('0').getChildren())) + self.assertEqual(144, len(xParStyle.getChild('0').getChildren())) self.assertEqual("Body Text\t", get_state_as_dict(xParStyle.getChild('1'))['Text']) self.assertEqual(6, len(xParStyle.getChild('1').getChildren())) @@ -144,7 +144,7 @@ class styleNavigator(UITestCase): # The cursor is on text without metadata self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(143, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(144, len(xListBox.getChild('0').getChild('0').getChildren())) self.assertEqual(0, len(xListBox.getChild('1').getChildren())) self.assertEqual(0, len(xListBox.getChild('2').getChildren())) self.assertEqual(0, len(xListBox.getChild('3').getChildren())) @@ -154,7 +154,7 @@ class styleNavigator(UITestCase): # The cursor is on text with paragraph metadata showed under direct paragraph formatting self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(143, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(144, len(xListBox.getChild('0').getChild('0').getChildren())) xParDirFormatting = xListBox.getChild('1') self.assertEqual(1, len(xParDirFormatting.getChildren())) @@ -207,7 +207,7 @@ class styleNavigator(UITestCase): # The cursor is on text without metadata self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(143, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(144, len(xListBox.getChild('0').getChild('0').getChildren())) self.assertEqual(0, len(xListBox.getChild('1').getChildren())) self.assertEqual(0, len(xListBox.getChild('2').getChildren())) self.assertEqual(0, len(xListBox.getChild('3').getChildren())) @@ -217,7 +217,7 @@ class styleNavigator(UITestCase): # The cursor is on text with paragraph metadata showed under direct paragraph formatting self.assertEqual(1, len(xListBox.getChild('1').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('1').getChild('0'))['Text']) - self.assertEqual(143, len(xListBox.getChild('1').getChild('0').getChildren())) + self.assertEqual(144, len(xListBox.getChild('1').getChild('0').getChildren())) # Outer bookmark xBookmarkFormatting = xListBox.getChild('0') @@ -264,7 +264,7 @@ class styleNavigator(UITestCase): # The cursor is on text without metadata self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(143, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(144, len(xListBox.getChild('0').getChild('0').getChildren())) self.assertEqual(0, len(xListBox.getChild('1').getChildren())) self.assertEqual(0, len(xListBox.getChild('2').getChildren())) diff --git a/sw/qa/uitest/styleInspector/tdf137513.py b/sw/qa/uitest/styleInspector/tdf137513.py index 936cb9400740..70f74c76a5e9 100644 --- a/sw/qa/uitest/styleInspector/tdf137513.py +++ b/sw/qa/uitest/styleInspector/tdf137513.py @@ -35,7 +35,7 @@ class tdf137513(UITestCase): self.assertEqual(2, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) self.assertEqual("Table Contents\t", get_state_as_dict(xListBox.getChild('0').getChild('1'))['Text']) - self.assertEqual(143, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(144, len(xListBox.getChild('0').getChild('0').getChildren())) xTableContent = xListBox.getChild('0').getChild('1') self.assertEqual(5, len(xTableContent.getChildren())) diff --git a/sw/source/core/inc/txtfrm.hxx b/sw/source/core/inc/txtfrm.hxx index f0166fd2d7aa..f2eb15cf187c 100644 --- a/sw/source/core/inc/txtfrm.hxx +++ b/sw/source/core/inc/txtfrm.hxx @@ -208,6 +208,7 @@ class SW_DLLPUBLIC SwTextFrame final : public SwContentFrame std::unique_ptr m_pMergedPara; TextFrameIndex mnOffset; // Is the offset in the Content (character count) + TextFrameIndex mnNoHyphOffset; // Is the offset of the last line to disable its hyphenation sal_uInt16 mnCacheIndex; // Index into the cache, USHRT_MAX if there's definitely no fitting object in the cache @@ -454,6 +455,8 @@ public: TextFrameIndex GetOffset() const { return mnOffset; } void SetOffset_(TextFrameIndex nNewOfst); inline void SetOffset (TextFrameIndex nNewOfst); + TextFrameIndex GetNoHyphOffset() const { return mnNoHyphOffset; } + void SetNoHyphOffset(TextFrameIndex const nNewOfst) { mnNoHyphOffset = nNewOfst; } void ManipOfst(TextFrameIndex const nNewOfst) { mnOffset = nNewOfst; } SwTextFrame *GetFrameAtPos ( const SwPosition &rPos); inline const SwTextFrame *GetFrameAtPos ( const SwPosition &rPos) const; diff --git a/sw/source/core/text/guess.cxx b/sw/source/core/text/guess.cxx index 680411147a35..6e4996b6f980 100644 --- a/sw/source/core/text/guess.cxx +++ b/sw/source/core/text/guess.cxx @@ -286,7 +286,11 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, } } - bool bHyph = rInf.IsHyphenate() && !rInf.IsHyphForbud(); + bool bHyph = rInf.IsHyphenate() && !rInf.IsHyphForbud() && + // disable hyphenation in the last line, when hyphenation-keep-line is enabled + ( rInf.GetTextFrame()->GetNoHyphOffset() == TextFrameIndex(COMPLETE_STRING) || + // when there is a portion in the last line, rInf.GetIdx() > GetNoHyphOffset() + rInf.GetTextFrame()->GetNoHyphOffset() > rInf.GetIdx() ); TextFrameIndex nHyphPos(0); // nCutPos is the first character not fitting to the current line diff --git a/sw/source/core/text/inftxt.cxx b/sw/source/core/text/inftxt.cxx index 37fdee6a346f..bde01b5bf5b1 100644 --- a/sw/source/core/text/inftxt.cxx +++ b/sw/source/core/text/inftxt.cxx @@ -1563,13 +1563,13 @@ static void lcl_InitHyphValues( PropertyValues &rVals, sal_Int16 nMinLeading, sal_Int16 nMinTrailing, bool bNoCapsHyphenation, bool bNoLastWordHyphenation, sal_Int16 nMinWordLength, sal_Int16 nTextHyphZone, bool bKeep, sal_Int16 nKeepType, - sal_Int16 nCompoundMinLeading ) + bool bKeepLine, sal_Int16 nCompoundMinLeading ) { sal_Int32 nLen = rVals.getLength(); if (0 == nLen) // yet to be initialized? { - rVals.realloc( 9 ); + rVals.realloc( 10 ); PropertyValue *pVal = rVals.getArray(); pVal[0].Name = UPN_HYPH_MIN_LEADING; @@ -1607,8 +1607,12 @@ static void lcl_InitHyphValues( PropertyValues &rVals, pVal[8].Name = UPN_HYPH_KEEP; pVal[8].Handle = UPH_HYPH_KEEP; pVal[8].Value <<= bKeep; + + pVal[9].Name = UPN_HYPH_KEEP_LINE; + pVal[9].Handle = UPH_HYPH_KEEP_LINE; + pVal[9].Value <<= bKeepLine; } - else if (9 == nLen) // already initialized once? + else if (10 == nLen) // already initialized once? { PropertyValue *pVal = rVals.getArray(); pVal[0].Value <<= nMinLeading; @@ -1620,6 +1624,7 @@ static void lcl_InitHyphValues( PropertyValues &rVals, pVal[6].Value <<= nKeepType; pVal[7].Value <<= nCompoundMinLeading; pVal[8].Value <<= bKeep; + pVal[9].Value <<= bKeepLine; } else { OSL_FAIL( "unexpected size of sequence" ); @@ -1628,7 +1633,7 @@ static void lcl_InitHyphValues( PropertyValues &rVals, const PropertyValues & SwTextFormatInfo::GetHyphValues() const { - OSL_ENSURE( 9 == m_aHyphVals.getLength(), + OSL_ENSURE( 10 == m_aHyphVals.getLength(), "hyphenation values not yet initialized" ); return m_aHyphVals; } @@ -1652,10 +1657,12 @@ bool SwTextFormatInfo::InitHyph( const bool bAutoHyphen ) const sal_Int16 nTextHyphZone = rAttr.GetTextHyphenZone(); const bool bKeep = rAttr.IsKeep(); const sal_Int16 nKeepType = rAttr.GetKeepType(); + const bool bKeepLine = rAttr.IsKeepLine(); const sal_Int16 nCompoundMinimalLeading = std::max(rAttr.GetCompoundMinLead(), sal_uInt8(2)); lcl_InitHyphValues( m_aHyphVals, nMinimalLeading, nMinimalTrailing, bNoCapsHyphenation, bNoLastWordHyphenation, - nMinimalWordLength, nTextHyphZone, bKeep, nKeepType, nCompoundMinimalLeading ); + nMinimalWordLength, nTextHyphZone, bKeep, nKeepType, + bKeepLine, nCompoundMinimalLeading ); } return bAuto; } diff --git a/sw/source/core/text/txtfrm.cxx b/sw/source/core/text/txtfrm.cxx index 8603c256c28f..9ed922e99656 100644 --- a/sw/source/core/text/txtfrm.cxx +++ b/sw/source/core/text/txtfrm.cxx @@ -773,6 +773,7 @@ SwTextFrame::SwTextFrame(SwTextNode * const pNode, SwFrame* pSib, , mnHeightOfLastLine( 0 ) , mnAdditionalFirstLineOffset( 0 ) , mnOffset( 0 ) + , mnNoHyphOffset( COMPLETE_STRING ) , mnCacheIndex( USHRT_MAX ) , mbLocked( false ) , mbWidow( false ) diff --git a/sw/source/core/text/widorp.cxx b/sw/source/core/text/widorp.cxx index 9d541644d652..d858b30d47a4 100644 --- a/sw/source/core/text/widorp.cxx +++ b/sw/source/core/text/widorp.cxx @@ -469,6 +469,7 @@ bool WidowsAndOrphans::FindWidows( SwTextFrame *pFrame, SwTextMargin &rLine ) // hyphenation-keep: truncate a hyphenated line at the end of // the column, page or spread (but not more) + // hyphenation-keep-line: disable hyphenation in the last line instead of truncating it int nExtraWidLines = 0; if( rLine.GetLineNr() >= m_nWidLines && pMaster->HasPara() && ( rLine.GetLineNr() == m_nWidLines || !rLine.GetCurr()->IsEndHyph() ) ) @@ -478,6 +479,7 @@ bool WidowsAndOrphans::FindWidows( SwTextFrame *pFrame, SwTextMargin &rLine ) const SvxHyphenZoneItem &rAttr = rSet.GetHyphenZone(); bool bKeep = rAttr.IsHyphen() && rAttr.IsKeep() && rAttr.GetKeepType(); + bool bKeepLine = bKeep && rAttr.IsKeepLine(); // if PAGE or SPREAD, allow hyphenation in the not last column or in the // not last linked frame on the same page @@ -499,7 +501,10 @@ bool WidowsAndOrphans::FindWidows( SwTextFrame *pFrame, SwTextMargin &rLine ) if ( bKeep && pMasterPara && pMasterPara->GetNext() ) { + // calculate the beginning of last hyphenated line + TextFrameIndex nIdx(pMasterPara->GetLen()); SwLineLayout * pNext = pMasterPara->GetNext(); + nIdx += pNext->GetLen(); SwLineLayout * pCurr = pNext; SwLineLayout * pPrev = pNext; while ( pNext->GetNext() ) @@ -507,21 +512,37 @@ bool WidowsAndOrphans::FindWidows( SwTextFrame *pFrame, SwTextMargin &rLine ) pPrev = pCurr; pCurr = pNext; pNext = pNext->GetNext(); + nIdx += pNext->GetLen(); } + nIdx -= pNext->GetLen(); // hyphenated line, but not the last remaining one - if ( pNext->IsEndHyph() && !pNext->IsLastHyph() ) + // in the case of shifting full line (bKeepLine = false) + if ( pNext->IsEndHyph() && ( bKeepLine || !pNext->IsLastHyph() ) ) { nExtraWidLines = rLine.GetLineNr() - m_nWidLines + 1; + // shift only a word: disable hyphenation in the line, if needed + if ( bKeepLine && nExtraWidLines ) + { + pMaster->SetNoHyphOffset(nIdx); + nExtraWidLines = 0; // no need to shift the full line + } + // shift full line: // set remaining line to "last remaining hyphenated line" // to avoid truncating multiple hyphenated lines instead // of a single one - if ( pCurr->IsEndHyph() ) + else if ( !bKeepLine && pCurr->IsEndHyph() ) pCurr->SetLastHyph( true ); // also unset the line before the remaining one // TODO: check also the line after the truncated line? if ( pPrev->IsLastHyph() ) pPrev->SetLastHyph( false ); } + else if ( !pNext->IsEndHyph() && pMaster->GetNoHyphOffset() != nIdx ) + { + // not hyphenated and not a last line with forbidden hyphenation: + // enable hyphenation for all the lines in the TextFrame again + pMaster->SetNoHyphOffset(TextFrameIndex(COMPLETE_STRING)); + } } } diff --git a/sw/source/core/unocore/unomapproperties.hxx b/sw/source/core/unocore/unomapproperties.hxx index 7e3f4c86216a..e441125576b7 100644 --- a/sw/source/core/unocore/unomapproperties.hxx +++ b/sw/source/core/unocore/unomapproperties.hxx @@ -123,6 +123,7 @@ { UNO_NAME_PARA_HYPHENATION_ZONE, RES_PARATR_HYPHENZONE, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_ZONE}, \ { UNO_NAME_PARA_HYPHENATION_KEEP, RES_PARATR_HYPHENZONE, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_KEEP }, \ { UNO_NAME_PARA_HYPHENATION_KEEP_TYPE, RES_PARATR_HYPHENZONE, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_KEEP_TYPE }, \ + { UNO_NAME_PARA_HYPHENATION_KEEP_LINE, RES_PARATR_HYPHENZONE, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_KEEP_LINE }, \ { UNO_NAME_CHAR_AUTO_KERNING, RES_CHRATR_AUTOKERN, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, 0 }, \ { UNO_NAME_CHAR_BACK_COLOR, RES_CHRATR_BACKGROUND, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, MID_BACK_COLOR }, \ { UNO_NAME_CHAR_BACKGROUND_COMPLEX_COLOR, RES_CHRATR_BACKGROUND, cppu::UnoType::get(), PROPERTY_NONE, MID_BACKGROUND_COMPLEX_COLOR },\ @@ -493,6 +494,7 @@ { UNO_NAME_PARA_HYPHENATION_ZONE, RES_PARATR_HYPHENZONE, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_ZONE},\ { UNO_NAME_PARA_HYPHENATION_KEEP, RES_PARATR_HYPHENZONE, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_KEEP},\ { UNO_NAME_PARA_HYPHENATION_KEEP_TYPE, RES_PARATR_HYPHENZONE, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_KEEP_TYPE},\ + { UNO_NAME_PARA_HYPHENATION_KEEP_LINE, RES_PARATR_HYPHENZONE, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_KEEP_LINE},\ { UNO_NAME_NUMBERING_STYLE_NAME, RES_PARATR_NUMRULE, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, 0},\ { UNO_NAME_NUMBERING_LEVEL, RES_PARATR_LIST_LEVEL, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, 0},\ { UNO_NAME_PARA_USER_DEFINED_ATTRIBUTES, RES_UNKNOWNATR_CONTAINER, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, 0 },\ diff --git a/sw/source/uibase/sidebar/WriterInspectorTextPanel.cxx b/sw/source/uibase/sidebar/WriterInspectorTextPanel.cxx index 8ee351f719a5..289520cbcb50 100644 --- a/sw/source/uibase/sidebar/WriterInspectorTextPanel.cxx +++ b/sw/source/uibase/sidebar/WriterInspectorTextPanel.cxx @@ -275,6 +275,7 @@ static OUString PropertyNametoRID(const OUString& rName) { "ParaHyphenationZone", RID_PARA_HYPHENATION_ZONE }, { "ParaHyphenationKeep", RID_PARA_HYPHENATION_KEEP }, { "ParaHyphenationKeepType", RID_PARA_HYPHENATION_KEEP_TYPE }, + { "ParaHyphenationKeepLine", RID_PARA_HYPHENATION_KEEP_LINE }, { "ParaInteropGrabBag", RID_PARA_INTEROP_GRAB_BAG }, { "ParaIsAutoFirstLineIndent", RID_PARA_IS_AUTO_FIRST_LINE_INDENT }, { "ParaIsCharacterDistance", RID_PARA_IS_CHARACTER_DISTANCE }, diff --git a/sw/source/writerfilter/dmapper/PropertyIds.cxx b/sw/source/writerfilter/dmapper/PropertyIds.cxx index d2676d56e77a..9854cf7b0584 100644 --- a/sw/source/writerfilter/dmapper/PropertyIds.cxx +++ b/sw/source/writerfilter/dmapper/PropertyIds.cxx @@ -102,6 +102,7 @@ const OUString & getPropertyName( PropertyIds eId ) { PROP_PARA_HYPHENATION_ZONE, u"ParaHyphenationZone"_ustr}, { PROP_PARA_HYPHENATION_KEEP, u"ParaHyphenationKeep"_ustr}, { PROP_PARA_HYPHENATION_KEEP_TYPE, u"ParaHyphenationKeepType"_ustr}, + { PROP_PARA_HYPHENATION_KEEP_LINE, u"ParaHyphenationKeepLine"_ustr}, { PROP_PARA_LINE_NUMBER_COUNT, u"ParaLineNumberCount"_ustr}, { PROP_PARA_IS_HANGING_PUNCTUATION, u"ParaIsHangingPunctuation"_ustr}, { PROP_PARA_LINE_SPACING, u"ParaLineSpacing"_ustr}, diff --git a/sw/source/writerfilter/dmapper/PropertyIds.hxx b/sw/source/writerfilter/dmapper/PropertyIds.hxx index 277d4139862b..6bf8f749a962 100644 --- a/sw/source/writerfilter/dmapper/PropertyIds.hxx +++ b/sw/source/writerfilter/dmapper/PropertyIds.hxx @@ -250,6 +250,7 @@ enum PropertyIds ,PROP_PARA_HYPHENATION_ZONE ,PROP_PARA_HYPHENATION_KEEP ,PROP_PARA_HYPHENATION_KEEP_TYPE + ,PROP_PARA_HYPHENATION_KEEP_LINE ,PROP_PARA_KEEP_TOGETHER ,PROP_PARA_LAST_LINE_ADJUST ,PROP_PARA_LEFT_MARGIN diff --git a/sw/source/writerfilter/dmapper/SettingsTable.cxx b/sw/source/writerfilter/dmapper/SettingsTable.cxx index 7ec07453d4f6..ef301915cd93 100644 --- a/sw/source/writerfilter/dmapper/SettingsTable.cxx +++ b/sw/source/writerfilter/dmapper/SettingsTable.cxx @@ -572,11 +572,20 @@ sal_Int16 SettingsTable::GetConsecutiveHyphenLimit() const } bool SettingsTable::GetHyphenationKeep() const +{ + // if allowHyphenationAtTrackBottom is false, also if it is not defined + // (which means the same) hyphenation is not allowed in the last line, so + // set ParaHyphenationKeep to TRUE, and set ParaHyphenationKeepType to COLUMN + return m_pImpl->m_nAllowHyphenationAtTrackBottom != 1; +} + +bool SettingsTable::GetHyphenationKeepLine() const { // if allowHyphenationAtTrackBottom is not true and useWord2013TrackBottomHyphenation is - // not present or it is true, set ParaHyphenationKeep to COLUMN - return m_pImpl->m_nAllowHyphenationAtTrackBottom != 1 && - m_pImpl->m_nUseWord2013TrackBottomHyphenation != 0; + // not present or it is false, shift only the hyphenated word to the next column or page, + // not the full line, so set ParaHyphenationKeepLine to TRUE + return GetHyphenationKeep() && + m_pImpl->m_nUseWord2013TrackBottomHyphenation != 1; } const OUString & SettingsTable::GetDecimalSymbol() const @@ -746,6 +755,7 @@ void SettingsTable::ApplyProperties(rtl::Reference const& xDoc) { xDefault->setPropertyValue(u"ParaHyphenationKeep"_ustr, uno::Any(true)); xDefault->setPropertyValue(u"ParaHyphenationKeepType"_ustr, uno::Any(text::ParagraphHyphenationKeepType::COLUMN)); + xDefault->setPropertyValue(u"ParaHyphenationKeepLine"_ustr, uno::Any(GetHyphenationKeepLine())); } } diff --git a/sw/source/writerfilter/dmapper/SettingsTable.hxx b/sw/source/writerfilter/dmapper/SettingsTable.hxx index c90f5f389abf..d385465e0d1b 100644 --- a/sw/source/writerfilter/dmapper/SettingsTable.hxx +++ b/sw/source/writerfilter/dmapper/SettingsTable.hxx @@ -81,6 +81,7 @@ public: sal_Int16 GetHyphenationZone() const; sal_Int16 GetConsecutiveHyphenLimit() const; bool GetHyphenationKeep() const; + bool GetHyphenationKeepLine() const; const OUString& GetDecimalSymbol() const; const OUString& GetListSeparator() const;