sw/qa/core/uwriter.cxx                    |    4 -
 sw/qa/extras/uiwriter/data/tdf164140.fodt |  117 ++++++++++++++++++++++++++++++
 sw/qa/extras/uiwriter/uiwriter9.cxx       |   51 +++++++++++++
 sw/source/core/inc/scriptinfo.hxx         |   25 ++++--
 sw/source/core/text/itradj.cxx            |   91 +++++++++++++++++++----
 sw/source/core/text/porlay.cxx            |   21 +----
 sw/source/core/text/porlay.hxx            |   12 +++
 7 files changed, 281 insertions(+), 40 deletions(-)

New commits:
commit fac5695974d8a2197edba1e9f69f86621196cae1
Author:     Jonathan Clark <jonat...@libreoffice.org>
AuthorDate: Thu Feb 27 00:55:11 2025 -0700
Commit:     Jonathan Clark <jonat...@libreoffice.org>
CommitDate: Thu Feb 27 15:38:18 2025 +0100

    tdf#164140 sw: Fix invalid string indices in kashida justification
    
    During layout, Writer has to mark certain lines and positions as
    ineligible for kashida. These lines and positions are stored using
    string indices on a per-paragraph basis.
    
    This change fixes a bug causing corruption of these kashida
    ineligibility indices during editing. Writer tries to reuse the existing
    layouts of lines whenever possible. Since these kashida indices are
    computed at the same time as layout, and the paragraph-relative subrange
    of a line may be different after inserting or deleting text in previous
    lines, the stored indices would point to different parts of the
    paragraph string than intended.
    
    Change-Id: Idb67d0a217841dc0743ed716ef8214d10e7b560b
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182286
    Tested-by: Jenkins
    Reviewed-by: Jonathan Clark <jonat...@libreoffice.org>

diff --git a/sw/qa/core/uwriter.cxx b/sw/qa/core/uwriter.cxx
index 8a03254e7c5d..36573a79153f 100644
--- a/sw/qa/core/uwriter.cxx
+++ b/sw/qa/core/uwriter.cxx
@@ -1986,9 +1986,9 @@ void SwDocTest::testTdf156211()
 
     CPPUNIT_ASSERT(!oSI.IsKashidaLine(TextFrameIndex{ 95 }));
 
-    oSI.ClearNoKashidaLine(TextFrameIndex{ 0 }, TextFrameIndex{ 89 });
+    oSI.ClearNoKashidaLines();
 
-    CPPUNIT_ASSERT(!oSI.IsKashidaLine(TextFrameIndex{ 95 }));
+    CPPUNIT_ASSERT(oSI.IsKashidaLine(TextFrameIndex{ 95 }));
 }
 
 void SwDocTest::testFillRubyList()
diff --git a/sw/qa/extras/uiwriter/data/tdf164140.fodt 
b/sw/qa/extras/uiwriter/data/tdf164140.fodt
new file mode 100644
index 000000000000..ec37896c25b7
--- /dev/null
+++ b/sw/qa/extras/uiwriter/data/tdf164140.fodt
@@ -0,0 +1,117 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/"; 
xmlns:grddl="http://www.w3.org/2003/g/data-view#"; 
xmlns:xhtml="http://www.w3.org/1999/xhtml"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xmlns:xsd="http://www.w3.org/2001/XMLSchema"; 
xmlns:xforms="http://www.w3.org/2002/xforms"; 
xmlns:dom="http://www.w3.org/2001/xml-events"; 
xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" 
xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" 
xmlns:math="http://www.w3.org/1998/Math/MathML"; 
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" 
xmlns:ooo="http://openoffice.org/2004/office"; 
xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" 
xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" 
xmlns:ooow="http://openoffice.org/2004/writer"; 
xmlns:xlink="http://www.w3.org/1999/xlink"; 
xmlns:drawooo="http://openoffice.org/2010/draw"; 
xmlns:oooc="http://openoffice.org/2004/calc"; 
xmlns:dc="http://purl.org/dc/elements/1.1/"; xmlns:c
 alcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" 
xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" 
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" 
xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" 
xmlns:tableooo="http://openoffice.org/2009/table"; 
xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" 
xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" 
xmlns:rpt="http://openoffice.org/2005/report"; 
xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0"
 xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" 
xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" 
xmlns:officeooo="http://openoffice.org/2009/office"; 
xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" 
xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" 
xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" 
xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:
 meta:1.0" 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"
 office:version="1.4" office:mimetype="application/vnd.oasis.opendocument.text">
+ 
<office:meta><meta:creation-date>2025-02-27T01:08:55.794571331</meta:creation-date><meta:editing-duration>PT1M23S</meta:editing-duration><meta:editing-cycles>3</meta:editing-cycles><meta:generator>LibreOfficeDev/25.8.0.0.alpha0$Linux_X86_64
 
LibreOffice_project/d09f3f24c38eda633825f4ac214731db06bc9a9a</meta:generator><dc:date>2025-02-27T01:18:29.823738756</dc:date><meta:document-statistic
 meta:table-count="0" meta:image-count="0" meta:object-count="0" 
meta:page-count="1" meta:paragraph-count="1" meta:word-count="52" 
meta:character-count="312" 
meta:non-whitespace-character-count="260"/></office:meta>
+ <office:font-face-decls>
+  <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation 
Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+  <style:font-face style:name="Lohit Devanagari1" svg:font-family="'Lohit 
Devanagari'" style:font-family-generic="system" style:font-pitch="variable"/>
+  <style:font-face style:name="Noto Sans" svg:font-family="'Noto Sans'" 
style:font-adornments="Regular" style:font-family-generic="swiss" 
style:font-pitch="variable"/>
+  <style:font-face style:name="Noto Sans Arabic" svg:font-family="'Noto Sans 
Arabic'" style:font-adornments="Regular" style:font-family-generic="swiss" 
style:font-pitch="variable"/>
+  <style:font-face style:name="Noto Serif CJK SC" svg:font-family="'Noto Serif 
CJK SC'" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+  <style:default-style style:family="graphic">
+   <style:graphic-properties svg:stroke-color="#3465a4" 
draw:fill-color="#729fcf" fo:wrap-option="no-wrap" 
draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" 
draw:start-line-spacing-horizontal="0.1114in" 
draw:start-line-spacing-vertical="0.1114in" 
draw:end-line-spacing-horizontal="0.1114in" 
draw:end-line-spacing-vertical="0.1114in" style:writing-mode="lr-tb" 
style:flow-with-text="false"/>
+   <style:paragraph-properties style:text-autospace="ideograph-alpha" 
style:line-break="strict" loext:tab-stop-distance="0in" 
style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
+    <style:tab-stops/>
+   </style:paragraph-properties>
+   <style:text-properties style:use-window-font-color="true" 
loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" 
fo:language="en" fo:country="US" style:letter-kerning="true" 
style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" 
style:language-asian="zh" style:country-asian="CN" 
style:font-name-complex="Lohit Devanagari1" style:font-size-complex="12pt" 
style:language-complex="hi" style:country-complex="IN"/>
+  </style:default-style>
+  <style:default-style style:family="paragraph">
+   <style:paragraph-properties fo:orphans="2" fo:widows="2" 
fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" 
loext:hyphenation-keep-type="column" style:text-autospace="ideograph-alpha" 
style:punctuation-wrap="hanging" style:line-break="strict" 
style:tab-stop-distance="0.4925in" style:writing-mode="page"/>
+   <style:text-properties style:use-window-font-color="true" 
loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" 
fo:language="en" fo:country="US" style:letter-kerning="true" 
style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" 
style:language-asian="zh" style:country-asian="CN" 
style:font-name-complex="Lohit Devanagari1" style:font-size-complex="12pt" 
style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" 
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="no-limit"/>
+  </style:default-style>
+  <style:default-style style:family="table">
+   <style:table-properties table:border-model="collapsing"/>
+  </style:default-style>
+  <style:default-style style:family="table-row">
+   <style:table-row-properties fo:keep-together="auto"/>
+  </style:default-style>
+  <style:style style:name="Standard" style:family="paragraph" 
style:class="text"/>
+  <text:outline-style style:name="Outline">
+   <text:outline-level-style text:level="1" loext:num-list-format="%1%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="2" loext:num-list-format="%2%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="3" loext:num-list-format="%3%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="4" loext:num-list-format="%4%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="5" loext:num-list-format="%5%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="6" loext:num-list-format="%6%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="7" loext:num-list-format="%7%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="8" loext:num-list-format="%8%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="9" loext:num-list-format="%9%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="10" loext:num-list-format="%10%" 
style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+  </text:outline-style>
+  <text:notes-configuration text:note-class="footnote" style:num-format="1" 
text:start-value="0" text:footnotes-position="page" 
text:start-numbering-at="document"/>
+  <text:notes-configuration text:note-class="endnote" style:num-format="i" 
text:start-value="0"/>
+  <text:linenumbering-configuration text:number-lines="false" 
text:offset="0.1965in" style:num-format="1" text:number-position="left" 
text:increment="5"/>
+  </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="P1" style:family="paragraph" 
style:parent-style-name="Standard">
+   <style:paragraph-properties fo:text-align="justify" 
style:justify-single-word="false" style:writing-mode="rl-tb"/>
+   <style:text-properties style:font-name="Noto Sans" 
style:font-name-complex="Noto Sans Arabic" style:language-complex="ar" 
style:country-complex="SA"/>
+  </style:style>
+  <style:page-layout style:name="pm1">
+   <style:page-layout-properties fo:page-width="8.5in" fo:page-height="11in" 
style:num-format="1" style:print-orientation="portrait" 
fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" 
fo:margin-right="0.7874in" style:writing-mode="lr-tb" 
style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" 
style:layout-grid-base-height="0.278in" style:layout-grid-ruby-height="0.139in" 
style:layout-grid-mode="none" style:layout-grid-ruby-below="false" 
style:layout-grid-print="false" style:layout-grid-display="false" 
style:footnote-max-height="0in" loext:margin-gutter="0in">
+    <style:footnote-sep style:width="0.0071in" 
style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" 
style:line-style="solid" style:adjustment="left" style:rel-width="25%" 
style:color="#000000"/>
+   </style:page-layout-properties>
+   <style:header-style/>
+   <style:footer-style/>
+  </style:page-layout>
+  <style:style style:name="dp1" style:family="drawing-page">
+   <style:drawing-page-properties draw:background-size="full"/>
+  </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+  <style:master-page style:name="Standard" style:page-layout-name="pm1" 
draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+  <office:text>
+   <text:sequence-decls>
+    <text:sequence-decl text:display-outline-level="0" 
text:name="Illustration"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+   </text:sequence-decls>
+   <text:p text:style-name="P1">متن آزمایشی متن آزمایشی متن آزمایشی متن 
آزمایشی متن آزمایشی متن آزمایشی متن آزمایشی متن آزمایشی متن آزمایشی متن آزمایشی 
متن آزمایشی متن آزمایشی متن آزمایشی متن آزمایشی متن آزمایشی متن آزمایشی متن 
آزمایشی متن آزمایشی متن آزمایشی متن <text:s/>آزمایشی متن آزمایشی متن آزمایشی 
متن آزمایشی متن آزمایشی متن آزمایشی متن آزمایشی</text:p>
+  </office:text>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/sw/qa/extras/uiwriter/uiwriter9.cxx 
b/sw/qa/extras/uiwriter/uiwriter9.cxx
index daf0402ba1cf..93d356c6cf0e 100644
--- a/sw/qa/extras/uiwriter/uiwriter9.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter9.cxx
@@ -38,6 +38,8 @@
 #include <swacorr.hxx>
 #include <sfx2/linkmgr.hxx>
 
+#include <scriptinfo.hxx>
+#include <txtfrm.hxx>
 #include <edtwin.hxx>
 #include <view.hxx>
 #include <wrtsh.hxx>
@@ -1142,6 +1144,55 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf162195)
                          xTables->getAnchor()->getString());
 }
 
+CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf164140)
+{
+    createSwDoc("tdf164140.fodt");
+    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
+
+    SwTextFrame& pTextFrame
+        = 
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));
+
+    // 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));
+}
+
 } // end of anonymous namespace
 CPPUNIT_PLUGIN_IMPLEMENT();
 
diff --git a/sw/source/core/inc/scriptinfo.hxx 
b/sw/source/core/inc/scriptinfo.hxx
index 5d4814bf2f63..77344ba7a335 100644
--- a/sw/source/core/inc/scriptinfo.hxx
+++ b/sw/source/core/inc/scriptinfo.hxx
@@ -296,10 +296,7 @@ public:
 
 /** Clears array of kashidas marked as invalid
  */
-    void ClearKashidaInvalid(TextFrameIndex const nStt, TextFrameIndex const 
nLen)
-    {
-        MarkOrClearKashidaInvalid(nStt, nLen, false, 0);
-    }
+    void ClearKashidaInvalid() { m_KashidaInvalid.clear(); }
 
 /** Marks nCnt kashida positions as invalid
    pKashidaPositions: array of char indices relative to the paragraph
@@ -337,17 +334,29 @@ public:
 */
     void SetNoKashidaLine(TextFrameIndex nStt, TextFrameIndex nLen);
 
-/** Clear forced blank justification for a given line.
-   nStt Start char index of the line referring to the paragraph.
-   nLen Number of characters in the line
+/** Clear all forced blank justification data for the paragraph.
 */
-    void ClearNoKashidaLine(TextFrameIndex nStt, TextFrameIndex nLen);
+    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;
+    }
+
 /** Checks if text is in a script that allows kashida justification.
 
      @descr  Checks if text is in a language that allows kashida justification.
diff --git a/sw/source/core/text/itradj.cxx b/sw/source/core/text/itradj.cxx
index 05f62d9ebefa..002d43f296d9 100644
--- a/sw/source/core/text/itradj.cxx
+++ b/sw/source/core/text/itradj.cxx
@@ -140,7 +140,8 @@ void SwTextAdjuster::FormatBlock( )
 
 static bool lcl_CheckKashidaPositions(SwScriptInfo& rSI, SwTextSizeInfo& rInf, 
SwTextIter& rItr,
                                       sal_Int32& rKashidas, TextFrameIndex& 
nGluePortion,
-                                      bool& rRemovedAllKashida)
+                                      bool& rRemovedAllKashida, SwLineLayout* 
pCurrLine,
+                                      TextFrameIndex nCurrLineBase)
 {
     rRemovedAllKashida = true;
 
@@ -225,7 +226,6 @@ static bool lcl_CheckKashidaPositions(SwScriptInfo& rSI, 
SwTextSizeInfo& rInf, S
                    [](TextFrameIndex nPos) { return 
static_cast<sal_Int32>(nPos); });
 
     std::vector<sal_Int32> aKashidaPosDropped;
-    std::vector<TextFrameIndex> aCastKashidaPosDropped;
 
     sal_Int32 nKashidaIdx = 0;
     while ( rKashidas && nIdx < nEnd )
@@ -278,11 +278,13 @@ static bool lcl_CheckKashidaPositions(SwScriptInfo& rSI, 
SwTextSizeInfo& rInf, S
                 rInf.GetRefDev()->SetLayoutMode(nOldLayout);
                 if ( nKashidasDropped )
                 {
-                    aCastKashidaPosDropped.clear();
-                    std::transform(std::cbegin(aKashidaPosDropped), 
std::cend(aKashidaPosDropped),
-                                   std::back_inserter(aCastKashidaPosDropped),
-                                   [](sal_Int32 nPos) { return TextFrameIndex{ 
nPos }; });
-                    rSI.MarkKashidasInvalid(nKashidasDropped, 
aCastKashidaPosDropped.data());
+                    // Convert dropped kashida positions so they are relative 
to the line
+                    for (auto& rPos : aKashidaPosDropped)
+                    {
+                        rPos = rPos - static_cast<sal_Int32>(nCurrLineBase);
+                    }
+
+                    pCurrLine->AddInvalidKashida(aKashidaPosDropped);
                     rKashidas -= nKashidasDropped;
                     nGluePortion -= TextFrameIndex(nKashidasDropped);
                 }
@@ -296,8 +298,10 @@ static bool lcl_CheckKashidaPositions(SwScriptInfo& rSI, 
SwTextSizeInfo& rInf, S
     return (rKashidas > 0);
 }
 
-static bool lcl_CheckKashidaWidth ( SwScriptInfo& rSI, SwTextSizeInfo& rInf, 
SwTextIter& rItr, sal_Int32& rKashidas,
-                             TextFrameIndex& nGluePortion, const tools::Long 
nGluePortionWidth, tools::Long& nSpaceAdd )
+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
@@ -340,8 +344,19 @@ static bool lcl_CheckKashidaWidth ( SwScriptInfo& rSI, 
SwTextSizeInfo& rInf, SwT
                     nSpaceAdd = nGluePortionWidth / sal_Int32(nGluePortion);
                     bAddSpaceChanged = true;
                 }
-                if( nKashidasDropped )
-                   rSI.MarkKashidasInvalid( nKashidasDropped, nIdx, nNext - 
nIdx );
+
+                // 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
@@ -373,19 +388,26 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent,
     SwTextSizeInfo aInf ( GetTextFrame() );
     SwTextIter aItr ( GetTextFrame(), &aInf );
 
+    TextFrameIndex nLineBase{ 0 };
+
     if ( rSI.CountKashida() )
     {
         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 ( aItr.GetStart(), aItr.GetLength() );
-            rSI.ClearNoKashidaLine( aItr.GetStart(), aItr.GetLength() );
+            rSI.ClearKashidaInvalid();
+            rSI.ClearNoKashidaLines();
+            pCurrent->SetKashidaAllowed(true);
+            pCurrent->ClearInvalidKashida();
         }
     }
 
@@ -468,7 +490,7 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent,
                     // 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))
+                                                   bRemovedAllKashida, 
pCurrent, nLineBase))
                     {
                         // all kashida positions are invalid
                         // do regular blank justification
@@ -500,7 +522,9 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent,
                     // i60594
                     if( rSI.CountKashida() && !bSkipKashida )
                     {
-                        if( !lcl_CheckKashidaWidth( rSI, aInf, aItr, 
nKashidas, nGluePortion, nGluePortionWidth, nSpaceAdd ))
+                        if (!lcl_CheckKashidaWidth(rSI, aInf, aItr, nKashidas, 
nGluePortion,
+                                                   nGluePortionWidth, 
nSpaceAdd, pCurrent,
+                                                   nLineBase))
                         {
                             // no kashidas left
                             // do regular blank justification
@@ -536,6 +560,43 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent,
         }
         pPos = pPos->GetNextPortion();
     }
+
+    // tdf#164140: Rebuild kashida exclusion indices after line adjustment
+    if (rSI.CountKashida())
+    {
+        rSI.ClearNoKashidaLines();
+        rSI.ClearKashidaInvalid();
+
+        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())
+            {
+                aInvalidKashida.push_back(aKashItr.GetStart() + 
TextFrameIndex{ nKashIdx });
+            }
+
+            if (!aKashItr.GetNextLine())
+            {
+                break;
+            }
+
+            aKashItr.NextLine();
+        }
+
+        if (!aInvalidKashida.empty())
+        {
+            rSI.MarkKashidasInvalid(aInvalidKashida.size(), 
aInvalidKashida.data());
+        }
+    }
 }
 
 SwTwips SwTextAdjuster::CalcKanaAdj( SwLineLayout* pCurrent )
diff --git a/sw/source/core/text/porlay.cxx b/sw/source/core/text/porlay.cxx
index 352a0b17e5f2..c9a521b93444 100644
--- a/sw/source/core/text/porlay.cxx
+++ b/sw/source/core/text/porlay.cxx
@@ -789,9 +789,9 @@ void SwLineLayout::dumpAsXml(xmlTextWriterPtr pWriter, 
const OUString& rText,
 
 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 = false;
+    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;
     m_eRedlineEnd = RedlineType::None;
 }
 
@@ -2289,19 +2289,10 @@ bool SwScriptInfo::IsKashidaLine(TextFrameIndex const 
nCharIdx) const
     return true;
 }
 
-void SwScriptInfo::ClearNoKashidaLine(TextFrameIndex const nStt, 
TextFrameIndex const nLen)
+void SwScriptInfo::ClearNoKashidaLines()
 {
-    size_t i = 0;
-    while (i < m_NoKashidaLine.size())
-    {
-        if (nStt + nLen > m_NoKashidaLine[i] && nStt < m_NoKashidaLineEnd[i])
-        {
-            m_NoKashidaLine.erase(m_NoKashidaLine.begin() + i);
-            m_NoKashidaLineEnd.erase(m_NoKashidaLineEnd.begin() + i);
-        }
-        else
-            ++i;
-    }
+    m_NoKashidaLine.clear();
+    m_NoKashidaLineEnd.clear();
 }
 
 // mark the given character indices as invalid kashida positions
diff --git a/sw/source/core/text/porlay.hxx b/sw/source/core/text/porlay.hxx
index 6634dff7cb13..338849b79ed4 100644
--- a/sw/source/core/text/porlay.hxx
+++ b/sw/source/core/text/porlay.hxx
@@ -81,6 +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;
     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;
@@ -99,6 +100,7 @@ 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
 
@@ -147,6 +149,8 @@ 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; }
@@ -210,6 +214,14 @@ 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; }
+
     /** determine ascent and descent for positioning of as-character anchored
         object
 

Reply via email to