tdf#173389 sw: add compound-based “smart” hyphenation for readability

As a natural extension of the "smart" hyphenation, following
traditional typography, prefer readability of the hyphenated
compound words and better word spacing in the following way:

* when it's possible to hyphenate the same compound word within
  the custom word spacing range, choose compound splitting or
  hyphenate just before the suffix or inside the suffix, not
  inside a compound constituent.

* limit plain, i.e. non-compound-based smart hyphenation, when
  it can results in better word spacing without losing readability:
  do not disable hyphenation within the custom word spacing range, if
  the hyphenation splits the compound word between its constituents;

* if also shrinking results in a non-compound splitting hyphenation
  inside the second or third etc. constituent of a compound word,
  always shrink the line to show bigger part of that non-first
  constituent (within the custom word spacing range).

For these, extend lingucomponent HYPH_COMPOUND_MIN_LEADING and
HYPH_COMPOUND_MIN_TRAILING API to support disabling hyphenation of
non-compound words, also disabling hyphenation inside the first
constituent (but not inside the suffix or just before the suffix).
This allows to recognize compound words during line breaking.
Details: Hyphenator::hyphenate().

The initial development enables compound-based "smart" hyphenation
only for the Hungarian language, yet. The patch contains minor
fixes of the compound-based Hungarian hyphenation, resulting
better compound-based "smart" hyphenation with recognition of
verb prefixes and leg-/legesleg- superlative prefixes. (Note:
this fixed also COMPOUND_MIN_LEADING/TRAILING settings for the
hyphenation of the compound words with dictionary-based hyphenation
(only hy:, without pa: Hunspell dictionary fields, i.e. without
compound splitting heuristics of Hunspell).

Change-Id: I26f31329cdea20f03dbde7742587089eb5df5ae2
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210423
Reviewed-by: László Németh <nemeth@numbertext.org>
Tested-by: Jenkins
This commit is contained in:
László Németh
2026-09-04 09:40:40 +02:00
parent 5593616d2e
commit f9f694f5d9
5 changed files with 760 additions and 24 deletions
@@ -471,6 +471,7 @@ Reference< XHyphenatedWord > SAL_CALL Hyphenator::hyphenate( const OUString& aWo
// also skip hyphenation inside compound constituents right before constituent boundaries in compound words
// or always skip hyphenation inside compound constituents, if minCompoundTrail == 0
// if both minCompoundLead and minCompoundTrail == 0, don't hyphenate inside suffixes, too
// if both minCompoundLead and minCompoundTrail >= 99, don't hyphenate inside suffixes, too
if ( bCompoundHyphenation && (nCompoundHyphenationPos > -1 || minCompoundLead == 0 || minCompoundTrail > 2 || minCompoundTrail == 0 ) &&
// check minimal distance from the left constituent boundary
( ( minCompoundLead > 2 && i - nCompoundHyphenationPos < minCompoundLead ) ||
@@ -478,7 +479,8 @@ Reference< XHyphenatedWord > SAL_CALL Hyphenator::hyphenate( const OUString& aWo
( minCompoundTrail > 2 && i - nCompoundHyphenationPos < minCompoundTrail ) ||
// special value: zero compound limit means forbidden hyphenation
// inside (stems of) compound constituents
minCompoundLead == 0 || minCompoundTrail == 0 ) )
minCompoundLead == 0 || minCompoundTrail == 0 ||
minCompoundLead >= 99 || minCompoundTrail >= 99 ) )
{
uno::Reference< XLinguServiceManager2 > xLngSvcMgr( GetLngSvcMgr_Impl() );
uno::Reference< XSpellChecker1 > xSpell;
@@ -566,21 +568,58 @@ Reference< XHyphenatedWord > SAL_CALL Hyphenator::hyphenate( const OUString& aWo
break;
}
// handle verb and leg-, legesleg- prefixes (Hungarian-only)
sal_Int32 nPrefixLen = 0;
if ( LANGUAGE_HUNGARIAN == nLanguage && sStems.isEmpty() )
{
if ( aWord.startsWith(u"leg") &&
morph.indexOf(u"ip:leg_") > -1 )
{
sStems = aWord.replaceAt(3, 0, u"||");
nPrefixLen = 5;
}
else if ( aWord.startsWith(u"legesleg") &&
morph.indexOf(u"ip:legesleg_") > -1 )
{
sStems = aWord.replaceAt(0, 8, u"leges||leg||");
nPrefixLen = 12;
}
if ( sStems.isEmpty() )
{
static constexpr OUString sVERB_PREFIX = u"ip:PREF sp:"_ustr;
nPa = morph.indexOf(sVERB_PREFIX);
if ( nPa > -1 )
{
nPa += sVERB_PREFIX.getLength();
OUString sVerbPrefix = morph.getToken(0, ' ', nPa);
nPrefixLen = sVerbPrefix.getLength();
if ( aWord.startsWith(sVerbPrefix) )
sStems = aWord.replaceAt(nPrefixLen++, 0, u"|");
}
}
}
// only hy:, but not pa:
if ( sStems.isEmpty() )
if ( sStems.isEmpty() || nPrefixLen > 0 )
{
// check hy: (pre-defined hyphenation)
sal_Int32 nHy = morph.indexOf(" hy:");
if (nHy > -1)
{
sStems = morph.getToken(1, ' ', nHy).copy(3);
if ( sStems.indexOf('|') == -1 && sStems.indexOf('-') == -1 )
OUString sHy(morph.getToken(1, ' ', nHy).copy(3));
if ( sHy.indexOf('|') == -1 && sHy.indexOf('-') == -1 )
{
if ( sal_Int32 nBreak = o3tl::toInt32(sStems) )
if ( sal_Int32 nBreak = o3tl::toInt32(sHy) )
{
if ( nBreak < aWord.getLength() )
sStems += OUString::Concat(aWord.subView(0, nBreak)) + u"|" +
aWord.subView(nBreak);
if ( sStems.isEmpty() )
sStems = aWord;
else
// correction for Hungarian (e.g. "leg||meg|lepőbb")
nBreak += nPrefixLen;
if ( nBreak < sStems.getLength() )
sStems = OUString::Concat(sStems.subView(0, nBreak)) + u"|" +
sStems.subView(nBreak);
}
}
}
@@ -589,7 +628,23 @@ Reference< XHyphenatedWord > SAL_CALL Hyphenator::hyphenate( const OUString& aWo
}
}
// handle string separated by |, e.g "program hy:pro|gram"
//
// hyphenation at special lead/trail combinations over compound splitting
//
// +: hyphenation, -: no hyphenation
//
// lead | trail || non-compound | first constituent | suffix |
// -----------------------------------------------------------
// 0 | 0 || + | + | - |
// 0 | x || + | + | + |
// x | 0 || + | + | + |
// x | x || + | + | + |
// 0 | 99<= || - | - | + |
// 99<= | 0 || - | - | + |
// 99<= | 99<= || - | - | - |
//
if ( sStems.indexOf('|') > -1 )
{
sal_Int32 nLetters = 0; // count not separator characters
@@ -613,13 +668,15 @@ Reference< XHyphenatedWord > SAL_CALL Hyphenator::hyphenate( const OUString& aWo
// too close to the left break point or special case?
( i - nCompoundHyphenationPos < minCompoundLead || minCompoundLead == 0 ) &&
// there is a stem boundary before the actual break point
nSepPos > -1 &&
// or special values: disable hyphenation in the first constituent, too
( nSepPos > -1 || minCompoundLead == 99 || minCompoundTrail == 99 ) &&
// and the break point is within a stem, i.e. not in the
// suffix of the last stem or both minCompoundLead and
// minCompoundTrail == 0, i.e. hyphenation is allowed only
// at compound constituent boundaries
( i < aWord.getLength() - nSuffixLen - 1 ||
( minCompoundLead == 0 && minCompoundTrail == 0) ) &&
( minCompoundLead == 0 && minCompoundTrail == 0 ) ||
( minCompoundLead >= 99 && minCompoundTrail >= 99 ) ) &&
// and it is not another stem boundary
j + 1 < sStems.getLength() &&
( sStems[j] != u'|' ||
@@ -630,7 +687,7 @@ Reference< XHyphenatedWord > SAL_CALL Hyphenator::hyphenate( const OUString& aWo
continue;
}
// skip break points near before the stem boundaries
// (hit is not mandatory, only compondhit, because the
// (hit is not mandatory, only compoundhit, because the
// compound boundary can be after the lead limit)
if (!hit &&
// too close to the right break point?
@@ -640,7 +697,8 @@ Reference< XHyphenatedWord > SAL_CALL Hyphenator::hyphenate( const OUString& aWo
// minCompoundTrail == 0, i.e. hyphenation is allowed only
// at compound constituent boundaries
( i < aWord.getLength() - nSuffixLen - 1 ||
( minCompoundLead == 0 && minCompoundTrail == 0) ) &&
( minCompoundLead == 0 && minCompoundTrail == 0) ||
( minCompoundLead >= 99 && minCompoundTrail >= 99 ) ) &&
// and it is a stem boundary
j + 1 < sStems.getLength() && sStems[j+1] == u'|' &&
// the previous break point is not a boundary
@@ -654,8 +712,15 @@ Reference< XHyphenatedWord > SAL_CALL Hyphenator::hyphenate( const OUString& aWo
bPrevCompound = nSepPos == j || nSepPos == j - 1;
}
else
{
// not a compound word
// special value: 99<= : if it is not a compound word, do not hyphenate it
if ( minCompoundLead >= 99 || minCompoundTrail >= 99 )
return nullptr;
bCompoundHyphenation = false;
}
}
else
// no SPELLML support, no morphological analysis
+517
View File
@@ -9,6 +9,9 @@
#include <swmodeltestbase.hxx>
#include <com/sun/star/linguistic2/XHyphenator.hpp>
#include <com/sun/star/linguistic2/XSpellAlternatives.hpp>
#include <com/sun/star/linguistic2/XSpellChecker1.hpp>
#include <i18nlangtag/languagetag.hxx>
#include <vcl/metaact.hxx>
#include <editeng/unolingu.hxx>
@@ -848,6 +851,520 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf172674_less_hyphenation)
assertXPath(pXmlDoc, "/root/page[1]/body/txt/SwParaPortion/SwLineLayout/SwHyphPortion", 3);
}
// check smart hyphenation and compound-based smart hyphenation
CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf119908_smart_hyphenation_hu)
{
uno::Reference<linguistic2::XHyphenator> xHyphenator = LinguMgr::GetHyphenator();
if (!xHyphenator->hasLocale(lang::Locale(u"hu"_ustr, u"HU"_ustr, OUString())))
return;
// compound-based hyphenation needs Hunspell morphological analysis
// (and an up-to-date Hungarian dictionary for full testing, which
// contains compound splitting information. E.g. dictionary version 1.7
// didn't contain data for the words "valamint" and "elismerésének")
uno::Reference<linguistic2::XSpellChecker1> xSpell = LinguMgr::GetSpellChecker();
LanguageType eLang
= LanguageTag::convertToLanguageType(lang::Locale(u"hu"_ustr, u"HU"_ustr, OUString()));
if (!xSpell.is() || !xSpell->hasLanguage(static_cast<sal_uInt16>(eLang)))
return;
std::vector<std::pair<OUString, OUString>> aCompound = {
{ u"jogorvoslatért"_ustr, "hy:3" }, { u"valamint"_ustr, "hy:4" },
{ u"alapfokon"_ustr, "pa:fokon" }, { u"jótéteményekben"_ustr, "hy:2" },
{ u"elismerésének"_ustr, "hy:2" },
};
// tests supported by the actual spelling dictionary
bool aTests[5];
// check Hunspell-based morphological analysis & hyphenation
bool bHunspell = xSpell->isValid("<?xml?>", static_cast<sal_uInt16>(eLang),
uno::Sequence<beans::PropertyValue>());
if (bHunspell)
{
// check dictionary support of compound splitting
for (size_t i = 0; i < aCompound.size(); ++i)
{
aTests[i] = false;
// get morphological analysis of the test word
uno::Reference<css::linguistic2::XSpellAlternatives> xTmpRes = xSpell->spell(
"<?xml?><query type='analyze'><word>" + aCompound[i].first + "</word></query>",
static_cast<sal_uInt16>(eLang), uno::Sequence<beans::PropertyValue>());
if (xTmpRes.is())
{
uno::Sequence<OUString> seq = xTmpRes->getAlternatives();
if (seq.hasElements())
{
sal_Int32 nEndOfFirstAnalysis = seq[0].indexOf("</a>");
// use only the first analysis, like the lingucomponent implementation
OUString morph(seq[0].copy(0, nEndOfFirstAnalysis));
// check compound data
aTests[i] = morph.indexOf(aCompound[i].second) > -1;
}
}
}
}
createSwDoc("tdf119908_smart_hyphenation_hu.odt");
// Ensure that all text portions are calculated before testing.
SwViewShell* pViewShell = getSwDoc()->getIDocumentLayoutAccess().GetCurrentViewShell();
CPPUNIT_ASSERT(pViewShell);
pViewShell->Reformat();
xmlDocUniquePtr pXmlDoc = parseLayoutDump();
// 53 hyphenations on 5 pages using compond-based smart hyphenation
// This was 67 without smart hyphenation and 53 without compound-based smart hyphenation
// (using only the plain or interoperable smart hyphenation).
// 13 hyphenations on page 1
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[3]/SwParaPortion/SwLineLayout[1]", "portion",
u"Tekintettel arra, hogy az emberiség családja minden egyes tagja méltóságának, vala");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[3]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
// "smart" hyphenation: disabled hyphenation of "emberiség" by shrinking
// This was "emberi-ség"
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]", "portion",
u"Tekintettel arra, hogy az emberi jogok el nem ismerése és semmibevevése az emberiség ");
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[5]/SwParaPortion/SwLineLayout[1]", "portion",
u"Tekintettel annak fontosságára, hogy az emberi jogokat a jog uralma védelmezze, ne");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[5]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[5]/SwParaPortion/SwLineLayout[2]", "portion",
u"hogy az ember végső szükségében a zsarnokság és az elnyomás elleni lázadásra kény");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[5]/SwParaPortion/SwLineLayout[2]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[6]/SwParaPortion/SwLineLayout[1]", "portion",
u"Tekintettel arra, hogy igen lényeges a nemzetek közötti baráti kapcsolatok kifejlődésé");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[6]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
// "smart" hyphenation: disabled hyphenation of "tettek" by shrinking
// This was "tet-tek"
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[7]/SwParaPortion/SwLineLayout[1]", "portion",
u"Tekintettel arra, hogy az Alapokmányban az Egyesült Nemzetek népei újból hitet tettek ");
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[7]/SwParaPortion/SwLineLayout[3]", "portion",
u"egyenjogúsága mellett, valamint kinyilvánították azt az elhatározásukat, hogy elősegí");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[7]/SwParaPortion/SwLineLayout[3]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout[1]", "portion",
u"Tekintettel arra, hogy a tagállamok kötelezték magukat arra, hogy az Egyesült Nemze");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(pXmlDoc, "/root/page[1]/body/txt[9]/SwParaPortion/SwLineLayout[1]", "portion",
u"A KÖZGYŰLÉS kinyilvánítja az EMBERI JOGOK EGYETEMES NYILATKOZA");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[9]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
// "smart" hyphenation: disabled hyphenation of "törekednie" by shifting
// This was "tö-rekednie"
assertXPath(pXmlDoc, "/root/page[1]/body/txt[9]/SwParaPortion/SwLineLayout[2]", "portion",
u"TÁT, mint azt a közös eszményt, amelynek elérésére minden népnek és nemzetnek ");
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[11]/SwParaPortion/SwLineLayout[1]", "portion",
u"Minden emberi lény szabadnak születik, és egyenlő méltósága és joga van. Az embe");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[11]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[2]", "portion",
u"nyelvre, vallásra, politikai vagy más véleményre, nemzeti vagy társadalmi szárma");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[2]/SwHyphPortion",
1);
assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[3]", "portion",
u"zásra, vagyonra, születésre vagy más körülményre vonatkozó mindennemű megkülön");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[3]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[4]", "portion",
u"böztetés nélkül mindenkit megilletnek. Ezenfelül nem lehet semmiféle megkülönböz");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[4]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[5]", "portion",
u"tetést tenni annak az országnak vagy területnek a politikai, jogi vagy nemzetközi hely");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[5]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[7]", "portion",
u"terület független, gyámság alatt áll, nem autonóm vagy szuverenitása bármely vonat");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[7]/SwHyphPortion",
1);
assertXPath(pXmlDoc, "/root/page[1]/body/txt/SwParaPortion/SwLineLayout/SwHyphPortion", 13);
// delete page 1 to update hyphenation on the next page
SwWrtShell* const pWrtShell = getSwDocShell()->GetWrtShell();
pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 2868, /*bBasicCall=*/false);
pWrtShell->Delete();
// 13 hyphenations on page 2
pViewShell->Reformat();
pXmlDoc = parseLayoutDump();
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[2]/SwParaPortion/SwLineLayout[1]", "portion",
u"Senkit sem lehet rabszolgaságban vagy szolgaságban tartani; a rabszolgaság és a rab");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]", "portion",
u"Senkit sem lehet kínvallatásnak, avagy kegyetlen, embertelen vagy lealacsonyító bá");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout[1]", "portion",
u"A törvény előtt mindenki egyenlő, és minden megkülönböztetés nélkül joga van a tör");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout[3]", "portion",
u"sértő minden megkülönböztetéssel és minden ilyen megkülönböztetésre irányuló fel");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout[3]/SwHyphPortion",
1);
// compound-based "smart" hyphenation of the compound "jog|orvoslat"
// This was hyphenated as "jogor-voslat" previously, now "jog-orvoslat"
if (bHunspell && aTests[0])
{
assertXPath(pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[2]", "portion",
u"alapvető jogokat sértő eljárások ellen az illetékes hazai bíróságokhoz "
u"tényleges jog");
}
assertXPath(pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[2]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[14]/SwParaPortion/SwLineLayout[2]", "portion",
u"bíróság méltányosan és nyilvánosan tárgyalja, s ez határozzon egyrészt jogai és köte");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[14]/SwParaPortion/SwLineLayout[2]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[16]/SwParaPortion/SwLineLayout[1]", "portion",
u"Minden büntetendő cselekménnyel vádolt személyt ártatlannak kell vélelmezni mind");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[16]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[16]/SwParaPortion/SwLineLayout[2]", "portion",
u"addig, amíg bűnösségét nyilvánosan lefolytatott perben, a védelméhez szükséges vala");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[16]/SwParaPortion/SwLineLayout[2]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[17]/SwParaPortion/SwLineLayout[1]", "portion",
u"Senkit sem szabad elítélni oly cselekményért vagy mulasztásért, amely elkövetése pil");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[17]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[17]/SwParaPortion/SwLineLayout[3]", "portion",
u"Ugyancsak nem szabad súlyosabb büntetést kiszabni, mint amely a büntetendő cselek");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[17]/SwParaPortion/SwLineLayout[3]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[19]/SwParaPortion/SwLineLayout[1]", "portion",
u"Senkinek magánéletébe, családi ügyeibe, otthonába vagy levelezésébe nem szabad ön");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[19]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
// "smart" hyphenation: disabled hyphenation of "minden" by shrinking
// This was "Min-den"
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[19]/SwParaPortion/SwLineLayout[2]", "portion",
u"kényesen beavatkozni, sem pedig becsületében vagy jó hírnevében megsérteni. Minden ");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[21]/SwParaPortion/SwLineLayout[1]", "portion",
u"Az államon belül minden személynek joga van szabadon mozogni és lakóhelyét sza");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[21]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
// compound-based "smart" hyphenation of the compound "vala|mint"
// This was hyphenated as "va-lamint" previously)
if (bHunspell && aTests[1])
{
assertXPath(pXmlDoc, "/root/page[1]/body/txt[22]/SwParaPortion/SwLineLayout[1]", "portion",
u"Minden személynek joga van minden országot, ideértve saját hazáját is, "
u"elhagyni, vala");
}
assertXPath(pXmlDoc, "/root/page[1]/body/txt[22]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(pXmlDoc, "/root/page[1]/body/txt/SwParaPortion/SwLineLayout/SwHyphPortion", 13);
// delete page 2 to update hyphenation on the next page
pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 2297, /*bBasicCall=*/false);
pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 2297, /*bBasicCall=*/false);
pWrtShell->Delete();
// 10 hyphenations on page 3
pViewShell->Reformat();
pXmlDoc = parseLayoutDump();
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[1]", "portion",
u"Erre a jogra nem lehet hivatkozni közönséges bűncselekmény miatti, kellőképpen meg");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[2]", "portion",
u"alapozott üldözés, sem pedig az Egyesült Nemzetek céljaival és elveivel ellentétes te");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[2]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]", "portion",
u"Senkit sem lehet sem állampolgárságától, sem állampolgársága megváltoztatásának jo");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[6]/SwParaPortion/SwLineLayout[1]", "portion",
u"Mind a férfinak, mind a nőnek a nagykorúság elérésétől kezdve joga van fajon, nem");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[6]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[6]/SwParaPortion/SwLineLayout[2]", "portion",
u"zetiségen vagy valláson alapuló korlátozás nélkül házasságot kötni és családot alapí");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[6]/SwParaPortion/SwLineLayout[2]/SwHyphPortion",
1);
assertXPath(pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[1]", "portion",
u"Minden személynek joga van a tulajdonhoz, mind egyénileg, mind másokkal együtte");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
// "smart" hyphenation: disabled hyphenation of "valamint" by shifting
// This was "va-lamint"
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[2]", "portion",
u"a jog magában foglalja a vallás és a meggyőződés megváltoztatásának szabadságát, ");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[15]/SwParaPortion/SwLineLayout[1]", "portion",
u"Minden személynek joga van a vélemény és a kifejezés szabadságához, amely magá");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[15]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[15]/SwParaPortion/SwLineLayout[2]", "portion",
u"ban foglalja azt a jogot, hogy véleménye miatt ne szenvedjen zaklatást, és hogy hatá");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[15]/SwParaPortion/SwLineLayout[2]/SwHyphPortion",
1);
// "smart" hyphenation: disabled hyphenation of "információkat" by shrinking
// This was "információ-kat"
assertXPath(pXmlDoc, "/root/page[1]/body/txt[15]/SwParaPortion/SwLineLayout[3]", "portion",
u"rokra való tekintet nélkül kutathasson, tájékozódhasson és terjeszthessen "
u"információkat ");
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[22]/SwParaPortion/SwLineLayout[2]", "portion",
u"rendszeres, az általános és egyenlő szavazati jogot és a titkos szavazást biztosító vá");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[22]/SwParaPortion/SwLineLayout[2]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[24]/SwParaPortion/SwLineLayout[1]", "portion",
u"Minden személynek mint a társadalom tagjának joga van a szociális biztonsághoz, to");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[24]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(pXmlDoc, "/root/page[1]/body/txt/SwParaPortion/SwLineLayout/SwHyphPortion", 10);
// delete page 3 to update hyphenation on the next page
pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 2566, /*bBasicCall=*/false);
pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 2566, /*bBasicCall=*/false);
pWrtShell->Delete();
// 12 hyphenations on page 4
pViewShell->Reformat();
pXmlDoc = parseLayoutDump();
assertXPath(pXmlDoc, "/root/page[1]/body/txt[3]/SwParaPortion/SwLineLayout[1]", "portion",
u"Minden személynek joga van a munkához, a munka szabad megválasztásához, a mél");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[3]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]", "portion",
u"Mindenkinek joga van egyenlő munkáért egyenlő bért kapni, bárminemű megkülön");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout[1]", "portion",
u"Minden személynek joga van a pihenéshez, a szabadidőhöz, beleértve a munkaidő ész");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[1]", "portion",
u"Minden személynek joga van saját maga és családja egészségének és jólétének bizto");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[3]", "portion",
u"ellátáshoz, valamint a szükséges szociális szolgáltatásokhoz, továbbá joga van a szoci");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[3]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[11]/SwParaPortion/SwLineLayout[2]", "portion",
u"gyermek, akár házasságból, akár házasságon kívül született, azonos szociális védelem");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[11]/SwParaPortion/SwLineLayout[2]/SwHyphPortion",
1);
// compound-based "smart" hyphenation of the compound "alap|fokon"
// This was hyphenated as "alapfo-kon" previously, now "alap-fokon"
if (bHunspell && aTests[2])
{
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[1]", "portion",
u"Minden személynek joga van az oktatáshoz. Az oktatásnak legalábbis elemi és alap");
}
assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[14]/SwParaPortion/SwLineLayout[1]", "portion",
u"A nevelésnek az emberi személyiség teljes kibontakoztatására, valamint az emberi jo");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[14]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[14]/SwParaPortion/SwLineLayout[4]", "portion",
u"közötti megértést, türelmet és barátságot, valamint támogatnia kell az Egyesült Nem");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[14]/SwParaPortion/SwLineLayout[4]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[15]/SwParaPortion/SwLineLayout[1]", "portion",
u"A szülőket elsőbbségi jog illeti meg a gyermekeiknek adandó oktatás megválasztásá");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[15]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
// compound-based "smart" hyphenation of the compound "jó|téteményekben"
// This wasn't hyphenated previously, now "jó-téteményekben"
if (bHunspell && aTests[3])
{
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[17]/SwParaPortion/SwLineLayout[2]", "portion",
u"a művészetek élvezéséhez, valamint a tudomány haladásában és az abból származó jó");
assertXPath(pXmlDoc,
"/root/page[1]/body/txt[17]/SwParaPortion/SwLineLayout[2]/SwHyphPortion", 1);
}
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[18]/SwParaPortion/SwLineLayout[1]", "portion",
u"Mindenkinek joga van minden általa alkotott tudományos, irodalmi és művészeti ter");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[18]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(pXmlDoc, "/root/page[1]/body/txt/SwParaPortion/SwLineLayout/SwHyphPortion", 13);
// delete page 4 to update hyphenation on the next page
pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 2766, /*bBasicCall=*/false);
pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 2766, /*bBasicCall=*/false);
pWrtShell->Delete();
// 5 hyphenations on page 5
pViewShell->Reformat();
pXmlDoc = parseLayoutDump();
assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[1]", "portion",
u"Minden személynek joga van ahhoz, hogy mind a társadalmi, mind a nemzetközi vi");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[2]", "portion",
u"szonyok tekintetében olyan rendszer uralkodjék, amelyben a jelen Nyilatkozatban ki");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[2]/SwHyphPortion",
1);
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]", "portion",
u"Jogainak gyakorlásában és szabadságainak élvezetében mindenki csak olyan korláto");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
// compound-based "smart" hyphenation of the compound "el|ismerésének"
// This wasn't hyphenated by the plain, i.e. not compound-based "smart" hyphenation previously.
if (bHunspell && aTests[4])
{
assertXPath(pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[2]", "portion",
u"zásoknak lehet alávetve, amelyeket a törvény kizárólag mások jogai és "
u"szabadságai el");
assertXPath(pXmlDoc,
"/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[2]/SwHyphPortion", 1);
}
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[5]/SwParaPortion/SwLineLayout[1]", "portion",
u"Ezeket a jogokat és szabadságokat semmi esetre sem lehet az Egyesült Nemzetek cél");
assertXPath(pXmlDoc, "/root/page[1]/body/txt[5]/SwParaPortion/SwLineLayout[1]/SwHyphPortion",
1);
// "smart" hyphenation: disabled hyphenation of "állam" by shifting
// This was "ál-lam"
assertXPath(
pXmlDoc, "/root/page[1]/body/txt[7]/SwParaPortion/SwLineLayout[1]", "portion",
u"A jelen Nyilatkozat egyetlen rendelkezése sem értelmezhető úgy, hogy az valamely ");
if (bHunspell && aTests[4])
assertXPath(pXmlDoc, "/root/page[1]/body/txt/SwParaPortion/SwLineLayout/SwHyphPortion", 5);
}
CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf158333)
{
createSwDoc("tdf130088.docx");
+161 -12
View File
@@ -17,7 +17,9 @@
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/i18n/ScriptType.hpp>
#include <com/sun/star/linguistic2/XHyphenatedWord.hpp>
#include <com/sun/star/uno/Reference.hxx>
#include <editeng/unolingu.hxx>
#include <i18nlangtag/mslangid.hxx>
@@ -38,6 +40,7 @@
#include <pam.hxx>
#include <doc.hxx>
#include <o3tl/temporary.hxx>
#include <unotools/linguprops.hxx>
#include <xmloff/odffields.hxx>
#include <viewopt.hxx>
@@ -312,6 +315,73 @@ void SwTextPortion::BreakCut( SwTextFormatInfo &rInf, const SwTextGuess &rGuess
}
}
// is it a hyphenation of a compound word, which splits the compound word between the
// compound constituents OR it hyphenates the compound word just before or inside its suffix?
bool SwTextPortion::IsCompoundSplit( SwTextFormatInfo &rInf, const SwTextGuess &rGuess,
bool* pbHasSplitBefore )
{
if ( !rGuess.HyphWord().is() )
return false;
LanguageType aLang = rInf.GetTextFrame()->GetLangOfChar(rInf.GetIdx(), 1, true);
// Hungarian-only yet
if ( LANGUAGE_HUNGARIAN != aLang )
return false;
LanguageTag aLanguageTag(aLang);
uno::Reference< linguistic2::XHyphenator > xHyph;
xHyph = ::GetHyphenator();
::css::uno::Sequence< ::css::beans::PropertyValue > aProperties(2);
::css::beans::PropertyValue *pVal = aProperties.getArray();
pVal[0].Name = UPN_HYPH_COMPOUND_MIN_LEADING;
pVal[0].Handle = UPH_HYPH_COMPOUND_MIN_LEADING;
// 0: compound splitting: do not hyphenate inside second, third etc.
// constituent of compound words
pVal[0].Value <<= sal_Int16(0);
pVal[1].Name = UPN_HYPH_COMPOUND_MIN_TRAILING;
pVal[1].Handle = UPH_HYPH_COMPOUND_MIN_TRAILING;
// 99<=: force compound splitting to recognize and handle compound words
// * do not hyphenate non-compound words
// * do not hyphenate the first constituent of compound words
// * but still hyphenate terminating suffix of compound words,
// including hyphenation just before the suffix, i.e. after the
// stem of the last constituent (condition: LEADING != TRAILING)
pVal[1].Value <<= sal_Int16(99);
css::uno::Reference< css::linguistic2::XHyphenatedWord >
xHyphWord = xHyph->hyphenate( rGuess.HyphWord()->getWord(),
aLanguageTag.getLocale(),
rGuess.HyphWord()->getHyphenationPos() + 1,
aProperties );
// compound splitting resulted the same break, so the word is really a compound word
// and the break point split compound constituents or it breaks the compound word
// 1) just before its suffix or 2) inside its suffix
if ( xHyphWord.is() &&
xHyphWord->getHyphenationPos() == rGuess.HyphWord()->getHyphenationPos() )
{
return true;
}
// when IsCompoundSplit() is false, pbHasSplitBefore's return value is true, if
// * the word is a compound word AND the break point
// * is not in a compound splitting position OR just before or inside the suffix AND
// * not inside the first constituent
// otherwise is false.
// Note: This value is used to optimize the hyphenation of compound words, trying to
// show more from the first constituent(s) and the broken one before the hyphenation,
// if it's not possible to skip the hyphenation completely.
if ( pbHasSplitBefore )
*pbHasSplitBefore = xHyphWord.is();
return false;
}
void SwTextPortion::BreakUnderflow( SwTextFormatInfo &rInf )
{
Truncate();
@@ -628,6 +698,7 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
if ( bWordSpacing )
{
std::optional<SwTextGuess> pGuess2(std::in_place);
std::optional<SwTextGuess> pGuess3(std::in_place);
// hyphenation slider, when 0.0 < fLevel < 0.5,
// enlarge both desired and maximum word spacing:
@@ -644,12 +715,27 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
? 0
: (0.5 - fLevel) * 200;
// FIXME no need for pGuess2, if the desired word space is 100%
bool bFull2 = !pGuess2->Guess( *this, rInf, Height(), nSpacesInLine,
nWeightedSpacing, nSpaceWidth, nExtraWordSpacing );
// perhaps it's possible to skip hyphenation
bool bOrigHyphenated2 = pGuess2->HyphWord().is() &&
pGuess2->BreakPos() > rInf.GetLineStart();
// possible compound splitting in the same word before the hyphenation
if ( bOrigHyphenated2 && pGuess2->HyphWord()->getHyphenationPos() >= 4 )
pGuess3->Guess( *this, rInf, Height(), nSpacesInLine,
// fix this heuristics!!!
(nWeightedSpacing+aAdjustItem.GetPropWordSpacingMaximum())/2, nSpaceWidth, nExtraWordSpacing );
bool bOrigHyphenated3 = pGuess3->HyphWord().is() &&
pGuess3->BreakPos() > rInf.GetLineStart();
// perhaps there is a compound splitting in the same word before the plain hyphenation
bool bPossibleCompoundSplit = bOrigHyphenated3 &&
pGuess3->HyphWord()->getHyphenationPos() < pGuess2->HyphWord()->getHyphenationPos() &&
pGuess3->HyphWord()->getWord().equals(pGuess2->HyphWord()->getWord());
if ( !bOrigHyphenated2 &&
// try to remove hyphenation in interoperability mode or
// allow desired word spacing
@@ -661,7 +747,8 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
sal_Int32 nSpacesInLine2 = rInf.GetLineSpaceCount( pGuess2->BreakPos() );
float fSpaceNormal2 = nSpacesInLine2 > 0
? ( rInf.GetLineWidth() - ( rInf.GetBreakWidth() -
nSpacesInLine2 * nSpaceWidth/10.0
nSpacesInLine2 * nSpaceWidth/10.0 +
( bOrigHyphenated2 ? nHyphenWidth : 0)
) ) / nSpacesInLine2
// no remaining space, only the space after the single word
: 1;
@@ -672,8 +759,12 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
// desired one
// FIXME if the desired word spacing is not 100%, and maximum word spacing
// is not the same, but minimum word spacing is, the fallback is still 100%
if ( z1NotWeighted <= ( nSpacingMaximum + nExtraWordSpacing )/100.0 ||
aAdjustItem.GetPropWordSpacing() == nSpacingMaximum )
if ( ( z1NotWeighted <= ( nSpacingMaximum + nExtraWordSpacing )/100.0 ||
aAdjustItem.GetPropWordSpacing() == nSpacingMaximum ) &&
// skip hyphenation only if it's not compound splitting
( !bOrigHyphenated || !IsCompoundSplit(rInf, *pGuess) ) &&
// skip hyphenation for another hyphenation only, if that is a compound splitting
( !bOrigHyphenated2 || IsCompoundSplit(rInf, *pGuess2) ) )
{
pGuess.emplace();
pGuess = std::move(pGuess2);
@@ -683,6 +774,46 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
bFull = bFull2;
}
}
// check compound
if ( bOrigHyphenated && bPossibleCompoundSplit &&
// try to remove hyphenation in interoperability mode or
// allow desired word spacing
( ( bOrigHyphenated && fLevel <= 0.5 ) || fLevel > 0.5 ||
aAdjustItem.GetPropWordSpacing() == nSpacingMaximum ) &&
( aAdjustItem.GetPropLetterSpacingMinimum() < 0 ||
rInf.GetBreakWidth() <= rInf.GetLineWidth() ) )
{
sal_Int32 nSpacesInLine3 = rInf.GetLineSpaceCount( pGuess3->BreakPos() );
float fSpaceNormal3 = nSpacesInLine3 > 0
? ( rInf.GetLineWidth() - ( rInf.GetBreakWidth() -
nSpacesInLine3 * nSpaceWidth/10.0 +
( bOrigHyphenated3 ? nHyphenWidth : 0)
) ) / nSpacesInLine3
// no remaining space, only the space after the single word
: 1;
float z1NotWeighted = ( nSpaceWidth/10.0 +
( fSpaceNormal3 - nSpaceWidth/10.0) ) / (nSpaceWidth/10.0);
// skip hyphenation/desired spacing, if spacing doesn't exceed the maximum
// word spacing, except if maximum word spacing is not different from the
// desired one
// FIXME if the desired word spacing is not 100%, and maximum word spacing
// is not the same, but minimum word spacing is, the fallback is still 100%
if ( ( z1NotWeighted <= ( nSpacingMaximum + nExtraWordSpacing )/100.0 ||
aAdjustItem.GetPropWordSpacing() == nSpacingMaximum ) &&
// skip hyphenation only if it's not compound splitting
( !bOrigHyphenated || !IsCompoundSplit(rInf, *pGuess) ) &&
// skip hyphenation for another hyphenation only, if that is a compound splitting
( !bOrigHyphenated3 || IsCompoundSplit(rInf, *pGuess3) ) )
{
pGuess.emplace();
pGuess = std::move(pGuess3);
SetSpacing(rInf, *pGuess, nSpacesInLine3, nSpaceWidth);
fSpaceNormal = fSpaceNormal3;
bOrigHyphenated = bOrigHyphenated3;
bFull = bFull2;
}
}
}
sal_Int32 nSpacesInLineShrink = 0;
@@ -781,22 +912,40 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
float z1 = (nSpaceWidth/10.0+((fSpaceNormal-nSpaceWidth/10.0)*fExpansionWeight))/(nSpaceWidth/10.0);
float z1NotWeighted = ( nSpaceWidth/10.0 +
( fSpaceNormal - nSpaceWidth/10.0) ) / (nSpaceWidth/10.0);
// true, if the pGuess2 hyphenation is inside the second or third etc. compound constituent
bool bCompoundSplitBefore = false;
// always shrink at average and lower hyphenation level,
// if it removes hyphenation
bool bRemoveHyphenation = bOrigHyphenated && !bShrunkHyphenated && fLevel <= 0.5;
// if it removes a non-compound word splitting hyphenation
bool bRemoveHyphenation = bOrigHyphenated && fLevel <= 0.5 &&
( !bShrunkHyphenated || IsCompoundSplit(rInf, *pGuess2, &bCompoundSplitBefore) ||
// if shrinking results in a non-compound splitting hyphenation
// inside the second or third etc. constituent of a compound word,
// always shrink the line to show bigger part of that non-first constituent
// (no need to check pGuess for bCompoundSplitBefore, too, because shrinking
// doesn't skip a compound splitting position between pGuess and pGuess2)
( bCompoundSplitBefore && !IsCompoundSplit(rInf, *pGuess) ) );
// Prefer shrinking, if 1) this is a portion OR,
// TODO shrink line portions only if needed
if ( bIsPortion ||
// 2) NOT shrinking would result very large spacing ( >150% ) OR
// 3) shrinking removes hyphenation OR ...
// 3) shrinking removes (worse) hyphenation OR ...
z1NotWeighted > nSpacingMaximum/100.0 || bRemoveHyphenation ||
// ... 4) * shrinking results better (weighted) spacing AND
// * doesn't result in an extra hyphenation
( z1 >= z0 && !( !bOrigHyphenated && bShrunkHyphenated ) ) ||
// or allow more and more hyphenation choosing better and better spacing
// * doesn't result in an extra hyphenation AND
// * no losing of compound splitting
( z1 >= z0 && !( !bOrigHyphenated && bShrunkHyphenated ) &&
!( bOrigHyphenated && bShrunkHyphenated && IsCompoundSplit(rInf, *pGuess) &&
!IsCompoundSplit(rInf, *pGuess2) ) ) ||
// 5) or allow more and more hyphenation choosing better and better spacing
// when shrinking results in an extra hyphenation
( z1 >= z0 && !bOrigHyphenated && bShrunkHyphenated &&
fLevel > 0.5 && z1 * ( fLevel - 0.5 ) >= z0 * ( 1 - fLevel ) ) )
( z1 >= z0 && !bOrigHyphenated && bShrunkHyphenated && (
( fLevel > 0.5 && z1 * ( fLevel - 0.5 ) >= z0 * ( 1 - fLevel ) ) ||
// except compound splitting, which is equally good,
// than no hyphenation, so always choose that,
// if it results in better spacing
IsCompoundSplit(rInf, *pGuess2) )
) )
{
pGuess = std::move(pGuess2);
SetSpacing(rInf, *pGuess, nSpacesInLineShrink, nSpaceWidth);
+5
View File
@@ -26,6 +26,11 @@ class SwTextGuess;
class SwTextPortion : public SwLinePortion
{
void BreakCut( SwTextFormatInfo &rInf, const SwTextGuess &rGuess );
// is this hyphenation is good enough for a compound word, i.e.
// it splits a compound word between compound constituents or
// it hyphenates the compound word just before or inside its suffix
static bool IsCompoundSplit( SwTextFormatInfo &rInf, const SwTextGuess &rGuess,
bool* pbCompoundSplitBefore = nullptr );
void BreakUnderflow( SwTextFormatInfo &rInf );
void SetSpacing( SwTextFormatInfo &rInf, SwTextGuess const &rGuess,
const sal_Int32 nSpaces, const sal_Int16 nWidthOf10Spaces );