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