tdf#166113 sw smart justify: adjust algorithm for interoperability
Skip space shrinking, if space expansion is closer (with a weight) to the desired (100%) word space. Import DOCX documents with 75% minimum word spacing, 100% desired word spacing and 133% maximum word spacing. These changes cover MSO smart justify algorithm. Hyphenation and text portions may need future adjustments. Modify unit tests according to the changes: – testTdf126154, testTdf126154_minimum_shrinking, testTdf126154_portion: choose maximum word spacing, i.e. expansion instead of shrinking, according to the weighted distance from the desired word space; – testTdf163720: set desired word spacing to the minimum word spacing in the test document to get back the original regression test. Note: the previous algorithm for disabled hyphenation within the minimum and maximum word spacing range is replaced by optimization for the desired word spacing (temporarily). Interoperability analysis and custom more/less hyphenation can change this. Change-Id: Iec0582a497945743f97686743af5ff6609866abe Reviewed-on: https://gerrit.libreoffice.org/c/core/+/186138 Tested-by: Jenkins Reviewed-by: László Németh <nemeth@numbertext.org>
This commit is contained in:
@@ -178,7 +178,7 @@
|
||||
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" text:number-lines="false" text:line-number="0" style:writing-mode="lr-tb"/>
|
||||
</style:style>
|
||||
<style:style style:name="P2" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:paragraph-properties fo:text-align="justify" style:justify-single-word="false" fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" loext:hyphenation-keep-type="column" style:writing-mode="lr-tb"/>
|
||||
<style:paragraph-properties fo:text-align="justify" style:justify-single-word="false" fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" loext:hyphenation-keep-type="column" style:writing-mode="lr-tb" loext:word-spacing-minimum="75%" loext:word-spacing="75%"/>
|
||||
<style:text-properties fo:hyphenate="true" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="567" loext:hyphenation-compound-remain-char-count="2"/>
|
||||
</style:style>
|
||||
<style:page-layout style:name="pm1">
|
||||
|
||||
@@ -1054,7 +1054,8 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf126154)
|
||||
u",,,,,,,, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vesti ");
|
||||
|
||||
// also minimum word space: 80%, 100%, 100%
|
||||
// only a single line is hyphenated from the previous ones
|
||||
// only a single line was hyphenated from the previous ones
|
||||
// TODO: fix possible interoperability issues, allow optional limitation of hyphenation again
|
||||
assertXPath(
|
||||
pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vesti bulum ");
|
||||
@@ -1067,10 +1068,10 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf126154)
|
||||
u"Vesti bulum ");
|
||||
assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",,,,, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
|
||||
u"Vesti bulum ");
|
||||
u"Vesti bu");
|
||||
assertXPath(pXmlDoc, "/root/page[1]/body/txt[14]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",,,,,, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
|
||||
u"Vesti bulum ");
|
||||
u"Vesti bu");
|
||||
assertXPath(
|
||||
pXmlDoc, "/root/page[1]/body/txt[15]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",,,,,,, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vesti bu");
|
||||
@@ -1079,8 +1080,9 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf126154)
|
||||
u",,,,,,,, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vesti ");
|
||||
|
||||
// minimum, desired and maximum word spacing: 80%, 100%, 133%
|
||||
// no hyphenation in the same text: hyphenation of all the short words are limited
|
||||
// no hyphenation in the same text: hyphenation of all the short words were limited
|
||||
// by the minimum and maximum word spacing settings
|
||||
// TODO: fix possible interoperability issues, allow optional limitation of hyphenation again
|
||||
assertXPath(
|
||||
pXmlDoc, "/root/page[1]/body/txt[18]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vesti bulum ");
|
||||
@@ -1093,10 +1095,10 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf126154)
|
||||
u"Vesti bulum ");
|
||||
assertXPath(pXmlDoc, "/root/page[1]/body/txt[21]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",,,,, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
|
||||
u"Vesti bulum ");
|
||||
u"Vesti bu");
|
||||
assertXPath(pXmlDoc, "/root/page[1]/body/txt[22]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",,,,,, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
|
||||
u"Vesti bulum ");
|
||||
u"Vesti bu");
|
||||
assertXPath(
|
||||
pXmlDoc, "/root/page[1]/body/txt[23]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",,,,,,, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vesti ");
|
||||
@@ -1167,7 +1169,8 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf126154_minimum_shrinking)
|
||||
u",,,,,,,, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vesti ");
|
||||
|
||||
// also minimum word space: 80%, 100%, 100%
|
||||
// only a single line is hyphenated from the previous ones
|
||||
// only a single line was yphenated from the previous ones
|
||||
// TODO: fix possible interoperability issues, allow optional limitation of hyphenation again
|
||||
assertXPath(
|
||||
pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vesti bulum ");
|
||||
@@ -1179,10 +1182,10 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf126154_minimum_shrinking)
|
||||
u"Vesti bulum ");
|
||||
assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",,,,, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
|
||||
u"Vesti bulum ");
|
||||
u"Vesti bu");
|
||||
assertXPath(pXmlDoc, "/root/page[1]/body/txt[14]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",,,,,, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
|
||||
u"Vesti bulum ");
|
||||
u"Vesti bu");
|
||||
assertXPath(
|
||||
pXmlDoc, "/root/page[1]/body/txt[15]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",,,,,,, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vesti bu");
|
||||
@@ -1191,8 +1194,9 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf126154_minimum_shrinking)
|
||||
u",,,,,,,, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vesti ");
|
||||
|
||||
// minimum, desired and maximum word spacing: 80%, 100%, 133%
|
||||
// no hyphenation in the same text: hyphenation of all the short words are limited
|
||||
// no hyphenation in the same text: hyphenation of all the short words were limited
|
||||
// by the minimum and maximum word spacing settings
|
||||
// TODO: fix possible interoperability issues, allow optional limitation of hyphenation again
|
||||
assertXPath(
|
||||
pXmlDoc, "/root/page[1]/body/txt[18]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vesti bulum ");
|
||||
@@ -1204,10 +1208,10 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf126154_minimum_shrinking)
|
||||
u"Vesti bulum ");
|
||||
assertXPath(pXmlDoc, "/root/page[1]/body/txt[21]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",,,,, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
|
||||
u"Vesti bulum ");
|
||||
u"Vesti bu");
|
||||
assertXPath(pXmlDoc, "/root/page[1]/body/txt[22]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",,,,,, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
|
||||
u"Vesti bulum ");
|
||||
u"Vesti bu");
|
||||
assertXPath(
|
||||
pXmlDoc, "/root/page[1]/body/txt[23]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",,,,,,, , , , , , , , Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vesti ");
|
||||
@@ -1237,11 +1241,12 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf126154_portion)
|
||||
u",,,,,,,,,,,,,,,,,,,,, , , , , , , , , , , , , , , , Lorem ipsum dolor sit amet, "
|
||||
u"consectetur adipiscing elit. Vesti ");
|
||||
|
||||
// prefer minimum word spacing, not maximum word spacing to disable hyphenation, if it's possible
|
||||
// between the minimum and maximum values. (minimum: 80%, desired: 100%, maximum word spacing: 133%)
|
||||
// minimum: 80%, desired: 100%, maximum word spacing: 133%
|
||||
// prefer maximum word spacing, not minimum word spacing, if the weighted word space
|
||||
// is nearer to the desired word space
|
||||
assertXPath(pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]", "portion",
|
||||
u",,,,,,,,,,,,,,,,,,,,, , , , , , , , , , , , , , , , Lorem ipsum dolor sit amet, "
|
||||
u"consectetur adipiscing elit. Vesti bulum ");
|
||||
u"consectetur adipiscing elit. Vesti ");
|
||||
|
||||
// paragraph end zone
|
||||
// This was "... other celes" (not disabled hyphenation because of text portion)
|
||||
|
||||
@@ -158,7 +158,7 @@ bool SwTextGuess::maybeAdjustPositionsForBlockAdjust(tools::Long& rMaxSizeDiff,
|
||||
// otherwise possible break or hyphenation position is determined
|
||||
bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
|
||||
const sal_uInt16 nPorHeight, sal_Int32 nSpacesInLine,
|
||||
sal_uInt16 nPropWordSpacing )
|
||||
sal_uInt16 nPropWordSpacing, sal_Int16 nSpaceWidth )
|
||||
{
|
||||
m_nCutPos = rInf.GetIdx();
|
||||
|
||||
@@ -191,19 +191,13 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
|
||||
|
||||
if ( nSpacesInLine )
|
||||
{
|
||||
static constexpr OUStringLiteral STR_BLANK = u" ";
|
||||
sal_Int16 nSpaceWidth = rInf.GetTextSize(STR_BLANK).Width();
|
||||
float fWordSpacing = nPropWordSpacing == SAL_MAX_UINT16
|
||||
? 0.75 // MSO interoperability value
|
||||
// allow up to 25% shrinking of the spaces
|
||||
: nPropWordSpacing / 100.0;
|
||||
SwTwips nExtraSpace = nSpacesInLine * nSpaceWidth * (1.0 - fWordSpacing);
|
||||
SwTwips nExtraSpace = nSpacesInLine * nSpaceWidth/10.0 * (1.0 - nPropWordSpacing / 100.0);
|
||||
nLineWidth += nExtraSpace;
|
||||
// convert maximum word spacing to hyphenation zone, if defined
|
||||
if ( nPropWordSpacing == aAdjustItem.GetPropWordSpacing() )
|
||||
{
|
||||
SwTwips nMaxDif = aAdjustItem.GetPropWordSpacingMaximum() - nPropWordSpacing;
|
||||
nWordSpacingMaximumZone = nSpacesInLine * nSpaceWidth * nMaxDif / 100.0;
|
||||
nWordSpacingMaximumZone = nSpacesInLine * nSpaceWidth/10.0 * nMaxDif / 100.0;
|
||||
}
|
||||
|
||||
rInf.SetExtraSpace(nExtraSpace);
|
||||
@@ -845,6 +839,7 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
|
||||
rInf.GetTextSize(&rSI, rInf.GetIdx(), nPorLen, std::nullopt, nMaxComp, m_nBreakWidth,
|
||||
nMaxSizeDiff, nExtraAscent, nExtraDescent, rInf.GetCachedVclData().get());
|
||||
|
||||
rInf.SetBreakWidth(m_nBreakWidth);
|
||||
// save maximum width for later use
|
||||
if ( nMaxSizeDiff )
|
||||
rInf.SetMaxWidthDiff( &rPor, nMaxSizeDiff );
|
||||
|
||||
@@ -45,7 +45,7 @@ public:
|
||||
// true, if current portion still fits to current line
|
||||
bool Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
|
||||
const sal_uInt16 nHeight, sal_Int32 nSpacesInLine = 0,
|
||||
sal_uInt16 nPropWordSpacing = 100 );
|
||||
sal_uInt16 nPropWordSpacing = 100, sal_Int16 nSpaceWidth = 0 );
|
||||
bool AlternativeSpelling( const SwTextFormatInfo &rInf, const TextFrameIndex nPos );
|
||||
|
||||
SwHangingPortion* GetHangingPortion() const { return m_pHanging.get(); }
|
||||
|
||||
@@ -225,6 +225,7 @@ SwTextSizeInfo::SwTextSizeInfo()
|
||||
, m_bSnapToGrid(false)
|
||||
, m_nDirection(0)
|
||||
, m_nExtraSpace(0)
|
||||
, m_nBreakWidth(0)
|
||||
{}
|
||||
|
||||
SwTextSizeInfo::SwTextSizeInfo( const SwTextSizeInfo &rNew )
|
||||
@@ -256,7 +257,8 @@ SwTextSizeInfo::SwTextSizeInfo( const SwTextSizeInfo &rNew )
|
||||
m_bForbiddenChars( rNew.HasForbiddenChars() ),
|
||||
m_bSnapToGrid( rNew.SnapToGrid() ),
|
||||
m_nDirection( rNew.GetDirection() ),
|
||||
m_nExtraSpace( rNew.GetExtraSpace() )
|
||||
m_nExtraSpace( rNew.GetExtraSpace() ),
|
||||
m_nBreakWidth( rNew.GetBreakWidth() )
|
||||
{
|
||||
#if OSL_DEBUG_LEVEL > 0
|
||||
ChkOutDev( *this );
|
||||
@@ -269,6 +271,7 @@ void SwTextSizeInfo::CtorInitTextSizeInfo( OutputDevice* pRenderContext, SwTextF
|
||||
m_pKanaComp = nullptr;
|
||||
m_nKanaIdx = 0;
|
||||
m_nExtraSpace = 0;
|
||||
m_nBreakWidth = 0;
|
||||
m_pFrame = pFrame;
|
||||
CtorInitTextInfo( m_pFrame );
|
||||
SwDoc const& rDoc(m_pFrame->GetDoc());
|
||||
@@ -370,7 +373,8 @@ SwTextSizeInfo::SwTextSizeInfo( const SwTextSizeInfo &rNew, const OUString* pTex
|
||||
m_bForbiddenChars( rNew.HasForbiddenChars() ),
|
||||
m_bSnapToGrid( rNew.SnapToGrid() ),
|
||||
m_nDirection( rNew.GetDirection() ),
|
||||
m_nExtraSpace( rNew.GetExtraSpace() )
|
||||
m_nExtraSpace( rNew.GetExtraSpace() ),
|
||||
m_nBreakWidth( rNew.GetBreakWidth() )
|
||||
{
|
||||
#if OSL_DEBUG_LEVEL > 0
|
||||
ChkOutDev( *this );
|
||||
@@ -2283,4 +2287,30 @@ bool SwTextFormatInfo::CheckCurrentPosBookmark()
|
||||
}
|
||||
}
|
||||
|
||||
sal_Int32 SwTextFormatInfo::GetLineSpaceCount(TextFrameIndex nBreakPos)
|
||||
{
|
||||
if ( sal_Int32(nBreakPos) >= GetText().getLength() )
|
||||
return 0;
|
||||
|
||||
sal_Int32 nSpaces = 0;
|
||||
sal_Int32 nInlineSpaces = -1;
|
||||
for (sal_Int32 i = sal_Int32(GetLineStart()); i < sal_Int32(nBreakPos); ++i)
|
||||
{
|
||||
sal_Unicode cChar = GetText()[i];
|
||||
if ( cChar == CH_BLANK )
|
||||
++nSpaces;
|
||||
else
|
||||
{
|
||||
if ( nInlineSpaces == -1 )
|
||||
{
|
||||
nInlineSpaces = 0;
|
||||
nSpaces = 0;
|
||||
}
|
||||
else
|
||||
nInlineSpaces = nSpaces;
|
||||
}
|
||||
}
|
||||
return nInlineSpaces == -1 ? 0: nInlineSpaces;
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
|
||||
@@ -174,6 +174,7 @@ protected:
|
||||
bool m_bSnapToGrid : 1; // paragraph snaps to grid
|
||||
sal_uInt8 m_nDirection : 2; // writing direction: 0/90/180/270 degree
|
||||
SwTwips m_nExtraSpace; // extra space before shrinking = nSpacesInLine * (nSpaceWidth/0.8 - nSpaceWidth)
|
||||
SwTwips m_nBreakWidth; // break width to calculate space width at justification
|
||||
|
||||
protected:
|
||||
void CtorInitTextSizeInfo( OutputDevice* pRenderContext, SwTextFrame *pFrame,
|
||||
@@ -302,6 +303,9 @@ public:
|
||||
// extra space before shrinking = nSpacesInLine * (nSpaceWidth/0.8 - nSpaceWidth)
|
||||
void SetExtraSpace(SwTwips nVal) { m_nExtraSpace = nVal; }
|
||||
SwTwips GetExtraSpace() const { return m_nExtraSpace; }
|
||||
// set break width to calculate space width later
|
||||
void SetBreakWidth(SwTwips nVal) { m_nBreakWidth = nVal; }
|
||||
SwTwips GetBreakWidth() const { return m_nBreakWidth; }
|
||||
|
||||
// If Kana Compression is enabled, a minimum and maximum portion width
|
||||
// is calculated. We format lines with minimal size and share remaining
|
||||
@@ -711,6 +715,9 @@ public:
|
||||
void SetTabOverflow( bool bOverflow ) { m_bTabOverflow = bOverflow; }
|
||||
bool IsTabOverflow() const { return m_bTabOverflow; }
|
||||
|
||||
// get line space count between line start and break position
|
||||
// by stripping also terminating spaces
|
||||
sal_Int32 GetLineSpaceCount(TextFrameIndex nBreakPos);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+101
-64
@@ -367,19 +367,19 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
|
||||
const SvxAdjust aAdjust = aAdjustItem.GetAdjust();
|
||||
bool bFullJustified = bFull && aAdjust == SvxAdjust::Block &&
|
||||
pGuess->BreakPos() != TextFrameIndex(COMPLETE_STRING);
|
||||
bool bInteropSmartJustify = bFullJustified &&
|
||||
bool bInteropSmartJustify =
|
||||
rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
|
||||
DocumentSettingId::JUSTIFY_LINES_WITH_SHRINKING) &&
|
||||
// support different Kashida etc. values
|
||||
aAdjustItem.GetPropWordSpacing() == 100 &&
|
||||
DocumentSettingId::JUSTIFY_LINES_WITH_SHRINKING);
|
||||
bool bNoWordSpacing = aAdjustItem.GetPropWordSpacing() == 100 &&
|
||||
aAdjustItem.GetPropWordSpacingMinimum() == 100 &&
|
||||
aAdjustItem.GetPropWordSpacingMaximum() == 100;
|
||||
bool bWordSpacing = bFullJustified && !bInteropSmartJustify &&
|
||||
aAdjustItem.GetPropWordSpacing() != 100;
|
||||
bool bWordSpacingMaximum = bFullJustified && !bInteropSmartJustify &&
|
||||
// support old ODT documents, where only JustifyLinesWithShrinking was set
|
||||
bool bOldInterop = bInteropSmartJustify && bNoWordSpacing;
|
||||
bool bWordSpacing = bFullJustified && (!bNoWordSpacing || bOldInterop);
|
||||
bool bWordSpacingMaximum = bWordSpacing && !bOldInterop &&
|
||||
aAdjustItem.GetPropWordSpacingMaximum() > aAdjustItem.GetPropWordSpacing();
|
||||
bool bWordSpacingMinimum = bFullJustified && !bInteropSmartJustify &&
|
||||
aAdjustItem.GetPropWordSpacingMinimum() < aAdjustItem.GetPropWordSpacing();
|
||||
bool bWordSpacingMinimum = bWordSpacing && ( bOldInterop ||
|
||||
aAdjustItem.GetPropWordSpacingMinimum() < aAdjustItem.GetPropWordSpacing() );
|
||||
|
||||
if ( ( bInteropSmartJustify || bWordSpacing || bWordSpacingMaximum || bWordSpacingMinimum ) &&
|
||||
// tdf#164499 no shrinking in tabulated line
|
||||
@@ -388,13 +388,9 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
|
||||
// very short word resulted endless loop
|
||||
!rInf.IsUnderflow() )
|
||||
{
|
||||
sal_Int32 nSpacesInLine(0);
|
||||
for (sal_Int32 i = sal_Int32(rInf.GetLineStart()); i < sal_Int32(pGuess->BreakPos()); ++i)
|
||||
{
|
||||
sal_Unicode cChar = rInf.GetText()[i];
|
||||
if ( cChar == CH_BLANK )
|
||||
++nSpacesInLine;
|
||||
}
|
||||
sal_Int32 nSpacesInLine = rInf.GetLineSpaceCount( pGuess->BreakPos() );
|
||||
sal_Int32 nSpacesInLineOrig = nSpacesInLine;
|
||||
SwTextSizeInfo aOrigInf( rInf );
|
||||
|
||||
// call with an extra space: shrinking can result a new word in the line
|
||||
// and a new space before that, which is also a shrank space
|
||||
@@ -410,70 +406,111 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
|
||||
++nSpacesInLine;
|
||||
}
|
||||
|
||||
if ( nSpacesInLine > 0 )
|
||||
// there are spaces in the line, so it's possible to shrink them
|
||||
if ( nSpacesInLineOrig > 0 )
|
||||
{
|
||||
SwTwips nOldWidth = pGuess->BreakWidth();
|
||||
if ( bInteropSmartJustify )
|
||||
bool bIsPortion = rInf.GetLineWidth() < rInf.GetBreakWidth();
|
||||
|
||||
// measure ten spaces for higher precision
|
||||
static constexpr OUStringLiteral STR_BLANK = u" ";
|
||||
sal_Int16 nSpaceWidth = rInf.GetTextSize(STR_BLANK).Width();
|
||||
sal_Int32 nRealSpaces = rInf.GetLineSpaceCount( pGuess->BreakPos() );
|
||||
float fSpaceNormal = (rInf.GetLineWidth() - (rInf.GetBreakWidth() - nRealSpaces * nSpaceWidth/10.0))/nRealSpaces;
|
||||
|
||||
bool bOrigHyphenated = pGuess->HyphWord().is() &&
|
||||
pGuess->BreakPos() > rInf.GetLineStart();
|
||||
// calculate line breaking with desired word spacing, also
|
||||
// if the desired word spacing is 100%, but there is a greater
|
||||
// maximum word spacing, and the word is hyphenated at the desired
|
||||
// word spacing: to skip hyphenation, if the maximum word spacing allows it
|
||||
if ( bWordSpacing || ( bWordSpacingMaximum && bOrigHyphenated ) )
|
||||
{
|
||||
pGuess.emplace();
|
||||
bFull = !pGuess->Guess( *this, rInf, Height(), nSpacesInLine, SAL_MAX_UINT16 );
|
||||
bFull = !pGuess->Guess( *this, rInf, Height(), nSpacesInLine, aAdjustItem.GetPropWordSpacing(), nSpaceWidth );
|
||||
sal_Int32 nSpacesInLine2 = rInf.GetLineSpaceCount( pGuess->BreakPos() );
|
||||
|
||||
if ( rInf.GetBreakWidth() <= rInf.GetLineWidth() )
|
||||
fSpaceNormal = (rInf.GetLineWidth() - (rInf.GetBreakWidth() - nSpacesInLine2 * nSpaceWidth/10.0))/nSpacesInLine2;
|
||||
}
|
||||
else
|
||||
|
||||
sal_Int32 nSpacesInLineShrink = 0;
|
||||
// TODO if both maximum word spacing or minimum word spacing can disable hyphenation, prefer the last one
|
||||
if ( bWordSpacingMinimum )
|
||||
{
|
||||
bool bOrigHyphenated = pGuess->HyphWord().is() &&
|
||||
pGuess->BreakPos() > rInf.GetLineStart();
|
||||
if ( bWordSpacing || bWordSpacingMaximum )
|
||||
std::optional<SwTextGuess> pGuess2(std::in_place);
|
||||
SwTwips nOldExtraSpace = rInf.GetExtraSpace();
|
||||
// break the line after the hyphenated word, if it's possible
|
||||
// (hyphenation is disabled in Guess(), when called with GetPropWordSpacingMinimum())
|
||||
sal_uInt16 nMinimum = bOldInterop ? 75 : aAdjustItem.GetPropWordSpacingMinimum();
|
||||
bool bFull2 = !pGuess2->Guess( *this, rInf, Height(), nSpacesInLine, nMinimum, nSpaceWidth );
|
||||
nSpacesInLineShrink = rInf.GetLineSpaceCount( pGuess2->BreakPos() );
|
||||
if ( pGuess2->BreakWidth() > nOldWidth )
|
||||
{
|
||||
pGuess.emplace();
|
||||
bFull = !pGuess->Guess( *this, rInf, Height(), nSpacesInLine, aAdjustItem.GetPropWordSpacing() );
|
||||
}
|
||||
// if both maximum word spacing or minimum word spacing can disable
|
||||
// hyphenation, prefer the last one
|
||||
if ( bWordSpacingMinimum && ( bWordSpacingMaximum ||
|
||||
( pGuess->HyphWord().is() && pGuess->BreakPos() > rInf.GetLineStart() ) ) &&
|
||||
// if the desired word spacing is 100% (!bWordSpacing), and it was possible
|
||||
// to break the line without hyphenation in the first run (where maximum
|
||||
// word spacing was not used), no need to check minimum word spacing
|
||||
// FIXME: avoid too much shrinking, if desired word spacing is not 100%
|
||||
( bWordSpacing || bOrigHyphenated )
|
||||
)
|
||||
{
|
||||
std::optional<SwTextGuess> pGuess2(std::in_place);
|
||||
SwTwips nOldExtraSpace = rInf.GetExtraSpace();
|
||||
// break the line after the hyphenated word, if it's possible
|
||||
// (hyphenation is disabled in Guess(), when called with GetPropWordSpacingMinimum())
|
||||
bool bFull2 = !pGuess2->Guess( *this, rInf, Height(), nSpacesInLine, aAdjustItem.GetPropWordSpacingMinimum() );
|
||||
if ( pGuess2->BreakWidth() > nOldWidth )
|
||||
// instead of the maximum shrinking, break after the word which was hyphenated before
|
||||
sal_Int32 i = sal_Int32(pGuess->BreakPos());
|
||||
sal_Int32 j = sal_Int32(pGuess2->BreakPos());
|
||||
// skip terminal spaces
|
||||
for (; i < j && rInf.GetText()[i] == CH_BLANK; ++i);
|
||||
for (; j > i && rInf.GetText()[i] == CH_BLANK; --j);
|
||||
sal_Int32 nOldBreakTrim = i;
|
||||
sal_Int32 nOldBreak = j - i;
|
||||
for (; i < j; ++i)
|
||||
{
|
||||
// instead of the maximum shrinking, break after the word which was hyphenated before
|
||||
for (sal_Int32 i = sal_Int32(pGuess->BreakPos()); i < sal_Int32(pGuess2->BreakPos()); ++i)
|
||||
sal_Unicode cChar = rInf.GetText()[i];
|
||||
// first space after the hyphenated word, and it's not the chosen one
|
||||
if ( cChar == CH_BLANK )
|
||||
{
|
||||
sal_Unicode cChar = rInf.GetText()[i];
|
||||
// first space after the hyphenated word, and it's not the chosen one
|
||||
if ( cChar == CH_BLANK )
|
||||
// using a weighted word spacing, try to break the line after the hyphenated word
|
||||
sal_Int32 nNewBreak = i - nOldBreakTrim;
|
||||
SwTwips nWeightedSpacing = nMinimum * (1.0 * nNewBreak/nOldBreak) +
|
||||
aAdjustItem.GetPropWordSpacing() * (1.0 * (nOldBreak - nNewBreak)/nOldBreak);
|
||||
std::optional<SwTextGuess> pGuess3(std::in_place);
|
||||
pGuess3->Guess( *this, rInf, Height(), nSpacesInLineShrink-1, nWeightedSpacing, nSpaceWidth );
|
||||
|
||||
sal_Int32 nSpacesInLineShrink2 = rInf.GetLineSpaceCount( pGuess3->BreakPos() );
|
||||
if ( nSpacesInLineShrink2 == nSpacesInLineShrink )
|
||||
{
|
||||
// using a weighted word spacing, try to break the line after the hyphenated word
|
||||
sal_Int32 nOldBreak = sal_Int32(pGuess2->BreakPos()) - sal_Int32(pGuess->BreakPos());
|
||||
sal_Int32 nNewBreak = i - sal_Int32(pGuess->BreakPos());
|
||||
SwTwips nWeightedSpacing = aAdjustItem.GetPropWordSpacingMinimum() * (1.0 * nNewBreak/nOldBreak) +
|
||||
aAdjustItem.GetPropWordSpacing() * (1.0 * (nOldBreak - nNewBreak)/nOldBreak);
|
||||
std::optional<SwTextGuess> pGuess3(std::in_place);
|
||||
pGuess3->Guess( *this, rInf, Height(), nSpacesInLine, nWeightedSpacing );
|
||||
if ( pGuess3->BreakWidth() > nOldWidth )
|
||||
{
|
||||
pGuess2.emplace();
|
||||
pGuess2 = std::move(pGuess3);
|
||||
}
|
||||
break;
|
||||
nNewBreak = i - nOldBreakTrim - 1;
|
||||
nWeightedSpacing = nMinimum * (1.0 * nNewBreak/nOldBreak) +
|
||||
aAdjustItem.GetPropWordSpacing() * (1.0 * (nOldBreak - nNewBreak)/nOldBreak);
|
||||
pGuess3->Guess( *this, rInf, Height(), nSpacesInLineShrink-1, nWeightedSpacing, nSpaceWidth );
|
||||
}
|
||||
|
||||
if ( pGuess3->BreakWidth() > nOldWidth )
|
||||
{
|
||||
pGuess2.emplace();
|
||||
pGuess2 = std::move(pGuess3);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nSpacesInLineShrink = rInf.GetLineSpaceCount( pGuess2->BreakPos() );
|
||||
if ( rInf.GetBreakWidth() > rInf.GetLineWidth() || bIsPortion )
|
||||
{
|
||||
float fExpansionWeight = static_cast<float>(1/1.7);
|
||||
float fSpaceShrunk = nSpacesInLineShrink > 0
|
||||
? (rInf.GetLineWidth() - (rInf.GetBreakWidth() - nSpacesInLineShrink * nSpaceWidth/10.0))/nSpacesInLineShrink
|
||||
: 0;
|
||||
float z0 = (nSpaceWidth/10.0)/fSpaceShrunk;
|
||||
float z1 = (nSpaceWidth/10.0+((fSpaceNormal-nSpaceWidth/10.0)*fExpansionWeight))/(nSpaceWidth/10.0);
|
||||
// TODO shrink line portions only if needed
|
||||
if ( z1 >= z0 || bIsPortion )
|
||||
{
|
||||
pGuess = std::move(pGuess2);
|
||||
bFull = bFull2;
|
||||
}
|
||||
}
|
||||
else if ( bOldInterop )
|
||||
{
|
||||
pGuess = std::move(pGuess2);
|
||||
bFull = bFull2;
|
||||
}
|
||||
else
|
||||
// minimum word spacing is not applicable
|
||||
rInf.SetExtraSpace(nOldExtraSpace);
|
||||
}
|
||||
else
|
||||
// minimum word spacing is not applicable
|
||||
rInf.SetExtraSpace(nOldExtraSpace);
|
||||
}
|
||||
|
||||
if ( pGuess->BreakWidth() != nOldWidth )
|
||||
|
||||
@@ -4887,6 +4887,12 @@ void DomainMapper::handleParaJustification(const sal_Int32 nIntValue, const ::to
|
||||
case NS_ooxml::LN_Value_ST_Jc_both:
|
||||
nAdjust = style::ParagraphAdjust_BLOCK;
|
||||
aStringValue = "both";
|
||||
// set default smart justify
|
||||
if ( GetSettingsTable()->GetWordCompatibilityMode() >= 15 )
|
||||
{
|
||||
rContext->Insert( PROP_PARA_WORD_SPACING_MINIMUM, uno::Any( sal_uInt16(75) ) );
|
||||
rContext->Insert( PROP_PARA_WORD_SPACING_MAXIMUM, uno::Any( sal_uInt16(133) ) );
|
||||
}
|
||||
break;
|
||||
case NS_ooxml::LN_Value_ST_Jc_lowKashida:
|
||||
nAdjust = style::ParagraphAdjust_BLOCK;
|
||||
|
||||
Reference in New Issue
Block a user