tdf#i165354 sw offapi DOCX: implement HyphenationKeepLine – part 1

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::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 9574a62add
"tdf#132599 cui offapi sw xmloff: implement hyphenate-keep".
and commit c8ee0e8f58
"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 <nemeth@numbertext.org>
Tested-by: Jenkins
This commit is contained in:
László Németh
2025-02-17 19:20:04 +01:00
parent 2aaa35d2a2
commit 3e02ffb76c
24 changed files with 135 additions and 29 deletions

View File

@@ -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<sal_Int16>(nKeepType);
break;
case MID_HYPHEN_KEEP_LINE:
rVal <<= bKeepLine;
break;
case MID_HYPHEN_COMPOUND_MIN_LEAD:
rVal <<= static_cast<sal_Int16>(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<sal_uInt8>(nNewVal);
break;
case MID_HYPHEN_KEEP_LINE:
bKeepLine = Any2Bool(rVal);
break;
case MID_HYPHEN_COMPOUND_MIN_LEAD:
nCompoundMinLead = static_cast<sal_uInt8>(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);

View File

@@ -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 ")

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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")

View File

@@ -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;

Binary file not shown.

View File

@@ -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<linguistic2::XHyphenator> 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)

View File

@@ -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()))

View File

@@ -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()))

View File

@@ -208,6 +208,7 @@ class SW_DLLPUBLIC SwTextFrame final : public SwContentFrame
std::unique_ptr<sw::MergedPara> 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;

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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 )

View File

@@ -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));
}
}
}

View File

@@ -123,6 +123,7 @@
{ UNO_NAME_PARA_HYPHENATION_ZONE, RES_PARATR_HYPHENZONE, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_ZONE}, \
{ UNO_NAME_PARA_HYPHENATION_KEEP, RES_PARATR_HYPHENZONE, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_KEEP }, \
{ UNO_NAME_PARA_HYPHENATION_KEEP_TYPE, RES_PARATR_HYPHENZONE, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_KEEP_TYPE }, \
{ UNO_NAME_PARA_HYPHENATION_KEEP_LINE, RES_PARATR_HYPHENZONE, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_KEEP_LINE }, \
{ UNO_NAME_CHAR_AUTO_KERNING, RES_CHRATR_AUTOKERN, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \
{ UNO_NAME_CHAR_BACK_COLOR, RES_CHRATR_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_BACK_COLOR }, \
{ UNO_NAME_CHAR_BACKGROUND_COMPLEX_COLOR, RES_CHRATR_BACKGROUND, cppu::UnoType<css::util::XComplexColor>::get(), PROPERTY_NONE, MID_BACKGROUND_COMPLEX_COLOR },\
@@ -493,6 +494,7 @@
{ UNO_NAME_PARA_HYPHENATION_ZONE, RES_PARATR_HYPHENZONE, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_ZONE},\
{ UNO_NAME_PARA_HYPHENATION_KEEP, RES_PARATR_HYPHENZONE, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_KEEP},\
{ UNO_NAME_PARA_HYPHENATION_KEEP_TYPE, RES_PARATR_HYPHENZONE, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_KEEP_TYPE},\
{ UNO_NAME_PARA_HYPHENATION_KEEP_LINE, RES_PARATR_HYPHENZONE, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_KEEP_LINE},\
{ UNO_NAME_NUMBERING_STYLE_NAME, RES_PARATR_NUMRULE, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0},\
{ UNO_NAME_NUMBERING_LEVEL, RES_PARATR_LIST_LEVEL, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, 0},\
{ UNO_NAME_PARA_USER_DEFINED_ATTRIBUTES, RES_UNKNOWNATR_CONTAINER, cppu::UnoType<css::container::XNameContainer>::get(), PropertyAttribute::MAYBEVOID, 0 },\

View File

@@ -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 },

View File

@@ -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},

View File

@@ -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

View File

@@ -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<SwXTextDocument> 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()));
}
}

View File

@@ -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;