tdf#140731: sw transliteration: avoid too many redlines

As a workaround for the performance regression
from commit 2d3c77e9b10f20091ef338e262ba7756eb280ce9
(tdf#109266 sw change tracking: track transliteration),
switch off redlining to avoid ~freezing, if a single
transliteration could result too many (>~500) redlines.

A single transliteration creates n redlines
for n paragraphs of the selected text, except in
the case of transliterating to title case, where it
creates n redlines for n words. It's very easy
to freeze Writer, because Writer's slowing down with
n redlines is described by an O(n²) (quadratic) time
complexity. Eg. in an experiment, title casing
~660 words was 6 sec, but ~3000 words was 85 sec,
regarding to creating 660 vs 3000 redlines.

Note: this is a partial revert of commit
2d3c77e9b10f20091ef338e262ba7756eb280ce9, if the
selection contains more than 500 paragraphs (or in the
case transliterating to title case, ~500 words).

Change-Id: Iad98943cc9e1ed64aa9779e49ee3e941abad02ac
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/111637
Tested-by: Jenkins
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/sw/inc/ndtxt.hxx b/sw/inc/ndtxt.hxx
index 5dbbbc72..50986f3 100644
--- a/sw/inc/ndtxt.hxx
+++ b/sw/inc/ndtxt.hxx
@@ -746,7 +746,7 @@ public:
    /// change text to Upper/Lower/Hiragana/Katakana/...
    void TransliterateText( utl::TransliterationWrapper& rTrans,
                            sal_Int32 nStart, sal_Int32 nEnd,
                            SwUndoTransliterate* pUndo );
                            SwUndoTransliterate* pUndo, bool bUseRedlining = false );

    /// count words in given range - returns true if we refreshed out count
    bool CountWords( SwDocStat& rStat, sal_Int32 nStart, sal_Int32 nEnd ) const;
diff --git a/sw/source/core/doc/DocumentContentOperationsManager.cxx b/sw/source/core/doc/DocumentContentOperationsManager.cxx
index b9846d8..e66ed8e 100644
--- a/sw/source/core/doc/DocumentContentOperationsManager.cxx
+++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx
@@ -74,6 +74,8 @@
#include <sal/log.hxx>
#include <unotools/charclass.hxx>
#include <unotools/configmgr.hxx>
#include <unotools/transliterationwrapper.hxx>
#include <i18nutil/transliteration.hxx>
#include <sfx2/Metadatable.hxx>
#include <sot/exchange.hxx>
#include <svl/stritem.hxx>
@@ -2821,6 +2823,44 @@ void DocumentContentOperationsManager::TransliterateText(
        }
    }

    bool bUseRedlining = m_rDoc.getIDocumentRedlineAccess().IsRedlineOn();
    // as a workaround for a known performance problem, switch off redlining
    // to avoid freezing, if transliteration could result too many redlines
    if ( bUseRedlining )
    {
        const sal_uLong nMaxRedlines = 500;
        const bool bIsTitleCase = rTrans.getType() == TransliterationFlags::TITLE_CASE;
        sal_uLong nAffectedNodes = 0;
        sal_uLong nAffectedChars = nEndCnt;
        SwNodeIndex aIdx( pStt->nNode );
        for( ; aIdx.GetIndex() <= nEndNd; ++aIdx )
        {
            SwTextNode* pAffectedNode = aIdx.GetNode().GetTextNode();

            // don't count not text nodes or empty text nodes
            if( !pAffectedNode || pAffectedNode->GetText().isEmpty() )
                continue;

            nAffectedNodes++;

            // count characters of the node (the last - maybe partially
            // selected - node was counted at initialization of nAffectedChars)
            if( aIdx.GetIndex() < nEndNd )
                nAffectedChars += pAffectedNode->GetText().getLength();

            // transliteration creates n redlines for n nodes, except in the
            // case of title case, where it creates n redlines for n words
            if( nAffectedNodes > nMaxRedlines ||
                    // estimate word count based on the character count, where
                    // 6 = average English word length is ~5 letters + space
                    ( bIsTitleCase && (nAffectedChars - nSttCnt)/6 > nMaxRedlines ) )
            {
                bUseRedlining = false;
                break;
            }
        }
    }

    if( nSttNd != nEndNd )  // is more than one text node involved?
    {
        // iterate over all effected text nodes, the first and the last one
@@ -2831,8 +2871,10 @@ void DocumentContentOperationsManager::TransliterateText(
        {
            ++aIdx;
            if( pTNd )
            {
                pTNd->TransliterateText(
                        rTrans, nSttCnt, pTNd->GetText().getLength(), pUndo.get());
                        rTrans, nSttCnt, pTNd->GetText().getLength(), pUndo.get(), bUseRedlining);
            }
        }

        for( ; aIdx.GetIndex() < nEndNd; ++aIdx )
@@ -2841,16 +2883,19 @@ void DocumentContentOperationsManager::TransliterateText(
            if (pTNd)
            {
                pTNd->TransliterateText(
                        rTrans, 0, pTNd->GetText().getLength(), pUndo.get());
                        rTrans, 0, pTNd->GetText().getLength(), pUndo.get(), bUseRedlining);
            }
        }

        if( nEndCnt && nullptr != ( pTNd = pEnd->nNode.GetNode().GetTextNode() ))
            pTNd->TransliterateText( rTrans, 0, nEndCnt, pUndo.get() );
        {
            pTNd->TransliterateText( rTrans, 0, nEndCnt, pUndo.get(), bUseRedlining );
        }
    }
    else if( pTNd && nSttCnt < nEndCnt )
        pTNd->TransliterateText( rTrans, nSttCnt, nEndCnt, pUndo.get() );

    {
        pTNd->TransliterateText( rTrans, nSttCnt, nEndCnt, pUndo.get(), bUseRedlining );
    }
    if( pUndo && pUndo->HasData() )
    {
        m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
diff --git a/sw/source/core/txtnode/txtedt.cxx b/sw/source/core/txtnode/txtedt.cxx
index ba25c5f..e734e47 100644
--- a/sw/source/core/txtnode/txtedt.cxx
+++ b/sw/source/core/txtnode/txtedt.cxx
@@ -1684,7 +1684,7 @@ namespace
void SwTextNode::TransliterateText(
    utl::TransliterationWrapper& rTrans,
    sal_Int32 nStt, sal_Int32 nEnd,
    SwUndoTransliterate* pUndo )
    SwUndoTransliterate* pUndo, bool bUseRedlining )
{
    if (nStt >= nEnd)
        return;
@@ -1908,7 +1908,7 @@ void SwTextNode::TransliterateText(
    // now apply the changes from end to start to leave the offsets of the
    // yet unchanged text parts remain the same.
    size_t nSum(0);
    bool bIsRedlineOn(GetDoc().getIDocumentRedlineAccess().IsRedlineOn());

    for (size_t i = 0; i < aChanges.size(); ++i)
    {   // check this here since AddChanges cannot be moved below
        // call to ReplaceTextOnly
@@ -1922,7 +1922,7 @@ void SwTextNode::TransliterateText(
            return;
        }

        if ( bIsRedlineOn )
        if ( bUseRedlining )
        {
            // create SwPaM with mark & point spanning the attributed text
            //SwPaM aCurPaM( *this, *this, nBegin, nBegin + nLen ); <-- wrong c-tor, does sth different