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 000000000000..3a90488e7f3b Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf165354.docx differ diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx index cc6cce590287..9f036b03c4d7 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx @@ -1496,13 +1496,20 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf160518_page_in_text_body_style) assertXPath(pXmlDoc, "/w:settings/w:compat/w:compatSetting[@w:name='allowHyphenationAtTrackBottom']", 0); } -CPPUNIT_TEST_FIXTURE(Test, testTdf160518_auto_in_text_body_style) +CPPUNIT_TEST_FIXTURE(Test, testTdf165354) { - // text body style contains hyphenation settings - loadAndReload("tdf160518_auto_in_text_body_style.fodt"); - xmlDocUniquePtr pXmlDoc = parseExport(u"word/settings.xml"_ustr); - assertXPath(pXmlDoc, "/w:settings/w:compat/w:compatSetting[@w:name='useWord2013TrackBottomHyphenation']", "val", u"1"); - assertXPath(pXmlDoc, "/w:settings/w:compat/w:compatSetting[@w:name='allowHyphenationAtTrackBottom']", "val", u"1"); + uno::Reference 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;