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