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: */