sw/qa/core/uwriter.cxx              |   14 -
 sw/qa/extras/uiwriter/uiwriter9.cxx |   41 ----
 sw/source/core/inc/scriptinfo.hxx   |  103 +----------
 sw/source/core/text/itradj.cxx      |  283 +++++--------------------------
 sw/source/core/text/porlay.cxx      |  327 +-----------------------------------
 sw/source/core/text/porlay.hxx      |   14 -
 sw/source/core/text/portxt.cxx      |   15 -
 sw/source/core/txtnode/fntcache.cxx |   24 +-
 sw/source/core/txtnode/justify.cxx  |   40 ++++
 sw/source/core/txtnode/justify.hxx  |   12 +
 10 files changed, 162 insertions(+), 711 deletions(-)

New commits:
commit 5c30681cab383ffe1a67101fad54bc084613a4fb
Author:     Jonathan Clark <jonat...@libreoffice.org>
AuthorDate: Mon Mar 3 09:51:39 2025 -0700
Commit:     Adolfo Jayme Barrientos <fit...@ubuntu.com>
CommitDate: Thu Mar 6 21:19:22 2025 +0100

    tdf#165540 sw: Fix kashida insertion position data corruption
    
    This change greatly simplifies the Writer kashida justification
    implementation. As part of this change, a number of string index
    invalidation bugs have been fixed by virtue of the implementation no
    longer needing to store or reference those indices.
    
    Change-Id: Ibd96963c98cc74462ae55501c0a0a1b944d8bcc0
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182507
    Tested-by: Jenkins
    Reviewed-by: Jonathan Clark <jonat...@libreoffice.org>
    (cherry picked from commit ef31a26abc5ff4851be5360fd8ecd325132c5592)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182587
    Reviewed-by: Adolfo Jayme Barrientos <fit...@ubuntu.com>

diff --git a/sw/qa/core/uwriter.cxx b/sw/qa/core/uwriter.cxx
index 4f08ced0995e..b70e19e7ddb4 100644
--- a/sw/qa/core/uwriter.cxx
+++ b/sw/qa/core/uwriter.cxx
@@ -131,7 +131,6 @@ public:
     void test64kPageDescs();
     void testTdf92308();
     void testTableCellComparison();
-    void testTdf156211();
     void testFillRubyList();
     void testSetRubyList();
 
@@ -171,7 +170,6 @@ public:
     CPPUNIT_TEST(test64kPageDescs);
     CPPUNIT_TEST(testTdf92308);
     CPPUNIT_TEST(testTableCellComparison);
-    CPPUNIT_TEST(testTdf156211);
     CPPUNIT_TEST(testFillRubyList);
     CPPUNIT_TEST(testSetRubyList);
     CPPUNIT_TEST_SUITE_END();
@@ -1975,18 +1973,6 @@ void SwDocTest::tearDown()
     BootstrapFixture::tearDown();
 }
 
-void SwDocTest::testTdf156211()
-{
-    SwScriptInfo oSI;
-    oSI.SetNoKashidaLine(TextFrameIndex{ 89 }, TextFrameIndex{ 95 });
-
-    CPPUNIT_ASSERT(!oSI.IsKashidaLine(TextFrameIndex{ 95 }));
-
-    oSI.ClearNoKashidaLines();
-
-    CPPUNIT_ASSERT(oSI.IsKashidaLine(TextFrameIndex{ 95 }));
-}
-
 void SwDocTest::testFillRubyList()
 {
     SwNodeIndex aIdx(m_pDoc->GetNodes().GetEndOfContent(), -1);
diff --git a/sw/qa/extras/uiwriter/uiwriter9.cxx 
b/sw/qa/extras/uiwriter/uiwriter9.cxx
index 382089d01256..06714fd6d79d 100644
--- a/sw/qa/extras/uiwriter/uiwriter9.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter9.cxx
@@ -1051,44 +1051,17 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf164140)
         = 
dynamic_cast<SwTextFrame&>(*pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower());
     const SwScriptInfo* pSI = pTextFrame.GetScriptInfo();
 
-    // Prior to editing, the three complete lines should be flagged as 
no-kashida:
-    auto stBeforeLines = pSI->GetNoKashidaLines();
-
-    CPPUNIT_ASSERT_EQUAL(size_t(4), stBeforeLines.size());
-    auto stBeforeIt = stBeforeLines.begin();
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), std::get<0>(*stBeforeIt));
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(88), std::get<1>(*stBeforeIt));
-    ++stBeforeIt;
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(88), std::get<0>(*stBeforeIt));
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(180), std::get<1>(*stBeforeIt));
-    ++stBeforeIt;
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(180), std::get<0>(*stBeforeIt));
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(269), std::get<1>(*stBeforeIt));
-    ++stBeforeIt;
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(269), std::get<0>(*stBeforeIt));
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(312), std::get<1>(*stBeforeIt));
+    // Prior to editing, there should be no kashida
+    auto stBeforeKashida = pSI->GetKashidaPositions();
+    CPPUNIT_ASSERT_EQUAL(size_t(0), stBeforeKashida.size());
 
     // Insert text at the beginning of the document
     pWrtShell->Insert(u"A"_ustr);
 
-    // After editing, the three complete lines should still be flagged as 
no-kashida
-    auto stAfterLines = pSI->GetNoKashidaLines();
-
-    // Without the fix, this will be 2
-    CPPUNIT_ASSERT_EQUAL(size_t(4), stAfterLines.size());
-
-    auto stAfterIt = stAfterLines.begin();
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), std::get<0>(*stAfterIt));
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(89), std::get<1>(*stAfterIt));
-    ++stAfterIt;
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(89), std::get<0>(*stAfterIt));
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(181), std::get<1>(*stAfterIt));
-    ++stAfterIt;
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(181), std::get<0>(*stAfterIt));
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(270), std::get<1>(*stAfterIt));
-    ++stAfterIt;
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(270), std::get<0>(*stAfterIt));
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(313), std::get<1>(*stAfterIt));
+    // After editing, there should still be no room for kashida
+    // Without the fix, this will be non-zero
+    auto stAfterKashida = pSI->GetKashidaPositions();
+    CPPUNIT_ASSERT_EQUAL(size_t(0), stAfterKashida.size());
 }
 
 } // end of anonymous namespace
diff --git a/sw/source/core/inc/scriptinfo.hxx 
b/sw/source/core/inc/scriptinfo.hxx
index 77344ba7a335..2b249033b568 100644
--- a/sw/source/core/inc/scriptinfo.hxx
+++ b/sw/source/core/inc/scriptinfo.hxx
@@ -66,11 +66,7 @@ private:
         DirectionChangeInfo(TextFrameIndex pos, sal_uInt8 typ) : 
position(pos), type(typ) {};
     };
     std::vector<DirectionChangeInfo> m_DirectionChanges;
-    std::deque<TextFrameIndex> m_Kashida;
-    /// indexes into m_Kashida
-    std::unordered_set<size_t> m_KashidaInvalid;
-    std::deque<TextFrameIndex> m_NoKashidaLine;
-    std::deque<TextFrameIndex> m_NoKashidaLineEnd;
+    std::vector<TextFrameIndex> m_Kashida;
     std::vector<TextFrameIndex> m_HiddenChg;
     std::vector<std::tuple<TextFrameIndex, MarkKind, Color, OUString, 
OUString>> m_Bookmarks;
     //! Records a single change in compression.
@@ -88,13 +84,8 @@ private:
 
     TextFrameIndex m_nInvalidityPos;
     sal_uInt8 m_nDefaultDir;
+    bool m_bParagraphContainsKashidaScript = false;
 
-    bool IsKashidaValid(size_t nKashPos) const;
-    // returns true if nKashPos is newly marked invalid
-    bool MarkKashidaInvalid(size_t nKashPos);
-    void ClearKashidaInvalid(size_t nKashPos);
-    bool MarkOrClearKashidaInvalid(TextFrameIndex nStt, TextFrameIndex nLen,
-            bool bMark, sal_Int32 nMarkCount);
     // examines the range [ nStart, nStart + nEnd ] if there are kanas
     // returns start index of kana entry in array, otherwise SAL_MAX_SIZE
     size_t HasKana(TextFrameIndex nStart, TextFrameIndex nEnd) const;
@@ -149,16 +140,7 @@ public:
         return m_DirectionChanges[ nCnt ].type;
     }
 
-    size_t CountKashida() const
-    {
-        return m_Kashida.size();
-    }
-
-    TextFrameIndex GetKashida(const size_t nCnt) const
-    {
-        assert(nCnt < m_Kashida.size());
-        return m_Kashida[nCnt];
-    }
+    bool ParagraphContainsKashidaScript() const { return 
m_bParagraphContainsKashidaScript; }
 
     size_t CountCompChg() const { return m_CompressionChanges.size(); };
     TextFrameIndex GetCompStart(const size_t nCnt) const
@@ -277,85 +259,20 @@ public:
                    const bool bCentered,
                    Point* pPoint = nullptr ) const;
 
-/** Performs a kashida justification on the kerning array
-
-    @descr  Add some extra space for kashida justification to the
-            positions in the kerning array.
-    @param  pKernArray
-                The printers kerning array. Optional.
-    @param  nStt
-                Start referring to the paragraph.
-    @param  nLen
-                The number of characters to be considered.
-    @param  nSpaceAdd
-                The value which has to be added to a kashida opportunity.
-    @return The number of kashida opportunities in the given range
+/** retrieves kashida opportunities for the paragraph.
 */
-    sal_Int32 KashidaJustify( KernArray* pKernArray, sal_Bool* pKashidaArray,
-          TextFrameIndex nStt, TextFrameIndex nLen, tools::Long nSpaceAdd = 0) 
const;
+    std::span<TextFrameIndex const> GetKashidaPositions() const { return 
m_Kashida; }
 
-/** Clears array of kashidas marked as invalid
- */
-    void ClearKashidaInvalid() { m_KashidaInvalid.clear(); }
-
-/** Marks nCnt kashida positions as invalid
-   pKashidaPositions: array of char indices relative to the paragraph
+/** returns the count of kashida opportunities for a substring.
 */
-    void MarkKashidasInvalid(sal_Int32 nCnt, const TextFrameIndex* 
pKashidaPositions);
+    tools::Long CountKashidaPositions(TextFrameIndex nIdx, TextFrameIndex 
nEnd) const;
 
-/** Marks nCnt kashida positions as invalid
-    in the given text range
- */
-    bool MarkKashidasInvalid(sal_Int32 const nCnt,
-            TextFrameIndex const nStt, TextFrameIndex const nLen)
-    {
-        return MarkOrClearKashidaInvalid(nStt, nLen, true, nCnt);
-    }
-
-/** retrieves kashida opportunities for a given text range.
-
-   rKashidaPositions: buffer to receive the char indices of the
-                      kashida opportunities relative to the paragraph
-*/
-    void GetKashidaPositions(TextFrameIndex nStt, TextFrameIndex nLen,
-                             std::vector<TextFrameIndex>& rKashidaPosition);
-
-/** replaces kashida opportunities for a given text range.
+/** replaces kashida opportunities for the paragraph
 
-   rKashidaPositions: buffer containing char indices of the
+   aKashidaPositions: buffer containing char indices of the
                       kashida opportunities relative to the paragraph
 */
-    void ReplaceKashidaPositions(TextFrameIndex nStt, TextFrameIndex nEnd,
-                                 const std::vector<TextFrameIndex>& 
rKashidaPositions);
-
-/** Use regular blank justification instead of kashdida justification for the 
given line of text.
-   nStt Start char index of the line referring to the paragraph.
-   nLen Number of characters in the line
-*/
-    void SetNoKashidaLine(TextFrameIndex nStt, TextFrameIndex nLen);
-
-/** Clear all forced blank justification data for the paragraph.
-*/
-    void ClearNoKashidaLines();
-
-/** Checks whether the character is on a line excluded from kashida 
justification.
-   nCharIdx Char index within the paragraph.
-*/
-    bool IsKashidaLine(TextFrameIndex nCharIdx) const;
-
-/** Returns an ordered copy of the no kashida lines, for testing purposes.
-*/
-    std::set<std::tuple<sal_Int32, sal_Int32>> GetNoKashidaLines() const
-    {
-        std::set<std::tuple<sal_Int32, sal_Int32>> stValue;
-        for (size_t nLine = 0; nLine < m_NoKashidaLine.size(); ++nLine)
-        {
-            stValue.emplace(static_cast<sal_Int32>(m_NoKashidaLine.at(nLine)),
-                            
static_cast<sal_Int32>(m_NoKashidaLineEnd.at(nLine)));
-        }
-
-        return stValue;
-    }
+    void ReplaceKashidaPositions(std::vector<TextFrameIndex> 
aKashidaPositions);
 
 /** Checks if text is in a script that allows kashida justification.
 
diff --git a/sw/source/core/text/itradj.cxx b/sw/source/core/text/itradj.cxx
index a0836bb02170..c8abfee4868a 100644
--- a/sw/source/core/text/itradj.cxx
+++ b/sw/source/core/text/itradj.cxx
@@ -139,31 +139,27 @@ void SwTextAdjuster::FormatBlock( )
     GetInfo().GetParaPortion()->GetRepaint().SetOffset(0);
 }
 
-static bool lcl_CheckKashidaPositions(SwScriptInfo& rSI, SwTextSizeInfo& rInf, 
SwTextIter& rItr,
-                                      sal_Int32& rKashidas, TextFrameIndex& 
nGluePortion,
-                                      bool& rRemovedAllKashida, SwLineLayout* 
pCurrLine,
-                                      TextFrameIndex nCurrLineBase)
+static bool lcl_ComputeKashidaPositions(SwTextSizeInfo& rInf, SwTextIter& rItr,
+                                        TextFrameIndex& nGluePortion,
+                                        const tools::Long nGluePortionWidth,
+                                        SwLineLayout* pCurrLine, 
TextFrameIndex nLineBaseIndex)
 {
-    rRemovedAllKashida = true;
-
     // i60594 validate Kashida justification
     TextFrameIndex nIdx = rItr.GetStart();
     TextFrameIndex nEnd = rItr.GetEnd();
 
-    // Get the initial kashida position set, for invalidation
-    std::vector<TextFrameIndex> aOldKashidaPositions;
-    rSI.GetKashidaPositions(nIdx, rItr.GetLength(), aOldKashidaPositions);
-
-    std::vector<TextFrameIndex> aNewKashidaPositions;
-    std::vector<bool> aValidPositions;
+    std::vector<TextFrameIndex> aKashidaPositions;
+    std::vector<tools::Long> aKashidaWidths;
+    tools::Long nMaxKashidaWidth = 0;
 
-    // Reparse the text, and reapply the kashida insertion rules
+    // Parse the text, and apply the kashida insertion rules
     std::function<LanguageType(sal_Int32, sal_Int32, bool)> const 
pGetLangOfChar(
         [&rInf](sal_Int32 const nBegin, sal_uInt16 const nScript, bool const 
bNoChar)
         { return rInf.GetTextFrame()->GetLangOfChar(TextFrameIndex{ nBegin }, 
nScript, bNoChar); });
     SwScanner aScanner(pGetLangOfChar, rInf.GetText(), nullptr, 
ModelToViewHelper(),
                        i18n::WordType::DICTIONARY_WORD, sal_Int32(nIdx), 
sal_Int32(nEnd));
 
+    std::vector<bool> aValidPositions;
     while (aScanner.NextWord())
     {
         const OUString& rWord = aScanner.GetWord();
@@ -176,8 +172,14 @@ static bool lcl_CheckKashidaPositions(SwScriptInfo& rSI, 
SwTextSizeInfo& rInf, S
 
             rItr.SeekAndChgAttrIter(TextFrameIndex{ aScanner.GetBegin() }, 
rInf.GetRefDev());
 
+            // Kashida glyph looks suspicious, skip Kashida justification
+            auto nFontMinKashida = rInf.GetRefDev()->GetMinKashida();
+            if (nFontMinKashida <= 0)
+                continue;
+
             vcl::text::ComplexTextLayoutFlags nOldLayout = 
rInf.GetRefDev()->GetLayoutMode();
-            rInf.GetRefDev()->SetLayoutMode(nOldLayout | 
vcl::text::ComplexTextLayoutFlags::BiDiRtl);
+            rInf.GetRefDev()->SetLayoutMode(nOldLayout
+                                            | 
vcl::text::ComplexTextLayoutFlags::BiDiRtl);
 
             rInf.GetRefDev()->GetWordKashidaPositions(rWord, &aValidPositions);
 
@@ -193,180 +195,45 @@ static bool lcl_CheckKashidaPositions(SwScriptInfo& rSI, 
SwTextSizeInfo& rInf, S
                 // the best kashida candidate position is on the first line.
                 if (nNewKashidaPos >= nIdx && nNewKashidaPos < nEnd)
                 {
-                    aNewKashidaPositions.push_back(nNewKashidaPos);
+                    aKashidaPositions.push_back(nNewKashidaPos - 
nLineBaseIndex);
+                    aKashidaWidths.push_back(nFontMinKashida);
+                    nMaxKashidaWidth = std::max(nMaxKashidaWidth, 
nFontMinKashida);
                 }
             }
         }
     }
 
-    if (aOldKashidaPositions != aNewKashidaPositions)
-    {
-        // Kashida positions have changed; restart CalcNewBlock
-        rSI.ReplaceKashidaPositions(nIdx, nEnd, aNewKashidaPositions);
-        rRemovedAllKashida = aNewKashidaPositions.empty();
-        return false;
-    }
-
-    // Note on calling KashidaJustify():
-    // Kashida positions may be marked as invalid. Therefore KashidaJustify 
may return the clean
-    // total number of kashida positions, or the number of kashida positions 
after some positions
-    // have been dropped.
-    // Here we want the clean total, which is OK: We have called 
ClearKashidaInvalid() before.
-    rKashidas = rSI.KashidaJustify(nullptr, nullptr, rItr.GetStart(), 
rItr.GetLength());
-
-    if (rKashidas <= 0) // nothing to do
-        return true;
-
-    // kashida positions found in SwScriptInfo are not necessarily valid in 
every font
-    // if two characters are replaced by a ligature glyph, there will be no 
place for a kashida
-    assert(aNewKashidaPositions.size() >= o3tl::make_unsigned(rKashidas));
-
-    std::vector<sal_Int32> aKashidaPos;
-    std::transform(std::cbegin(aNewKashidaPositions), 
std::cend(aNewKashidaPositions),
-                   std::back_inserter(aKashidaPos),
-                   [](TextFrameIndex nPos) { return 
static_cast<sal_Int32>(nPos); });
+    nGluePortion += TextFrameIndex{ aKashidaPositions.size() };
 
-    std::vector<sal_Int32> aKashidaPosDropped;
+    // The line may not have enough extra space for all possible kashida.
+    // Remove them from the beginning of the line to the end.
+    std::reverse(aKashidaPositions.begin(), aKashidaPositions.end());
+    std::reverse(aKashidaWidths.begin(), aKashidaWidths.end());
 
-    sal_Int32 nKashidaIdx = 0;
-    while ( rKashidas && nIdx < nEnd )
+    while (nGluePortion && !aKashidaPositions.empty())
     {
-        rItr.SeekAndChgAttrIter(nIdx, rInf.GetRefDev());
-        TextFrameIndex nNext = rItr.GetNextAttr();
-
-        // is there also a script change before?
-        // if there is, nNext should point to the script change
-        TextFrameIndex const nNextScript = rSI.NextScriptChg( nIdx );
-        if( nNextScript < nNext )
-            nNext = nNextScript;
-
-        if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd)
-            nNext = nEnd;
-
-        // Use an expanded context to validate kashida insertions between spans
-        TextFrameIndex nWholeNext = nNextScript;
-        if (nWholeNext == TextFrameIndex(COMPLETE_STRING) || nWholeNext > nEnd)
+        tools::Long nSpaceAdd = nGluePortionWidth / sal_Int32(nGluePortion);
+        if (nSpaceAdd / SPACING_PRECISION_FACTOR >= nMaxKashidaWidth)
         {
-            nWholeNext = nEnd;
+            break;
         }
 
-        sal_Int32 nKashidasInAttr = rSI.KashidaJustify(nullptr, nullptr, nIdx, 
nNext - nIdx);
-        if (nKashidasInAttr > 0)
-        {
-            // Kashida glyph looks suspicious, skip Kashida justification
-            if (rInf.GetRefDev()->GetMinKashida() <= 0)
-            {
-                return false;
-            }
-
-            sal_Int32 nKashidasDropped = 0;
-            if (!SwScriptInfo::IsKashidaScriptText(rInf.GetText(), nIdx, nNext 
- nIdx))
-            {
-                nKashidasDropped = nKashidasInAttr;
-                rKashidas -= nKashidasDropped;
-            }
-            else
-            {
-                vcl::text::ComplexTextLayoutFlags nOldLayout = 
rInf.GetRefDev()->GetLayoutMode();
-                rInf.GetRefDev()->SetLayoutMode(nOldLayout
-                                                | 
vcl::text::ComplexTextLayoutFlags::BiDiRtl);
-                nKashidasDropped = rInf.GetRefDev()->ValidateKashidas(
-                    rInf.GetText(), /*nIdx=*/sal_Int32{ nIdx },
-                    /*nLen=*/sal_Int32{ nWholeNext - nIdx },
-                    /*nPartIdx=*/sal_Int32{ nIdx }, /*nPartLen=*/sal_Int32{ 
nNext - nIdx },
-                    std::span(aKashidaPos).subspan(nKashidaIdx, 
nKashidasInAttr),
-                    &aKashidaPosDropped);
-                rInf.GetRefDev()->SetLayoutMode(nOldLayout);
-                if ( nKashidasDropped )
-                {
-                    // Convert dropped kashida positions so they are relative 
to the line
-                    for (auto& rPos : aKashidaPosDropped)
-                    {
-                        rPos = rPos - static_cast<sal_Int32>(nCurrLineBase);
-                    }
+        aKashidaPositions.pop_back();
+        aKashidaWidths.pop_back();
 
-                    pCurrLine->AddInvalidKashida(aKashidaPosDropped);
-                    rKashidas -= nKashidasDropped;
-                    nGluePortion -= TextFrameIndex(nKashidasDropped);
-                }
-            }
-            nKashidaIdx += nKashidasInAttr;
+        nMaxKashidaWidth = 0;
+        if (!aKashidaWidths.empty())
+        {
+            nMaxKashidaWidth = *std::max_element(aKashidaWidths.begin(), 
aKashidaWidths.end());
         }
-        nIdx = nNext;
-    }
 
-    // return false if all kashidas have been eliminated
-    return (rKashidas > 0);
-}
+        --nGluePortion;
+    }
 
-static bool lcl_CheckKashidaWidth(SwScriptInfo& rSI, SwTextSizeInfo& rInf, 
SwTextIter& rItr,
-                                  sal_Int32& rKashidas, TextFrameIndex& 
nGluePortion,
-                                  const tools::Long nGluePortionWidth, 
tools::Long& nSpaceAdd,
-                                  SwLineLayout* pCurrLine, TextFrameIndex 
const nCurrLineBase)
-{
-    // check kashida width
-    // if width is smaller than minimal kashida width allowed by fonts in the 
current line
-    // drop one kashida after the other until kashida width is OK
-    while (rKashidas)
-    {
-        bool bAddSpaceChanged = false;
-        TextFrameIndex nIdx = rItr.GetStart();
-        TextFrameIndex nEnd = rItr.GetEnd();
-        while ( nIdx < nEnd )
-        {
-            rItr.SeekAndChgAttrIter(nIdx, rInf.GetRefDev());
-            TextFrameIndex nNext = rItr.GetNextAttr();
-
-            // is there also a script change before?
-            // if there is, nNext should point to the script change
-            TextFrameIndex const nNextScript = rSI.NextScriptChg( nIdx );
-            if( nNextScript < nNext )
-               nNext = nNextScript;
-
-            if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd)
-                nNext = nEnd;
-            sal_Int32 nKashidasInAttr = rSI.KashidaJustify(nullptr, nullptr, 
nIdx, nNext - nIdx);
-
-            tools::Long nFontMinKashida = rInf.GetRefDev()->GetMinKashida();
-            if (nFontMinKashida && nKashidasInAttr > 0
-                && SwScriptInfo::IsKashidaScriptText(rInf.GetText(), nIdx, 
nNext - nIdx))
-            {
-                sal_Int32 nKashidasDropped = 0;
-                while ( rKashidas && nGluePortion && nKashidasInAttr > 0 &&
-                        nSpaceAdd / SPACING_PRECISION_FACTOR < nFontMinKashida 
)
-                {
-                    --nGluePortion;
-                    --rKashidas;
-                    --nKashidasInAttr;
-                    ++nKashidasDropped;
-                    if( !rKashidas || !nGluePortion ) // nothing left, return 
false to
-                        return false;                 // do regular blank 
justification
-
-                    nSpaceAdd = nGluePortionWidth / sal_Int32(nGluePortion);
-                    bAddSpaceChanged = true;
-                }
+    std::reverse(aKashidaPositions.begin(), aKashidaPositions.end());
+    pCurrLine->SetKashida(std::move(aKashidaPositions));
 
-                // Remove nKashidasDropped kashida from nIdx to nNext:
-                for (size_t nKashIdx = 0; nKashidasDropped && nKashIdx < 
rSI.CountKashida();
-                     ++nKashIdx)
-                {
-                    TextFrameIndex nKashPos = rSI.GetKashida(nKashIdx);
-                    if (nKashPos >= nIdx && nKashPos < nNext)
-                    {
-                        pCurrLine->AddInvalidKashida(
-                            static_cast<sal_Int32>(nKashPos - nCurrLineBase));
-                        --nKashidasDropped;
-                    }
-                }
-            }
-            if ( bAddSpaceChanged )
-                break; // start all over again
-            nIdx = nNext;
-        }
-        if ( !bAddSpaceChanged )
-            break; // everything was OK
-    }
-    return true;
+    return !aKashidaWidths.empty();
 }
 
 // CalcNewBlock() must only be called _after_ CalcLine()!
@@ -390,26 +257,14 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent,
     SwTextIter aItr ( GetTextFrame(), &aInf );
 
     TextFrameIndex nLineBase{ 0 };
-
-    if ( rSI.CountKashida() )
+    if (rSI.ParagraphContainsKashidaScript())
     {
         while (aItr.GetCurr() != pCurrent && aItr.GetNext())
            aItr.Next();
 
         nLineBase = aItr.GetStart();
-
-        if( bSkipKashida )
-        {
-            rSI.SetNoKashidaLine ( aItr.GetStart(), aItr.GetLength());
-            pCurrent->SetKashidaAllowed(false);
-        }
-        else
-        {
-            rSI.ClearKashidaInvalid();
-            rSI.ClearNoKashidaLines();
-            pCurrent->SetKashidaAllowed(true);
-            pCurrent->ClearInvalidKashida();
-        }
+        rSI.ReplaceKashidaPositions({});
+        pCurrent->SetKashida({});
     }
 
     // Do not forget: CalcRightMargin() sets pCurrent->Width() to the line 
width!
@@ -482,20 +337,16 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent,
                 const tools::Long nGluePortionWidth = 
static_cast<SwGluePortion*>(pPos)->GetPrtGlue() *
                                                SPACING_PRECISION_FACTOR;
 
-                sal_Int32 nKashidas = 0;
-                if( nGluePortion && rSI.CountKashida() && !bSkipKashida )
+                if (rSI.ParagraphContainsKashidaScript() && !bSkipKashida)
                 {
-                    // kashida positions found in SwScriptInfo are not 
necessarily valid in every font
-                    // if two characters are replaced by a ligature glyph, 
there will be no place for a kashida
-                    bool bRemovedAllKashida = false;
-                    if (!lcl_CheckKashidaPositions(rSI, aInf, aItr, nKashidas, 
nGluePortion,
-                                                   bRemovedAllKashida, 
pCurrent, nLineBase))
+                    if (!lcl_ComputeKashidaPositions(aInf, aItr, nGluePortion, 
nGluePortionWidth,
+                                                     pCurrent, nLineBase))
                     {
-                        // all kashida positions are invalid
+                        // no kashidas left
                         // do regular blank justification
                         pCurrent->FinishSpaceAdd();
-                        GetInfo().SetIdx( m_nStart );
-                        CalcNewBlock(pCurrent, pStopAt, nReal, 
bRemovedAllKashida);
+                        GetInfo().SetIdx(m_nStart);
+                        CalcNewBlock(pCurrent, pStopAt, nReal, true);
                         return;
                     }
                 }
@@ -518,22 +369,6 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent,
                             ? -nSpaceAdd + LONG_MAX/2
                             : 0;
 
-                    // i60594
-                    if( rSI.CountKashida() && !bSkipKashida )
-                    {
-                        if (!lcl_CheckKashidaWidth(rSI, aInf, aItr, nKashidas, 
nGluePortion,
-                                                   nGluePortionWidth, 
nSpaceAdd, pCurrent,
-                                                   nLineBase))
-                        {
-                            // no kashidas left
-                            // do regular blank justification
-                            pCurrent->FinishSpaceAdd();
-                            GetInfo().SetIdx( m_nStart );
-                            CalcNewBlock( pCurrent, pStopAt, nReal, true );
-                            return;
-                        }
-                    }
-
                     pCurrent->SetLLSpaceAdd( nSpaceSub ? nSpaceSub : 
nSpaceAdd, nSpaceIdx );
                     pPos->Width( 
static_cast<SwGluePortion*>(pPos)->GetFixWidth() );
                 }
@@ -564,27 +399,20 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent,
         pPos = pPos->GetNextPortion();
     }
 
-    // tdf#164140: Rebuild kashida exclusion indices after line adjustment
-    if (rSI.CountKashida())
+    // tdf#164140: Rebuild kashida position indices after line adjustment
+    if (rSI.ParagraphContainsKashidaScript())
     {
-        rSI.ClearNoKashidaLines();
-        rSI.ClearKashidaInvalid();
+        std::vector<TextFrameIndex> aKashidaPositions;
 
         SwTextSizeInfo aKashInf(GetTextFrame());
         SwTextIter aKashItr(GetTextFrame(), &aKashInf);
 
-        std::vector<TextFrameIndex> aInvalidKashida;
         while (true)
         {
             const SwLineLayout* pCurrLine = aKashItr.GetCurr();
-            if (!pCurrLine->KashidaAllowed())
-            {
-                rSI.SetNoKashidaLine(aKashItr.GetStart(), 
aKashItr.GetLength());
-            }
-
-            for (auto nKashIdx : pCurrLine->GetInvalidKashida())
+            for (const auto& nPos : pCurrLine->GetKashida())
             {
-                aInvalidKashida.push_back(aKashItr.GetStart() + 
TextFrameIndex{ nKashIdx });
+                aKashidaPositions.push_back(nPos + aKashItr.GetStart());
             }
 
             if (!aKashItr.GetNextLine())
@@ -595,10 +423,7 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent,
             aKashItr.NextLine();
         }
 
-        if (!aInvalidKashida.empty())
-        {
-            rSI.MarkKashidasInvalid(aInvalidKashida.size(), 
aInvalidKashida.data());
-        }
+        rSI.ReplaceKashidaPositions(std::move(aKashidaPositions));
     }
 }
 
diff --git a/sw/source/core/text/porlay.cxx b/sw/source/core/text/porlay.cxx
index 6d80ba49ee58..25081d086629 100644
--- a/sw/source/core/text/porlay.cxx
+++ b/sw/source/core/text/porlay.cxx
@@ -791,7 +791,7 @@ void SwLineLayout::ResetFlags()
 {
     m_bFormatAdj = m_bDummy = m_bEndHyph = m_bMidHyph = m_bLastHyph = m_bFly = 
m_bRest = m_bBlinking
         = m_bClipping = m_bContent = m_bRedline = m_bRedlineEnd = 
m_bForcedLeftMargin = m_bHanging
-        = m_bKashidaAllowed = false;
+        = false;
     m_eRedlineEnd = RedlineType::None;
 }
 
@@ -1305,8 +1305,6 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode,
     size_t nCnt = 0;
     // counter for compression information arrays
     size_t nCntComp = 0;
-    // counter for kashida array
-    size_t nCntKash = 0;
 
     sal_Int16 nScript = i18n::ScriptType::LATIN;
 
@@ -1344,15 +1342,6 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& 
rNode,
                 nCntComp++;
             }
         }
-        if ( bAdjustBlock )
-        {
-            while( nCntKash < CountKashida() )
-            {
-                if ( nChg <= GetKashida( nCntKash ) )
-                    break;
-                nCntKash++;
-            }
-        }
     }
 
     // ADJUST nChg VALUE:
@@ -1397,17 +1386,6 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& 
rNode,
     m_CompressionChanges.erase(m_CompressionChanges.begin() + nCntComp,
             m_CompressionChanges.end());
 
-    // get the start of the last kashida group
-    TextFrameIndex nLastKashida = nChg;
-    if( nCntKash && i18n::ScriptType::COMPLEX == nScript )
-    {
-        --nCntKash;
-        nLastKashida = GetKashida( nCntKash );
-    }
-
-    // remove invalid entries from kashida array
-    m_Kashida.erase(m_Kashida.begin() + nCntKash, m_Kashida.end());
-
     // Construct the script change scanner and advance it to the change range
     auto pDirScanner = i18nutil::MakeDirectionChangeScanner(rText, 
m_nDefaultDir);
     auto pScriptScanner = i18nutil::MakeScriptChangeScanner(
@@ -1507,73 +1485,21 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& 
rNode,
                 }
             }
         }
-
-        // we search for connecting opportunities (kashida)
-        else if ( bAdjustBlock && i18n::ScriptType::COMPLEX == nScript )
+        else if (bAdjustBlock && i18n::ScriptType::COMPLEX == nScript)
         {
-            // sw_redlinehide: this is the only place that uses SwScanner with
-            // frame text, so we convert to sal_Int32 here
-            std::function<LanguageType (sal_Int32, sal_Int32, bool)> const 
pGetLangOfCharM(
-                [&pMerged](sal_Int32 const nBegin, sal_uInt16 const script, 
bool const bNoChar)
-                    {
-                        std::pair<SwTextNode const*, sal_Int32> const pos(
-                            sw::MapViewToModel(*pMerged, 
TextFrameIndex(nBegin)));
-                        return pos.first->GetLang(pos.second, bNoChar ? 0 : 1, 
script);
-                    });
-            std::function<LanguageType (sal_Int32, sal_Int32, bool)> const 
pGetLangOfChar1(
-                [&rNode](sal_Int32 const nBegin, sal_uInt16 const script, bool 
const bNoChar)
-                    { return rNode.GetLang(nBegin, bNoChar ? 0 : 1, script); 
});
-            auto pGetLangOfChar(pMerged ? pGetLangOfCharM : pGetLangOfChar1);
-            SwScanner aScanner( std::move(pGetLangOfChar), rText, nullptr, 
ModelToViewHelper(),
-                                i18n::WordType::DICTIONARY_WORD,
-                                sal_Int32(nLastKashida), sal_Int32(nChg));
-
-            // the search has to be performed on a per word base
-            while ( aScanner.NextWord() )
+            if (SwScriptInfo::IsKashidaScriptText(
+                    rText, TextFrameIndex{ stChange.m_nStartIndex },
+                    TextFrameIndex{ stChange.m_nEndIndex - 
stChange.m_nStartIndex }))
             {
-                if (SwScriptInfo::IsKashidaScriptText(rText, TextFrameIndex{ 
aScanner.GetBegin() },
-                                                      TextFrameIndex{ 
aScanner.GetLen() }))
-                {
-                    const OUString& rWord = aScanner.GetWord();
-                    auto stKashidaPos = 
i18nutil::GetWordKashidaPosition(rWord);
-
-                    if (stKashidaPos.has_value())
-                    {
-                        // Only populate kashida positions for the invalidated 
tail
-                        TextFrameIndex nNewKashidaPos{ aScanner.GetBegin() + 
stKashidaPos->nIndex };
-                        if (nNewKashidaPos >= nLastKashida)
-                        {
-                            m_Kashida.insert(m_Kashida.begin() + nCntKash, 
nNewKashidaPos);
-                            nCntKash++;
-                        }
-                    }
-                }
-            } // end of kashida search
+                m_bParagraphContainsKashidaScript = true;
+            }
         }
 
         if (nChg < TextFrameIndex(rText.getLength()))
             nScript = 
static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType(rText, 
sal_Int32(nChg)));
 
         nLastCompression = nChg;
-        nLastKashida = nChg;
-    }
-
-#if OSL_DEBUG_LEVEL > 0
-    // check kashida data
-    TextFrameIndex nTmpKashidaPos(-1);
-    bool bWrongKash = false;
-    for (size_t i = 0; i < m_Kashida.size(); ++i)
-    {
-        TextFrameIndex nCurrKashidaPos = GetKashida( i );
-        if ( nCurrKashidaPos <= nTmpKashidaPos )
-        {
-            bWrongKash = true;
-            break;
-        }
-        nTmpKashidaPos = nCurrKashidaPos;
     }
-    SAL_WARN_IF( bWrongKash, "sw.core", "Kashida array contains wrong data" );
-#endif
 
     // remove invalid entries from direction information arrays
     m_DirectionChanges.clear();
@@ -2041,96 +1967,6 @@ tools::Long SwScriptInfo::Compress(KernArray& 
rKernArray, TextFrameIndex nIdx, T
     return nSub;
 }
 
-// Note on calling KashidaJustify():
-// Kashida positions may be marked as invalid. Therefore KashidaJustify may 
return the clean
-// total number of kashida positions, or the number of kashida positions after 
some positions
-// have been dropped, depending on the state of the m_KashidaInvalid set.
-
-sal_Int32 SwScriptInfo::KashidaJustify( KernArray* pKernArray,
-                                        sal_Bool* pKashidaArray,
-                                        TextFrameIndex const nStt,
-                                        TextFrameIndex const nLen,
-                                        tools::Long nSpaceAdd ) const
-{
-    SAL_WARN_IF( !nLen, "sw.core", "Kashida justification without text?!" );
-
-    if( !IsKashidaLine(nStt))
-        return -1;
-
-    // evaluate kashida information in collected in SwScriptInfo
-
-    size_t nCntKash = 0;
-    while( nCntKash < CountKashida() )
-    {
-        if ( nStt <= GetKashida( nCntKash ) )
-            break;
-        ++nCntKash;
-    }
-
-    const TextFrameIndex nEnd = nStt + nLen;
-
-    size_t nCntKashEnd = nCntKash;
-    while ( nCntKashEnd < CountKashida() )
-    {
-        if ( nEnd <= GetKashida( nCntKashEnd ) )
-            break;
-        ++nCntKashEnd;
-    }
-
-    size_t nActualKashCount = nCntKashEnd - nCntKash;
-    for (size_t i = nCntKash; i < nCntKashEnd; ++i)
-    {
-        if ( nActualKashCount && !IsKashidaValid ( i ) )
-            --nActualKashCount;
-    }
-
-    if ( !pKernArray )
-        return nActualKashCount;
-
-    // do nothing if there is no more kashida
-    if ( nCntKash < CountKashida() )
-    {
-        // skip any invalid kashidas
-        while (nCntKash < nCntKashEnd && !IsKashidaValid(nCntKash))
-            ++nCntKash;
-
-        TextFrameIndex nIdx = nCntKash < nCntKashEnd && 
IsKashidaValid(nCntKash)
-            ? GetKashida(nCntKash)
-            : nEnd;
-        tools::Long nKashAdd = nSpaceAdd;
-
-        while ( nIdx < nEnd )
-        {
-            TextFrameIndex nArrayPos = nIdx - nStt;
-
-            // mark Kashida insertion positions, code in VCL will use this
-            // array to know where to insert Kashida.
-            if (pKashidaArray)
-                pKashidaArray[sal_Int32(nArrayPos)] = true;
-
-            // next kashida position
-            ++nCntKash;
-            while (nCntKash < nCntKashEnd && !IsKashidaValid(nCntKash))
-                ++nCntKash;
-
-            nIdx = nCntKash < nCntKashEnd && IsKashidaValid(nCntKash) ? 
GetKashida(nCntKash) : nEnd;
-            if ( nIdx > nEnd )
-                nIdx = nEnd;
-
-            const TextFrameIndex nArrayEnd = nIdx - nStt;
-
-            while ( nArrayPos < nArrayEnd )
-            {
-                (*pKernArray)[sal_Int32(nArrayPos)] += nKashAdd;
-                ++nArrayPos;
-            }
-            nKashAdd += nSpaceAdd;
-        }
-    }
-
-    return 0;
-}
-
 // Checks if the text is in Arabic or Syriac. Note that only the first
 // character has to be checked because a ctl portion only contains one
 // script, see NewTextPortion
@@ -2173,155 +2009,24 @@ bool SwScriptInfo::IsKashidaScriptText(const OUString& 
rText,
     return false;
 }
 
-bool SwScriptInfo::IsKashidaValid(size_t const nKashPos) const
-{
-    return m_KashidaInvalid.find(nKashPos) == m_KashidaInvalid.end();
-}
-
-void SwScriptInfo::ClearKashidaInvalid(size_t const nKashPos)
-{
-    m_KashidaInvalid.erase(nKashPos);
-}
-
-// bMark == true:
-// marks the first valid kashida in the given text range as invalid
-// bMark == false:
-// clears all kashida invalid flags in the given text range
-bool SwScriptInfo::MarkOrClearKashidaInvalid(
-    TextFrameIndex const nStt, TextFrameIndex const nLen,
-    bool bMark, sal_Int32 nMarkCount)
-{
-    size_t nCntKash = 0;
-    while( nCntKash < CountKashida() )
-    {
-        if ( nStt <= GetKashida( nCntKash ) )
-            break;
-        nCntKash++;
-    }
-
-    const TextFrameIndex nEnd = nStt + nLen;
-
-    while ( nCntKash < CountKashida() )
-    {
-        if ( nEnd <= GetKashida( nCntKash ) )
-            break;
-        if(bMark)
-        {
-            if ( MarkKashidaInvalid ( nCntKash ) )
-            {
-                --nMarkCount;
-                if (!nMarkCount)
-                    return true;
-            }
-        }
-        else
-        {
-            ClearKashidaInvalid ( nCntKash );
-        }
-        nCntKash++;
-    }
-    return false;
-}
-
-bool SwScriptInfo::MarkKashidaInvalid(size_t const nKashPos)
-{
-    return m_KashidaInvalid.insert(nKashPos).second;
-}
-
-// retrieve the kashida positions in the given text range
-void SwScriptInfo::GetKashidaPositions(
-    TextFrameIndex const nStt, TextFrameIndex const nLen,
-    std::vector<TextFrameIndex>& rKashidaPosition)
+tools::Long SwScriptInfo::CountKashidaPositions(TextFrameIndex nIdx, 
TextFrameIndex nEnd) const
 {
-    size_t nCntKash = 0;
-    while( nCntKash < CountKashida() )
+    tools::Long nCount = 0;
+    for (const auto& nPos : m_Kashida)
     {
-        if ( nStt <= GetKashida( nCntKash ) )
+        if (nPos >= nEnd)
             break;
-        nCntKash++;
-    }
-
-    const TextFrameIndex nEnd = nStt + nLen;
 
-    size_t nCntKashEnd = nCntKash;
-    while ( nCntKashEnd < CountKashida() )
-    {
-        if ( nEnd <= GetKashida( nCntKashEnd ) )
-            break;
-        rKashidaPosition.push_back(GetKashida(nCntKashEnd));
-        nCntKashEnd++;
+        if (nPos >= nIdx)
+            ++nCount;
     }
-}
 
-void SwScriptInfo::ReplaceKashidaPositions(TextFrameIndex const nStt, 
TextFrameIndex const nEnd,
-                                           const std::vector<TextFrameIndex>& 
rKashidaPositions)
-{
-    auto it = m_Kashida.begin();
-    while (it != m_Kashida.end() && *it < nStt)
-    {
-        ++it;
-    }
-
-    it = m_Kashida.insert(it, rKashidaPositions.begin(), 
rKashidaPositions.end());
-
-    it += rKashidaPositions.size();
-    auto jt = it;
-    while (jt != m_Kashida.end() && *jt < nEnd)
-    {
-        ++jt;
-    }
-
-    m_Kashida.erase(it, jt);
-}
-
-void SwScriptInfo::SetNoKashidaLine(TextFrameIndex const nStt, TextFrameIndex 
const nLen)
-{
-    m_NoKashidaLine.push_back( nStt );
-    m_NoKashidaLineEnd.push_back( nStt + nLen );
-}
-
-// determines if the line uses kashida justification
-bool SwScriptInfo::IsKashidaLine(TextFrameIndex const nCharIdx) const
-{
-    for (size_t i = 0; i < m_NoKashidaLine.size(); ++i)
-    {
-        if (nCharIdx >= m_NoKashidaLine[i] && nCharIdx < m_NoKashidaLineEnd[i])
-            return false;
-    }
-    return true;
-}
-
-void SwScriptInfo::ClearNoKashidaLines()
-{
-    m_NoKashidaLine.clear();
-    m_NoKashidaLineEnd.clear();
+    return nCount;
 }
 
-// mark the given character indices as invalid kashida positions
-void SwScriptInfo::MarkKashidasInvalid(sal_Int32 const nCnt,
-        const TextFrameIndex* pKashidaPositions)
+void SwScriptInfo::ReplaceKashidaPositions(std::vector<TextFrameIndex> 
aKashidaPositions)
 {
-    SAL_WARN_IF( !pKashidaPositions || nCnt == 0, "sw.core", "Where are 
kashidas?" );
-
-    size_t nCntKash = 0;
-    sal_Int32 nKashidaPosIdx = 0;
-
-    while (nCntKash < CountKashida() && nKashidaPosIdx < nCnt)
-    {
-        assert(pKashidaPositions && "Where are kashidas?");
-
-        if ( pKashidaPositions [nKashidaPosIdx] > GetKashida( nCntKash ) )
-        {
-            ++nCntKash;
-            continue;
-        }
-
-        if ( pKashidaPositions [nKashidaPosIdx] != GetKashida( nCntKash ) || 
!IsKashidaValid ( nCntKash ) )
-            return; // something is wrong
-
-        MarkKashidaInvalid ( nCntKash );
-        nKashidaPosIdx++;
-    }
+    m_Kashida = std::move(aKashidaPositions);
 }
 
 TextFrameIndex SwScriptInfo::ThaiJustify( std::u16string_view aText, 
KernArray* pKernArray,
diff --git a/sw/source/core/text/porlay.hxx b/sw/source/core/text/porlay.hxx
index 04b28b0012cd..a9e65a315386 100644
--- a/sw/source/core/text/porlay.hxx
+++ b/sw/source/core/text/porlay.hxx
@@ -81,7 +81,7 @@ private:
     SwLineLayout *m_pNext;                // The next Line
     std::unique_ptr<std::vector<tools::Long>> m_pLLSpaceAdd;     // Used for 
justified alignment
     std::unique_ptr<std::deque<sal_uInt16>> m_pKanaComp;  // Used for Kana 
compression
-    std::vector<sal_Int32> m_aInvalidKashida;
+    std::vector<TextFrameIndex> m_aKashida;
     SwTwips m_nRealHeight;             // The height resulting from line 
spacing and register
     SwTwips m_nTextHeight;             // The max height of all non-FlyCnt 
portions in this Line
     SwTwips m_nExtraAscent = 0;
@@ -100,7 +100,6 @@ private:
     bool m_bRedlineEnd: 1; // Redlining for paragraph mark: tracked change at 
the end
     bool m_bForcedLeftMargin : 1; // Left adjustment moved by the Fly
     bool m_bHanging : 1; // Contains a hanging portion in the margin
-    bool m_bKashidaAllowed : 1;
 
     enum RedlineType m_eRedlineEnd; // redline type of pilcrow and line break 
symbols
 
@@ -149,8 +148,6 @@ public:
     bool HasForcedLeftMargin() const { return m_bForcedLeftMargin; }
     void SetHanging( const bool bNew ) { m_bHanging = bNew; }
     bool IsHanging() const { return m_bHanging; }
-    void SetKashidaAllowed(bool bNew) { m_bKashidaAllowed = bNew; }
-    bool KashidaAllowed() const { return m_bKashidaAllowed; }
 
     // Respecting empty dummy lines
     void SetDummy( const bool bNew ) { m_bDummy = bNew; }
@@ -214,13 +211,8 @@ public:
     std::deque<sal_uInt16>* GetpKanaComp() const { return m_pKanaComp.get(); }
     std::deque<sal_uInt16>& GetKanaComp() { return *m_pKanaComp; }
 
-    void ClearInvalidKashida() { m_aInvalidKashida.clear(); }
-    void AddInvalidKashida(std::span<const sal_Int32> aNew)
-    {
-        std::copy(aNew.begin(), aNew.end(), 
std::back_inserter(m_aInvalidKashida));
-    }
-    void AddInvalidKashida(sal_Int32 nNew) { 
m_aInvalidKashida.push_back(nNew); }
-    std::span<const sal_Int32> GetInvalidKashida() const { return 
m_aInvalidKashida; }
+    void SetKashida(std::vector<TextFrameIndex> aNew) { m_aKashida = 
std::move(aNew); }
+    std::span<const TextFrameIndex> GetKashida() const { return m_aKashida; }
 
     /** determine ascent and descent for positioning of as-character anchored
         object
diff --git a/sw/source/core/text/portxt.cxx b/sw/source/core/text/portxt.cxx
index d72bc719a730..471f7dce9492 100644
--- a/sw/source/core/text/portxt.cxx
+++ b/sw/source/core/text/portxt.cxx
@@ -116,17 +116,12 @@ static TextFrameIndex lcl_AddSpace(const SwTextSizeInfo 
&rInf,
     // Kashida Justification: Insert Kashidas
     if ( nEnd > nPos && pSI && COMPLEX == nScript )
     {
-        if (SwScriptInfo::IsKashidaScriptText(*pStr, nPos, nEnd - nPos) && 
pSI->CountKashida())
+        if (pSI->ParagraphContainsKashidaScript()
+            && SwScriptInfo::IsKashidaScriptText(*pStr, nPos, nEnd - nPos))
         {
-            const sal_Int32 nKashRes = pSI->KashidaJustify(nullptr, nullptr, 
nPos, nEnd - nPos);
-            // i60591: need to check result of KashidaJustify
-            // determine if kashida justification is applicable
-            if (nKashRes != -1)
-            {
-                // tdf#163105: For kashida justification, also expand 
whitespace.
-                auto nCntLatin = lcl_AddSpace_Latin(rInf, pStr, rPor, nPos, 
nEnd, pSI, nScript);
-                return nCntLatin + TextFrameIndex{ nKashRes };
-            }
+            // tdf#163105: For kashida justification, also expand whitespace.
+            return lcl_AddSpace_Latin(rInf, pStr, rPor, nPos, nEnd, pSI, 
nScript)
+                   + TextFrameIndex{ pSI->CountKashidaPositions(nPos, nEnd) };
         }
     }
 
diff --git a/sw/source/core/txtnode/fntcache.cxx 
b/sw/source/core/txtnode/fntcache.cxx
index 54cbbd4fe780..75acf618eed2 100644
--- a/sw/source/core/txtnode/fntcache.cxx
+++ b/sw/source/core/txtnode/fntcache.cxx
@@ -1153,9 +1153,11 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
                                                           rInf.GetLen()))
                     {
                         aKashidaArray.resize(aKernArray.size(), false);
-                        if ( pSI && pSI->CountKashida() &&
-                            pSI->KashidaJustify( &aKernArray, 
aKashidaArray.data(), rInf.GetIdx(),
-                                                 rInf.GetLen(), nSpaceAdd ) != 
-1 )
+                        if (pSI && pSI->ParagraphContainsKashidaScript()
+                            && sw::Justify::KashidaJustify(
+                                pSI->GetKashidaPositions(), aKernArray, 
aKashidaArray.data(),
+                                static_cast<sal_Int32>(rInf.GetIdx()),
+                                static_cast<sal_Int32>(rInf.GetLen()), 
nSpaceAdd))
                         {
                             bSpecialJust = true;
 
@@ -1370,9 +1372,11 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
                 if (SwScriptInfo::IsKashidaScriptText(rInf.GetText(), 
rInf.GetIdx(), rInf.GetLen()))
                 {
                     aKashidaArray.resize(aKernArray.size(), false);
-                    if ( pSI && pSI->CountKashida() &&
-                         pSI->KashidaJustify( &aKernArray, 
aKashidaArray.data(), rInf.GetIdx(),
-                                              rInf.GetLen(), nSpaceAdd ) != -1 
)
+                    if (pSI && pSI->ParagraphContainsKashidaScript()
+                        && sw::Justify::KashidaJustify(
+                            pSI->GetKashidaPositions(), aKernArray, 
aKashidaArray.data(),
+                            static_cast<sal_Int32>(rInf.GetIdx()),
+                            static_cast<sal_Int32>(rInf.GetLen()), nSpaceAdd))
                     {
                         // Intentionally do not clear nSpaceAdd for kashida 
justification.
                         // The rest of the space will be handled below.
@@ -1861,9 +1865,11 @@ TextFrameIndex 
SwFntObj::GetModelPositionForViewPoint(SwDrawTextInfo &rInf)
         {
             if (SwScriptInfo::IsKashidaScriptText(rInf.GetText(), 
rInf.GetIdx(), rInf.GetLen()))
             {
-                if ( pSI && pSI->CountKashida() &&
-                    pSI->KashidaJustify( &aKernArray, nullptr, rInf.GetIdx(), 
rInf.GetLen(),
-                                         nSpaceAdd ) != -1 )
+                if (pSI && pSI->ParagraphContainsKashidaScript()
+                    && sw::Justify::KashidaJustify(pSI->GetKashidaPositions(), 
aKernArray, nullptr,
+                                                   
static_cast<sal_Int32>(rInf.GetIdx()),
+                                                   
static_cast<sal_Int32>(rInf.GetLen()),
+                                                   nSpaceAdd))
                 {
                     // Intentionally do not clear nSpaceAdd for kashida 
justification.
                     // The rest of the space will be handled below.
diff --git a/sw/source/core/txtnode/justify.cxx 
b/sw/source/core/txtnode/justify.cxx
index 31a7611977ff..41a107303588 100644
--- a/sw/source/core/txtnode/justify.cxx
+++ b/sw/source/core/txtnode/justify.cxx
@@ -292,6 +292,46 @@ void SnapToGridEdge(KernArray& rKernArray, sal_Int32 nLen, 
tools::Long nGridWidt
         ++nLast;
     }
 }
+
+bool KashidaJustify(std::span<TextFrameIndex const> aKashPositions, KernArray& 
rKernArray,
+                    sal_Bool* pKashidaArray, sal_Int32 nStt, sal_Int32 nLen, 
tools::Long nSpaceAdd)
+{
+    SAL_WARN_IF(!nLen, "sw.core", "Kashida justification without text?!");
+
+    auto stKashPosIt = aKashPositions.begin();
+
+    tools::Long nKashAdd = 0;
+    bool bHasAnyKashida = false;
+    for (sal_Int32 nIdx = 0; nIdx < nLen; ++nIdx)
+    {
+        bool bInsert = false;
+        while (stKashPosIt != aKashPositions.end())
+        {
+            auto nRelKashIdx = static_cast<sal_Int32>(*stKashPosIt) - nStt;
+            bInsert = (nRelKashIdx == nIdx);
+
+            if (nRelKashIdx >= nIdx)
+                break;
+
+            ++stKashPosIt;
+        }
+
+        if (bInsert)
+        {
+            if (pKashidaArray)
+            {
+                pKashidaArray[nIdx] = true;
+            }
+
+            nKashAdd += nSpaceAdd;
+            bHasAnyKashida = true;
+        }
+
+        rKernArray[nIdx] += nKashAdd;
+    }
+
+    return bHasAnyKashida;
+}
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/justify.hxx 
b/sw/source/core/txtnode/justify.hxx
index b9755d3efb01..059c7daa71c7 100644
--- a/sw/source/core/txtnode/justify.hxx
+++ b/sw/source/core/txtnode/justify.hxx
@@ -9,6 +9,7 @@
 
 #pragma once
 #include <sal/types.h>
+#include <TextFrameIndex.hxx>
 
 namespace sw::Justify
 {
@@ -63,6 +64,17 @@ SW_DLLPUBLIC tools::Long SnapToGrid(KernArray& rKernArray, 
std::u16string_view a
 SW_DLLPUBLIC void SnapToGridEdge(KernArray& rKernArray, sal_Int32 nLen, 
tools::Long nGridWidth,
                                  tools::Long nSpace, tools::Long nKern, 
tools::Long nBaseFontSize,
                                  bool bUseMsoCompatibleGrid);
+
+/// Performs a kashida justification on the kerning array
+/// @param aKashPositions Array of kashida insertion positions relative to 
paragraph
+/// @param[in,out] rKernArray text positions from OutDev::GetTextArray()
+/// @param[out] pKashidaArray Array marking locations for inserted tatweel 
glyphs. Optional.
+/// @param nStt String start index relative to the paragraph
+/// @param nLen Length of substring
+/// @param nSpaceAdd Amount of space to add to each kashida insertion 
opportunity
+SW_DLLPUBLIC bool KashidaJustify(std::span<TextFrameIndex const> 
aKashPositions,
+                                 KernArray& rKernArray, sal_Bool* 
pKashidaArray, sal_Int32 nStt,
+                                 sal_Int32 nLen, tools::Long nSpaceAdd);
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to