sw/qa/core/txtnode/justify.cxx      |   31 +++++++
 sw/source/core/txtnode/fntcache.cxx |   71 ++++++-----------
 sw/source/core/txtnode/justify.cxx  |  149 +++++++++++++++++-------------------
 sw/source/core/txtnode/justify.hxx  |   11 ++
 4 files changed, 139 insertions(+), 123 deletions(-)

New commits:
commit 7f310b8c2d8d496b38dd62aa6b06e61442e1a2d0
Author:     Mark Hung <mark...@gmail.com>
AuthorDate: Mon May 30 08:18:16 2022 +0800
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Tue Jun 7 08:51:58 2022 +0200

    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 <vmik...@collabora.com>

diff --git a/sw/qa/core/txtnode/justify.cxx b/sw/qa/core/txtnode/justify.cxx
index ec63c2b8c5a4..8f8956f5a736 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 8036af0e56c5..559633126cf8 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);
-
-            const sal_uLong i = nAvgWidthPerChar ?
-                            ( nAvgWidthPerChar - 1 ) / nGridWidth + 1:
-                            1;
+            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);
 
-            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 0d97ed470f96..f9a9a271a3be 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 nNextFix = 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)
-    {
-        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;
-    }
+    tools::Long nMinWidth = lcl_MinGridWidth(nGridWidth, nCharWidth);
+    tools::Long nDelta = lcl_OffsetFromGridEdge(nMinWidth, nCharWidth, 
rText[nStt], bForceLeft);
+    tools::Long nEdge = nMinWidth - nDelta;
+
+    sal_Int32 nLast = 0;
 
-    // calculate offsets
-    for (sal_Int32 j = 1; j < nLen; ++j)
+    for (sal_Int32 i = 1; i < nLen; ++i)
     {
-        tools::Long nCurrentCharWidth = rKernArray[j] - rKernArray[j - 1];
-        nNextFix += nAvgWidthPerChar;
+        if (rKernArray[i] == rKernArray[nLast])
+            continue;
 
-        // 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
+        nCharWidth = rKernArray[i] - rKernArray[nLast];
+        nMinWidth = lcl_MinGridWidth(nGridWidth, nCharWidth);
+        tools::Long nX
+            = nEdge + lcl_OffsetFromGridEdge(nMinWidth, nCharWidth, rText[nStt 
+ i], bForceLeft);
+        nEdge += nMinWidth;
 
-        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;
-        }
+        while (nLast < i)
+            rKernArray[nLast++] = nX;
     }
 
-    // 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 157fe95d2d4b..25ba4fe78fd3 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: */

Reply via email to