tdf#149365 tdf#149214 handle IVS and mix width chars
in Justify::SnapToGrid correctly, and use it in
SwFntObj::GetTextBreak() and SwFntObj::GetModelPositionForViewPoint()
to get correct text positions before any further calculation.
In fntcache.cxx a simple formula was used to calculate the average
width of text grids occupied by ideographs. That's incorrect because 1)
for strings containing Unicode IVS, a ideograph is composed of several
sal_Unicode and the length of the string is not the same as the number
of ideographs contained. 2) The average width doesn't work because
character width varies, like ideograph and half-width kana in the
test case.
Change-Id: I863e8d8e346f555ff184a2f47d615e513b965b34
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135342
Tested-by: Jenkins
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
diff --git a/sw/qa/core/txtnode/justify.cxx b/sw/qa/core/txtnode/justify.cxx
index ec63c2b..8f8956f 100644
--- a/sw/qa/core/txtnode/justify.cxx
+++ b/sw/qa/core/txtnode/justify.cxx
@@ -121,8 +121,37 @@ CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSnapToGrid)
1360, 1040, 1200, 1200, 1200, 1200, 1200, 1200, 1040, 1360, 1200, 1040
};
aActual.InvokeWithKernArray(
[&] { nDelta = Justify::SnapToGrid(aActual.maArray, aText, 0, 12, 400, 14400); });
[&] { nDelta = Justify::SnapToGrid(aActual.maArray, aText, 0, 12, 400, false); });
CPPUNIT_ASSERT_EQUAL(aExpected, aActual);
CPPUNIT_ASSERT_EQUAL(tools::Long(160), nDelta);
}
CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSnapToGridMixWidth)
{
// Related to: tdf#149365
tools::Long nDelta = 0;
// "中中中ケコサシスセソカケコ" ( mixing fullwidth ideograph and half-width kana )
static const OUStringLiteral aText
= u"\u4e2d\u4e2d\u4e2d\uff79\uff7a\uff7b\uff7c\uff7d\uff7e\uff7f\uff76\uff79\uff7a";
CharWidthArray aActual{ 640, 640, 640, 320, 320, 320, 320, 320, 320, 320, 320, 320, 320 };
CharWidthArray aExpected{ 800, 800, 760, 400, 400, 400, 400, 400, 400, 400, 400, 400, 360 };
aActual.InvokeWithKernArray(
[&] { nDelta = Justify::SnapToGrid(aActual.maArray, aText, 0, 13, 400, false); });
CPPUNIT_ASSERT_EQUAL(aExpected, aActual);
CPPUNIT_ASSERT_EQUAL(tools::Long(80), nDelta);
}
CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSnapToGridIVS)
{
// Related to: tdf#149214
tools::Long nDelta = 0;
static const OUStringLiteral aText = u"\u9053\u9ad8\u4e00\U000E01E2\u5c3a\u5316";
CharWidthArray aActual{ 800, 800, 800, 0, 0, 800, 800 };
CharWidthArray aExpected{ 800, 800, 800, 0, 0, 800, 800 };
aActual.InvokeWithKernArray(
[&] { nDelta = Justify::SnapToGrid(aActual.maArray, aText, 0, 7, 400, false); });
CPPUNIT_ASSERT_EQUAL(aExpected, aActual);
CPPUNIT_ASSERT_EQUAL(tools::Long(0), nDelta);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/fntcache.cxx b/sw/source/core/txtnode/fntcache.cxx
index 8036af0..5596331 100644
--- a/sw/source/core/txtnode/fntcache.cxx
+++ b/sw/source/core/txtnode/fntcache.cxx
@@ -951,7 +951,7 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
tools::Long nDelta
= Justify::SnapToGrid(aKernArray, rInf.GetText(), sal_Int32(rInf.GetIdx()),
sal_Int32(rInf.GetLen()), nGridWidth, rInf.GetWidth());
sal_Int32(rInf.GetLen()), nGridWidth, false);
if (nDelta)
aTextOriginPos.AdjustX(nDelta);
@@ -1703,13 +1703,12 @@ Size SwFntObj::GetTextSize( SwDrawTextInfo& rInf )
aTextSize.setHeight( pOutDev->GetTextHeight() +
GetFontLeading( rInf.GetShell(), rInf.GetOut() ) );
tools::Long nAvgWidthPerChar = aTextSize.Width() / sal_Int32(nLn);
std::vector<sal_Int32> aKernArray;
GetTextArray(*pOutDev, rInf, aKernArray, sal_Int32(rInf.GetLen()));
Justify::SnapToGrid(aKernArray, rInf.GetText(), sal_Int32(rInf.GetIdx()),
sal_Int32(rInf.GetLen()), nGridWidth, true);
const sal_uLong i = nAvgWidthPerChar ?
( nAvgWidthPerChar - 1 ) / nGridWidth + 1:
1;
aTextSize.setWidth(i * nGridWidth * sal_Int32(nLn));
aTextSize.setWidth(aKernArray[sal_Int32(rInf.GetLen()) - 1]);
rInf.SetKanaDiff( 0 );
return aTextSize;
}
@@ -1842,6 +1841,23 @@ TextFrameIndex SwFntObj::GetModelPositionForViewPoint(SwDrawTextInfo &rInf)
GetTextArray(rInf.GetOut(), rInf, aKernArray);
}
if ( rInf.GetFrame() && rInf.GetLen() && rInf.SnapToGrid() &&
rInf.GetFont() && SwFontScript::CJK == rInf.GetFont()->GetActual() )
{
SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame()));
if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && pGrid->IsSnapToChars() )
{
const SwDoc* pDoc = rInf.GetShell()->GetDoc();
const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, *pDoc);
Justify::SnapToGrid(aKernArray, rInf.GetText(), sal_Int32(rInf.GetIdx()),
sal_Int32(rInf.GetLen()), nGridWidth, true);
return TextFrameIndex(Justify::GetModelPosition(aKernArray, sal_Int32(rInf.GetLen()),
rInf.GetOffset()));
}
}
const SwScriptInfo* pSI = rInf.GetScriptInfo();
if ( rInf.GetFont() && rInf.GetLen() )
{
@@ -1909,32 +1925,6 @@ TextFrameIndex SwFntObj::GetModelPositionForViewPoint(SwDrawTextInfo &rInf)
tools::Long nSpaceSum = 0;
tools::Long nKernSum = 0;
if ( rInf.GetFrame() && rInf.GetLen() && rInf.SnapToGrid() &&
rInf.GetFont() && SwFontScript::CJK == rInf.GetFont()->GetActual() )
{
SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame()));
if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && pGrid->IsSnapToChars() )
{
const SwDoc* pDoc = rInf.GetShell()->GetDoc();
const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, *pDoc);
tools::Long nAvgWidthPerChar = aKernArray[sal_Int32(rInf.GetLen()) - 1] / sal_Int32(rInf.GetLen());
sal_uLong i = nAvgWidthPerChar ?
( nAvgWidthPerChar - 1 ) / nGridWidth + 1:
1;
nAvgWidthPerChar = i * nGridWidth;
// stupid CLANG
nCnt = TextFrameIndex(rInf.GetOffset() / nAvgWidthPerChar);
if (2 * (rInf.GetOffset() - sal_Int32(nCnt) * nAvgWidthPerChar) > nAvgWidthPerChar)
++nCnt;
return nCnt;
}
}
//for textgrid refactor
if ( rInf.GetFrame() && rInf.GetLen() && rInf.SnapToGrid() &&
rInf.GetFont() && SwFontScript::CJK == rInf.GetFont()->GetActual() )
@@ -2152,20 +2142,11 @@ TextFrameIndex SwFont::GetTextBreak(SwDrawTextInfo const & rInf, tools::Long nTe
GetTextArray( rInf.GetOut(), rInf.GetText(), aKernArray,
sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
tools::Long nAvgWidthPerChar = aKernArray[sal_Int32(rInf.GetLen()) - 1] / sal_Int32(rInf.GetLen());
Justify::SnapToGrid(aKernArray, rInf.GetText(), sal_Int32(rInf.GetIdx()),
sal_Int32(rInf.GetLen()), nGridWidth, true);
const sal_uLong i = nAvgWidthPerChar ?
( nAvgWidthPerChar - 1 ) / nGridWidth + 1:
1;
nAvgWidthPerChar = i * nGridWidth;
tools::Long nCurrPos = nAvgWidthPerChar;
while( nTextBreak < rInf.GetLen() && nTextWidth >= nCurrPos )
{
nCurrPos += nAvgWidthPerChar;
while(nTextBreak < rInf.GetLen() && aKernArray[sal_Int32(nTextBreak)] <= nTextWidth)
++nTextBreak;
}
return nTextBreak + rInf.GetIdx();
}
diff --git a/sw/source/core/txtnode/justify.cxx b/sw/source/core/txtnode/justify.cxx
index 0d97ed4..f9a9a27 100644
--- a/sw/source/core/txtnode/justify.cxx
+++ b/sw/source/core/txtnode/justify.cxx
@@ -37,10 +37,66 @@ IdeographicPunctuationClass lcl_WhichPunctuationClass(sal_Unicode cChar)
return IdeographicPunctuationClass::OPEN_BRACKET;
}
tools::Long lcl_MinGridWidth(tools::Long nGridWidth, tools::Long nCharWidth)
{
tools::Long nCount = nCharWidth > nGridWidth ? (nCharWidth - 1) / nGridWidth + 1 : 1;
return nCount * nGridWidth;
}
tools::Long lcl_OffsetFromGridEdge(tools::Long nMinWidth, tools::Long nCharWidth, sal_Unicode cChar,
bool bForceLeft)
{
if (bForceLeft)
return 0;
tools::Long nOffset = 0;
switch (lcl_WhichPunctuationClass(cChar))
{
case IdeographicPunctuationClass::NONE:
// Centered
nOffset = (nMinWidth - nCharWidth) / 2;
break;
case IdeographicPunctuationClass::OPEN_BRACKET:
// Align to next edge, closer to next ideograph
nOffset = nMinWidth - nCharWidth;
break;
default:
// CLOSE_BRACKET ro COMMA_OR_FULLSTOP:
// Align to previous edge, closer to previous ideograph.
break;
}
return nOffset;
}
}
namespace Justify
{
sal_Int32 GetModelPosition(const std::vector<sal_Int32>& rKernArray, sal_Int32 nLen, tools::Long nX)
{
tools::Long nLeft = 0, nRight = 0;
sal_Int32 nLast = 0, nIdx = 0;
do
{
nRight = rKernArray[nLast];
++nIdx;
while (nIdx < nLen && rKernArray[nIdx] == rKernArray[nLast])
++nIdx;
if (nIdx < nLen)
{
if (nX < nRight)
return (nX - nLeft < nRight - nX) ? nLast : nIdx;
nLeft = nRight;
nLast = nIdx;
}
} while (nIdx < nLen);
return nIdx;
}
void SpaceDistribution(std::vector<sal_Int32>& rKernArray, const OUString& rText, sal_Int32 nStt,
sal_Int32 nLen, tools::Long nSpaceAdd, tools::Long nKern, bool bNoHalfSpace)
{
@@ -114,92 +170,35 @@ void SpaceDistribution(std::vector<sal_Int32>& rKernArray, const OUString& rText
}
tools::Long SnapToGrid(std::vector<sal_Int32>& rKernArray, const OUString& rText, sal_Int32 nStt,
sal_Int32 nLen, tools::Long nGridWidth, tools::Long nWidth)
sal_Int32 nLen, tools::Long nGridWidth, bool bForceLeft)
{
assert(nStt + nLen <= rText.getLength());
assert(nLen <= sal_Int32(rKernArray.size()));
tools::Long nDelta = 0; // delta offset to text origin
// Change the average width per character to an appropriate grid width
// basically get the ratio of the avg width to the grid unit width, then
// multiple this ratio to give the new avg width - which in this case
// gives a new grid width unit size
tools::Long nAvgWidthPerChar = rKernArray[nLen - 1] / nLen;
const sal_uLong nRatioAvgWidthCharToGridWidth
= nAvgWidthPerChar ? (nAvgWidthPerChar - 1) / nGridWidth + 1 : 1;
nAvgWidthPerChar = nRatioAvgWidthCharToGridWidth * nGridWidth;
// the absolute end position of the first character is also its width
tools::Long nCharWidth = rKernArray[0];
sal_uLong nHalfWidth = nAvgWidthPerChar / 2;
tools::Long nMinWidth = lcl_MinGridWidth(nGridWidth, nCharWidth);
tools::Long nDelta = lcl_OffsetFromGridEdge(nMinWidth, nCharWidth, rText[nStt], bForceLeft);
tools::Long nEdge = nMinWidth - nDelta;
tools::Long nNextFix = 0;
sal_Int32 nLast = 0;
// we work out the start position (origin) of the first character,
// and we set the next "fix" offset to half the width of the char.
// The exceptions are for punctuation characters that are not centered
// so in these cases we just add half a regular "average" character width
// to the first characters actual width to allow the next character to
// be centered automatically
// If the character is "special right", then the offset is correct already
// so the fix offset is as normal - half the average character width
sal_Unicode cChar = rText[nStt];
IdeographicPunctuationClass eClass = lcl_WhichPunctuationClass(cChar);
switch (eClass)
for (sal_Int32 i = 1; i < nLen; ++i)
{
case IdeographicPunctuationClass::NONE:
// Centered
nDelta = (nAvgWidthPerChar - nCharWidth) / 2;
nNextFix = nCharWidth / 2;
break;
case IdeographicPunctuationClass::CLOSE_BRACKET:
case IdeographicPunctuationClass::COMMA_OR_FULLSTOP:
// Closer to previous ideograph
nNextFix = nHalfWidth;
break;
default:
// case IdeographicPunctuationClass::OPEN_BRACKET: closer to next ideograph.
nDelta = nAvgWidthPerChar - nCharWidth;
nNextFix = nCharWidth - nHalfWidth;
if (rKernArray[i] == rKernArray[nLast])
continue;
nCharWidth = rKernArray[i] - rKernArray[nLast];
nMinWidth = lcl_MinGridWidth(nGridWidth, nCharWidth);
tools::Long nX
= nEdge + lcl_OffsetFromGridEdge(nMinWidth, nCharWidth, rText[nStt + i], bForceLeft);
nEdge += nMinWidth;
while (nLast < i)
rKernArray[nLast++] = nX;
}
// calculate offsets
for (sal_Int32 j = 1; j < nLen; ++j)
{
tools::Long nCurrentCharWidth = rKernArray[j] - rKernArray[j - 1];
nNextFix += nAvgWidthPerChar;
// almost the same as getting the offset for the first character:
// punctuation characters are not centered, so just add half an
// average character width minus the characters actual char width
// to get the offset into the centre of the next character
cChar = rText[nStt + j];
eClass = lcl_WhichPunctuationClass(cChar);
switch (eClass)
{
case IdeographicPunctuationClass::NONE:
// Centered
rKernArray[j - 1] = nNextFix - (nCurrentCharWidth / 2);
break;
case IdeographicPunctuationClass::CLOSE_BRACKET:
case IdeographicPunctuationClass::COMMA_OR_FULLSTOP:
// Closer to previous ideograph
rKernArray[j - 1] = nNextFix - nHalfWidth;
break;
default:
// case IdeographicPunctuationClass::OPEN_BRACKET: closer to next ideograph.
rKernArray[j - 1] = nNextFix + nHalfWidth - nCurrentCharWidth;
}
}
// the layout engine requires the total width of the output
rKernArray[nLen - 1] = nWidth - nDelta;
while (nLast < nLen)
rKernArray[nLast++] = nEdge;
return nDelta;
}
diff --git a/sw/source/core/txtnode/justify.hxx b/sw/source/core/txtnode/justify.hxx
index 157fe95..25ba4fe 100644
--- a/sw/source/core/txtnode/justify.hxx
+++ b/sw/source/core/txtnode/justify.hxx
@@ -12,6 +12,12 @@
namespace Justify
{
/// Get model position base on given kern array.
/// @param rKernArray text positions from OutDev::GetTextArray().
/// @param nLen number of elements to process in rKernArray.
/// @param nX the visual position
SW_DLLPUBLIC sal_Int32 GetModelPosition(const std::vector<sal_Int32>& rKernArray, sal_Int32 nLen,
tools::Long nX);
/// Distribute space between words and letters.
/// @param[in,out] rKernArray text positions from OutDev::GetTextArray().
/// @param rText string used to determine where space and kern are inserted.
@@ -38,11 +44,12 @@ SW_DLLPUBLIC void SpaceDistribution(std::vector<sal_Int32>& rKernArray, const OU
/// @param nStt starting index of rText.
/// @param nLen number of elements to process in rKernArray and rText.
/// @param nGridWidth width of a text grid
/// @param nWidth width of the whole portion.
/// @param bForceLeft for align to the left edge of the grid disregard of the punctuation type.
/// This is useful for calculate text width, line break, and conversion model position.
/// @return the delta offset of first glyph so text origin can be updated accordingly.
SW_DLLPUBLIC tools::Long SnapToGrid(std::vector<sal_Int32>& rKernArray, const OUString& rText,
sal_Int32 nStt, sal_Int32 nLen, tools::Long nGridWidth,
tools::Long nWidth);
bool bForceLeft);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */