xref: /core/sw/source/core/txtnode/txtedt.cxx (revision 44e61809)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <config_wasm_strip.h>
21 
22 #include <hintids.hxx>
23 #include <utility>
24 #include <vcl/svapp.hxx>
25 #include <svl/itemiter.hxx>
26 #include <svl/languageoptions.hxx>
27 #include <editeng/splwrap.hxx>
28 #include <editeng/langitem.hxx>
29 #include <editeng/fontitem.hxx>
30 #include <editeng/hangulhanja.hxx>
31 #include <i18nutil/transliteration.hxx>
32 #include <linguistic/misc.hxx>
33 #include <SwSmartTagMgr.hxx>
34 #include <o3tl/safeint.hxx>
35 #include <osl/diagnose.h>
36 #include <officecfg/Office/Writer.hxx>
37 #include <unotools/transliterationwrapper.hxx>
38 #include <unotools/charclass.hxx>
39 #include <sal/log.hxx>
40 #include <swmodule.hxx>
41 #include <splargs.hxx>
42 #include <viewopt.hxx>
43 #include <acmplwrd.hxx>
44 #include <doc.hxx>
45 #include <IDocumentRedlineAccess.hxx>
46 #include <IDocumentLayoutAccess.hxx>
47 #include <docsh.hxx>
48 #include <txtfld.hxx>
49 #include <txatbase.hxx>
50 #include <charatr.hxx>
51 #include <pam.hxx>
52 #include <hints.hxx>
53 #include <ndtxt.hxx>
54 #include <txtfrm.hxx>
55 #include <SwGrammarMarkUp.hxx>
56 #include <rootfrm.hxx>
57 #include <swscanner.hxx>
58 
59 #include <breakit.hxx>
60 #include <UndoOverwrite.hxx>
61 #include <txatritr.hxx>
62 #include <redline.hxx>
63 #include <docary.hxx>
64 #include <scriptinfo.hxx>
65 #include <docstat.hxx>
66 #include <editsh.hxx>
67 #include <unotextmarkup.hxx>
68 #include <txtatr.hxx>
69 #include <fmtautofmt.hxx>
70 #include <istyleaccess.hxx>
71 #include <unicode/uchar.h>
72 #include <DocumentSettingManager.hxx>
73 
74 #include <com/sun/star/i18n/WordType.hpp>
75 #include <com/sun/star/i18n/ScriptType.hpp>
76 #include <com/sun/star/i18n/XBreakIterator.hpp>
77 
78 #include <vector>
79 
80 #include <unotextrange.hxx>
81 
82 using namespace ::com::sun::star;
83 using namespace ::com::sun::star::frame;
84 using namespace ::com::sun::star::i18n;
85 using namespace ::com::sun::star::beans;
86 using namespace ::com::sun::star::uno;
87 using namespace ::com::sun::star::linguistic2;
88 using namespace ::com::sun::star::smarttags;
89 
90 namespace
91 {
DetectAndMarkMissingDictionaries(SwDoc & rDoc,const uno::Reference<XSpellChecker1> & xSpell,const LanguageType eActLang)92     void DetectAndMarkMissingDictionaries( SwDoc& rDoc,
93                                            const uno::Reference< XSpellChecker1 >& xSpell,
94                                            const LanguageType eActLang )
95     {
96         if( xSpell.is() && !xSpell->hasLanguage( eActLang.get() ) )
97             rDoc.SetMissingDictionaries( true );
98         else
99             rDoc.SetMissingDictionaries( false );
100     }
101 }
102 
lcl_HasComments(const SwTextNode & rNode)103 static bool lcl_HasComments(const SwTextNode& rNode)
104 {
105     sal_Int32 nPosition = rNode.GetText().indexOf(CH_TXTATR_INWORD);
106     while (nPosition != -1)
107     {
108         const SwTextAttr* pAttr = rNode.GetTextAttrForCharAt(nPosition);
109         if (pAttr && pAttr->Which() == RES_TXTATR_ANNOTATION)
110             return true;
111         nPosition = rNode.GetText().indexOf(CH_TXTATR_INWORD, nPosition + 1);
112     }
113     return false;
114 }
115 
116 // possible delimiter characters within URLs for word breaking
lcl_IsDelim(const sal_Unicode c)117 static bool lcl_IsDelim( const sal_Unicode c )
118 {
119    return '#' == c || '$' == c || '%' == c || '&' == c || '+' == c ||
120           ',' == c || '-' == c || '.' == c || '/' == c || ':' == c ||
121           ';' == c || '=' == c || '?' == c || '@' == c || '_' == c;
122 }
123 
124 // allow to check normal text with hyperlink by recognizing (parts of) URLs
lcl_IsURL(std::u16string_view rWord,SwTextNode & rNode,sal_Int32 nBegin,sal_Int32 nLen)125 static bool lcl_IsURL(std::u16string_view rWord,
126     SwTextNode &rNode, sal_Int32 nBegin, sal_Int32 nLen)
127 {
128     // not a text with hyperlink
129     if ( !rNode.GetTextAttrAt(nBegin, RES_TXTATR_INETFMT) )
130         return false;
131 
132     // there is a dot in the word, which is not a period ("example.org")
133     const size_t nPosAt = rWord.find('.');
134     if (nPosAt != std::u16string_view::npos && nPosAt < rWord.length() - 1)
135         return true;
136 
137     // an e-mail address ("user@example")
138     if ( rWord.find('@') != std::u16string_view::npos )
139         return true;
140 
141     const OUString& rText = rNode.GetText();
142 
143     // scheme (e.g. "http" in "http://" or "mailto" in "mailto:address"):
144     // word is followed by 1) ':' + an alphanumeric character; 2) or ':' + a delimiter
145     if ( nBegin + nLen + 2 <= rText.getLength() && ':' == rText[nBegin + nLen] )
146     {
147          sal_Unicode c = rText[nBegin + nLen + 1];
148          if ( u_isalnum(c) || lcl_IsDelim(c) )
149              return true;
150     }
151 
152     // path, query, fragment (e.g. "path" in "example.org/path"):
153     // word is preceded by 1) an alphanumeric character + a delimiter; 2) or two delimiters
154     if ( 2 <= nBegin && lcl_IsDelim(rText[nBegin - 1]) )
155     {
156         sal_Unicode c = rText[nBegin - 2];
157         if ( u_isalnum(c) || lcl_IsDelim(c) )
158             return true;
159     }
160 
161     return false;
162 }
163 
164 /*
165  * This has basically the same function as SwScriptInfo::MaskHiddenRanges,
166  * only for deleted redlines
167  */
168 
169 static sal_Int32
lcl_MaskRedlines(const SwTextNode & rNode,OUStringBuffer & rText,sal_Int32 nStt,sal_Int32 nEnd,const sal_Unicode cChar)170 lcl_MaskRedlines( const SwTextNode& rNode, OUStringBuffer& rText,
171                          sal_Int32 nStt, sal_Int32 nEnd,
172                          const sal_Unicode cChar )
173 {
174     sal_Int32 nNumOfMaskedRedlines = 0;
175 
176     const SwDoc& rDoc = rNode.GetDoc();
177 
178     for ( SwRedlineTable::size_type nAct = rDoc.getIDocumentRedlineAccess().GetRedlinePos( rNode, RedlineType::Any ); nAct < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(); ++nAct )
179     {
180         const SwRangeRedline* pRed = rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nAct ];
181 
182         if ( pRed->Start()->GetNode() > rNode )
183             break;
184 
185         if( RedlineType::Delete == pRed->GetType() )
186         {
187             sal_Int32 nRedlineEnd;
188             sal_Int32 nRedlineStart;
189 
190             pRed->CalcStartEnd( rNode.GetIndex(), nRedlineStart, nRedlineEnd );
191 
192             if ( nRedlineEnd < nStt || nRedlineStart > nEnd )
193                 continue;
194 
195             while ( nRedlineStart < nRedlineEnd && nRedlineStart < nEnd )
196             {
197                 if (nRedlineStart >= nStt)
198                 {
199                     rText[nRedlineStart] = cChar;
200                     ++nNumOfMaskedRedlines;
201                 }
202                 ++nRedlineStart;
203             }
204         }
205     }
206 
207     return nNumOfMaskedRedlines;
208 }
209 
210 /**
211  * Used for spell checking. Deleted redlines and hidden characters are masked
212  */
213 static bool
lcl_MaskRedlinesAndHiddenText(const SwTextNode & rNode,OUStringBuffer & rText,sal_Int32 nStt,sal_Int32 nEnd,const sal_Unicode cChar=CH_TXTATR_INWORD)214 lcl_MaskRedlinesAndHiddenText( const SwTextNode& rNode, OUStringBuffer& rText,
215                                       sal_Int32 nStt, sal_Int32 nEnd,
216                                       const sal_Unicode cChar = CH_TXTATR_INWORD )
217 {
218     sal_Int32 nRedlinesMasked = 0;
219     sal_Int32 nHiddenCharsMasked = 0;
220 
221     const SwDoc& rDoc = rNode.GetDoc();
222     const bool bShowChg = IDocumentRedlineAccess::IsShowChanges( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() );
223 
224     // If called from word count or from spell checking, deleted redlines
225     // should be masked:
226     if ( bShowChg )
227     {
228         nRedlinesMasked = lcl_MaskRedlines( rNode, rText, nStt, nEnd, cChar );
229     }
230 
231     const bool bHideHidden = !SW_MOD()->GetViewOption(rDoc.GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE))->IsShowHiddenChar();
232 
233     // If called from word count, we want to mask the hidden ranges even
234     // if they are visible:
235     if ( bHideHidden )
236     {
237         nHiddenCharsMasked =
238             SwScriptInfo::MaskHiddenRanges( rNode, rText, nStt, nEnd, cChar );
239     }
240 
241     return (nRedlinesMasked > 0) || (nHiddenCharsMasked > 0);
242 }
243 
244 /**
245  * Used for spell checking. Calculates a rectangle for repaint.
246  */
lcl_CalculateRepaintRect(const SwTextFrame & rTextFrame,const SwTextNode & rNode,sal_Int32 const nChgStart,sal_Int32 const nChgEnd)247 static SwRect lcl_CalculateRepaintRect(
248         const SwTextFrame & rTextFrame, const SwTextNode & rNode,
249         sal_Int32 const nChgStart, sal_Int32 const nChgEnd)
250 {
251     TextFrameIndex const iChgStart(rTextFrame.MapModelToView(&rNode, nChgStart));
252     TextFrameIndex const iChgEnd(rTextFrame.MapModelToView(&rNode, nChgEnd));
253 
254     SwRect aRect = rTextFrame.GetPaintArea();
255     SwRect aTmp = rTextFrame.GetPaintArea();
256 
257     const SwTextFrame* pStartFrame = &rTextFrame;
258     while( pStartFrame->HasFollow() &&
259            iChgStart >= pStartFrame->GetFollow()->GetOffset())
260         pStartFrame = pStartFrame->GetFollow();
261     const SwTextFrame* pEndFrame = pStartFrame;
262     while( pEndFrame->HasFollow() &&
263            iChgEnd >= pEndFrame->GetFollow()->GetOffset())
264         pEndFrame = pEndFrame->GetFollow();
265 
266     bool bSameFrame = true;
267 
268     if( rTextFrame.HasFollow() )
269     {
270         if( pEndFrame != pStartFrame )
271         {
272             bSameFrame = false;
273             SwRect aStFrame( pStartFrame->GetPaintArea() );
274             {
275                 SwRectFnSet aRectFnSet(pStartFrame);
276                 aRectFnSet.SetLeft( aTmp, aRectFnSet.GetLeft(aStFrame) );
277                 aRectFnSet.SetRight( aTmp, aRectFnSet.GetRight(aStFrame) );
278                 aRectFnSet.SetBottom( aTmp, aRectFnSet.GetBottom(aStFrame) );
279             }
280             aStFrame = pEndFrame->GetPaintArea();
281             {
282                 SwRectFnSet aRectFnSet(pEndFrame);
283                 aRectFnSet.SetTop( aRect, aRectFnSet.GetTop(aStFrame) );
284                 aRectFnSet.SetLeft( aRect, aRectFnSet.GetLeft(aStFrame) );
285                 aRectFnSet.SetRight( aRect, aRectFnSet.GetRight(aStFrame) );
286             }
287             aRect.Union( aTmp );
288             while( true )
289             {
290                 pStartFrame = pStartFrame->GetFollow();
291                 if( pStartFrame == pEndFrame )
292                     break;
293                 aRect.Union( pStartFrame->GetPaintArea() );
294             }
295         }
296     }
297     if( bSameFrame )
298     {
299         SwRectFnSet aRectFnSet(pStartFrame);
300         if( aRectFnSet.GetTop(aTmp) == aRectFnSet.GetTop(aRect) )
301             aRectFnSet.SetLeft( aRect, aRectFnSet.GetLeft(aTmp) );
302         else
303         {
304             SwRect aStFrame( pStartFrame->GetPaintArea() );
305             aRectFnSet.SetLeft( aRect, aRectFnSet.GetLeft(aStFrame) );
306             aRectFnSet.SetRight( aRect, aRectFnSet.GetRight(aStFrame) );
307             aRectFnSet.SetTop( aRect, aRectFnSet.GetTop(aTmp) );
308         }
309 
310         if( aTmp.Height() > aRect.Height() )
311             aRect.Height( aTmp.Height() );
312     }
313 
314     return aRect;
315 }
316 
317 /**
318  * Used for automatic styles. Used during RstAttr.
319  */
lcl_HaveCommonAttributes(IStyleAccess & rStyleAccess,const SfxItemSet * pSet1,sal_uInt16 nWhichId,const SfxItemSet & rSet2,std::shared_ptr<SfxItemSet> & pStyleHandle)320 static bool lcl_HaveCommonAttributes( IStyleAccess& rStyleAccess,
321                                       const SfxItemSet* pSet1,
322                                       sal_uInt16 nWhichId,
323                                       const SfxItemSet& rSet2,
324                                       std::shared_ptr<SfxItemSet>& pStyleHandle )
325 {
326     bool bRet = false;
327 
328     std::unique_ptr<SfxItemSet> pNewSet;
329 
330     if ( !pSet1 )
331     {
332         OSL_ENSURE( nWhichId, "lcl_HaveCommonAttributes not used correctly" );
333         if ( SfxItemState::SET == rSet2.GetItemState( nWhichId, false ) )
334         {
335             pNewSet = rSet2.Clone();
336             pNewSet->ClearItem( nWhichId );
337         }
338     }
339     else if ( pSet1->Count() )
340     {
341         SfxItemIter aIter( *pSet1 );
342         const SfxPoolItem* pItem = aIter.GetCurItem();
343         do
344         {
345             if ( SfxItemState::SET == rSet2.GetItemState( pItem->Which(), false ) )
346             {
347                 if ( !pNewSet )
348                     pNewSet = rSet2.Clone();
349                 pNewSet->ClearItem( pItem->Which() );
350             }
351 
352             pItem = aIter.NextItem();
353         } while (pItem);
354     }
355 
356     if ( pNewSet )
357     {
358         if ( pNewSet->Count() )
359             pStyleHandle = rStyleAccess.getAutomaticStyle( *pNewSet, IStyleAccess::AUTO_STYLE_CHAR );
360         bRet = true;
361     }
362 
363     return bRet;
364 }
365 
366 /** Delete all attributes
367  *
368  * 5 cases:
369  * 1) The attribute is completely in the deletion range:
370  *    -> delete it
371  * 2) The end of the attribute is in the deletion range:
372  *    -> delete it, then re-insert it with new end
373  * 3) The start of the attribute is in the deletion range:
374  *    -> delete it, then re-insert it with new start
375  * 4) The attribute contains the deletion range:
376  *       Split, i.e.,
377  *    -> Delete, re-insert from old start to start of deletion range
378  *    -> insert new attribute from end of deletion range to old end
379  * 5) The attribute is outside the deletion range
380  *    -> nothing to do
381  *
382  * @param nStt starting position
383  * @param nLen length of the deletion
384  * @param nthat ???
385  * @param pSet ???
386  * @param bInclRefToxMark ???
387  */
388 
RstTextAttr(sal_Int32 nStt,const sal_Int32 nLen,const sal_uInt16 nWhich,const SfxItemSet * pSet,const bool bInclRefToxMark,const bool bExactRange)389 void SwTextNode::RstTextAttr(
390     sal_Int32 nStt,
391     const sal_Int32 nLen,
392     const sal_uInt16 nWhich,
393     const SfxItemSet* pSet,
394     const bool bInclRefToxMark,
395     const bool bExactRange )
396 {
397     if ( !GetpSwpHints() )
398         return;
399 
400     sal_Int32 nEnd = nStt + nLen;
401     {
402         // enlarge range for the reset of text attributes in case of an overlapping input field
403         const SwTextInputField* pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt(nStt, RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent));
404         if ( pTextInputField == nullptr )
405         {
406             pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt(nEnd, RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent));
407         }
408         if ( pTextInputField != nullptr )
409         {
410             if ( nStt > pTextInputField->GetStart() )
411             {
412                 nStt = pTextInputField->GetStart();
413             }
414             if ( nEnd < *(pTextInputField->End()) )
415             {
416                 nEnd = *(pTextInputField->End());
417             }
418         }
419     }
420 
421     bool bChanged = false;
422 
423     // nMin and nMax initialized to maximum / minimum (inverse)
424     sal_Int32 nMin = m_Text.getLength();
425     sal_Int32 nMax = nStt;
426     const bool bNoLen = nMin == 0;
427 
428     // We have to remember the "new" attributes that have
429     // been introduced by splitting surrounding attributes (case 2,3,4).
430     std::vector<SwTextAttr *> newAttributes;
431     std::vector<SwTextAttr *> delAttributes;
432 
433     // iterate over attribute array until start of attribute is behind deletion range
434     m_pSwpHints->SortIfNeedBe(); // trigger sorting now, we don't want it during iteration
435     size_t i = 0;
436     sal_Int32 nAttrStart = sal_Int32();
437     SwTextAttr *pHt = nullptr;
438     while ( (i < m_pSwpHints->Count())
439             && ( ( ( nAttrStart = m_pSwpHints->GetWithoutResorting(i)->GetStart()) < nEnd )
440                  || nLen==0 || (nEnd == nAttrStart && nAttrStart == m_Text.getLength()))
441             && !bExactRange)
442     {
443         pHt = m_pSwpHints->GetWithoutResorting(i);
444 
445         // attributes without end stay in!
446         // but consider <bInclRefToxMark> used by Undo
447         const sal_Int32* const pAttrEnd = pHt->GetEnd();
448         const bool bKeepAttrWithoutEnd =
449             pAttrEnd == nullptr
450             && ( !bInclRefToxMark
451                  || ( RES_TXTATR_REFMARK != pHt->Which()
452                       && RES_TXTATR_TOXMARK != pHt->Which()
453                       && RES_TXTATR_META != pHt->Which()
454                       && RES_TXTATR_METAFIELD != pHt->Which() ) );
455         if ( bKeepAttrWithoutEnd )
456         {
457 
458             i++;
459             continue;
460         }
461         // attributes with content stay in
462         if ( pHt->HasContent() )
463         {
464             ++i;
465             continue;
466         }
467 
468         // Default behavior is to process all attributes:
469         bool bSkipAttr = false;
470         std::shared_ptr<SfxItemSet> pStyleHandle;
471 
472         // 1. case: We want to reset only the attributes listed in pSet:
473         if ( pSet )
474         {
475             bSkipAttr = SfxItemState::SET != pSet->GetItemState( pHt->Which(), false );
476             if ( bSkipAttr && RES_TXTATR_AUTOFMT == pHt->Which() )
477             {
478                 // if the current attribute is an autostyle, we have to check if the autostyle
479                 // and pSet have any attributes in common. If so, pStyleHandle will contain
480                 // a handle to AutoStyle / pSet:
481                 bSkipAttr = !lcl_HaveCommonAttributes( getIDocumentStyleAccess(), pSet, 0, *static_cast<const SwFormatAutoFormat&>(pHt->GetAttr()).GetStyleHandle(), pStyleHandle );
482             }
483         }
484         else if ( nWhich )
485         {
486             // 2. case: We want to reset only the attributes with WhichId nWhich:
487             bSkipAttr = nWhich != pHt->Which();
488             if ( bSkipAttr && RES_TXTATR_AUTOFMT == pHt->Which() )
489             {
490                 bSkipAttr = !lcl_HaveCommonAttributes( getIDocumentStyleAccess(), nullptr, nWhich, *static_cast<const SwFormatAutoFormat&>(pHt->GetAttr()).GetStyleHandle(), pStyleHandle );
491             }
492         }
493         else if ( !bInclRefToxMark )
494         {
495             // 3. case: Reset all attributes except from ref/toxmarks:
496             // skip hints with CH_TXTATR here
497             // (deleting those is ONLY allowed for UNDO!)
498             bSkipAttr = RES_TXTATR_REFMARK   == pHt->Which()
499                      || RES_TXTATR_TOXMARK   == pHt->Which()
500                      || RES_TXTATR_META      == pHt->Which()
501                      || RES_TXTATR_METAFIELD == pHt->Which();
502         }
503 
504         if ( bSkipAttr )
505         {
506             i++;
507             continue;
508         }
509 
510         if (nStt <= nAttrStart)     // Case: 1,3,5
511         {
512             const sal_Int32 nAttrEnd = pAttrEnd != nullptr
513                                         ? *pAttrEnd
514                                         : nAttrStart;
515             if (nEnd > nAttrStart
516                 || (nEnd == nAttrEnd && nEnd == nAttrStart)) // Case: 1,3
517             {
518                 if ( nMin > nAttrStart )
519                     nMin = nAttrStart;
520                 if ( nMax < nAttrEnd )
521                     nMax = nAttrEnd;
522                 // If only a no-extent hint is deleted, no resorting is needed
523                 bChanged = bChanged || nEnd > nAttrStart || bNoLen;
524                 if (nAttrEnd <= nEnd)   // Case: 1
525                 {
526                     delAttributes.push_back(pHt);
527 
528                     if ( pStyleHandle )
529                     {
530                         SwTextAttr* pNew = MakeTextAttr( GetDoc(),
531                                 *pStyleHandle, nAttrStart, nAttrEnd );
532                         newAttributes.push_back(pNew);
533                     }
534                 }
535                 else    // Case: 3
536                 {
537                     bChanged = true;
538                     m_pSwpHints->NoteInHistory( pHt );
539                     // UGLY: this may temporarily destroy the sorting!
540                     pHt->SetStart(nEnd);
541                     m_pSwpHints->NoteInHistory( pHt, true );
542 
543                     if ( pStyleHandle && nAttrStart < nEnd )
544                     {
545                         SwTextAttr* pNew = MakeTextAttr( GetDoc(),
546                                 *pStyleHandle, nAttrStart, nEnd );
547                         newAttributes.push_back(pNew);
548                     }
549                 }
550             }
551         }
552         else if (pAttrEnd != nullptr)         // Case: 2,4,5
553         {
554             if (*pAttrEnd > nStt)       // Case: 2,4
555             {
556                 if (*pAttrEnd < nEnd)   // Case: 2
557                 {
558                     if ( nMin > nAttrStart )
559                         nMin = nAttrStart;
560                     if ( nMax < *pAttrEnd )
561                         nMax = *pAttrEnd;
562                     bChanged = true;
563 
564                     const sal_Int32 nAttrEnd = *pAttrEnd;
565 
566                     m_pSwpHints->NoteInHistory( pHt );
567                     // UGLY: this may temporarily destroy the sorting!
568                     pHt->SetEnd(nStt);
569                     m_pSwpHints->NoteInHistory( pHt, true );
570 
571                     if ( pStyleHandle )
572                     {
573                         SwTextAttr* pNew = MakeTextAttr( GetDoc(),
574                             *pStyleHandle, nStt, nAttrEnd );
575                         newAttributes.push_back(pNew);
576                     }
577                 }
578                 else if (nLen)  // Case: 4
579                 {
580                     // for Length 0 both hints would be merged again by
581                     // InsertHint, so leave them alone!
582                     if ( nMin > nAttrStart )
583                         nMin = nAttrStart;
584                     if ( nMax < *pAttrEnd )
585                         nMax = *pAttrEnd;
586                     bChanged = true;
587                     const sal_Int32 nTmpEnd = *pAttrEnd;
588                     m_pSwpHints->NoteInHistory( pHt );
589                     // UGLY: this may temporarily destroy the sorting!
590                     pHt->SetEnd(nStt);
591                     m_pSwpHints->NoteInHistory( pHt, true );
592 
593                     if ( pStyleHandle && nStt < nEnd )
594                     {
595                         SwTextAttr* pNew = MakeTextAttr( GetDoc(),
596                             *pStyleHandle, nStt, nEnd );
597                         newAttributes.push_back(pNew);
598                     }
599 
600                     if( nEnd < nTmpEnd )
601                     {
602                         SwTextAttr* pNew = MakeTextAttr( GetDoc(),
603                             pHt->GetAttr(), nEnd, nTmpEnd );
604                         if ( pNew )
605                         {
606                             SwTextCharFormat* pCharFormat = dynamic_cast<SwTextCharFormat*>(pHt);
607                             if ( pCharFormat )
608                                 static_txtattr_cast<SwTextCharFormat*>(pNew)->SetSortNumber(pCharFormat->GetSortNumber());
609 
610                             newAttributes.push_back(pNew);
611                         }
612                     }
613                 }
614             }
615         }
616         ++i;
617     }
618 
619     if (bExactRange)
620     {
621         // Only delete the hints which start at nStt and end at nEnd.
622         for (i = 0; i < m_pSwpHints->Count(); ++i)
623         {
624             SwTextAttr* pHint = m_pSwpHints->Get(i);
625             if ( (isTXTATR_WITHEND(pHint->Which()) && RES_TXTATR_AUTOFMT != pHint->Which())
626                 || pHint->GetStart() != nStt)
627                 continue;
628 
629             const sal_Int32* pHintEnd = pHint->GetEnd();
630             if (!pHintEnd || *pHintEnd != nEnd)
631                 continue;
632 
633             delAttributes.push_back(pHint);
634         }
635     }
636 
637     if (bChanged && !delAttributes.empty())
638     {   // Delete() calls GetStartOf() - requires sorted hints!
639         m_pSwpHints->Resort();
640     }
641 
642     // delay deleting the hints because it re-sorts the hints array
643     for (SwTextAttr *const pDel : delAttributes)
644     {
645         m_pSwpHints->Delete(pDel);
646         DestroyAttr(pDel);
647     }
648 
649     // delay inserting the hints because it re-sorts the hints array
650     for (SwTextAttr *const pNew : newAttributes)
651     {
652         InsertHint(pNew, SetAttrMode::NOHINTADJUST);
653     }
654 
655     TryDeleteSwpHints();
656 
657     if (!bChanged)
658         return;
659 
660     if ( HasHints() )
661     {   // possibly sometimes Resort would be sufficient, but...
662         m_pSwpHints->MergePortions(*this);
663     }
664 
665     // TextFrame's respond to aHint, others to aNew
666     SwUpdateAttr aHint(
667         nMin,
668         nMax,
669         0);
670 
671     CallSwClientNotify(sw::LegacyModifyHint(nullptr, &aHint));
672     SwFormatChg aNew( GetFormatColl() );
673     CallSwClientNotify(sw::LegacyModifyHint(nullptr, &aNew));
674 }
675 
clipIndexBounds(std::u16string_view aStr,sal_Int32 nPos)676 static sal_Int32 clipIndexBounds(std::u16string_view aStr, sal_Int32 nPos)
677 {
678     if (nPos < 0)
679         return 0;
680     if (nPos > sal_Int32(aStr.size()))
681         return aStr.size();
682     return nPos;
683 }
684 
685 // Return current word:
686 // Search from left to right, so find the word before nPos.
687 // Except if at the start of the paragraph, then return the first word.
688 // If the first word consists only of whitespace, return an empty string.
GetCurWord(SwPosition const & rPos) const689 OUString SwTextFrame::GetCurWord(SwPosition const& rPos) const
690 {
691     TextFrameIndex const nPos(MapModelToViewPos(rPos));
692     SwTextNode *const pTextNode(rPos.GetNode().GetTextNode());
693     assert(pTextNode);
694     OUString const& rText(GetText());
695     assert(sal_Int32(nPos) <= rText.getLength()); // invalid index
696 
697     if (rText.isEmpty() || IsHiddenNow())
698         return OUString();
699 
700     assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
701     const uno::Reference< XBreakIterator > &rxBreak = g_pBreakIt->GetBreakIter();
702     sal_Int16 nWordType = WordType::DICTIONARY_WORD;
703     lang::Locale aLocale( g_pBreakIt->GetLocale(pTextNode->GetLang(rPos.GetContentIndex())) );
704     Boundary aBndry =
705         rxBreak->getWordBoundary(rText, sal_Int32(nPos), aLocale, nWordType, true);
706 
707     // if no word was found use previous word (if any)
708     if (aBndry.startPos == aBndry.endPos)
709     {
710         aBndry = rxBreak->previousWord(rText, sal_Int32(nPos), aLocale, nWordType);
711     }
712 
713     // check if word was found and if it uses a symbol font, if so
714     // enforce returning an empty string
715     if (aBndry.endPos != aBndry.startPos
716         && IsSymbolAt(TextFrameIndex(aBndry.startPos)))
717     {
718         aBndry.endPos = aBndry.startPos;
719     }
720 
721     // can have -1 as start/end of bounds not found
722     aBndry.startPos = clipIndexBounds(rText, aBndry.startPos);
723     aBndry.endPos = clipIndexBounds(rText, aBndry.endPos);
724 
725     return  rText.copy(aBndry.startPos,
726                        aBndry.endPos - aBndry.startPos);
727 }
728 
SwScanner(const SwTextNode & rNd,const OUString & rText,const LanguageType * pLang,const ModelToViewHelper & rConvMap,sal_uInt16 nType,sal_Int32 nStart,sal_Int32 nEnd,bool bClp)729 SwScanner::SwScanner( const SwTextNode& rNd, const OUString& rText,
730     const LanguageType* pLang, const ModelToViewHelper& rConvMap,
731     sal_uInt16 nType, sal_Int32 nStart, sal_Int32 nEnd, bool bClp )
732     : SwScanner(
733         [&rNd](sal_Int32 const nBegin, sal_uInt16 const nScript, bool const bNoChar)
734             { return rNd.GetLang(nBegin, bNoChar ? 0 : 1, nScript); }
735         , rText, pLang, rConvMap, nType, nStart, nEnd, bClp)
736 {
737 }
738 
SwScanner(std::function<LanguageType (sal_Int32,sal_Int32,bool)> aGetLangOfChar,OUString aText,const LanguageType * pLang,ModelToViewHelper aConvMap,sal_uInt16 nType,sal_Int32 nStart,sal_Int32 nEnd,bool bClp)739 SwScanner::SwScanner(std::function<LanguageType(sal_Int32, sal_Int32, bool)> aGetLangOfChar,
740                      OUString aText, const LanguageType* pLang,
741                      ModelToViewHelper aConvMap, sal_uInt16 nType, sal_Int32 nStart,
742                      sal_Int32 nEnd, bool bClp)
743     : m_pGetLangOfChar(std::move(aGetLangOfChar))
744     , m_aPreDashReplacementText(std::move(aText))
745     , m_pLanguage(pLang)
746     , m_ModelToView(std::move(aConvMap))
747     , m_nLength(0)
748     , m_nOverriddenDashCount(0)
749     , m_nWordType(nType)
750     , m_bClip(bClp)
751 {
752     m_nStartPos = m_nBegin = nStart;
753     m_nEndPos = nEnd;
754 
755     //MSWord f.e has special emdash and endash behaviour in that they break
756     //words for the purposes of word counting, while a hyphen etc. doesn't.
757 
758     //The default configuration treats emdash/endash as a word break, but
759     //additional ones can be added in under tools->options
760     if (m_nWordType == i18n::WordType::WORD_COUNT)
761     {
762         OUString sDashes = officecfg::Office::Writer::WordCount::AdditionalSeparators::get();
763         OUStringBuffer aBuf(m_aPreDashReplacementText);
764         for (sal_Int32 i = m_nStartPos; i < m_nEndPos; ++i)
765         {
766             if (i < 0)
767                 continue;
768             sal_Unicode cChar = aBuf[i];
769             if (sDashes.indexOf(cChar) != -1)
770             {
771                 aBuf[i] = ' ';
772                 ++m_nOverriddenDashCount;
773             }
774         }
775         m_aText = aBuf.makeStringAndClear();
776     }
777     else
778         m_aText = m_aPreDashReplacementText;
779 
780     assert(m_aPreDashReplacementText.getLength() == m_aText.getLength());
781 
782     if ( m_pLanguage )
783     {
784         m_aCurrentLang = *m_pLanguage;
785     }
786     else
787     {
788         ModelToViewHelper::ModelPosition aModelBeginPos =
789             m_ModelToView.ConvertToModelPosition( m_nBegin );
790         m_aCurrentLang = m_pGetLangOfChar(aModelBeginPos.mnPos, 0, true);
791     }
792 }
793 
794 namespace
795 {
796     //fdo#45271 for Asian words count characters instead of words
forceEachAsianCodePointToWord(const OUString & rText,sal_Int32 nBegin,sal_Int32 nLen)797     sal_Int32 forceEachAsianCodePointToWord(const OUString &rText, sal_Int32 nBegin, sal_Int32 nLen)
798     {
799         if (nLen > 1)
800         {
801             const uno::Reference< XBreakIterator > &rxBreak = g_pBreakIt->GetBreakIter();
802 
803             sal_uInt16 nCurrScript = rxBreak->getScriptType( rText, nBegin );
804 
805             sal_Int32 indexUtf16 = nBegin;
806             rText.iterateCodePoints(&indexUtf16);
807 
808             //First character is Asian, consider it a word :-(
809             if (nCurrScript == i18n::ScriptType::ASIAN)
810             {
811                 nLen = indexUtf16 - nBegin;
812                 return nLen;
813             }
814 
815             //First character was not Asian, consider appearance of any Asian character
816             //to be the end of the word
817             while (indexUtf16 < nBegin + nLen)
818             {
819                 nCurrScript = rxBreak->getScriptType( rText, indexUtf16 );
820                 if (nCurrScript == i18n::ScriptType::ASIAN)
821                 {
822                     nLen = indexUtf16 - nBegin;
823                     return nLen;
824                 }
825                 rText.iterateCodePoints(&indexUtf16);
826             }
827         }
828         return nLen;
829     }
830 }
831 
NextWord()832 bool SwScanner::NextWord()
833 {
834     m_nBegin = m_nBegin + m_nLength;
835     Boundary aBound;
836 
837     std::optional<CharClass> xLocalCharClass;
838 
839     while ( true )
840     {
841         // skip non-letter characters:
842         while (m_nBegin < m_aText.getLength())
843         {
844             if (m_nBegin >= 0 && !u_isspace(m_aText[m_nBegin]))
845             {
846                 if ( !m_pLanguage )
847                 {
848                     const sal_uInt16 nNextScriptType = g_pBreakIt->GetBreakIter()->getScriptType( m_aText, m_nBegin );
849                     ModelToViewHelper::ModelPosition aModelBeginPos =
850                         m_ModelToView.ConvertToModelPosition( m_nBegin );
851                     m_aCurrentLang = m_pGetLangOfChar(aModelBeginPos.mnPos, nNextScriptType, false);
852                 }
853 
854                 if ( m_nWordType != i18n::WordType::WORD_COUNT )
855                 {
856                     xLocalCharClass.emplace(LanguageTag( g_pBreakIt->GetLocale( m_aCurrentLang ) ));
857                     if ( xLocalCharClass->isLetterNumeric(OUString(m_aText[m_nBegin])) )
858                         break;
859                 }
860                 else
861                     break;
862             }
863             ++m_nBegin;
864         }
865 
866         if ( m_nBegin >= m_aText.getLength() || m_nBegin >= m_nEndPos )
867             return false;
868 
869         // get the word boundaries
870         aBound = g_pBreakIt->GetBreakIter()->getWordBoundary( m_aText, m_nBegin,
871                 g_pBreakIt->GetLocale( m_aCurrentLang ), m_nWordType, true );
872         OSL_ENSURE( aBound.endPos >= aBound.startPos, "broken aBound result" );
873 
874         // we don't want to include preceding text
875         // to count words in text with mixed script punctuation correctly,
876         // but we want to include preceding symbols (eg. percent sign, section sign,
877         // degree sign defined by dict_word_hu to spell check their affixed forms).
878         if (m_nWordType == i18n::WordType::WORD_COUNT && aBound.startPos < m_nBegin)
879             aBound.startPos = m_nBegin;
880 
881         //no word boundaries could be found
882         if(aBound.endPos == aBound.startPos)
883             return false;
884 
885         //if a word before is found it has to be searched for the next
886         if(aBound.endPos == m_nBegin)
887             ++m_nBegin;
888         else
889             break;
890     } // end while( true )
891 
892     // #i89042, as discussed with HDU: don't evaluate script changes for word count. Use whole word.
893     if ( m_nWordType == i18n::WordType::WORD_COUNT )
894     {
895         m_nBegin = std::max(aBound.startPos, m_nBegin);
896         m_nLength   = 0;
897         if (aBound.endPos > m_nBegin)
898             m_nLength = aBound.endPos - m_nBegin;
899     }
900     else
901     {
902         // we have to differentiate between these cases:
903         if ( aBound.startPos <= m_nBegin )
904         {
905             OSL_ENSURE( aBound.endPos >= m_nBegin, "Unexpected aBound result" );
906 
907             // restrict boundaries to script boundaries and nEndPos
908             const sal_uInt16 nCurrScript = g_pBreakIt->GetBreakIter()->getScriptType( m_aText, m_nBegin );
909             OUString aTmpWord = m_aText.copy( m_nBegin, aBound.endPos - m_nBegin );
910             const sal_Int32 nScriptEnd = m_nBegin +
911                 g_pBreakIt->GetBreakIter()->endOfScript( aTmpWord, 0, nCurrScript );
912             const sal_Int32 nEnd = std::min( aBound.endPos, nScriptEnd );
913 
914             // restrict word start to last script change position
915             sal_Int32 nScriptBegin = 0;
916             if ( aBound.startPos < m_nBegin )
917             {
918                 // search from nBegin backwards until the next script change
919                 aTmpWord = m_aText.copy( aBound.startPos,
920                                        m_nBegin - aBound.startPos + 1 );
921                 nScriptBegin = aBound.startPos +
922                     g_pBreakIt->GetBreakIter()->beginOfScript( aTmpWord, m_nBegin - aBound.startPos,
923                                                     nCurrScript );
924             }
925 
926             m_nBegin = std::max( aBound.startPos, nScriptBegin );
927             m_nLength = nEnd - m_nBegin;
928         }
929         else
930         {
931             const sal_uInt16 nCurrScript = g_pBreakIt->GetBreakIter()->getScriptType( m_aText, aBound.startPos );
932             OUString aTmpWord = m_aText.copy( aBound.startPos,
933                                              aBound.endPos - aBound.startPos );
934             const sal_Int32 nScriptEnd = aBound.startPos +
935                 g_pBreakIt->GetBreakIter()->endOfScript( aTmpWord, 0, nCurrScript );
936             const sal_Int32 nEnd = std::min( aBound.endPos, nScriptEnd );
937             m_nBegin = aBound.startPos;
938             m_nLength = nEnd - m_nBegin;
939         }
940     }
941 
942     // optionally clip the result of getWordBoundaries:
943     if ( m_bClip )
944     {
945         aBound.startPos = std::max( aBound.startPos, m_nStartPos );
946         aBound.endPos = std::min( aBound.endPos, m_nEndPos );
947         if (aBound.endPos < aBound.startPos)
948         {
949             m_nBegin = m_nEndPos;
950             m_nLength = 0; // found word is outside of search interval
951         }
952         else
953         {
954             m_nBegin = aBound.startPos;
955             m_nLength = aBound.endPos - m_nBegin;
956         }
957     }
958 
959     if( ! m_nLength )
960         return false;
961 
962     if ( m_nWordType == i18n::WordType::WORD_COUNT )
963         m_nLength = forceEachAsianCodePointToWord(m_aText, m_nBegin, m_nLength);
964 
965     m_aPrevWord = m_aWord;
966     m_aWord = m_aPreDashReplacementText.copy( m_nBegin, m_nLength );
967 
968     return true;
969 }
970 
971 // Note: this is a clone of SwTextFrame::AutoSpell_, so keep them in sync when fixing things!
Spell(SwSpellArgs * pArgs)972 bool SwTextNode::Spell(SwSpellArgs* pArgs)
973 {
974     // modify string according to redline information and hidden text
975     const OUString aOldText( m_Text );
976     OUStringBuffer buf(m_Text);
977     const bool bContainsComments = lcl_HasComments(*this);
978     const bool bRestoreString =
979         lcl_MaskRedlinesAndHiddenText(*this, buf, 0, m_Text.getLength());
980     if (bRestoreString)
981     {   // ??? UGLY: is it really necessary to modify m_Text here?
982         m_Text = buf.makeStringAndClear();
983     }
984 
985     sal_Int32 nBegin = ( &pArgs->pStartPos->GetNode() != this )
986         ? 0
987         : pArgs->pStartPos->GetContentIndex();
988 
989     sal_Int32 nEnd = ( &pArgs->pEndPos->GetNode() != this )
990             ? m_Text.getLength()
991             : pArgs->pEndPos->GetContentIndex();
992 
993     pArgs->xSpellAlt = nullptr;
994 
995     // 4 cases:
996 
997     // 1. IsWrongDirty = 0 and GetWrong = 0
998     //      Everything is checked and correct
999     // 2. IsWrongDirty = 0 and GetWrong = 1
1000     //      Everything is checked and errors are identified in the wrong list
1001     // 3. IsWrongDirty = 1 and GetWrong = 0
1002     //      Nothing has been checked
1003     // 4. IsWrongDirty = 1 and GetWrong = 1
1004     //      Text has been checked but there is an invalid range in the wrong list
1005 
1006     // Nothing has to be done for case 1.
1007     if ( ( IsWrongDirty() || GetWrong() ) && m_Text.getLength() )
1008     {
1009         if (nBegin > m_Text.getLength())
1010         {
1011             nBegin = m_Text.getLength();
1012         }
1013         if (nEnd > m_Text.getLength())
1014         {
1015             nEnd = m_Text.getLength();
1016         }
1017 
1018         if(!IsWrongDirty())
1019         {
1020             const sal_Int32 nTemp = GetWrong()->NextWrong( nBegin );
1021             if(nTemp > nEnd)
1022             {
1023                 // reset original text
1024                 if ( bRestoreString )
1025                 {
1026                     m_Text = aOldText;
1027                 }
1028                 return false;
1029             }
1030             if(nTemp > nBegin)
1031                 nBegin = nTemp;
1032 
1033         }
1034 
1035         // In case 2. we pass the wrong list to the scanned, because only
1036         // the words in the wrong list have to be checked
1037         SwScanner aScanner( *this, m_Text, nullptr, ModelToViewHelper(),
1038                             WordType::DICTIONARY_WORD,
1039                             nBegin, nEnd );
1040         bool bNextWord = aScanner.NextWord();
1041         while( !pArgs->xSpellAlt.is() && bNextWord )
1042         {
1043             bool bCalledNextWord = false;
1044 
1045             const OUString& rWord = aScanner.GetWord();
1046 
1047             // get next language for next word, consider language attributes
1048             // within the word
1049             LanguageType eActLang = aScanner.GetCurrentLanguage();
1050             DetectAndMarkMissingDictionaries( GetTextNode()->GetDoc(), pArgs->xSpeller, eActLang );
1051 
1052             if( rWord.getLength() > 0 && LANGUAGE_NONE != eActLang &&
1053                 !lcl_IsURL(rWord, *this, aScanner.GetBegin(), aScanner.GetLen() ) )
1054             {
1055                 if (pArgs->xSpeller.is())
1056                 {
1057                     SvxSpellWrapper::CheckSpellLang( pArgs->xSpeller, eActLang );
1058                     pArgs->xSpellAlt = pArgs->xSpeller->spell( rWord, static_cast<sal_uInt16>(eActLang),
1059                                             Sequence< PropertyValue >() );
1060                 }
1061                 if( pArgs->xSpellAlt.is() )
1062                 {
1063                     if ( IsSymbolAt(aScanner.GetBegin()) ||
1064                         // redlines can leave "in word" character within word,
1065                         // we must remove them before spell checking
1066                         // to avoid false alarm
1067                         ( (bRestoreString || bContainsComments) && pArgs->xSpeller->isValid( rWord.replaceAll(OUStringChar(CH_TXTATR_INWORD), ""),
1068                             static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() ) ) )
1069                     {
1070                         pArgs->xSpellAlt = nullptr;
1071                     }
1072                     else
1073                     {
1074                         OUString sPrevWord = aScanner.GetPrevWord();
1075                         auto nWordBegin = aScanner.GetBegin();
1076                         auto nWordEnd = aScanner.GetEnd();
1077                         bNextWord = aScanner.NextWord();
1078                         const OUString& rActualWord = aScanner.GetPrevWord();
1079                         bCalledNextWord = true;
1080                         // check space separated word pairs in the dictionary, e.g. "vice versa"
1081                         if ( !((bNextWord && !linguistic::HasDigits(aScanner.GetWord()) &&
1082                             pArgs->xSpeller->isValid( rActualWord + " " + aScanner.GetWord(),
1083                                 static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() )) ||
1084                            ( !sPrevWord.isEmpty() && !linguistic::HasDigits(sPrevWord) &&
1085                             pArgs->xSpeller->isValid( sPrevWord + " " + rActualWord,
1086                                 static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() ))) )
1087                         {
1088                             // make sure the selection build later from the data
1089                             // below does not include "in word" character to the
1090                             // left and right in order to preserve those. Therefore
1091                             // count those "in words" in order to modify the
1092                             // selection accordingly.
1093                             const sal_Unicode* pChar = aScanner.GetPrevWord().getStr();
1094                             sal_Int32 nLeft = 0;
1095                             while (*pChar++ == CH_TXTATR_INWORD)
1096                                 ++nLeft;
1097                             pChar = rActualWord.getLength() ? rActualWord.getStr() + rActualWord.getLength() - 1 : nullptr;
1098                             sal_Int32 nRight = 0;
1099                             while (pChar && *pChar-- == CH_TXTATR_INWORD)
1100                                 ++nRight;
1101 
1102                             pArgs->pStartPos->Assign(*this, nWordEnd - nRight );
1103                             pArgs->pEndPos->Assign(*this, nWordBegin + nLeft );
1104                         }
1105                         else
1106                         {
1107                             pArgs->xSpellAlt = nullptr;
1108                         }
1109                     }
1110                 }
1111             }
1112 
1113             if ( !bCalledNextWord )
1114                 bNextWord = aScanner.NextWord();
1115         }
1116     }
1117 
1118     // reset original text
1119     if ( bRestoreString )
1120     {
1121         m_Text = aOldText;
1122     }
1123 
1124     return pArgs->xSpellAlt.is();
1125 }
1126 
SetLanguageAndFont(const SwPaM & rPaM,LanguageType nLang,sal_uInt16 nLangWhichId,const vcl::Font * pFont,sal_uInt16 nFontWhichId)1127 void SwTextNode::SetLanguageAndFont( const SwPaM &rPaM,
1128     LanguageType nLang, sal_uInt16 nLangWhichId,
1129     const vcl::Font *pFont,  sal_uInt16 nFontWhichId )
1130 {
1131     SwEditShell *pEditShell = GetDoc().GetEditShell();
1132     if (!pEditShell)
1133         return;
1134     SfxItemSet aSet(pEditShell->GetAttrPool(), nLangWhichId, nLangWhichId );
1135     if (pFont)
1136         aSet.MergeRange(nFontWhichId, nFontWhichId); // Keep it sorted
1137     aSet.Put( SvxLanguageItem( nLang, nLangWhichId ) );
1138 
1139     OSL_ENSURE( pFont, "target font missing?" );
1140     if (pFont)
1141     {
1142         SvxFontItem aFontItem = static_cast<const SvxFontItem&>( aSet.Get( nFontWhichId ) );
1143         aFontItem.SetFamilyName(   pFont->GetFamilyName());
1144         aFontItem.SetFamily(       pFont->GetFamilyType());
1145         aFontItem.SetStyleName(    pFont->GetStyleName());
1146         aFontItem.SetPitch(        pFont->GetPitch());
1147         aFontItem.SetCharSet( pFont->GetCharSet() );
1148         aSet.Put( aFontItem );
1149     }
1150 
1151     GetDoc().getIDocumentContentOperations().InsertItemSet( rPaM, aSet );
1152     // SetAttr( aSet );    <- Does not set language attribute of empty paragraphs correctly,
1153     //                     <- because since there is no selection the flag to garbage
1154     //                     <- collect all attributes is set, and therefore attributes spanned
1155     //                     <- over empty selection are removed.
1156 
1157 }
1158 
Convert(SwConversionArgs & rArgs)1159 bool SwTextNode::Convert( SwConversionArgs &rArgs )
1160 {
1161     // get range of text within node to be converted
1162     // (either all the text or the text within the selection
1163     // when the conversion was started)
1164     const sal_Int32 nTextBegin = ( &rArgs.pStartPos->GetNode() == this )
1165         ? std::min(rArgs.pStartPos->GetContentIndex(), m_Text.getLength())
1166         : 0;
1167 
1168     const sal_Int32 nTextEnd = ( &rArgs.pEndPos->GetNode() == this )
1169         ?  std::min(rArgs.pEndPos->GetContentIndex(), m_Text.getLength())
1170         :  m_Text.getLength();
1171 
1172     rArgs.aConvText.clear();
1173 
1174     // modify string according to redline information and hidden text
1175     const OUString aOldText( m_Text );
1176     OUStringBuffer buf(m_Text);
1177     const bool bRestoreString =
1178         lcl_MaskRedlinesAndHiddenText(*this, buf, 0, m_Text.getLength());
1179     if (bRestoreString)
1180     {   // ??? UGLY: is it really necessary to modify m_Text here?
1181         m_Text = buf.makeStringAndClear();
1182     }
1183 
1184     bool    bFound  = false;
1185     sal_Int32  nBegin  = nTextBegin;
1186     sal_Int32  nLen = 0;
1187     LanguageType nLangFound = LANGUAGE_NONE;
1188     if (m_Text.isEmpty())
1189     {
1190         if (rArgs.bAllowImplicitChangesForNotConvertibleText)
1191         {
1192             // create SwPaM with mark & point spanning empty paragraph
1193             //SwPaM aCurPaM( *this, *this, nBegin, nBegin + nLen ); <-- wrong c-tor, does sth different
1194             SwPaM aCurPaM( *this, 0 );
1195 
1196             SetLanguageAndFont( aCurPaM,
1197                     rArgs.nConvTargetLang, RES_CHRATR_CJK_LANGUAGE,
1198                     rArgs.pTargetFont, RES_CHRATR_CJK_FONT );
1199         }
1200     }
1201     else
1202     {
1203         SwLanguageIterator aIter( *this, nBegin );
1204 
1205         // Implicit changes require setting new attributes, which in turn destroys
1206         // the attribute sequence on that aIter iterates. We store the necessary
1207         // coordinates and apply those changes after iterating through the text.
1208         typedef std::pair<sal_Int32, sal_Int32> ImplicitChangesRange;
1209         std::vector<ImplicitChangesRange> aImplicitChanges;
1210 
1211         // find non zero length text portion of appropriate language
1212         do {
1213             nLangFound = aIter.GetLanguage();
1214             bool bLangOk =  (nLangFound == rArgs.nConvSrcLang) ||
1215                                 (editeng::HangulHanjaConversion::IsChinese( nLangFound ) &&
1216                                  editeng::HangulHanjaConversion::IsChinese( rArgs.nConvSrcLang ));
1217 
1218             sal_Int32 nChPos = aIter.GetChgPos();
1219             // the position at the end of the paragraph is COMPLETE_STRING and
1220             // thus must be cut to the end of the actual string.
1221             assert(nChPos != -1);
1222             if (nChPos == -1 || nChPos == COMPLETE_STRING)
1223             {
1224                 nChPos = m_Text.getLength();
1225             }
1226 
1227             nLen = nChPos - nBegin;
1228             bFound = bLangOk && nLen > 0;
1229             if (!bFound)
1230             {
1231                 // create SwPaM with mark & point spanning the attributed text
1232                 //SwPaM aCurPaM( *this, *this, nBegin, nBegin + nLen ); <-- wrong c-tor, does sth different
1233                 SwPaM aCurPaM( *this, nBegin );
1234                 aCurPaM.SetMark();
1235                 aCurPaM.GetPoint()->SetContent(nBegin + nLen);
1236 
1237                 // check script type of selected text
1238                 if (SwEditShell *pEditShell = GetDoc().GetEditShell())
1239                 {
1240                     pEditShell->Push();             // save current cursor on stack
1241                     pEditShell->SetSelection( aCurPaM );
1242                     bool bIsAsianScript = (SvtScriptType::ASIAN == pEditShell->GetScriptType());
1243                     pEditShell->Pop(SwCursorShell::PopMode::DeleteCurrent); // restore cursor from stack
1244 
1245                     if (!bIsAsianScript && rArgs.bAllowImplicitChangesForNotConvertibleText)
1246                     {
1247                         // Store for later use
1248                         aImplicitChanges.emplace_back(nBegin, nBegin+nLen);
1249                     }
1250                 }
1251                 nBegin = nChPos;    // start of next language portion
1252             }
1253         } while (!bFound && aIter.Next());  /* loop while nothing was found and still sth is left to be searched */
1254 
1255         // Apply implicit changes, if any, now that aIter is no longer used
1256         for (const auto& rImplicitChange : aImplicitChanges)
1257         {
1258             SwPaM aPaM( *this, rImplicitChange.first );
1259             aPaM.SetMark();
1260             aPaM.GetPoint()->SetContent( rImplicitChange.second );
1261             SetLanguageAndFont( aPaM, rArgs.nConvTargetLang, RES_CHRATR_CJK_LANGUAGE, rArgs.pTargetFont, RES_CHRATR_CJK_FONT );
1262         }
1263 
1264     }
1265 
1266     // keep resulting text within selection / range of text to be converted
1267     if (nBegin < nTextBegin)
1268         nBegin = nTextBegin;
1269     if (nBegin + nLen > nTextEnd)
1270         nLen = nTextEnd - nBegin;
1271     bool bInSelection = nBegin < nTextEnd;
1272 
1273     if (bFound && bInSelection)     // convertible text found within selection/range?
1274     {
1275         OSL_ENSURE( !m_Text.isEmpty(), "convertible text portion missing!" );
1276         rArgs.aConvText     = m_Text.copy(nBegin, nLen);
1277         rArgs.nConvTextLang = nLangFound;
1278 
1279         // position where to start looking in next iteration (after current ends)
1280         rArgs.pStartPos->Assign(*this, nBegin + nLen );
1281         // end position (when we have travelled over the whole document)
1282         rArgs.pEndPos->Assign(*this, nBegin );
1283     }
1284 
1285     // restore original text
1286     if ( bRestoreString )
1287     {
1288         m_Text = aOldText;
1289     }
1290 
1291     return !rArgs.aConvText.isEmpty();
1292 }
1293 
1294 // Note: this is a clone of SwTextNode::Spell, so keep them in sync when fixing things!
AutoSpell_(SwTextNode & rNode,sal_Int32 nActPos)1295 SwRect SwTextFrame::AutoSpell_(SwTextNode & rNode, sal_Int32 nActPos)
1296 {
1297     SwRect aRect;
1298     assert(sw::FrameContainsNode(*this, rNode.GetIndex()));
1299     SwTextNode *const pNode(&rNode);
1300     if (!nActPos)
1301         nActPos = COMPLETE_STRING;
1302 
1303     SwAutoCompleteWord& rACW = SwDoc::GetAutoCompleteWords();
1304 
1305     // modify string according to redline information and hidden text
1306     const OUString aOldText( pNode->GetText() );
1307     OUStringBuffer buf(pNode->m_Text);
1308     const bool bContainsComments = lcl_HasComments(rNode);
1309     const bool bRestoreString =
1310         lcl_MaskRedlinesAndHiddenText(*pNode, buf, 0, pNode->GetText().getLength());
1311     if (bRestoreString)
1312     {   // ??? UGLY: is it really necessary to modify m_Text here? just for GetLang()?
1313         pNode->m_Text = buf.makeStringAndClear();
1314     }
1315 
1316     // a change of data indicates that at least one word has been modified
1317 
1318     sal_Int32 nBegin = 0;
1319     sal_Int32 nEnd = pNode->GetText().getLength();
1320     sal_Int32 nInsertPos = 0;
1321     sal_Int32 nChgStart = COMPLETE_STRING;
1322     sal_Int32 nChgEnd = 0;
1323     sal_Int32 nInvStart = COMPLETE_STRING;
1324     sal_Int32 nInvEnd = 0;
1325 
1326     const bool bAddAutoCmpl = pNode->IsAutoCompleteWordDirty() &&
1327                                   SwViewOption::IsAutoCompleteWords();
1328 
1329     if( pNode->GetWrong() )
1330     {
1331         nBegin = pNode->GetWrong()->GetBeginInv();
1332         if( COMPLETE_STRING != nBegin )
1333         {
1334             nEnd = std::max(pNode->GetWrong()->GetEndInv(), pNode->GetText().getLength());
1335         }
1336 
1337         // get word around nBegin, we start at nBegin - 1
1338         if ( COMPLETE_STRING != nBegin )
1339         {
1340             if ( nBegin )
1341                 --nBegin;
1342 
1343             LanguageType eActLang = pNode->GetLang( nBegin );
1344             Boundary aBound =
1345                 g_pBreakIt->GetBreakIter()->getWordBoundary( pNode->GetText(), nBegin,
1346                     g_pBreakIt->GetLocale( eActLang ),
1347                     WordType::DICTIONARY_WORD, true );
1348             nBegin = aBound.startPos;
1349         }
1350 
1351         // get the position in the wrong list
1352         nInsertPos = pNode->GetWrong()->GetWrongPos( nBegin );
1353 
1354         // sometimes we have to skip one entry
1355         if( nInsertPos < pNode->GetWrong()->Count() &&
1356             nBegin == pNode->GetWrong()->Pos( nInsertPos ) +
1357                       pNode->GetWrong()->Len( nInsertPos ) )
1358                 nInsertPos++;
1359     }
1360 
1361     bool bFresh = nBegin < nEnd;
1362     bool bPending(false);
1363 
1364     if( bFresh )
1365     {
1366         uno::Reference< XSpellChecker1 > xSpell( ::GetSpellChecker() );
1367         SwDoc& rDoc = pNode->GetDoc();
1368 
1369         SwScanner aScanner( *pNode, pNode->GetText(), nullptr, ModelToViewHelper(),
1370                             WordType::DICTIONARY_WORD, nBegin, nEnd);
1371 
1372         bool bNextWord = aScanner.NextWord();
1373         while( bNextWord )
1374         {
1375             const OUString& rWord = aScanner.GetWord();
1376             nBegin = aScanner.GetBegin();
1377             sal_Int32 nLen = aScanner.GetLen();
1378             bool bCalledNextWord = false;
1379 
1380             // get next language for next word, consider language attributes
1381             // within the word
1382             LanguageType eActLang = aScanner.GetCurrentLanguage();
1383             DetectAndMarkMissingDictionaries( rDoc, xSpell, eActLang );
1384 
1385             bool bSpell = xSpell.is() && xSpell->hasLanguage( static_cast<sal_uInt16>(eActLang) );
1386             if( bSpell && !rWord.isEmpty() && !lcl_IsURL(rWord, *pNode, nBegin, nLen) )
1387             {
1388                 // check for: bAlter => xHyphWord.is()
1389                 OSL_ENSURE(!bSpell || xSpell.is(), "NULL pointer");
1390                 if( !xSpell->isValid( rWord, static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() ) &&
1391                     // redlines can leave "in word" character within word,
1392                     // we must remove them before spell checking
1393                     // to avoid false alarm
1394                     ((!bRestoreString && !bContainsComments) || !xSpell->isValid( rWord.replaceAll(OUStringChar(CH_TXTATR_INWORD), ""),
1395                             static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() ) ) )
1396                 {
1397                     OUString sPrevWord = aScanner.GetPrevWord();
1398                     bNextWord = aScanner.NextWord();
1399                     bCalledNextWord = true;
1400                     // check space separated word pairs in the dictionary, e.g. "vice versa"
1401                     if ( !((bNextWord && !linguistic::HasDigits(aScanner.GetWord()) &&
1402                             xSpell->isValid( aScanner.GetPrevWord() + " " + aScanner.GetWord(),
1403                                 static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() )) ||
1404                            (!sPrevWord.isEmpty() && !linguistic::HasDigits(sPrevWord) &&
1405                             xSpell->isValid( sPrevWord + " " + aScanner.GetPrevWord(),
1406                                 static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() ))) )
1407                     {
1408                         sal_Int32 nSmartTagStt = nBegin;
1409                         sal_Int32 nDummy = 1;
1410                         if ( !pNode->GetSmartTags() || !pNode->GetSmartTags()->InWrongWord( nSmartTagStt, nDummy ) )
1411                         {
1412                             if( !pNode->GetWrong() )
1413                             {
1414                                 pNode->SetWrong( std::make_unique<SwWrongList>( WRONGLIST_SPELL ) );
1415                                 pNode->GetWrong()->SetInvalid( 0, nEnd );
1416                             }
1417                             SwWrongList::FreshState const eState(pNode->GetWrong()->Fresh(
1418                                 nChgStart, nChgEnd, nBegin, nLen, nInsertPos, nActPos));
1419                             switch (eState)
1420                             {
1421                                 case SwWrongList::FreshState::FRESH:
1422                                     pNode->GetWrong()->Insert(OUString(), nullptr, nBegin, nLen, nInsertPos++);
1423                                     break;
1424                                 case SwWrongList::FreshState::CURSOR:
1425                                     bPending = true;
1426                                     [[fallthrough]]; // to mark as invalid
1427                                 case SwWrongList::FreshState::NOTHING:
1428                                     nInvStart = nBegin;
1429                                     nInvEnd = nBegin + nLen;
1430                                     break;
1431                             }
1432                         }
1433                     }
1434                     else if( bAddAutoCmpl && rACW.GetMinWordLen() <= aScanner.GetPrevWord().getLength() )
1435                     {
1436                         // tdf#119695 only add the word if the cursor position is outside the word
1437                         // so that the incomplete words are not added as autocomplete candidates
1438                         bool bCursorOutsideWord = nActPos > nBegin + nLen || nActPos < nBegin;
1439                         if (bCursorOutsideWord)
1440                             rACW.InsertWord(aScanner.GetPrevWord(), rDoc);
1441                     }
1442                 }
1443                 else if( bAddAutoCmpl && rACW.GetMinWordLen() <= rWord.getLength() )
1444                 {
1445                     // tdf#119695 only add the word if the cursor position is outside the word
1446                     // so that the incomplete words are not added as autocomplete candidates
1447                     bool bCursorOutsideWord = nActPos > nBegin + nLen || nActPos < nBegin;
1448                     if (bCursorOutsideWord)
1449                         rACW.InsertWord(rWord, rDoc);
1450                 }
1451             }
1452 
1453             if ( !bCalledNextWord )
1454                  bNextWord = aScanner.NextWord();
1455         }
1456     }
1457 
1458     // reset original text
1459     // i63141 before calling GetCharRect(..) with formatting!
1460     if ( bRestoreString )
1461     {
1462         pNode->m_Text = aOldText;
1463     }
1464     if( pNode->GetWrong() )
1465     {
1466         if( bFresh )
1467             pNode->GetWrong()->Fresh( nChgStart, nChgEnd,
1468                                       nEnd, 0, nInsertPos, nActPos );
1469 
1470         // Calculate repaint area:
1471 
1472         if( nChgStart < nChgEnd )
1473         {
1474             aRect = lcl_CalculateRepaintRect(*this, rNode, nChgStart, nChgEnd);
1475 
1476             // fdo#71558 notify misspelled word to accessibility
1477 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
1478             SwViewShell* pViewSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr;
1479             if( pViewSh )
1480                 pViewSh->InvalidateAccessibleParaAttrs( *this );
1481 #endif
1482         }
1483 
1484         pNode->GetWrong()->SetInvalid( nInvStart, nInvEnd );
1485         pNode->SetWrongDirty(
1486             (COMPLETE_STRING != pNode->GetWrong()->GetBeginInv())
1487                 ? (bPending
1488                     ? sw::WrongState::PENDING
1489                     : sw::WrongState::TODO)
1490                 : sw::WrongState::DONE);
1491         if( !pNode->GetWrong()->Count() && ! pNode->IsWrongDirty() )
1492             pNode->ClearWrong();
1493 
1494         if (bPending && getRootFrame())
1495         {
1496             if (SwViewShell* pViewSh = getRootFrame()->GetCurrShell())
1497             {
1498                 pViewSh->OnSpellWrongStatePending();
1499             }
1500         }
1501     }
1502     else
1503         pNode->SetWrongDirty(sw::WrongState::DONE);
1504 
1505     if( bAddAutoCmpl )
1506         pNode->SetAutoCompleteWordDirty( false );
1507 
1508     return aRect;
1509 }
1510 
1511 /** Function: SmartTagScan
1512 
1513     Function scans words in current text and checks them in the
1514     smarttag libraries. If the check returns true to bounds of the
1515     recognized words are stored into a list that is used later for drawing
1516     the underline.
1517 
1518     @return SwRect Repaint area
1519 */
SmartTagScan(SwTextNode & rNode)1520 SwRect SwTextFrame::SmartTagScan(SwTextNode & rNode)
1521 {
1522     SwRect aRet;
1523 
1524     assert(sw::FrameContainsNode(*this, rNode.GetIndex()));
1525     SwTextNode *const pNode = &rNode;
1526     const OUString& rText = pNode->GetText();
1527 
1528     // Iterate over language portions
1529     SmartTagMgr& rSmartTagMgr = SwSmartTagMgr::Get();
1530 
1531     SwWrongList* pSmartTagList = pNode->GetSmartTags();
1532 
1533     sal_Int32 nBegin = 0;
1534     sal_Int32 nEnd = rText.getLength();
1535 
1536     if ( pSmartTagList )
1537     {
1538         if ( pSmartTagList->GetBeginInv() != COMPLETE_STRING )
1539         {
1540             nBegin = pSmartTagList->GetBeginInv();
1541             nEnd = std::min( pSmartTagList->GetEndInv(), rText.getLength() );
1542 
1543             if ( nBegin < nEnd )
1544             {
1545                 const LanguageType aCurrLang = pNode->GetLang( nBegin );
1546                 const css::lang::Locale aCurrLocale = g_pBreakIt->GetLocale( aCurrLang );
1547                 nBegin = g_pBreakIt->GetBreakIter()->beginOfSentence( rText, nBegin, aCurrLocale );
1548                 nEnd = g_pBreakIt->GetBreakIter()->endOfSentence(rText, nEnd, aCurrLocale);
1549                 if (nEnd > rText.getLength() || nEnd < 0)
1550                     nEnd = rText.getLength();
1551             }
1552         }
1553     }
1554 
1555     const sal_uInt16 nNumberOfEntries = pSmartTagList ? pSmartTagList->Count() : 0;
1556     sal_uInt16 nNumberOfRemovedEntries = 0;
1557     sal_uInt16 nNumberOfInsertedEntries = 0;
1558 
1559     // clear smart tag list between nBegin and nEnd:
1560     if ( 0 != nNumberOfEntries )
1561     {
1562         sal_Int32 nChgStart = COMPLETE_STRING;
1563         sal_Int32 nChgEnd = 0;
1564         const sal_uInt16 nCurrentIndex = pSmartTagList->GetWrongPos( nBegin );
1565         pSmartTagList->Fresh( nChgStart, nChgEnd, nBegin, nEnd - nBegin, nCurrentIndex, COMPLETE_STRING );
1566         nNumberOfRemovedEntries = nNumberOfEntries - pSmartTagList->Count();
1567     }
1568 
1569     if ( nBegin < nEnd )
1570     {
1571         // Expand the string:
1572         const ModelToViewHelper aConversionMap(*pNode, getRootFrame() /*TODO - replace or expand fields for smart tags?*/);
1573         const OUString& aExpandText = aConversionMap.getViewText();
1574 
1575         // Ownership ov ConversionMap is passed to SwXTextMarkup object!
1576         uno::Reference<text::XTextMarkup> const xTextMarkup =
1577              new SwXTextMarkup(pNode, aConversionMap);
1578 
1579         css::uno::Reference< css::frame::XController > xController = pNode->GetDoc().GetDocShell()->GetController();
1580 
1581         SwPosition start(*pNode, nBegin);
1582         SwPosition end  (*pNode, nEnd);
1583         rtl::Reference<SwXTextRange> xRange = SwXTextRange::CreateXTextRange(pNode->GetDoc(), start, &end);
1584 
1585         rSmartTagMgr.RecognizeTextRange(xRange, xTextMarkup, xController);
1586 
1587         sal_Int32 nLangBegin = nBegin;
1588         sal_Int32 nLangEnd;
1589 
1590         // smart tag recognition has to be done for each language portion:
1591         SwLanguageIterator aIter( *pNode, nLangBegin );
1592 
1593         do
1594         {
1595             const LanguageType nLang = aIter.GetLanguage();
1596             const css::lang::Locale aLocale = g_pBreakIt->GetLocale( nLang );
1597             nLangEnd = std::min<sal_Int32>( nEnd, aIter.GetChgPos() );
1598 
1599             const sal_Int32 nExpandBegin = aConversionMap.ConvertToViewPosition( nLangBegin );
1600             const sal_Int32 nExpandEnd   = aConversionMap.ConvertToViewPosition( nLangEnd );
1601 
1602             rSmartTagMgr.RecognizeString(aExpandText, xTextMarkup, xController, aLocale, nExpandBegin, nExpandEnd - nExpandBegin );
1603 
1604             nLangBegin = nLangEnd;
1605         }
1606         while ( aIter.Next() && nLangEnd < nEnd );
1607 
1608         pSmartTagList = pNode->GetSmartTags();
1609 
1610         const sal_uInt16 nNumberOfEntriesAfterRecognize = pSmartTagList ? pSmartTagList->Count() : 0;
1611         nNumberOfInsertedEntries = nNumberOfEntriesAfterRecognize - ( nNumberOfEntries - nNumberOfRemovedEntries );
1612     }
1613 
1614     if( pSmartTagList )
1615     {
1616         // Update WrongList stuff
1617         pSmartTagList->SetInvalid( COMPLETE_STRING, 0 );
1618         pNode->SetSmartTagDirty( COMPLETE_STRING != pSmartTagList->GetBeginInv() );
1619 
1620         if( !pSmartTagList->Count() && !pNode->IsSmartTagDirty() )
1621             pNode->ClearSmartTags();
1622 
1623         // Calculate repaint area:
1624         if ( nBegin < nEnd && ( 0 != nNumberOfRemovedEntries ||
1625                                 0 != nNumberOfInsertedEntries ) )
1626         {
1627             aRet = lcl_CalculateRepaintRect(*this, rNode, nBegin, nEnd);
1628         }
1629     }
1630     else
1631         pNode->SetSmartTagDirty( false );
1632 
1633     return aRet;
1634 }
1635 
CollectAutoCmplWrds(SwTextNode & rNode,sal_Int32 nActPos)1636 void SwTextFrame::CollectAutoCmplWrds(SwTextNode & rNode, sal_Int32 nActPos)
1637 {
1638     assert(sw::FrameContainsNode(*this, rNode.GetIndex())); (void) this;
1639     SwTextNode *const pNode(&rNode);
1640     if (!nActPos)
1641         nActPos = COMPLETE_STRING;
1642 
1643     SwDoc& rDoc = pNode->GetDoc();
1644     SwAutoCompleteWord& rACW = SwDoc::GetAutoCompleteWords();
1645 
1646     sal_Int32  nBegin = 0;
1647     sal_Int32  nEnd = pNode->GetText().getLength();
1648     sal_Int32  nLen;
1649     bool bACWDirty = false;
1650 
1651     if( nBegin < nEnd )
1652     {
1653         int nCnt = 200;
1654         SwScanner aScanner( *pNode, pNode->GetText(), nullptr, ModelToViewHelper(),
1655                             WordType::DICTIONARY_WORD, nBegin, nEnd );
1656         while( aScanner.NextWord() )
1657         {
1658             nBegin = aScanner.GetBegin();
1659             nLen = aScanner.GetLen();
1660             if( rACW.GetMinWordLen() <= nLen )
1661             {
1662                 const OUString& rWord = aScanner.GetWord();
1663 
1664                 if( nActPos < nBegin || ( nBegin + nLen ) < nActPos )
1665                 {
1666                     if( rACW.GetMinWordLen() <= rWord.getLength() )
1667                         rACW.InsertWord( rWord, rDoc );
1668                 }
1669                 else
1670                     bACWDirty = true;
1671             }
1672             if( !--nCnt )
1673             {
1674                 // don't wait for TIMER here, so we can finish big paragraphs
1675                 if (Application::AnyInput(VCL_INPUT_ANY & VclInputFlags(~VclInputFlags::TIMER)))
1676                     return;
1677                 nCnt = 100;
1678             }
1679         }
1680     }
1681 
1682     if (!bACWDirty)
1683         pNode->SetAutoCompleteWordDirty( false );
1684 }
1685 
SwInterHyphInfoTextFrame(SwTextFrame const & rFrame,SwTextNode const & rNode,SwInterHyphInfo const & rHyphInfo)1686 SwInterHyphInfoTextFrame::SwInterHyphInfoTextFrame(
1687         SwTextFrame const& rFrame, SwTextNode const& rNode,
1688         SwInterHyphInfo const& rHyphInfo)
1689     : m_nStart(rFrame.MapModelToView(&rNode, rHyphInfo.m_nStart))
1690     , m_nEnd(rFrame.MapModelToView(&rNode, rHyphInfo.m_nEnd))
1691     , m_nWordStart(0)
1692     , m_nWordLen(0)
1693 {
1694 }
1695 
UpdateTextNodeHyphInfo(SwTextFrame const & rFrame,SwTextNode const & rNode,SwInterHyphInfo & o_rHyphInfo)1696 void SwInterHyphInfoTextFrame::UpdateTextNodeHyphInfo(SwTextFrame const& rFrame,
1697         SwTextNode const& rNode, SwInterHyphInfo & o_rHyphInfo)
1698 {
1699     std::pair<SwTextNode const*, sal_Int32> const wordStart(rFrame.MapViewToModel(m_nWordStart));
1700     std::pair<SwTextNode const*, sal_Int32> const wordEnd(rFrame.MapViewToModel(m_nWordStart+m_nWordLen));
1701     if (wordStart.first != &rNode || wordEnd.first != &rNode)
1702     {   // not sure if this can happen since nStart/nEnd are in rNode
1703         SAL_WARN("sw.core", "UpdateTextNodeHyphInfo: outside of node");
1704         return;
1705     }
1706     o_rHyphInfo.m_nWordStart = wordStart.second;
1707     o_rHyphInfo.m_nWordLen = wordEnd.second - wordStart.second;
1708     o_rHyphInfo.SetHyphWord(m_xHyphWord);
1709 }
1710 
1711 /// Find the SwTextFrame and call its Hyphenate
Hyphenate(SwInterHyphInfo & rHyphInf)1712 bool SwTextNode::Hyphenate( SwInterHyphInfo &rHyphInf )
1713 {
1714     // shortcut: paragraph doesn't have a language set:
1715     if ( LANGUAGE_NONE == GetSwAttrSet().GetLanguage().GetLanguage()
1716          && LanguageType(USHRT_MAX) == GetLang(0, m_Text.getLength()))
1717     {
1718         return false;
1719     }
1720 
1721     SwTextFrame *pFrame = ::sw::SwHyphIterCacheLastTextFrame(this,
1722         [&rHyphInf, this]() {
1723             std::pair<Point, bool> tmp;
1724             Point const*const pPoint = rHyphInf.GetCursorPos();
1725             if (pPoint)
1726             {
1727                 tmp.first = *pPoint;
1728                 tmp.second = true;
1729             }
1730             return static_cast<SwTextFrame*>(this->getLayoutFrame(
1731                 this->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(),
1732                 nullptr, pPoint ? &tmp : nullptr));
1733         });
1734     if (!pFrame)
1735     {
1736         // There was a comment here that claimed that the following assertion
1737         // shouldn't exist as it's triggered by "Trennung ueber Sonderbereiche",
1738         // (hyphenation across special sections?), whatever that means.
1739         OSL_ENSURE( pFrame, "!SwTextNode::Hyphenate: can't find any frame" );
1740         return false;
1741     }
1742     SwInterHyphInfoTextFrame aHyphInfo(*pFrame, *this, rHyphInf);
1743 
1744     pFrame = &(pFrame->GetFrameAtOfst( aHyphInfo.m_nStart ));
1745 
1746     while( pFrame )
1747     {
1748         if (pFrame->Hyphenate(aHyphInfo))
1749         {
1750             // The layout is not robust wrt. "direct formatting"
1751             // cf. layact.cxx, SwLayAction::TurboAction_(), if( !pCnt->IsValid() ...
1752             pFrame->SetCompletePaint();
1753             aHyphInfo.UpdateTextNodeHyphInfo(*pFrame, *this, rHyphInf);
1754             return true;
1755         }
1756         pFrame = pFrame->GetFollow();
1757         if( pFrame )
1758         {
1759             aHyphInfo.m_nEnd = aHyphInfo.m_nEnd - (pFrame->GetOffset() - aHyphInfo.m_nStart);
1760             aHyphInfo.m_nStart = pFrame->GetOffset();
1761         }
1762     }
1763     return false;
1764 }
1765 
1766 namespace
1767 {
1768     struct swTransliterationChgData
1769     {
1770         sal_Int32               nStart;
1771         sal_Int32               nLen;
1772         OUString                sChanged;
1773         Sequence< sal_Int32 >   aOffsets;
1774     };
1775 }
1776 
1777 // change text to Upper/Lower/Hiragana/Katakana/...
TransliterateText(utl::TransliterationWrapper & rTrans,sal_Int32 nStt,sal_Int32 nEnd,SwUndoTransliterate * pUndo,bool bUseRedlining)1778 void SwTextNode::TransliterateText(
1779     utl::TransliterationWrapper& rTrans,
1780     sal_Int32 nStt, sal_Int32 nEnd,
1781     SwUndoTransliterate* pUndo, bool bUseRedlining )
1782 {
1783     if (nStt >= nEnd)
1784         return;
1785 
1786     const sal_Int32 selStart = nStt;
1787     const sal_Int32 selEnd = nEnd;
1788 
1789     // since we don't use Hiragana/Katakana or half-width/full-width transliterations here
1790     // it is fine to use ANYWORD_IGNOREWHITESPACES. (ANY_WORD btw is broken and will
1791     // occasionally miss words in consecutive sentences). Also with ANYWORD_IGNOREWHITESPACES
1792     // text like 'just-in-time' will be converted to 'Just-In-Time' which seems to be the
1793     // proper thing to do.
1794     const sal_Int16 nWordType = WordType::ANYWORD_IGNOREWHITESPACES;
1795 
1796     // In order to have less trouble with changing text size, e.g. because
1797     // of ligatures or German small sz being resolved, we need to process
1798     // the text replacements from end to start.
1799     // This way the offsets for the yet to be changed words will be
1800     // left unchanged by the already replaced text.
1801     // For this we temporarily save the changes to be done in this vector
1802     std::vector< swTransliterationChgData >   aChanges;
1803     swTransliterationChgData                  aChgData;
1804 
1805     if (rTrans.getType() == TransliterationFlags::TITLE_CASE)
1806     {
1807         // for 'capitalize every word' we need to iterate over each word
1808 
1809         Boundary aSttBndry;
1810         Boundary aEndBndry;
1811         aSttBndry = g_pBreakIt->GetBreakIter()->getWordBoundary(
1812                     GetText(), nStt,
1813                     g_pBreakIt->GetLocale( GetLang( nStt ) ),
1814                     nWordType,
1815                     true /*prefer forward direction*/);
1816         aEndBndry = g_pBreakIt->GetBreakIter()->getWordBoundary(
1817                     GetText(), nEnd,
1818                     g_pBreakIt->GetLocale( GetLang( nEnd ) ),
1819                     nWordType,
1820                     false /*prefer backward direction*/);
1821 
1822         // prevent backtracking to the previous word if selection is at word boundary
1823         if (aSttBndry.endPos <= nStt)
1824         {
1825             aSttBndry = g_pBreakIt->GetBreakIter()->nextWord(
1826                     GetText(), aSttBndry.endPos,
1827                     g_pBreakIt->GetLocale( GetLang( aSttBndry.endPos ) ),
1828                     nWordType);
1829         }
1830         // prevent advancing to the next word if selection is at word boundary
1831         if (aEndBndry.startPos >= nEnd)
1832         {
1833             aEndBndry = g_pBreakIt->GetBreakIter()->previousWord(
1834                     GetText(), aEndBndry.startPos,
1835                     g_pBreakIt->GetLocale( GetLang( aEndBndry.startPos ) ),
1836                     nWordType);
1837         }
1838 
1839         /* Nothing to do if user selection lies entirely outside of word start and end boundary computed above.
1840          * Skip this node, because otherwise the below logic for constraining to the selection will fail */
1841         if (aSttBndry.startPos >= selEnd || aEndBndry.endPos <= selStart) {
1842             return;
1843         }
1844 
1845         // prevent going outside of the user's selection, which may
1846         // start in the middle of a word
1847         aSttBndry.startPos = std::max(aSttBndry.startPos, selStart);
1848         aEndBndry.startPos = std::max(aSttBndry.startPos, aEndBndry.startPos);
1849 
1850         Boundary aCurWordBndry( aSttBndry );
1851         while (aCurWordBndry.startPos <= aEndBndry.startPos)
1852         {
1853             nStt = aCurWordBndry.startPos;
1854             nEnd = aCurWordBndry.endPos;
1855             const sal_Int32 nLen = nEnd - nStt;
1856             OSL_ENSURE( nLen > 0, "invalid word length of 0" );
1857 
1858             Sequence <sal_Int32> aOffsets;
1859             OUString const sChgd( rTrans.transliterate(
1860                         GetText(), GetLang(nStt), nStt, nLen, &aOffsets) );
1861 
1862             assert(nStt < m_Text.getLength());
1863             if (0 != rtl_ustr_shortenedCompare_WithLength(
1864                         m_Text.getStr() + nStt, m_Text.getLength() - nStt,
1865                         sChgd.getStr(), sChgd.getLength(), nLen))
1866             {
1867                 aChgData.nStart     = nStt;
1868                 aChgData.nLen       = nLen;
1869                 aChgData.sChanged   = sChgd;
1870                 aChgData.aOffsets   = aOffsets;
1871                 aChanges.push_back( aChgData );
1872             }
1873 
1874             aCurWordBndry = g_pBreakIt->GetBreakIter()->nextWord(
1875                     GetText(), nStt,
1876                     g_pBreakIt->GetLocale(GetLang(nStt, 1)),
1877                     nWordType);
1878         }
1879     }
1880     else if (rTrans.getType() == TransliterationFlags::SENTENCE_CASE)
1881     {
1882         // For 'sentence case' we need to iterate sentence by sentence.
1883         // nLastStart and nLastEnd are the boundaries of the last sentence in
1884         // the user's selection.
1885         sal_Int32 nLastStart = g_pBreakIt->GetBreakIter()->beginOfSentence(
1886                 GetText(), nEnd,
1887                 g_pBreakIt->GetLocale( GetLang( nEnd ) ) );
1888         sal_Int32 nLastEnd = g_pBreakIt->GetBreakIter()->endOfSentence(
1889                 GetText(), nLastStart,
1890                 g_pBreakIt->GetLocale( GetLang( nLastStart ) ) );
1891 
1892         // Begin with the starting point of the user's selection (it may not be
1893         // the beginning of a sentence)...
1894         sal_Int32 nCurrentStart = nStt;
1895         // ...And extend to the end of the first sentence
1896         sal_Int32 nCurrentEnd = g_pBreakIt->GetBreakIter()->endOfSentence(
1897                 GetText(), nCurrentStart,
1898                 g_pBreakIt->GetLocale( GetLang( nCurrentStart ) ) );
1899 
1900         // prevent backtracking to the previous sentence if selection starts at end of a sentence
1901         if (nCurrentEnd <= nStt)
1902         {
1903             // now nCurrentStart is probably located on a non-letter word. (unless we
1904             // are in Asian text with no spaces...)
1905             // Thus to get the real sentence start we should locate the next real word,
1906             // that is one found by DICTIONARY_WORD
1907             i18n::Boundary aBndry = g_pBreakIt->GetBreakIter()->nextWord(
1908                     GetText(), nCurrentEnd,
1909                     g_pBreakIt->GetLocale( GetLang( nCurrentEnd ) ),
1910                     i18n::WordType::DICTIONARY_WORD);
1911 
1912             // now get new current sentence boundaries
1913             nCurrentStart = g_pBreakIt->GetBreakIter()->beginOfSentence(
1914                     GetText(), aBndry.startPos,
1915                     g_pBreakIt->GetLocale( GetLang( aBndry.startPos) ) );
1916             nCurrentEnd = g_pBreakIt->GetBreakIter()->endOfSentence(
1917                     GetText(), nCurrentStart,
1918                     g_pBreakIt->GetLocale( GetLang( nCurrentStart) ) );
1919         }
1920         // prevent advancing to the next sentence if selection ends at start of a sentence
1921         if (nLastStart >= nEnd)
1922         {
1923             // now nCurrentStart is probably located on a non-letter word. (unless we
1924             // are in Asian text with no spaces...)
1925             // Thus to get the real sentence start we should locate the previous real word,
1926             // that is one found by DICTIONARY_WORD
1927             i18n::Boundary aBndry = g_pBreakIt->GetBreakIter()->previousWord(
1928                     GetText(), nLastStart,
1929                     g_pBreakIt->GetLocale( GetLang( nLastStart) ),
1930                     i18n::WordType::DICTIONARY_WORD);
1931             nLastEnd = g_pBreakIt->GetBreakIter()->endOfSentence(
1932                     GetText(), aBndry.startPos,
1933                     g_pBreakIt->GetLocale( GetLang( aBndry.startPos) ) );
1934             if (nCurrentEnd > nLastEnd)
1935                 nCurrentEnd = nLastEnd;
1936         }
1937 
1938         // Prevent going outside of the user's selection
1939         nCurrentStart = std::max(selStart, nCurrentStart);
1940         nCurrentEnd = std::min(selEnd, nCurrentEnd);
1941         nLastEnd = std::min(selEnd, nLastEnd);
1942 
1943         while (nCurrentStart < nLastEnd)
1944         {
1945             sal_Int32 nLen = nCurrentEnd - nCurrentStart;
1946             OSL_ENSURE( nLen > 0, "invalid word length of 0" );
1947 
1948             Sequence <sal_Int32> aOffsets;
1949             OUString const sChgd( rTrans.transliterate(GetText(),
1950                 GetLang(nCurrentStart), nCurrentStart, nLen, &aOffsets) );
1951 
1952             assert(nStt < m_Text.getLength());
1953             if (0 != rtl_ustr_shortenedCompare_WithLength(
1954                         m_Text.getStr() + nStt, m_Text.getLength() - nStt,
1955                         sChgd.getStr(), sChgd.getLength(), nLen))
1956             {
1957                 aChgData.nStart     = nCurrentStart;
1958                 aChgData.nLen       = nLen;
1959                 aChgData.sChanged   = sChgd;
1960                 aChgData.aOffsets   = aOffsets;
1961                 aChanges.push_back( aChgData );
1962             }
1963 
1964             Boundary aFirstWordBndry = g_pBreakIt->GetBreakIter()->nextWord(
1965                     GetText(), nCurrentEnd,
1966                     g_pBreakIt->GetLocale( GetLang( nCurrentEnd ) ),
1967                     nWordType);
1968             nCurrentStart = aFirstWordBndry.startPos;
1969             nCurrentEnd = g_pBreakIt->GetBreakIter()->endOfSentence(
1970                     GetText(), nCurrentStart,
1971                     g_pBreakIt->GetLocale( GetLang( nCurrentStart ) ) );
1972         }
1973     }
1974     else
1975     {
1976         // here we may transliterate over complete language portions...
1977 
1978         std::unique_ptr<SwLanguageIterator> pIter;
1979         if( rTrans.needLanguageForTheMode() )
1980             pIter.reset(new SwLanguageIterator( *this, nStt ));
1981 
1982         sal_Int32 nEndPos = 0;
1983         LanguageType nLang = LANGUAGE_NONE;
1984         sal_Int32 nLoopControlRuns = 0;
1985         do {
1986             if( pIter )
1987             {
1988                 nLang = pIter->GetLanguage();
1989                 nEndPos = pIter->GetChgPos();
1990                 if( nEndPos > nEnd )
1991                     nEndPos = nEnd;
1992             }
1993             else
1994             {
1995                 nLang = LANGUAGE_SYSTEM;
1996                 nEndPos = nEnd;
1997             }
1998             const sal_Int32 nLen = nEndPos - nStt;
1999 
2000             Sequence <sal_Int32> aOffsets;
2001             OUString const sChgd( rTrans.transliterate(
2002                         m_Text, nLang, nStt, nLen, &aOffsets) );
2003 
2004             assert(nStt < m_Text.getLength());
2005             if (0 != rtl_ustr_shortenedCompare_WithLength(
2006                         m_Text.getStr() + nStt, m_Text.getLength() - nStt,
2007                         sChgd.getStr(), sChgd.getLength(), nLen))
2008             {
2009                 aChgData.nStart     = nStt;
2010                 aChgData.nLen       = nLen;
2011                 aChgData.sChanged   = sChgd;
2012                 aChgData.aOffsets   = aOffsets;
2013                 aChanges.push_back( aChgData );
2014             }
2015 
2016             nStt = nEndPos;
2017 
2018             // tdf#157937 selection containing tracked changes needs loop control:
2019             // stop looping, if there are too much empty transliterations
2020             if ( sChgd.isEmpty() )
2021                 ++nLoopControlRuns;
2022 
2023         } while( nEndPos < nEnd && pIter && pIter->Next() && nLoopControlRuns < 100 );
2024     }
2025 
2026     if (aChanges.empty())
2027         return;
2028 
2029     // now apply the changes from end to start to leave the offsets of the
2030     // yet unchanged text parts remain the same.
2031     size_t nSum(0);
2032 
2033     for (size_t i = 0; i < aChanges.size(); ++i)
2034     {   // check this here since AddChanges cannot be moved below
2035         // call to ReplaceTextOnly
2036         swTransliterationChgData & rData =
2037             aChanges[ aChanges.size() - 1 - i ];
2038 
2039         nSum += rData.sChanged.getLength() - rData.nLen;
2040         if (nSum > o3tl::make_unsigned(GetSpaceLeft()))
2041         {
2042             SAL_WARN("sw.core", "SwTextNode::ReplaceTextOnly: "
2043                     "node text with insertion > node capacity.");
2044             return;
2045         }
2046 
2047         if ( bUseRedlining )
2048         {
2049             // create SwPaM with mark & point spanning the attributed text
2050             //SwPaM aCurPaM( *this, *this, nBegin, nBegin + nLen ); <-- wrong c-tor, does sth different
2051             SwPaM aCurPaM( *this, rData.nStart );
2052             aCurPaM.SetMark();
2053             aCurPaM.GetPoint()->SetContent( rData.nStart + rData.nLen );
2054             // replace the changed words
2055             if ( aCurPaM.GetText() != rData.sChanged )
2056                 GetDoc().getIDocumentContentOperations().ReplaceRange( aCurPaM, rData.sChanged, false );
2057         }
2058         else
2059         {
2060             if (pUndo)
2061                 pUndo->AddChanges( *this, rData.nStart, rData.nLen, rData.aOffsets );
2062             ReplaceTextOnly( rData.nStart, rData.nLen, rData.sChanged, rData.aOffsets );
2063         }
2064     }
2065 }
2066 
ReplaceTextOnly(sal_Int32 nPos,sal_Int32 nLen,std::u16string_view aText,const Sequence<sal_Int32> & rOffsets)2067 void SwTextNode::ReplaceTextOnly( sal_Int32 nPos, sal_Int32 nLen,
2068                                 std::u16string_view aText,
2069                                 const Sequence<sal_Int32>& rOffsets )
2070 {
2071     assert(sal_Int32(aText.size()) - nLen <= GetSpaceLeft());
2072 
2073     m_Text = m_Text.replaceAt(nPos, nLen, aText);
2074 
2075     sal_Int32 nTLen = aText.size();
2076     const sal_Int32* pOffsets = rOffsets.getConstArray();
2077     // now look for no 1-1 mapping -> move the indices!
2078     sal_Int32 nMyOff = nPos;
2079     for( sal_Int32 nI = 0; nI < nTLen; ++nI )
2080     {
2081         const sal_Int32 nOff = pOffsets[ nI ];
2082         if( nOff < nMyOff )
2083         {
2084             // something is inserted
2085             sal_Int32 nCnt = 1;
2086             while( nI + nCnt < nTLen && nOff == pOffsets[ nI + nCnt ] )
2087                 ++nCnt;
2088 
2089             Update(SwContentIndex(this, nMyOff), nCnt, UpdateMode::Default);
2090             nMyOff = nOff;
2091             //nMyOff -= nCnt;
2092             nI += nCnt - 1;
2093         }
2094         else if( nOff > nMyOff )
2095         {
2096             // something is deleted
2097             Update(SwContentIndex(this, nMyOff + 1), nOff - nMyOff, UpdateMode::Negative);
2098             nMyOff = nOff;
2099         }
2100         ++nMyOff;
2101     }
2102     if( nMyOff < nLen )
2103         // something is deleted at the end
2104         Update(SwContentIndex(this, nMyOff), nLen - nMyOff, UpdateMode::Negative);
2105 
2106     // notify the layout!
2107     const auto aDelHint = sw::DeleteText(nPos, nTLen);
2108     CallSwClientNotify(aDelHint);
2109 
2110     const auto aInsHint = sw::MakeInsertText(*this, nPos, nTLen);
2111     CallSwClientNotify(aInsHint);
2112 }
2113 
2114 // the return values allows us to see if we did the heavy-
2115 // lifting required to actually break and count the words.
CountWords(SwDocStat & rStat,sal_Int32 nStt,sal_Int32 nEnd) const2116 bool SwTextNode::CountWords( SwDocStat& rStat,
2117                             sal_Int32 nStt, sal_Int32 nEnd ) const
2118 {
2119     if( nStt > nEnd )
2120     {   // bad call
2121         return false;
2122     }
2123     if (IsInRedlines())
2124     {   //not counting txtnodes used to hold deleted redline content
2125         return false;
2126     }
2127     bool bCountAll = ( (0 == nStt) && (GetText().getLength() == nEnd) );
2128     ++rStat.nAllPara; // #i93174#: count _all_ paragraphs
2129     if ( IsHidden() )
2130     {   // not counting hidden paras
2131         return false;
2132     }
2133     // count words in numbering string if started at beginning of para:
2134     bool bCountNumbering = nStt == 0;
2135     bool bHasBullet = false, bHasNumbering = false;
2136     OUString sNumString;
2137     if (bCountNumbering)
2138     {
2139         sNumString = GetNumString();
2140         bHasNumbering = !sNumString.isEmpty();
2141         if (!bHasNumbering)
2142             bHasBullet = HasBullet();
2143         bCountNumbering = bHasNumbering || bHasBullet;
2144     }
2145 
2146     if( nStt == nEnd && !bCountNumbering)
2147     {   // unnumbered empty node or empty selection
2148         if (bCountAll)
2149         {
2150             SetWordCountDirty( false ); // reset flag to speed up DoIdleJob
2151         }
2152         return false;
2153     }
2154 
2155     // count of non-empty paras
2156     ++rStat.nPara;
2157 
2158     // Shortcut when counting whole paragraph and current count is clean
2159     if ( bCountAll && !IsWordCountDirty() )
2160     {
2161         // accumulate into DocStat record to return the values
2162 
2163         rStat.nWord += m_aParagraphIdleData.nNumberOfWords;
2164         rStat.nAsianWord += m_aParagraphIdleData.nNumberOfAsianWords;
2165         rStat.nChar += m_aParagraphIdleData.nNumberOfChars;
2166         rStat.nCharExcludingSpaces += m_aParagraphIdleData.nNumberOfCharsExcludingSpaces;
2167         return false;
2168     }
2169 
2170     // ConversionMap to expand fields, remove invisible and redline deleted text for scanner
2171     const ModelToViewHelper aConversionMap(*this,
2172         getIDocumentLayoutAccess().GetCurrentLayout(),
2173         ExpandMode::ExpandFields | ExpandMode::ExpandFootnote | ExpandMode::HideInvisible | ExpandMode::HideDeletions | ExpandMode::HideFieldmarkCommands);
2174     const OUString& aExpandText = aConversionMap.getViewText();
2175 
2176     if (aExpandText.isEmpty() && !bCountNumbering)
2177     {
2178         if (bCountAll)
2179         {
2180             SetWordCountDirty( false ); // reset flag to speed up DoIdleJob
2181         }
2182         return false;
2183     }
2184 
2185     // map start and end points onto the ConversionMap
2186     const sal_Int32 nExpandBegin = aConversionMap.ConvertToViewPosition( nStt );
2187     const sal_Int32 nExpandEnd   = aConversionMap.ConvertToViewPosition( nEnd );
2188 
2189     //do the count
2190     // all counts exclude hidden paras and hidden+redlined within para
2191     // definition of space/white chars in SwScanner (and BreakIter!)
2192     // uses both u_isspace and BreakIter getWordBoundary in SwScanner
2193     sal_uInt32 nTmpWords = 0;        // count of all words
2194     sal_uInt32 nTmpAsianWords = 0;   //count of all Asian codepoints
2195     sal_uInt32 nTmpChars = 0;        // count of all chars
2196     sal_uInt32 nTmpCharsExcludingSpaces = 0;  // all non-white chars
2197 
2198     // count words in masked and expanded text:
2199     if (!aExpandText.isEmpty())
2200     {
2201         assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
2202 
2203         // zero is NULL for pLanguage -----------v               last param = true for clipping
2204         SwScanner aScanner( *this, aExpandText, nullptr, aConversionMap, i18n::WordType::WORD_COUNT,
2205                             nExpandBegin, nExpandEnd, true );
2206 
2207         // used to filter out scanner returning almost empty strings (len=1; unichar=0x0001)
2208         const OUString aBreakWord( CH_TXTATR_BREAKWORD );
2209 
2210         while ( aScanner.NextWord() )
2211         {
2212             if( !aExpandText.match(aBreakWord, aScanner.GetBegin() ))
2213             {
2214                 ++nTmpWords;
2215                 const OUString &rWord = aScanner.GetWord();
2216                 if (g_pBreakIt->GetBreakIter()->getScriptType(rWord, 0) == i18n::ScriptType::ASIAN)
2217                     ++nTmpAsianWords;
2218                 nTmpCharsExcludingSpaces += g_pBreakIt->getGraphemeCount(rWord);
2219             }
2220         }
2221 
2222         nTmpCharsExcludingSpaces += aScanner.getOverriddenDashCount();
2223 
2224         nTmpChars = g_pBreakIt->getGraphemeCount(aExpandText, nExpandBegin, nExpandEnd);
2225     }
2226 
2227     // no nTmpCharsExcludingSpaces adjust needed neither for blanked out MaskedChars
2228     // nor for mid-word selection - set scanner bClip = true at creation
2229 
2230     // count outline number label - ? no expansion into map
2231     // always counts all of number-ish label
2232     if (bHasNumbering) // count words in numbering string
2233     {
2234         LanguageType aLanguage = GetLang( 0 );
2235 
2236         SwScanner aScanner( *this, sNumString, &aLanguage, ModelToViewHelper(),
2237                             i18n::WordType::WORD_COUNT, 0, sNumString.getLength(), true );
2238 
2239         while ( aScanner.NextWord() )
2240         {
2241             ++nTmpWords;
2242             const OUString &rWord = aScanner.GetWord();
2243             if (g_pBreakIt->GetBreakIter()->getScriptType(rWord, 0) == i18n::ScriptType::ASIAN)
2244                 ++nTmpAsianWords;
2245             nTmpCharsExcludingSpaces += g_pBreakIt->getGraphemeCount(rWord);
2246         }
2247 
2248         nTmpCharsExcludingSpaces += aScanner.getOverriddenDashCount();
2249         nTmpChars += g_pBreakIt->getGraphemeCount(sNumString);
2250     }
2251     else if ( bHasBullet )
2252     {
2253         ++nTmpWords;
2254         ++nTmpChars;
2255         ++nTmpCharsExcludingSpaces;
2256     }
2257 
2258     // If counting the whole para then update cached values and mark clean
2259     if ( bCountAll )
2260     {
2261         m_aParagraphIdleData.nNumberOfWords = nTmpWords;
2262         m_aParagraphIdleData.nNumberOfAsianWords = nTmpAsianWords;
2263         m_aParagraphIdleData.nNumberOfChars = nTmpChars;
2264         m_aParagraphIdleData.nNumberOfCharsExcludingSpaces = nTmpCharsExcludingSpaces;
2265         SetWordCountDirty( false );
2266     }
2267     // accumulate into DocStat record to return the values
2268     rStat.nWord += nTmpWords;
2269     rStat.nAsianWord += nTmpAsianWords;
2270     rStat.nChar += nTmpChars;
2271     rStat.nCharExcludingSpaces += nTmpCharsExcludingSpaces;
2272 
2273     return true;
2274 }
2275 
SetWrong(std::unique_ptr<SwWrongList> pNew)2276 void SwTextNode::SetWrong( std::unique_ptr<SwWrongList> pNew )
2277 {
2278     m_aParagraphIdleData.pWrong = std::move(pNew);
2279 }
2280 
ClearWrong()2281 void SwTextNode::ClearWrong()
2282 {
2283     m_aParagraphIdleData.pWrong.reset();
2284 }
2285 
ReleaseWrong()2286 std::unique_ptr<SwWrongList> SwTextNode::ReleaseWrong()
2287 {
2288     return std::move(m_aParagraphIdleData.pWrong);
2289 }
2290 
GetWrong()2291 SwWrongList* SwTextNode::GetWrong()
2292 {
2293     return m_aParagraphIdleData.pWrong.get();
2294 }
2295 
2296 // #i71360#
GetWrong() const2297 const SwWrongList* SwTextNode::GetWrong() const
2298 {
2299     return m_aParagraphIdleData.pWrong.get();
2300 }
2301 
SetGrammarCheck(std::unique_ptr<SwGrammarMarkUp> pNew)2302 void SwTextNode::SetGrammarCheck( std::unique_ptr<SwGrammarMarkUp> pNew )
2303 {
2304     m_aParagraphIdleData.pGrammarCheck = std::move(pNew);
2305 }
2306 
ClearGrammarCheck()2307 void SwTextNode::ClearGrammarCheck()
2308 {
2309     m_aParagraphIdleData.pGrammarCheck.reset();
2310 }
2311 
ReleaseGrammarCheck()2312 std::unique_ptr<SwGrammarMarkUp> SwTextNode::ReleaseGrammarCheck()
2313 {
2314     return std::move(m_aParagraphIdleData.pGrammarCheck);
2315 }
2316 
GetGrammarCheck()2317 SwGrammarMarkUp* SwTextNode::GetGrammarCheck()
2318 {
2319     return m_aParagraphIdleData.pGrammarCheck.get();
2320 }
2321 
GetGrammarCheck() const2322 SwWrongList const* SwTextNode::GetGrammarCheck() const
2323 {
2324     return static_cast<SwWrongList const*>(const_cast<SwTextNode*>(this)->GetGrammarCheck());
2325 }
2326 
SetSmartTags(std::unique_ptr<SwWrongList> pNew)2327 void SwTextNode::SetSmartTags( std::unique_ptr<SwWrongList> pNew )
2328 {
2329     OSL_ENSURE( !pNew || SwSmartTagMgr::Get().IsSmartTagsEnabled(),
2330             "Weird - we have a smart tag list without any recognizers?" );
2331 
2332     m_aParagraphIdleData.pSmartTags = std::move(pNew);
2333 }
2334 
ClearSmartTags()2335 void SwTextNode::ClearSmartTags()
2336 {
2337     m_aParagraphIdleData.pSmartTags.reset();
2338 }
2339 
ReleaseSmartTags()2340 std::unique_ptr<SwWrongList> SwTextNode::ReleaseSmartTags()
2341 {
2342     return std::move(m_aParagraphIdleData.pSmartTags);
2343 }
2344 
GetSmartTags()2345 SwWrongList* SwTextNode::GetSmartTags()
2346 {
2347     return m_aParagraphIdleData.pSmartTags.get();
2348 }
2349 
GetSmartTags() const2350 SwWrongList const* SwTextNode::GetSmartTags() const
2351 {
2352     return const_cast<SwWrongList const*>(const_cast<SwTextNode*>(this)->GetSmartTags());
2353 }
2354 
SetWordCountDirty(bool bNew) const2355 void SwTextNode::SetWordCountDirty( bool bNew ) const
2356 {
2357     m_aParagraphIdleData.bWordCountDirty = bNew;
2358 }
2359 
IsWordCountDirty() const2360 bool SwTextNode::IsWordCountDirty() const
2361 {
2362     return m_aParagraphIdleData.bWordCountDirty;
2363 }
2364 
SetWrongDirty(sw::WrongState eNew) const2365 void SwTextNode::SetWrongDirty(sw::WrongState eNew) const
2366 {
2367     m_aParagraphIdleData.eWrongDirty = eNew;
2368 }
2369 
GetWrongDirty() const2370 sw::WrongState SwTextNode::GetWrongDirty() const
2371 {
2372     return m_aParagraphIdleData.eWrongDirty;
2373 }
2374 
IsWrongDirty() const2375 bool SwTextNode::IsWrongDirty() const
2376 {
2377     return m_aParagraphIdleData.eWrongDirty != sw::WrongState::DONE;
2378 }
2379 
SetGrammarCheckDirty(bool bNew) const2380 void SwTextNode::SetGrammarCheckDirty( bool bNew ) const
2381 {
2382     m_aParagraphIdleData.bGrammarCheckDirty = bNew;
2383 }
2384 
IsGrammarCheckDirty() const2385 bool SwTextNode::IsGrammarCheckDirty() const
2386 {
2387     return m_aParagraphIdleData.bGrammarCheckDirty;
2388 }
2389 
SetSmartTagDirty(bool bNew) const2390 void SwTextNode::SetSmartTagDirty( bool bNew ) const
2391 {
2392     m_aParagraphIdleData.bSmartTagDirty = bNew;
2393 }
2394 
IsSmartTagDirty() const2395 bool SwTextNode::IsSmartTagDirty() const
2396 {
2397     return m_aParagraphIdleData.bSmartTagDirty;
2398 }
2399 
SetAutoCompleteWordDirty(bool bNew) const2400 void SwTextNode::SetAutoCompleteWordDirty( bool bNew ) const
2401 {
2402     m_aParagraphIdleData.bAutoComplDirty = bNew;
2403 }
2404 
IsAutoCompleteWordDirty() const2405 bool SwTextNode::IsAutoCompleteWordDirty() const
2406 {
2407     return m_aParagraphIdleData.bAutoComplDirty;
2408 }
2409 
2410 // <-- Paragraph statistics end
2411 
2412 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2413