Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sw / source / core / txtnode / thints.cxx
blob75e80abb242a2415326b460cb1af396ea3b740d7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/config.h>
21 #include <sal/log.hxx>
23 #include <DocumentContentOperationsManager.hxx>
24 #include <hintids.hxx>
25 #include <editeng/rsiditem.hxx>
26 #include <osl/diagnose.h>
27 #include <svl/whiter.hxx>
28 #include <svl/itemiter.hxx>
29 #include <editeng/charhiddenitem.hxx>
30 #include <editeng/langitem.hxx>
31 #include <editeng/lrspitem.hxx>
32 #include <txtinet.hxx>
33 #include <txtflcnt.hxx>
34 #include <fmtfld.hxx>
35 #include <fmtrfmrk.hxx>
36 #include <fmtanchr.hxx>
37 #include <fmtinfmt.hxx>
38 #include <txtatr.hxx>
39 #include <fchrfmt.hxx>
40 #include <fmtautofmt.hxx>
41 #include <fmtflcnt.hxx>
42 #include <fmtftn.hxx>
43 #include <txttxmrk.hxx>
44 #include <txtrfmrk.hxx>
45 #include <txtftn.hxx>
46 #include <textlinebreak.hxx>
47 #include <txtfld.hxx>
48 #include <txtannotationfld.hxx>
49 #include <unotools/fltrcfg.hxx>
50 #include <charfmt.hxx>
51 #include <frmfmt.hxx>
52 #include <ftnidx.hxx>
53 #include <fmtruby.hxx>
54 #include <fmtmeta.hxx>
55 #include <formatcontentcontrol.hxx>
56 #include <textcontentcontrol.hxx>
57 #include <breakit.hxx>
58 #include <doc.hxx>
59 #include <IDocumentUndoRedo.hxx>
60 #include <IDocumentFieldsAccess.hxx>
61 #include <IDocumentLayoutAccess.hxx>
62 #include <IDocumentStylePoolAccess.hxx>
63 #include <fldbas.hxx>
64 #include <pam.hxx>
65 #include <ndtxt.hxx>
66 #include <txtfrm.hxx>
67 #include <rootfrm.hxx>
68 #include <rolbck.hxx>
69 #include <ddefld.hxx>
70 #include <docufld.hxx>
71 #include <expfld.hxx>
72 #include <usrfld.hxx>
73 #include <poolfmt.hxx>
74 #include <istyleaccess.hxx>
75 #include <docsh.hxx>
76 #include <algorithm>
77 #include <map>
78 #include <memory>
80 #include <rdfhelper.hxx>
81 #include <hints.hxx>
83 #ifdef DBG_UTIL
84 #define CHECK Check(true);
85 #define CHECK_NOTMERGED Check(false);
86 #else
87 #define CHECK_NOTMERGED
88 #endif
90 using namespace ::com::sun::star::i18n;
92 SwpHints::SwpHints(const SwTextNode& rParent)
93 : m_rParent(rParent)
94 , m_pHistory(nullptr)
95 , m_bInSplitNode(false)
96 , m_bCalcHiddenParaField(false)
97 , m_bHiddenByParaField(false)
98 , m_bFootnote(false)
99 , m_bDDEFields(false)
100 , m_bStartMapNeedsSorting(false)
101 , m_bEndMapNeedsSorting(false)
102 , m_bWhichMapNeedsSorting(false)
106 static void TextAttrDelete( SwDoc & rDoc, SwTextAttr * const pAttr )
108 if (RES_TXTATR_META == pAttr->Which() ||
109 RES_TXTATR_METAFIELD == pAttr->Which())
111 static_txtattr_cast<SwTextMeta *>(pAttr)->ChgTextNode(nullptr); // prevents ASSERT
113 else if (pAttr->Which() == RES_TXTATR_CONTENTCONTROL)
115 static_txtattr_cast<SwTextContentControl*>(pAttr)->ChgTextNode(nullptr);
117 SwTextAttr::Destroy( pAttr, rDoc.GetAttrPool() );
120 static bool TextAttrContains(const sal_Int32 nPos, const SwTextAttrEnd * const pAttr)
122 return (pAttr->GetStart() < nPos) && (nPos < *pAttr->End());
125 // a: |-----|
126 // b:
127 // |---| => valid: b before a
128 // |-----| => valid: start == end; b before a
129 // |---------| => invalid: overlap (1)
130 // |-----------| => valid: same end; b around a
131 // |-----------------| => valid: b around a
132 // |---| => valid; same start; b within a
133 // |-----| => valid; same start and end; b around or within a?
134 // |-----------| => valid: same start: b around a
135 // |-| => valid: b within a
136 // |---| => valid: same end; b within a
137 // |---------| => invalid: overlap (2)
138 // |-----| => valid: end == start; b after a
139 // |---| => valid: b after a
140 // ===> 2 invalid overlap cases
141 static
142 bool isOverlap(const sal_Int32 nStart1, const sal_Int32 nEnd1,
143 const sal_Int32 nStart2, const sal_Int32 nEnd2)
145 return
146 ((nStart1 > nStart2) && (nStart1 < nEnd2) && (nEnd1 > nEnd2)) // (1)
147 || ((nStart1 < nStart2) && (nStart2 < nEnd1) && (nEnd1 < nEnd2)); // (2)
150 /// #i106930#: now asymmetric: empty hint1 is _not_ nested, but empty hint2 is
151 static
152 bool isNestedAny(const sal_Int32 nStart1, const sal_Int32 nEnd1,
153 const sal_Int32 nStart2, const sal_Int32 nEnd2)
155 return ((nStart1 == nStart2) || (nEnd1 == nEnd2))
156 // same start/end: nested except if hint1 empty and hint2 not empty
157 ? (nStart1 != nEnd1) || (nStart2 == nEnd2)
158 : ((nStart1 < nStart2) ? (nEnd1 >= nEnd2) : (nEnd1 <= nEnd2));
161 static
162 bool isSelfNestable(const sal_uInt16 nWhich)
164 if ((RES_TXTATR_INETFMT == nWhich) ||
165 (RES_TXTATR_CJK_RUBY == nWhich) ||
166 (RES_TXTATR_INPUTFIELD == nWhich))
167 return false;
168 assert((RES_TXTATR_META == nWhich) ||
169 (RES_TXTATR_METAFIELD == nWhich) ||
170 (RES_TXTATR_CONTENTCONTROL == nWhich));
171 return true;
174 static
175 bool isSplittable(const sal_uInt16 nWhich)
177 if ((RES_TXTATR_INETFMT == nWhich) ||
178 (RES_TXTATR_CJK_RUBY == nWhich))
179 return true;
180 assert((RES_TXTATR_META == nWhich) ||
181 (RES_TXTATR_METAFIELD == nWhich) ||
182 (RES_TXTATR_INPUTFIELD == nWhich) ||
183 (RES_TXTATR_CONTENTCONTROL == nWhich));
184 return false;
187 namespace {
189 enum Split_t { FAIL, SPLIT_NEW, SPLIT_OTHER };
194 Calculate splitting policy for overlapping hints, based on what kind of
195 hint is inserted, and what kind of existing hint overlaps.
197 static Split_t
198 splitPolicy(const sal_uInt16 nWhichNew, const sal_uInt16 nWhichOther)
200 if (!isSplittable(nWhichOther))
202 if (!isSplittable(nWhichNew))
203 return FAIL;
204 else
205 return SPLIT_NEW;
207 else
209 if ( RES_TXTATR_INPUTFIELD == nWhichNew )
210 return FAIL;
211 else if ( (RES_TXTATR_INETFMT == nWhichNew) &&
212 (RES_TXTATR_CJK_RUBY == nWhichOther) )
213 return SPLIT_NEW;
214 else
215 return SPLIT_OTHER;
219 void SwTextINetFormat::InitINetFormat(SwTextNode & rNode)
221 ChgTextNode(&rNode);
222 SwCharFormat * const pFormat(
223 rNode.GetDoc().getIDocumentStylePoolAccess().GetCharFormatFromPool(RES_POOLCHR_INET_NORMAL) );
224 pFormat->Add( this );
227 void SwTextRuby::InitRuby(SwTextNode & rNode)
229 ChgTextNode(&rNode);
230 SwCharFormat * const pFormat(
231 rNode.GetDoc().getIDocumentStylePoolAccess().GetCharFormatFromPool(RES_POOLCHR_RUBYTEXT) );
232 pFormat->Add( this );
236 Create a new nesting text hint.
238 static SwTextAttrNesting *
239 MakeTextAttrNesting(SwTextNode & rNode, SwTextAttrNesting & rNesting,
240 const sal_Int32 nStart, const sal_Int32 nEnd)
242 SwTextAttr * const pNew( MakeTextAttr(
243 rNode.GetDoc(), rNesting.GetAttr(), nStart, nEnd ) );
244 switch (pNew->Which())
246 case RES_TXTATR_INETFMT:
248 static_txtattr_cast<SwTextINetFormat*>(pNew)->InitINetFormat(rNode);
249 break;
251 case RES_TXTATR_CJK_RUBY:
253 static_txtattr_cast<SwTextRuby*>(pNew)->InitRuby(rNode);
254 break;
256 default:
257 assert(!"MakeTextAttrNesting: what the hell is that?");
258 break;
260 return static_txtattr_cast<SwTextAttrNesting*>(pNew);
263 typedef std::vector<SwTextAttrNesting *> NestList_t;
265 static NestList_t::iterator
266 lcl_DoSplitImpl(NestList_t & rSplits, SwTextNode & rNode,
267 NestList_t::iterator const iter, sal_Int32 const nSplitPos,
268 bool const bSplitAtStart, bool const bOtherDummy)
270 const sal_Int32 nStartPos( // skip other's dummy character!
271 (bSplitAtStart && bOtherDummy) ? nSplitPos + 1 : nSplitPos );
272 SwTextAttrNesting * const pNew( MakeTextAttrNesting(
273 rNode, **iter, nStartPos, *(*iter)->GetEnd() ) );
274 (*iter)->SetEnd(nSplitPos);
275 return rSplits.insert(iter + 1, pNew);
278 static void
279 lcl_DoSplitNew(NestList_t & rSplits, SwTextNode & rNode,
280 const sal_Int32 nNewStart,
281 const sal_Int32 nOtherStart, const sal_Int32 nOtherEnd, bool bOtherDummy)
283 const bool bSplitAtStart(nNewStart < nOtherStart);
284 const sal_Int32 nSplitPos( bSplitAtStart ? nOtherStart : nOtherEnd );
285 // first find the portion that is split (not necessarily the last one!)
286 NestList_t::iterator const iter(
287 std::find_if( rSplits.begin(), rSplits.end(),
288 [nSplitPos](SwTextAttrEnd * const pAttr) {
289 return TextAttrContains(nSplitPos, pAttr);
290 } ) );
291 if (iter != rSplits.end()) // already split here?
293 lcl_DoSplitImpl(rSplits, rNode, iter, nSplitPos, bSplitAtStart, bOtherDummy);
298 Insert nesting hint into the hints array. Also calls NoteInHistory.
299 @param rNewHint the hint to be inserted (must not overlap existing!)
301 void SwpHints::InsertNesting(SwTextAttrNesting & rNewHint)
303 Insert(& rNewHint);
304 NoteInHistory( & rNewHint, true );
309 The following hints correspond to well-formed XML elements in ODF:
310 RES_TXTATR_INETFMT, RES_TXTATR_CJK_RUBY, RES_TXTATR_META, RES_TXTATR_METAFIELD,
311 RES_TXTATR_CONTENTCONTROL
313 The writer core must ensure that these do not overlap; if they did,
314 the document would not be storable as ODF.
316 Also, a Hyperlink must not be nested within another Hyperlink,
317 and a Ruby must not be nested within another Ruby.
319 The ODF export in xmloff will only put a hyperlink into a ruby, never a ruby
320 into a hyperlink.
322 Unfortunately the UNO API for Hyperlink and Ruby consists of the properties
323 Hyperlink* and Ruby* of the css.text.CharacterProperties service. In other
324 words, they are treated as formatting attributes, not as content entities.
325 Furthermore, for API users it is not possible to easily test whether a certain
326 range would be overlapping with other nested attributes, and most importantly,
327 <em>which ones</em>, so we can hardly refuse to insert these in cases of
328 overlap.
330 It is possible to split Hyperlink and Ruby into multiple portions, such that
331 the result is properly nested.
333 meta and meta-field must not be split, because they have xml:id.
335 content controls should not split, either.
337 These constraints result in the following design:
339 RES_TXTATR_INETFMT:
340 always succeeds
341 inserts n attributes split at RES_TXTATR_CJK_RUBY, RES_TXTATR_META,
342 RES_TXTATR_METAFIELD
343 may replace existing RES_TXTATR_INETFMT at overlap
344 RES_TXTATR_CJK_RUBY:
345 always succeeds
346 inserts n attributes split at RES_TXTATR_META, RES_TXTATR_METAFIELD
347 may replace existing RES_TXTATR_CJK_RUBY at overlap
348 may split existing overlapping RES_TXTATR_INETFMT
349 RES_TXTATR_META:
350 may fail if overlapping existing RES_TXTATR_META/RES_TXTATR_METAFIELD
351 may split existing overlapping RES_TXTATR_INETFMT or RES_TXTATR_CJK_RUBY
352 inserts 1 attribute
353 RES_TXTATR_METAFIELD:
354 may fail if overlapping existing RES_TXTATR_META/RES_TXTATR_METAFIELD
355 may split existing overlapping RES_TXTATR_INETFMT or RES_TXTATR_CJK_RUBY
356 inserts 1 attribute
358 The nesting is expressed by the position of the hints.
359 RES_TXTATR_META and RES_TXTATR_METAFIELD have a CH_TXTATR, and there can
360 only be one such hint starting and ending at a given position.
361 Only RES_TXTATR_INETFMT and RES_TXTATR_CJK_RUBY lack a CH_TXTATR.
362 The interpretation given is that RES_TXTATR_CJK_RUBY is always around
363 a RES_TXTATR_INETFMT at the same start and end position (which corresponds
364 with the UNO API).
365 Both of these are always around a nesting hint with CH_TXTATR at the same
366 start and end position (if they should be inside, then the start should be
367 after the CH_TXTATR).
368 It would probably be a bad idea to add another nesting hint without
369 CH_TXTATR; on the other hand, it would be difficult adding a CH_TXTATR to
370 RES_TXTATR_INETFMT and RES_TXTATR_CJK_RUBY, due to the overwriting and
371 splitting of existing hints that is necessary for backward compatibility.
373 @param rNode the text node
374 @param rHint the hint to be inserted
375 @returns true iff hint was successfully inserted
377 bool
378 SwpHints::TryInsertNesting( SwTextNode & rNode, SwTextAttrNesting & rNewHint )
380 // INVARIANT: the nestable hints in the array are properly nested
381 const sal_uInt16 nNewWhich( rNewHint.Which() );
382 const sal_Int32 nNewStart( rNewHint.GetStart() );
383 const sal_Int32 nNewEnd ( *rNewHint.GetEnd() );
384 const bool bNewSelfNestable( isSelfNestable(nNewWhich) );
386 assert( (RES_TXTATR_INETFMT == nNewWhich) ||
387 (RES_TXTATR_CJK_RUBY == nNewWhich) ||
388 (RES_TXTATR_META == nNewWhich) ||
389 (RES_TXTATR_METAFIELD == nNewWhich) ||
390 (RES_TXTATR_CONTENTCONTROL == nNewWhich) ||
391 (RES_TXTATR_INPUTFIELD == nNewWhich));
393 NestList_t OverlappingExisting; // existing hints to be split
394 NestList_t OverwrittenExisting; // existing hints to be replaced
395 NestList_t SplitNew; // new hints to be inserted
397 SplitNew.push_back(& rNewHint);
399 // pass 1: split the inserted hint into fragments if necessary
400 for ( size_t i = 0; i < Count(); ++i )
402 SwTextAttr * const pOther = GetSortedByEnd(i);
404 if (pOther->IsNesting())
406 const sal_uInt16 nOtherWhich( pOther->Which() );
407 const sal_Int32 nOtherStart( pOther->GetStart() );
408 const sal_Int32 nOtherEnd ( *pOther->GetEnd() );
409 if (isOverlap(nNewStart, nNewEnd, nOtherStart, nOtherEnd ))
411 switch (splitPolicy(nNewWhich, nOtherWhich))
413 case FAIL:
414 SAL_INFO("sw.core", "cannot insert hint: overlap");
415 for (const auto& aSplit : SplitNew)
416 TextAttrDelete(rNode.GetDoc(), aSplit);
417 return false;
418 case SPLIT_NEW:
419 lcl_DoSplitNew(SplitNew, rNode, nNewStart,
420 nOtherStart, nOtherEnd, pOther->HasDummyChar());
421 break;
422 case SPLIT_OTHER:
423 OverlappingExisting.push_back(
424 static_txtattr_cast<SwTextAttrNesting*>(pOther));
425 break;
426 default:
427 assert(!"bad code monkey");
428 break;
431 else if (isNestedAny(nNewStart, nNewEnd, nOtherStart, nOtherEnd))
433 if (!bNewSelfNestable && (nNewWhich == nOtherWhich))
435 // ruby and hyperlink: if there is nesting, _overwrite_
436 OverwrittenExisting.push_back(
437 static_txtattr_cast<SwTextAttrNesting*>(pOther));
439 else if ((nNewStart == nOtherStart) && pOther->HasDummyChar())
441 if (rNewHint.HasDummyChar())
443 assert(!"ERROR: inserting duplicate CH_TXTATR hint");
444 return false;
445 } else if (nNewEnd < nOtherEnd) {
446 // other has dummy char, new is inside other, but
447 // new contains the other's dummy char?
448 // should be corrected because it may lead to problems
449 // in SwXMeta::createEnumeration
450 // SplitNew is sorted, so this is the first split
451 assert(SplitNew.front()->GetStart() == nNewStart);
452 SplitNew.front()->SetStart(nNewStart + 1);
459 // pass 1b: tragically need to check for fieldmarks here too
460 for (auto iter = SplitNew.begin(); iter != SplitNew.end(); ++iter)
462 SwPaM const temp(rNode, (*iter)->GetStart(), rNode, *(*iter)->GetEnd());
463 std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
464 sw::CalcBreaks(Breaks, temp, true);
465 if (!Breaks.empty())
467 if (!isSplittable(nNewWhich))
469 SAL_INFO("sw.core", "cannot insert hint: fieldmark overlap");
470 assert(SplitNew.size() == 1);
471 TextAttrDelete(rNode.GetDoc(), &rNewHint);
472 return false;
474 else
476 for (auto const& rPos : Breaks)
478 assert(rPos.first == rNode.GetIndex());
479 iter = lcl_DoSplitImpl(SplitNew, rNode, iter,
480 rPos.second, true, true);
486 assert((isSplittable(nNewWhich) || SplitNew.size() == 1) &&
487 "splitting the unsplittable ???");
489 // pass 2: split existing hints that overlap/nest with new hint
490 // do not iterate over hints array, but over remembered set of overlapping
491 // hints, to keep things simple w.r.t. insertion/removal
492 // N.B: if there is a hint that splits the inserted hint, then
493 // that hint would also have already split any hint in OverlappingExisting
494 // so any hint in OverlappingExisting can be split at most by one hint
495 // in SplitNew, or even not at all (this is not true for existing hints
496 // that go _around_ new hint, which is the reason d'^etre for pass 4)
497 for (auto& rpOther : OverlappingExisting)
499 const sal_Int32 nOtherStart( rpOther->GetStart() );
500 const sal_Int32 nOtherEnd ( *rpOther->GetEnd() );
502 for (const auto& rpNew : SplitNew)
504 const sal_Int32 nSplitNewStart( rpNew->GetStart() );
505 const sal_Int32 nSplitNewEnd ( *rpNew->GetEnd() );
506 // 4 cases: within, around, overlap l, overlap r, (OTHER: no action)
507 const bool bRemoveOverlap(
508 !bNewSelfNestable && (nNewWhich == rpOther->Which()) );
510 switch (ComparePosition(nSplitNewStart, nSplitNewEnd,
511 nOtherStart, nOtherEnd))
513 case SwComparePosition::Inside:
515 assert(!bRemoveOverlap &&
516 "this one should be in OverwrittenExisting?");
518 break;
519 case SwComparePosition::Outside:
520 case SwComparePosition::Equal:
522 assert(!"existing hint inside new hint: why?");
524 break;
525 case SwComparePosition::OverlapBefore:
527 Delete( rpOther ); // this also does NoteInHistory!
528 rpOther->SetStart(nSplitNewEnd);
529 InsertNesting( *rpOther );
530 if (!bRemoveOverlap)
532 if ( MAX_HINTS <= Count() )
534 SAL_INFO("sw.core", "hints array full :-(");
535 return false;
537 SwTextAttrNesting * const pOtherLeft(
538 MakeTextAttrNesting( rNode, *rpOther,
539 nOtherStart, nSplitNewEnd ) );
540 InsertNesting( *pOtherLeft );
543 break;
544 case SwComparePosition::OverlapBehind:
546 Delete( rpOther ); // this also does NoteInHistory!
547 rpOther->SetEnd(nSplitNewStart);
548 InsertNesting( *rpOther );
549 if (!bRemoveOverlap)
551 if ( MAX_HINTS <= Count() )
553 SAL_INFO("sw.core", "hints array full :-(");
554 return false;
556 SwTextAttrNesting * const pOtherRight(
557 MakeTextAttrNesting( rNode, *rpOther,
558 nSplitNewStart, nOtherEnd ) );
559 InsertNesting( *pOtherRight );
562 break;
563 default:
564 break; // overlap resolved by splitting new: nothing to do
569 if ( MAX_HINTS <= Count() || MAX_HINTS - Count() <= SplitNew.size() )
571 SAL_INFO("sw.core", "hints array full :-(");
572 return false;
575 // pass 3: insert new hints
576 for (const auto& rpHint : SplitNew)
578 InsertNesting(*rpHint);
581 // pass 4: handle overwritten hints
582 // RES_TXTATR_INETFMT and RES_TXTATR_CJK_RUBY should displace attributes
583 // of the same kind.
584 for (auto& rpOther : OverwrittenExisting)
586 const sal_Int32 nOtherStart( rpOther->GetStart() );
587 const sal_Int32 nOtherEnd ( *rpOther->GetEnd() );
589 // overwritten portion is given by start/end of inserted hint
590 if ((nNewStart <= nOtherStart) && (nOtherEnd <= nNewEnd))
592 Delete(rpOther);
593 rNode.DestroyAttr( rpOther );
595 else
597 assert((nOtherStart < nNewStart) || (nNewEnd < nOtherEnd));
598 // scenario: there is a RUBY, and contained within that a META;
599 // now a RUBY is inserted within the META => the existing RUBY is split:
600 // here it is not possible to simply insert the left/right fragment
601 // of the existing RUBY because they <em>overlap</em> with the META!
602 Delete( rpOther ); // this also does NoteInHistory!
603 if (nNewEnd < nOtherEnd)
605 SwTextAttrNesting * const pOtherRight(
606 MakeTextAttrNesting(
607 rNode, *rpOther, nNewEnd, nOtherEnd ) );
608 bool const bSuccess( TryInsertNesting(rNode, *pOtherRight) );
609 SAL_WARN_IF(!bSuccess, "sw.core", "recursive call 1 failed?");
611 if (nOtherStart < nNewStart)
613 rpOther->SetEnd(nNewStart);
614 bool const bSuccess( TryInsertNesting(rNode, *rpOther) );
615 SAL_WARN_IF(!bSuccess, "sw.core", "recursive call 2 failed?");
617 else
619 rNode.DestroyAttr(rpOther);
624 return true;
627 // This function takes care for the following text attribute:
628 // RES_TXTATR_CHARFMT, RES_TXTATR_AUTOFMT
629 // These attributes have to be handled in a special way (Portion building).
631 // The new attribute will be split by any existing RES_TXTATR_AUTOFMT or
632 // RES_TXTATR_CHARFMT. The new attribute itself will
633 // split any existing RES_TXTATR_AUTOFMT or RES_TXTATR_CHARFMT.
635 void SwpHints::BuildPortions( SwTextNode& rNode, SwTextAttr& rNewHint,
636 const SetAttrMode nMode )
638 const sal_uInt16 nWhich = rNewHint.Which();
640 const sal_Int32 nThisStart = rNewHint.GetStart();
641 const sal_Int32 nThisEnd = *rNewHint.GetEnd();
642 const bool bNoLengthAttribute = nThisStart == nThisEnd;
644 std::vector<SwTextAttr*> aInsDelHints;
646 assert( RES_TXTATR_CHARFMT == rNewHint.Which() ||
647 RES_TXTATR_AUTOFMT == rNewHint.Which() );
649 // 2. Find the hints which cover the start and end position
650 // of the new hint. These hints have to be split into two portions:
652 if ( !bNoLengthAttribute ) // nothing to do for no length attributes
654 for ( size_t i = 0; i < Count(); ++i )
656 // we're modifying stuff here which affects the sorting, and we
657 // don't want it changing underneath us
658 SwTextAttr* pOther = GetWithoutResorting(i);
660 if ( RES_TXTATR_CHARFMT != pOther->Which() &&
661 RES_TXTATR_AUTOFMT != pOther->Which() )
662 continue;
664 sal_Int32 nOtherStart = pOther->GetStart();
665 const sal_Int32 nOtherEnd = *pOther->GetEnd();
667 // Check if start of new attribute overlaps with pOther:
668 // Split pOther if necessary:
669 if ( nOtherStart < nThisStart && nThisStart < nOtherEnd )
671 SwTextAttr* pNewAttr = MakeTextAttr( rNode.GetDoc(),
672 pOther->GetAttr(), nOtherStart, nThisStart );
673 if ( RES_TXTATR_CHARFMT == pOther->Which() )
675 static_txtattr_cast<SwTextCharFormat*>(pNewAttr)->SetSortNumber(
676 static_txtattr_cast<SwTextCharFormat*>(pOther)->GetSortNumber() );
678 aInsDelHints.push_back( pNewAttr );
680 NoteInHistory( pOther );
681 pOther->SetStart(nThisStart);
682 NoteInHistory( pOther, true );
684 nOtherStart = nThisStart;
687 // Check if end of new attribute overlaps with pOther:
688 // Split pOther if necessary:
689 if ( nOtherStart < nThisEnd && nThisEnd < nOtherEnd )
691 SwTextAttr* pNewAttr = MakeTextAttr( rNode.GetDoc(),
692 pOther->GetAttr(), nOtherStart, nThisEnd );
693 if ( RES_TXTATR_CHARFMT == pOther->Which() )
695 static_txtattr_cast<SwTextCharFormat*>(pNewAttr)->SetSortNumber(
696 static_txtattr_cast<SwTextCharFormat*>(pOther)->GetSortNumber());
698 aInsDelHints.push_back( pNewAttr );
700 NoteInHistory( pOther );
701 pOther->SetStart(nThisEnd);
702 NoteInHistory( pOther, true );
706 // Insert the newly created attributes:
707 for ( const auto& rpHint : aInsDelHints )
709 Insert( rpHint );
710 NoteInHistory( rpHint, true );
714 #ifdef DBG_UTIL
715 if( !rNode.GetDoc().IsInReading() )
716 CHECK_NOTMERGED; // ignore flags not set properly yet, don't check them
717 #endif
719 // 4. Split rNewHint into 1 ... n new hints:
721 o3tl::sorted_vector<sal_Int32> aBounds;
722 aBounds.insert( nThisStart );
723 aBounds.insert( nThisEnd );
725 if ( !bNoLengthAttribute ) // nothing to do for no length attributes
727 for ( size_t i = 0; i < Count(); ++i )
729 const SwTextAttr* pOther = Get(i);
731 if ( RES_TXTATR_CHARFMT != pOther->Which() &&
732 RES_TXTATR_AUTOFMT != pOther->Which() )
733 continue;
735 const sal_Int32 nOtherStart = pOther->GetStart();
736 const sal_Int32 nOtherEnd = *pOther->End();
738 if (nThisStart <= nOtherStart && nOtherStart <= nThisEnd)
739 aBounds.insert( nOtherStart );
740 if (nThisStart <= nOtherEnd && nOtherEnd <= nThisEnd)
741 aBounds.insert( nOtherEnd );
745 auto aStartIter = aBounds.lower_bound( nThisStart );
746 auto aEndIter = aBounds.upper_bound( nThisEnd );
747 sal_Int32 nPorStart = *aStartIter;
748 ++aStartIter;
749 bool bDestroyHint = true;
751 // Insert the 1...n new parts of the new attribute:
753 while ( aStartIter != aEndIter || bNoLengthAttribute )
755 OSL_ENSURE( bNoLengthAttribute || nPorStart < *aStartIter, "AUTOSTYLES: BuildPortion trouble" );
757 const sal_Int32 nPorEnd = bNoLengthAttribute ? nPorStart : *aStartIter;
758 aInsDelHints.clear();
760 // Get all hints that are in [nPorStart, nPorEnd[:
761 for ( size_t i = 0; i < Count(); ++i )
763 // we get called from TryInsertHint, which changes ordering
764 SwTextAttr *pOther = GetWithoutResorting(i);
766 if ( RES_TXTATR_CHARFMT != pOther->Which() &&
767 RES_TXTATR_AUTOFMT != pOther->Which() )
768 continue;
770 const sal_Int32 nOtherStart = pOther->GetStart();
772 if ( nOtherStart > nPorStart )
773 break;
775 if ( pOther->GetEnd() && *pOther->GetEnd() == nPorEnd && nOtherStart == nPorStart )
777 OSL_ENSURE( *pOther->GetEnd() == nPorEnd, "AUTOSTYLES: BuildPortion trouble" );
778 aInsDelHints.push_back( pOther );
782 SwTextAttr* pNewAttr = nullptr;
783 if ( RES_TXTATR_CHARFMT == nWhich )
785 // pNewHint can be inserted after calculating the sort value.
786 // This should ensure, that pNewHint comes behind the already present
787 // character style
788 sal_uInt16 nCharStyleCount = 0;
789 for ( const auto& rpHint : aInsDelHints )
791 if ( RES_TXTATR_CHARFMT == rpHint->Which() )
793 // #i74589#
794 const SwFormatCharFormat& rOtherCharFormat = rpHint->GetCharFormat();
795 const SwFormatCharFormat& rThisCharFormat = rNewHint.GetCharFormat();
796 const bool bSameCharFormat = rOtherCharFormat.GetCharFormat() == rThisCharFormat.GetCharFormat();
798 // #i90311#
799 // Do not remove existing character format hint during XML import
800 if ( !rNode.GetDoc().IsInXMLImport() &&
801 ( !( SetAttrMode::DONTREPLACE & nMode ) ||
802 bNoLengthAttribute ||
803 bSameCharFormat ) )
805 // Remove old hint
806 Delete( rpHint );
807 rNode.DestroyAttr( rpHint );
809 else
810 ++nCharStyleCount;
812 else
814 // remove all attributes from auto styles, which are explicitly set in
815 // the new character format:
816 OSL_ENSURE( RES_TXTATR_AUTOFMT == rpHint->Which(), "AUTOSTYLES - Misc trouble" );
817 SwTextAttr* pOther = rpHint;
818 const std::shared_ptr<SfxItemSet> & pOldStyle = static_cast<const SwFormatAutoFormat&>(pOther->GetAttr()).GetStyleHandle();
820 // For each attribute in the automatic style check if it
821 // is also set the new character style:
822 SfxItemSet aNewSet( *pOldStyle->GetPool(),
823 aCharAutoFormatSetRange);
824 SfxItemIter aItemIter( *pOldStyle );
825 const SfxPoolItem* pItem = aItemIter.GetCurItem();
828 if ( !CharFormat::IsItemIncluded( pItem->Which(), &rNewHint ) )
830 aNewSet.Put( *pItem );
833 pItem = aItemIter.NextItem();
834 } while (pItem);
836 // Remove old hint
837 Delete( pOther );
838 rNode.DestroyAttr( pOther );
840 // Create new AutoStyle
841 if ( aNewSet.Count() )
843 pNewAttr = MakeTextAttr( rNode.GetDoc(),
844 aNewSet, nPorStart, nPorEnd );
845 Insert( pNewAttr );
846 NoteInHistory( pNewAttr, true );
851 // If there is no current hint and start and end of rNewHint
852 // is ok, we do not need to create a new txtattr.
853 if ( nPorStart == nThisStart &&
854 nPorEnd == nThisEnd &&
855 !nCharStyleCount )
857 pNewAttr = &rNewHint;
858 bDestroyHint = false;
860 else
862 pNewAttr = MakeTextAttr( rNode.GetDoc(), rNewHint.GetAttr(),
863 nPorStart, nPorEnd );
864 static_txtattr_cast<SwTextCharFormat*>(pNewAttr)->SetSortNumber(nCharStyleCount);
867 else
869 // Find the current autostyle. Mix attributes if necessary.
870 SwTextAttr* pCurrentAutoStyle = nullptr;
871 SwTextAttr* pCurrentCharFormat = nullptr;
872 for ( const auto& rpHint : aInsDelHints )
874 if ( RES_TXTATR_AUTOFMT == rpHint->Which() )
875 pCurrentAutoStyle = rpHint;
876 else if ( RES_TXTATR_CHARFMT == rpHint->Which() )
877 pCurrentCharFormat = rpHint;
880 std::shared_ptr<SfxItemSet> pNewStyle = static_cast<const SwFormatAutoFormat&>(rNewHint.GetAttr()).GetStyleHandle();
881 if ( pCurrentAutoStyle )
883 const std::shared_ptr<SfxItemSet> & pCurrentStyle = static_cast<const SwFormatAutoFormat&>(pCurrentAutoStyle->GetAttr()).GetStyleHandle();
885 // Merge attributes
886 SfxItemSet aNewSet( *pCurrentStyle );
887 aNewSet.Put( *pNewStyle );
889 // #i75750# Remove attributes already set at whole paragraph
890 // #i81764# This should not be applied for no length attributes!!! <--
891 if ( !bNoLengthAttribute && rNode.HasSwAttrSet() && aNewSet.Count() )
893 SfxItemIter aIter2( aNewSet );
894 const SfxPoolItem* pItem = aIter2.GetCurItem();
895 const SfxItemSet& rWholeParaAttrSet = rNode.GetSwAttrSet();
899 const SfxPoolItem* pTmpItem = nullptr;
900 if ( SfxItemState::SET == rWholeParaAttrSet.GetItemState( pItem->Which(), false, &pTmpItem ) &&
901 pTmpItem == pItem )
903 // Do not clear item if the attribute is set in a character format:
904 if ( !pCurrentCharFormat || nullptr == CharFormat::GetItem( *pCurrentCharFormat, pItem->Which() ) )
905 aIter2.ClearItem();
908 while ((pItem = aIter2.NextItem()));
911 // Remove old hint
912 Delete( pCurrentAutoStyle );
913 rNode.DestroyAttr( pCurrentAutoStyle );
915 // Create new AutoStyle
916 if ( aNewSet.Count() )
917 pNewAttr = MakeTextAttr( rNode.GetDoc(), aNewSet,
918 nPorStart, nPorEnd );
920 else
922 // Remove any attributes which are already set at the whole paragraph:
923 bool bOptimizeAllowed = true;
925 // #i75750# Remove attributes already set at whole paragraph
926 // #i81764# This should not be applied for no length attributes!!! <--
927 if ( !bNoLengthAttribute && rNode.HasSwAttrSet() && pNewStyle->Count() )
929 std::unique_ptr<SfxItemSet> pNewSet;
931 SfxItemIter aIter2( *pNewStyle );
932 const SfxPoolItem* pItem = aIter2.GetCurItem();
933 const SfxItemSet& rWholeParaAttrSet = rNode.GetSwAttrSet();
937 const SfxPoolItem* pTmpItem = nullptr;
938 if ( SfxItemState::SET == rWholeParaAttrSet.GetItemState( pItem->Which(), false, &pTmpItem ) &&
939 pTmpItem == pItem )
941 // Do not clear item if the attribute is set in a character format:
942 if ( !pCurrentCharFormat || nullptr == CharFormat::GetItem( *pCurrentCharFormat, pItem->Which() ) )
944 if ( !pNewSet )
945 pNewSet = pNewStyle->Clone();
946 pNewSet->ClearItem( pItem->Which() );
950 while ((pItem = aIter2.NextItem()));
952 if ( pNewSet )
954 bOptimizeAllowed = false;
955 if ( pNewSet->Count() )
956 pNewStyle = rNode.getIDocumentStyleAccess().getAutomaticStyle( *pNewSet, IStyleAccess::AUTO_STYLE_CHAR );
957 else
958 pNewStyle.reset();
962 // Create new AutoStyle
963 // If there is no current hint and start and end of rNewHint
964 // is ok, we do not need to create a new txtattr.
965 if ( bOptimizeAllowed &&
966 nPorStart == nThisStart &&
967 nPorEnd == nThisEnd )
969 pNewAttr = &rNewHint;
970 bDestroyHint = false;
972 else if ( pNewStyle )
974 pNewAttr = MakeTextAttr( rNode.GetDoc(), *pNewStyle,
975 nPorStart, nPorEnd );
980 if ( pNewAttr )
982 Insert( pNewAttr );
983 // if ( bDestroyHint )
984 NoteInHistory( pNewAttr, true );
987 if ( !bNoLengthAttribute )
989 nPorStart = *aStartIter;
990 ++aStartIter;
992 else
993 break;
996 if ( bDestroyHint )
997 rNode.DestroyAttr( &rNewHint );
1000 SwTextAttr* MakeRedlineTextAttr( SwDoc & rDoc, SfxPoolItem const & rAttr )
1002 // this is intended _only_ for special-purpose redline attributes!
1003 switch (rAttr.Which())
1005 case RES_CHRATR_COLOR:
1006 case RES_CHRATR_WEIGHT:
1007 case RES_CHRATR_CJK_WEIGHT:
1008 case RES_CHRATR_CTL_WEIGHT:
1009 case RES_CHRATR_POSTURE:
1010 case RES_CHRATR_CJK_POSTURE:
1011 case RES_CHRATR_CTL_POSTURE:
1012 case RES_CHRATR_UNDERLINE:
1013 case RES_CHRATR_CROSSEDOUT:
1014 case RES_CHRATR_CASEMAP:
1015 case RES_CHRATR_BACKGROUND:
1016 break;
1017 default:
1018 assert(!"unsupported redline attribute");
1019 break;
1022 // Put new attribute into pool
1023 // FIXME: this const_cast is evil!
1024 SfxPoolItem& rNew =
1025 const_cast<SfxPoolItem&>( rDoc.GetAttrPool().Put( rAttr ) );
1026 return new SwTextAttrEnd( rNew, 0, 0 );
1029 // create new text attribute
1030 SwTextAttr* MakeTextAttr(
1031 SwDoc & rDoc,
1032 SfxPoolItem& rAttr,
1033 sal_Int32 const nStt,
1034 sal_Int32 const nEnd,
1035 CopyOrNewType const bIsCopy,
1036 SwTextNode *const pTextNode )
1038 if ( isCHRATR(rAttr.Which()) )
1040 // Somebody wants to build a SwTextAttr for a character attribute.
1041 // Sorry, this is not allowed any longer.
1042 // You'll get a brand new autostyle attribute:
1043 SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aItemSet( rDoc.GetAttrPool() );
1044 aItemSet.Put( rAttr );
1045 return MakeTextAttr( rDoc, aItemSet, nStt, nEnd );
1047 else if ( RES_TXTATR_AUTOFMT == rAttr.Which() &&
1048 static_cast<const SwFormatAutoFormat&>(rAttr).GetStyleHandle()->
1049 GetPool() != &rDoc.GetAttrPool() )
1051 // If the attribute is an auto-style which refers to a pool that is
1052 // different from rDoc's pool, we have to correct this:
1053 const std::shared_ptr<SfxItemSet> & pAutoStyle = static_cast<const SwFormatAutoFormat&>(rAttr).GetStyleHandle();
1054 std::unique_ptr<const SfxItemSet> pNewSet(
1055 pAutoStyle->SfxItemSet::Clone( true, &rDoc.GetAttrPool() ));
1056 SwTextAttr* pNew = MakeTextAttr( rDoc, *pNewSet, nStt, nEnd );
1057 return pNew;
1060 // Put new attribute into pool
1061 // FIXME: this const_cast is evil!
1062 SfxPoolItem& rNew =
1063 const_cast<SfxPoolItem&>( rDoc.GetAttrPool().Put( rAttr ) );
1065 SwTextAttr* pNew = nullptr;
1066 switch( rNew.Which() )
1068 case RES_TXTATR_CHARFMT:
1070 SwFormatCharFormat &rFormatCharFormat = static_cast<SwFormatCharFormat&>(rNew);
1071 if( !rFormatCharFormat.GetCharFormat() )
1073 rFormatCharFormat.SetCharFormat( rDoc.GetDfltCharFormat() );
1076 pNew = new SwTextCharFormat( rFormatCharFormat, nStt, nEnd );
1078 break;
1079 case RES_TXTATR_INETFMT:
1080 pNew = new SwTextINetFormat( static_cast<SwFormatINetFormat&>(rNew), nStt, nEnd );
1081 break;
1083 case RES_TXTATR_FIELD:
1084 pNew = new SwTextField( static_cast<SwFormatField &>(rNew), nStt,
1085 rDoc.IsClipBoard() );
1086 break;
1088 case RES_TXTATR_ANNOTATION:
1090 pNew = new SwTextAnnotationField( static_cast<SwFormatField &>(rNew), nStt, rDoc.IsClipBoard() );
1091 if (bIsCopy == CopyOrNewType::Copy)
1093 // On copy of the annotation field do not keep the annotated text range by removing
1094 // the relation to its annotation mark (relation established via annotation field's name).
1095 // If the annotation mark is also copied, the relation and thus the annotated text range will be reestablished,
1096 // when the annotation mark is created and inserted into the document.
1097 auto& pField = const_cast<SwPostItField&>(dynamic_cast<const SwPostItField&>(*(pNew->GetFormatField().GetField())));
1098 pField.SetName(OUString());
1099 pField.SetPostItId();
1102 break;
1104 case RES_TXTATR_INPUTFIELD:
1105 pNew = new SwTextInputField( static_cast<SwFormatField &>(rNew), nStt, nEnd,
1106 rDoc.IsClipBoard() );
1107 break;
1109 case RES_TXTATR_FLYCNT:
1111 // finally, copy the frame format (with content)
1112 pNew = new SwTextFlyCnt( static_cast<SwFormatFlyCnt&>(rNew), nStt );
1113 if ( static_cast<const SwFormatFlyCnt &>(rAttr).GetTextFlyCnt() )
1115 // if it has an existing attr then the format must be copied
1116 static_cast<SwTextFlyCnt *>(pNew)->CopyFlyFormat( rDoc );
1119 break;
1120 case RES_TXTATR_FTN:
1121 pNew = new SwTextFootnote( static_cast<SwFormatFootnote&>(rNew), nStt );
1122 // copy note's SeqNo
1123 if( static_cast<SwFormatFootnote&>(rAttr).GetTextFootnote() )
1124 static_cast<SwTextFootnote*>(pNew)->SetSeqNo( static_cast<SwFormatFootnote&>(rAttr).GetTextFootnote()->GetSeqRefNo() );
1125 break;
1126 case RES_TXTATR_REFMARK:
1127 pNew = nStt == nEnd
1128 ? new SwTextRefMark( static_cast<SwFormatRefMark&>(rNew), nStt )
1129 : new SwTextRefMark( static_cast<SwFormatRefMark&>(rNew), nStt, &nEnd );
1130 break;
1131 case RES_TXTATR_TOXMARK:
1133 SwTOXMark& rMark = static_cast<SwTOXMark&>(rNew);
1135 // tdf#98868 if the SwTOXType is from a different document that the
1136 // target, re-register the TOXMark against a matching SwTOXType from
1137 // the target document instead
1138 const SwTOXType* pTOXType = rMark.GetTOXType();
1139 if (pTOXType && &pTOXType->GetDoc() != &rDoc)
1141 SwTOXType* pToxType = SwHistorySetTOXMark::GetSwTOXType(rDoc, pTOXType->GetType(),
1142 pTOXType->GetTypeName());
1143 rMark.RegisterToTOXType(*pToxType);
1146 pNew = new SwTextTOXMark(rMark, nStt, &nEnd);
1147 break;
1149 case RES_TXTATR_CJK_RUBY:
1150 pNew = new SwTextRuby( static_cast<SwFormatRuby&>(rNew), nStt, nEnd );
1151 break;
1152 case RES_TXTATR_META:
1153 case RES_TXTATR_METAFIELD:
1154 pNew = SwTextMeta::CreateTextMeta( rDoc.GetMetaFieldManager(), pTextNode,
1155 static_cast<SwFormatMeta&>(rNew), nStt, nEnd, bIsCopy == CopyOrNewType::Copy );
1156 break;
1157 case RES_TXTATR_LINEBREAK:
1158 pNew = new SwTextLineBreak(static_cast<SwFormatLineBreak&>(rNew), nStt);
1159 break;
1160 case RES_TXTATR_CONTENTCONTROL:
1161 pNew = SwTextContentControl::CreateTextContentControl(
1162 rDoc, pTextNode, static_cast<SwFormatContentControl&>(rNew), nStt, nEnd,
1163 bIsCopy == CopyOrNewType::Copy);
1164 break;
1165 default:
1166 assert(RES_TXTATR_AUTOFMT == rNew.Which());
1167 pNew = new SwTextAttrEnd( rNew, nStt, nEnd );
1168 break;
1171 return pNew;
1174 SwTextAttr* MakeTextAttr( SwDoc & rDoc, const SfxItemSet& rSet,
1175 sal_Int32 nStt, sal_Int32 nEnd )
1177 IStyleAccess& rStyleAccess = rDoc.GetIStyleAccess();
1178 const std::shared_ptr<SfxItemSet> pAutoStyle = rStyleAccess.getAutomaticStyle( rSet, IStyleAccess::AUTO_STYLE_CHAR );
1179 SwFormatAutoFormat aNewAutoFormat;
1180 aNewAutoFormat.SetStyleHandle( pAutoStyle );
1181 SwTextAttr* pNew = MakeTextAttr( rDoc, aNewAutoFormat, nStt, nEnd );
1182 return pNew;
1185 // delete the text attribute and unregister its item at the pool
1186 void SwTextNode::DestroyAttr( SwTextAttr* pAttr )
1188 if( !pAttr )
1189 return;
1191 // some things need to be done before deleting the formatting attribute
1192 SwDoc& rDoc = GetDoc();
1193 switch( pAttr->Which() )
1195 case RES_TXTATR_FLYCNT:
1197 SwFrameFormat* pFormat = pAttr->GetFlyCnt().GetFrameFormat();
1198 if( pFormat ) // set to 0 by Undo?
1199 rDoc.getIDocumentLayoutAccess().DelLayoutFormat( pFormat );
1201 break;
1203 case RES_CHRATR_HIDDEN:
1204 SetCalcHiddenCharFlags();
1205 break;
1207 case RES_TXTATR_FTN:
1208 static_cast<SwTextFootnote*>(pAttr)->SetStartNode( nullptr );
1209 static_cast<SwFormatFootnote&>(pAttr->GetAttr()).InvalidateFootnote();
1210 break;
1212 case RES_TXTATR_FIELD:
1213 case RES_TXTATR_ANNOTATION:
1214 case RES_TXTATR_INPUTFIELD:
1215 if( !rDoc.IsInDtor() )
1217 SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pAttr));
1218 SwFieldType* pFieldType = pAttr->GetFormatField().GetField()->GetTyp();
1220 if (SwFieldIds::Dde != pFieldType->Which()
1221 && !pTextField->GetpTextNode())
1223 break; // was not yet inserted
1226 //JP 06-08-95: DDE-fields are an exception
1227 assert(SwFieldIds::Dde == pFieldType->Which() ||
1228 this == pTextField->GetpTextNode());
1230 // certain fields must update the SwDoc's calculation flags
1232 // Certain fields (like HiddenParaField) must trigger recalculation of visible flag
1233 if (GetDoc().FieldCanHideParaWeight(pFieldType->Which()))
1234 SetCalcHiddenParaField();
1236 switch( pFieldType->Which() )
1238 case SwFieldIds::HiddenPara:
1239 case SwFieldIds::DbSetNumber:
1240 case SwFieldIds::GetExp:
1241 case SwFieldIds::Database:
1242 case SwFieldIds::SetExp:
1243 case SwFieldIds::HiddenText:
1244 case SwFieldIds::DbNumSet:
1245 case SwFieldIds::DbNextSet:
1246 if( !rDoc.getIDocumentFieldsAccess().IsNewFieldLst() && GetNodes().IsDocNodes() )
1247 rDoc.getIDocumentFieldsAccess().InsDelFieldInFieldLst(false, *pTextField);
1248 break;
1249 case SwFieldIds::Dde:
1250 if (GetNodes().IsDocNodes() && pTextField->GetpTextNode())
1251 static_cast<SwDDEFieldType*>(pFieldType)->DecRefCnt();
1252 break;
1253 case SwFieldIds::Postit:
1255 const_cast<SwFormatField&>(pAttr->GetFormatField()).Broadcast(
1256 SwFormatFieldHint(&pTextField->GetFormatField(), SwFormatFieldHintWhich::REMOVED));
1257 break;
1259 default: break;
1262 static_cast<SwFormatField&>(pAttr->GetAttr()).InvalidateField();
1263 break;
1265 case RES_TXTATR_TOXMARK:
1266 static_cast<SwTOXMark&>(pAttr->GetAttr()).InvalidateTOXMark();
1267 break;
1269 case RES_TXTATR_REFMARK:
1270 static_cast<SwFormatRefMark&>(pAttr->GetAttr()).InvalidateRefMark();
1271 break;
1273 case RES_TXTATR_META:
1274 case RES_TXTATR_METAFIELD:
1276 auto pTextMeta = static_txtattr_cast<SwTextMeta*>(pAttr);
1277 SwFormatMeta & rFormatMeta( static_cast<SwFormatMeta &>(pTextMeta->GetAttr()) );
1278 if (::sw::Meta* pMeta = rFormatMeta.GetMeta())
1280 if (SwDocShell* pDocSh = rDoc.GetDocShell())
1282 static constexpr OUStringLiteral metaNS(u"urn:bails");
1283 const css::uno::Reference<css::rdf::XResource> xSubject = pMeta->MakeUnoObject();
1284 uno::Reference<frame::XModel> xModel = pDocSh->GetBaseModel();
1285 SwRDFHelper::clearStatements(xModel, metaNS, xSubject);
1289 static_txtattr_cast<SwTextMeta*>(pAttr)->ChgTextNode(nullptr);
1291 break;
1292 case RES_TXTATR_CONTENTCONTROL:
1294 static_txtattr_cast<SwTextContentControl*>(pAttr)->ChgTextNode(nullptr);
1295 break;
1298 default:
1299 break;
1302 SwTextAttr::Destroy( pAttr, rDoc.GetAttrPool() );
1305 SwTextAttr* SwTextNode::InsertItem(
1306 SfxPoolItem& rAttr,
1307 const sal_Int32 nStart,
1308 const sal_Int32 nEnd,
1309 const SetAttrMode nMode )
1311 // character attributes will be inserted as automatic styles:
1312 assert( !isCHRATR(rAttr.Which()) && "AUTOSTYLES - "
1313 "SwTextNode::InsertItem should not be called with character attributes");
1315 SwTextAttr *const pNew =
1316 MakeTextAttr(
1317 GetDoc(),
1318 rAttr,
1319 nStart,
1320 nEnd,
1321 (nMode & SetAttrMode::IS_COPY) ? CopyOrNewType::Copy : CopyOrNewType::New,
1322 this );
1324 if ( pNew )
1326 const bool bSuccess( InsertHint( pNew, nMode ) );
1327 // N.B.: also check that the hint is actually in the hints array,
1328 // because hints of certain types may be merged after successful
1329 // insertion, and thus destroyed!
1330 if (!bSuccess || !m_pSwpHints->Contains( pNew ))
1332 return nullptr;
1336 return pNew;
1339 // take ownership of pAttr; if insertion fails, delete pAttr
1340 bool SwTextNode::InsertHint( SwTextAttr * const pAttr, const SetAttrMode nMode )
1342 bool bHiddenPara = false;
1344 assert(pAttr && pAttr->GetStart() <= Len());
1345 assert(!pAttr->GetEnd() || (*pAttr->GetEnd() <= Len()));
1347 // translate from SetAttrMode to InsertMode (for hints with CH_TXTATR)
1348 const SwInsertFlags nInsertFlags =
1349 (nMode & SetAttrMode::NOHINTEXPAND)
1350 ? SwInsertFlags::NOHINTEXPAND
1351 : (nMode & SetAttrMode::FORCEHINTEXPAND)
1352 ? (SwInsertFlags::FORCEHINTEXPAND | SwInsertFlags::EMPTYEXPAND)
1353 : SwInsertFlags::EMPTYEXPAND;
1355 // need this after TryInsertHint, when pAttr may be deleted
1356 const sal_Int32 nStart( pAttr->GetStart() );
1357 const bool bDummyChar( pAttr->HasDummyChar() );
1358 if (bDummyChar)
1360 SetAttrMode nInsMode = nMode;
1361 switch( pAttr->Which() )
1363 case RES_TXTATR_FLYCNT:
1365 SwTextFlyCnt *pFly = static_cast<SwTextFlyCnt *>(pAttr);
1366 SwFrameFormat* pFormat = pAttr->GetFlyCnt().GetFrameFormat();
1367 if( !(SetAttrMode::NOTXTATRCHR & nInsMode) )
1369 // Need to insert char first, because SetAnchor() reads
1370 // GetStart().
1371 //JP 11.05.98: if the anchor is already set correctly,
1372 // fix it after inserting the char, so that clients don't
1373 // have to worry about it.
1374 const SwFormatAnchor* pAnchor = pFormat->GetItemIfSet( RES_ANCHOR, false );
1376 SwContentIndex aIdx( this, pAttr->GetStart() );
1377 const OUString c(GetCharOfTextAttr(*pAttr));
1378 OUString const ins( InsertText(c, aIdx, nInsertFlags) );
1379 if (ins.isEmpty())
1381 // do not record deletion of Format!
1382 ::sw::UndoGuard const ug(
1383 pFormat->GetDoc()->GetIDocumentUndoRedo());
1384 DestroyAttr(pAttr);
1385 return false; // text node full :(
1387 nInsMode |= SetAttrMode::NOTXTATRCHR;
1389 if (pAnchor &&
1390 (RndStdIds::FLY_AS_CHAR == pAnchor->GetAnchorId()) &&
1391 pAnchor->GetAnchorNode() &&
1392 *pAnchor->GetAnchorNode() == *this &&
1393 pAnchor->GetAnchorContentOffset() == aIdx.GetIndex() )
1395 const_cast<SwPosition*>(pAnchor->GetContentAnchor())->AdjustContent(-1);
1398 pFly->SetAnchor( this );
1400 // format pointer could have changed in SetAnchor,
1401 // when copying to other docs!
1402 pFormat = pAttr->GetFlyCnt().GetFrameFormat();
1403 SwDoc *pDoc = pFormat->GetDoc();
1405 // OD 26.06.2003 - allow drawing objects in header/footer.
1406 // But don't allow control objects in header/footer
1407 if( RES_DRAWFRMFMT == pFormat->Which() &&
1408 pDoc->IsInHeaderFooter( *pFormat->GetAnchor().GetAnchorNode() ) )
1410 bool bCheckControlLayer = false;
1411 pFormat->CallSwClientNotify(sw::CheckDrawFrameFormatLayerHint(&bCheckControlLayer));
1412 if( bCheckControlLayer )
1414 // This should not be allowed, prevent it here.
1415 // The dtor of the SwTextAttr does not delete the
1416 // char, so delete it explicitly here.
1417 if( SetAttrMode::NOTXTATRCHR & nInsMode )
1419 // delete the char from the string
1420 assert(CH_TXTATR_BREAKWORD == m_Text[pAttr->GetStart()]
1421 || CH_TXTATR_INWORD == m_Text[pAttr->GetStart()]);
1422 m_Text = m_Text.replaceAt(pAttr->GetStart(), 1, u"");
1423 // Update SwIndexes
1424 SwContentIndex aTmpIdx( this, pAttr->GetStart() );
1425 Update(aTmpIdx, 1, UpdateMode::Negative);
1427 // do not record deletion of Format!
1428 ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo());
1429 DestroyAttr( pAttr );
1430 return false;
1433 break;
1436 case RES_TXTATR_FTN :
1438 // Footnotes: create text node and put it into Inserts-section
1439 SwDoc& rDoc = GetDoc();
1440 SwNodes &rNodes = rDoc.GetNodes();
1442 // check that footnote is inserted into body or redline section
1443 if( StartOfSectionIndex() < rNodes.GetEndOfAutotext().GetIndex() )
1445 // This should not be allowed, prevent it here.
1446 // The dtor of the SwTextAttr does not delete the
1447 // char, so delete it explicitly here.
1448 if( SetAttrMode::NOTXTATRCHR & nInsMode )
1450 // delete the char from the string
1451 assert(CH_TXTATR_BREAKWORD == m_Text[pAttr->GetStart()]
1452 || CH_TXTATR_INWORD == m_Text[pAttr->GetStart()]);
1453 m_Text = m_Text.replaceAt(pAttr->GetStart(), 1, u"");
1454 // Update SwIndexes
1455 SwContentIndex aTmpIdx( this, pAttr->GetStart() );
1456 Update(aTmpIdx, 1, UpdateMode::Negative);
1458 DestroyAttr( pAttr );
1459 return false;
1462 // is a new footnote being inserted?
1463 bool bNewFootnote = nullptr == static_cast<SwTextFootnote*>(pAttr)->GetStartNode();
1464 if( bNewFootnote )
1466 static_cast<SwTextFootnote*>(pAttr)->MakeNewTextSection( GetNodes() );
1467 SwRegHistory* pHist = GetpSwpHints()
1468 ? GetpSwpHints()->GetHistory() : nullptr;
1469 if( pHist )
1470 pHist->ChangeNodeIndex( GetIndex() );
1472 else if ( !GetpSwpHints() || !GetpSwpHints()->IsInSplitNode() )
1474 // existing footnote: delete all layout frames of its
1475 // footnote section
1476 SwNodeOffset nSttIdx =
1477 static_cast<SwTextFootnote*>(pAttr)->GetStartNode()->GetIndex();
1478 SwNodeOffset nEndIdx = rNodes[ nSttIdx++ ]->EndOfSectionIndex();
1479 for( ; nSttIdx < nEndIdx; ++nSttIdx )
1481 SwContentNode* pCNd = rNodes[ nSttIdx ]->GetContentNode();
1482 if( nullptr != pCNd )
1483 pCNd->DelFrames(nullptr);
1484 else if (SwTableNode *const pTable = rNodes[nSttIdx]->GetTableNode())
1486 pTable->DelFrames();
1491 if( !(SetAttrMode::NOTXTATRCHR & nInsMode) )
1493 // must insert first, to prevent identical indexes
1494 // that could later prevent insertion into SwDoc's
1495 // footnote array
1496 SwContentIndex aNdIdx( this, pAttr->GetStart() );
1497 const OUString c(GetCharOfTextAttr(*pAttr));
1498 OUString const ins( InsertText(c, aNdIdx, nInsertFlags) );
1499 if (ins.isEmpty())
1501 DestroyAttr(pAttr);
1502 return false; // text node full :(
1504 nInsMode |= SetAttrMode::NOTXTATRCHR;
1507 // insert into SwDoc's footnote index array
1508 SwTextFootnote* pTextFootnote = nullptr;
1509 if( !bNewFootnote )
1511 // moving an existing footnote (e.g. SplitNode)
1512 for( size_t n = 0; n < rDoc.GetFootnoteIdxs().size(); ++n )
1513 if( pAttr == rDoc.GetFootnoteIdxs()[n] )
1515 // assign new index by removing and re-inserting
1516 pTextFootnote = rDoc.GetFootnoteIdxs()[n];
1517 rDoc.GetFootnoteIdxs().erase( rDoc.GetFootnoteIdxs().begin() + n );
1518 break;
1520 // if the Undo set the StartNode, the Index isn't
1521 // in the doc's array yet!
1523 if( !pTextFootnote )
1524 pTextFootnote = static_cast<SwTextFootnote*>(pAttr);
1526 // to update the numbers and for sorting, the Node must be set
1527 static_cast<SwTextFootnote*>(pAttr)->ChgTextNode( this );
1529 // do not insert footnote in redline section into footnote array
1530 if( StartOfSectionIndex() > rNodes.GetEndOfRedlines().GetIndex() )
1532 const bool bSuccess = rDoc.GetFootnoteIdxs().insert(pTextFootnote).second;
1533 OSL_ENSURE( bSuccess, "FootnoteIdx not inserted." );
1535 rDoc.GetFootnoteIdxs().UpdateFootnote( *this );
1536 static_cast<SwTextFootnote*>(pAttr)->SetSeqRefNo();
1538 break;
1540 case RES_TXTATR_FIELD:
1542 // trigger notification for relevant fields, like HiddenParaFields
1543 if (GetDoc().FieldCanHideParaWeight(
1544 pAttr->GetFormatField().GetField()->GetTyp()->Which()))
1546 bHiddenPara = true;
1549 break;
1550 case RES_TXTATR_LINEBREAK :
1552 static_cast<SwTextLineBreak*>(pAttr)->SetTextNode(this);
1554 break;
1557 // CH_TXTATR_* are inserted for SwTextHints without EndIndex
1558 // If the caller is SwTextNode::Copy, the char has already been copied,
1559 // and SETATTR_NOTXTATRCHR prevents inserting it again here.
1560 if( !(SetAttrMode::NOTXTATRCHR & nInsMode) )
1562 SwContentIndex aIdx( this, pAttr->GetStart() );
1563 OUString const ins( InsertText(OUString(GetCharOfTextAttr(*pAttr)),
1564 aIdx, nInsertFlags) );
1565 if (ins.isEmpty())
1567 DestroyAttr(pAttr);
1568 return false; // text node full :(
1571 // adjust end of hint to account for inserted CH_TXTATR
1572 const sal_Int32* pEnd(pAttr->GetEnd());
1573 if (pEnd)
1575 pAttr->SetEnd(*pEnd + 1);
1578 if (pAttr->Which() == RES_TXTATR_CONTENTCONTROL)
1580 // Content controls have a dummy character at their end as well.
1581 SwContentIndex aEndIdx(this, *pAttr->GetEnd());
1582 OUString aEnd
1583 = InsertText(OUString(GetCharOfTextAttr(*pAttr)), aEndIdx, nInsertFlags);
1584 if (aEnd.isEmpty())
1586 DestroyAttr(pAttr);
1587 return false;
1590 pEnd = pAttr->GetEnd();
1591 if (pEnd)
1593 pAttr->SetEnd(*pEnd + 1);
1599 // handle attributes which provide content
1600 sal_Int32 nEnd = nStart;
1601 bool bInputFieldStartCharInserted = false;
1602 bool bInputFieldEndCharInserted = false;
1603 const bool bHasContent( pAttr->HasContent() );
1604 if ( bHasContent )
1606 switch( pAttr->Which() )
1608 case RES_TXTATR_INPUTFIELD:
1610 SwTextInputField* pTextInputField = dynamic_cast<SwTextInputField*>(pAttr);
1611 if ( pTextInputField )
1613 if( !(SetAttrMode::NOTXTATRCHR & nMode) )
1615 SwContentIndex aIdx( this, pAttr->GetStart() );
1616 const OUString aContent = OUStringChar(CH_TXT_ATR_INPUTFIELDSTART)
1617 + pTextInputField->GetFieldContent() + OUStringChar(CH_TXT_ATR_INPUTFIELDEND);
1618 InsertText( aContent, aIdx, nInsertFlags );
1620 const sal_Int32* const pEnd(pAttr->GetEnd());
1621 assert(pEnd != nullptr);
1622 pAttr->SetEnd(*pEnd + aContent.getLength());
1623 nEnd = *pAttr->GetEnd();
1625 else
1627 // assure that CH_TXT_ATR_INPUTFIELDSTART and CH_TXT_ATR_INPUTFIELDEND are inserted.
1628 if ( m_Text[ pAttr->GetStart() ] != CH_TXT_ATR_INPUTFIELDSTART )
1630 SwContentIndex aIdx( this, pAttr->GetStart() );
1631 InsertText( OUString(CH_TXT_ATR_INPUTFIELDSTART), aIdx, nInsertFlags );
1632 bInputFieldStartCharInserted = true;
1633 const sal_Int32* const pEnd(pAttr->GetEnd());
1634 assert(pEnd != nullptr);
1635 pAttr->SetEnd(*pEnd + 1);
1636 nEnd = *pAttr->GetEnd();
1639 const sal_Int32* const pEnd(pAttr->GetEnd());
1640 assert(pEnd != nullptr);
1641 if (m_Text[ *pEnd - 1 ] != CH_TXT_ATR_INPUTFIELDEND)
1643 SwContentIndex aIdx( this, *pEnd );
1644 InsertText( OUString(CH_TXT_ATR_INPUTFIELDEND), aIdx, nInsertFlags );
1645 bInputFieldEndCharInserted = true;
1646 pAttr->SetEnd(*pEnd + 1);
1647 nEnd = *pAttr->GetEnd();
1652 break;
1653 default:
1654 break;
1658 GetOrCreateSwpHints();
1660 // handle overlap with an existing InputField
1661 bool bInsertHint = true;
1663 const SwTextInputField* pTextInputField = GetOverlappingInputField( *pAttr );
1664 if ( pTextInputField != nullptr )
1666 if ( pAttr->End() == nullptr )
1668 bInsertHint = false;
1669 DestroyAttr(pAttr);
1671 else
1673 if ( pAttr->GetStart() > pTextInputField->GetStart() )
1675 pAttr->SetStart( pTextInputField->GetStart() );
1677 if ( *(pAttr->End()) < *(pTextInputField->End()) )
1679 pAttr->SetEnd(*(pTextInputField->End()));
1685 if (bInsertHint)
1687 // Handle the invariant that a plain text content control has the same character formatting
1688 // for all of its content.
1689 auto* pTextContentControl = static_txtattr_cast<SwTextContentControl*>(
1690 GetTextAttrAt(pAttr->GetStart(), RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Parent));
1691 if (pTextContentControl)
1693 auto& rFormatContentControl
1694 = static_cast<SwFormatContentControl&>(pTextContentControl->GetAttr());
1695 std::shared_ptr<SwContentControl> pContentControl
1696 = rFormatContentControl.GetContentControl();
1697 if (pAttr->End() != nullptr && pContentControl->GetPlainText())
1699 if (pAttr->GetStart() > pTextContentControl->GetStart())
1701 pAttr->SetStart(pTextContentControl->GetStart());
1703 if (*pAttr->End() < *pTextContentControl->End())
1705 pAttr->SetEnd(*pTextContentControl->End());
1711 const bool bRet = bInsertHint
1712 && m_pSwpHints->TryInsertHint( pAttr, *this, nMode );
1714 if ( !bRet )
1716 if ( bDummyChar
1717 && !(SetAttrMode::NOTXTATRCHR & nMode) )
1719 // undo insertion of dummy character
1720 // N.B. cannot insert the dummy character after inserting the hint,
1721 // because if the hint has no extent it will be moved in InsertText,
1722 // resulting in infinite recursion
1723 assert((CH_TXTATR_BREAKWORD == m_Text[nStart] ||
1724 CH_TXTATR_INWORD == m_Text[nStart] ));
1725 SwContentIndex aIdx( this, nStart );
1726 EraseText( aIdx, 1 );
1729 if ( bHasContent )
1731 if ( !(SetAttrMode::NOTXTATRCHR & nMode)
1732 && (nEnd - nStart) > 0 )
1734 SwContentIndex aIdx( this, nStart );
1735 EraseText( aIdx, (nEnd - nStart) );
1737 else
1739 if ( bInputFieldEndCharInserted
1740 && (nEnd - nStart) > 0 )
1742 SwContentIndex aIdx( this, nEnd - 1 );
1743 EraseText( aIdx, 1 );
1746 if ( bInputFieldStartCharInserted )
1748 SwContentIndex aIdx( this, nStart );
1749 EraseText( aIdx, 1 );
1755 if ( bHiddenPara )
1757 SetCalcHiddenParaField();
1760 return bRet;
1763 void SwTextNode::DeleteAttribute( SwTextAttr * const pAttr )
1765 if ( !HasHints() )
1767 OSL_FAIL("DeleteAttribute called, but text node without hints?");
1768 return;
1771 if ( pAttr->HasDummyChar() )
1773 // copy index!
1774 const SwContentIndex aIdx( this, pAttr->GetStart() );
1775 // erase the CH_TXTATR, which will also delete pAttr
1776 EraseText( aIdx, 1 );
1778 else if ( pAttr->HasContent() )
1780 const SwContentIndex aIdx( this, pAttr->GetStart() );
1781 assert(pAttr->End() != nullptr);
1782 EraseText( aIdx, *pAttr->End() - pAttr->GetStart() );
1784 else
1786 // create MsgHint before start/end become invalid
1787 SwUpdateAttr aHint(
1788 pAttr->GetStart(),
1789 *pAttr->GetEnd(),
1790 pAttr->Which());
1792 m_pSwpHints->Delete( pAttr );
1793 SwTextAttr::Destroy( pAttr, GetDoc().GetAttrPool() );
1794 CallSwClientNotify(sw::LegacyModifyHint(nullptr, &aHint));
1796 TryDeleteSwpHints();
1800 //FIXME: this does NOT respect SORT NUMBER (for CHARFMT)!
1801 void SwTextNode::DeleteAttributes(
1802 const sal_uInt16 nWhich,
1803 const sal_Int32 nStart,
1804 const sal_Int32 nEnd )
1806 if ( !HasHints() )
1807 return;
1809 for ( size_t nPos = 0; m_pSwpHints && nPos < m_pSwpHints->Count(); ++nPos )
1811 SwTextAttr * const pTextHt = m_pSwpHints->Get( nPos );
1812 const sal_Int32 nHintStart = pTextHt->GetStart();
1813 if (nStart < nHintStart)
1815 break; // sorted by start
1817 else if ( (nStart == nHintStart) && (nWhich == pTextHt->Which()) )
1819 if ( nWhich == RES_CHRATR_HIDDEN )
1821 assert(!"hey, that's a CHRATR! how did that get in?");
1822 SetCalcHiddenCharFlags();
1824 else if ( nWhich == RES_TXTATR_CHARFMT )
1826 // Check if character format contains hidden attribute:
1827 const SwCharFormat* pFormat = pTextHt->GetCharFormat().GetCharFormat();
1828 if ( SfxItemState::SET == pFormat->GetItemState( RES_CHRATR_HIDDEN ) )
1829 SetCalcHiddenCharFlags();
1831 // #i75430# Recalc hidden flags if necessary
1832 else if ( nWhich == RES_TXTATR_AUTOFMT )
1834 // Check if auto style contains hidden attribute:
1835 const SfxPoolItem* pHiddenItem = CharFormat::GetItem( *pTextHt, RES_CHRATR_HIDDEN );
1836 if ( pHiddenItem )
1837 SetCalcHiddenCharFlags();
1838 // for auto styles DeleteAttributes is only called from Undo
1839 // so it shouldn't need to care about ignore start/end flags
1842 sal_Int32 const * const pEndIdx = pTextHt->GetEnd();
1844 if ( pTextHt->HasDummyChar() )
1846 // copy index!
1847 const SwContentIndex aIdx( this, nStart );
1848 // erase the CH_TXTATR, which will also delete pTextHt
1849 EraseText( aIdx, 1 );
1851 else if ( pTextHt->HasContent() )
1853 const SwContentIndex aIdx( this, nStart );
1854 OSL_ENSURE( pTextHt->End() != nullptr, "<SwTextNode::DeleteAttributes(..)> - missing End() at <SwTextAttr> instance which has content" );
1855 EraseText( aIdx, *pTextHt->End() - nStart );
1857 else if( *pEndIdx == nEnd )
1859 // Create MsgHint before Start and End are gone.
1860 // For HiddenParaFields it's not necessary to call
1861 // SetCalcHiddenParaField because the dtor does that.
1862 SwUpdateAttr aHint(
1863 nStart,
1864 *pEndIdx,
1865 nWhich);
1867 m_pSwpHints->DeleteAtPos( nPos );
1868 SwTextAttr::Destroy( pTextHt, GetDoc().GetAttrPool() );
1869 CallSwClientNotify(sw::LegacyModifyHint(nullptr, &aHint));
1873 TryDeleteSwpHints();
1876 void SwTextNode::DelSoftHyph( const sal_Int32 nStt, const sal_Int32 nEnd )
1878 sal_Int32 nFndPos = nStt;
1879 sal_Int32 nEndPos = nEnd;
1880 for (;;)
1882 nFndPos = m_Text.indexOf(CHAR_SOFTHYPHEN, nFndPos);
1883 if (nFndPos<0 || nFndPos>=nEndPos )
1885 break;
1887 const SwContentIndex aIdx( this, nFndPos );
1888 EraseText( aIdx, 1 );
1889 --nEndPos;
1893 bool SwTextNode::IsIgnoredCharFormatForNumbering(const sal_uInt16 nWhich, bool bIsCharStyle)
1895 // LO can save the char background as either shading or highlight, so check which mode is currently chosen.
1896 // Shading does not affect the numbering. Highlighting does (but isn't allowed in a char style).
1897 if (nWhich == RES_CHRATR_BACKGROUND)
1898 return bIsCharStyle || SvtFilterOptions::Get().IsCharBackground2Shading();
1900 return (nWhich == RES_CHRATR_UNDERLINE
1901 || nWhich == RES_CHRATR_ESCAPEMENT);
1904 // Set these attributes on SwTextNode. If they apply to the entire paragraph
1905 // text, set them in the SwTextNode's item set (SwContentNode::SetAttr).
1906 bool SwTextNode::SetAttr(
1907 const SfxItemSet& rSet,
1908 const sal_Int32 nStt,
1909 const sal_Int32 nEnd,
1910 const SetAttrMode nMode,
1911 SwTextAttr **ppNewTextAttr )
1913 if( !rSet.Count() )
1914 return false;
1916 // split sets (for selection in nodes)
1917 const SfxItemSet* pSet = &rSet;
1918 SfxItemSetFixed<RES_TXTATR_BEGIN, RES_TXTATR_END-1> aTextSet( *rSet.GetPool() );
1920 // entire paragraph
1921 if ( !nStt && (nEnd == m_Text.getLength()) &&
1922 !(nMode & SetAttrMode::NOFORMATATTR ) )
1924 // if the node already has CharFormat hints, the new attributes must
1925 // be set as hints too to override those.
1926 bool bHasCharFormats = false;
1927 if ( HasHints() )
1929 for ( size_t n = 0; n < m_pSwpHints->Count(); ++n )
1931 if ( m_pSwpHints->Get( n )->IsCharFormatAttr() )
1933 bHasCharFormats = true;
1934 break;
1939 if( !bHasCharFormats )
1941 aTextSet.Put( rSet );
1942 // If there are any character attributes in rSet,
1943 // we want to set them at the paragraph:
1944 if( aTextSet.Count() != rSet.Count() )
1946 const bool bRet = SetAttr( rSet );
1947 if( !aTextSet.Count() )
1948 return bRet;
1951 // check for auto style:
1952 if ( const SwFormatAutoFormat* pItem = aTextSet.GetItemIfSet( RES_TXTATR_AUTOFMT, false ) )
1954 const bool bRet = SetAttr( *pItem->GetStyleHandle() );
1955 if( 1 == aTextSet.Count() )
1956 return bRet;
1959 // Continue with the text attributes:
1960 pSet = &aTextSet;
1964 GetOrCreateSwpHints();
1966 SfxItemSet aCharSet( *rSet.GetPool(), aCharAutoFormatSetRange );
1968 size_t nCount = 0;
1969 SfxItemIter aIter( *pSet );
1970 const SfxPoolItem* pItem = aIter.GetCurItem();
1974 if (!IsInvalidItem(pItem))
1976 const sal_uInt16 nWhich = pItem->Which();
1977 OSL_ENSURE( isCHRATR(nWhich) || isTXTATR(nWhich),
1978 "SwTextNode::SetAttr(): unknown attribute" );
1979 if ( isCHRATR(nWhich) || isTXTATR(nWhich) )
1981 if ((RES_TXTATR_CHARFMT == nWhich) &&
1982 (GetDoc().GetDfltCharFormat() ==
1983 static_cast<const SwFormatCharFormat*>(pItem)->GetCharFormat()))
1985 RstTextAttr( nStt, nEnd - nStt, RES_TXTATR_CHARFMT );
1986 DontExpandFormat( nStt );
1988 else
1990 if (isCHRATR(nWhich) ||
1991 (RES_TXTATR_UNKNOWN_CONTAINER == nWhich))
1993 aCharSet.Put( *pItem );
1995 else
1998 SwTextAttr *const pNew = MakeTextAttr( GetDoc(),
1999 const_cast<SfxPoolItem&>(*pItem), nStt, nEnd );
2000 if ( pNew )
2002 // store the first one we create into the pp
2003 if (ppNewTextAttr && !*ppNewTextAttr)
2004 *ppNewTextAttr = pNew;
2005 if ( nEnd != nStt && !pNew->GetEnd() )
2007 OSL_FAIL("Attribute without end, but area marked");
2008 DestroyAttr( pNew ); // do not insert
2010 else if ( InsertHint( pNew, nMode ) )
2012 ++nCount;
2019 pItem = aIter.NextItem();
2020 } while(pItem);
2022 if ( aCharSet.Count() )
2024 SwTextAttr* pTmpNew = MakeTextAttr( GetDoc(), aCharSet, nStt, nEnd );
2025 if ( InsertHint( pTmpNew, nMode ) )
2027 ++nCount;
2031 TryDeleteSwpHints();
2033 return nCount != 0;
2036 static void lcl_MergeAttr( SfxItemSet& rSet, const SfxPoolItem& rAttr )
2038 if ( RES_TXTATR_AUTOFMT == rAttr.Which() )
2040 const SfxItemSet* pCFSet = CharFormat::GetItemSet( rAttr );
2041 if ( !pCFSet )
2042 return;
2043 SfxWhichIter aIter( *pCFSet );
2044 sal_uInt16 nWhich = aIter.FirstWhich();
2045 while( nWhich )
2047 const SfxPoolItem* pItem = nullptr;
2048 if( ( nWhich < RES_CHRATR_END ||
2049 RES_TXTATR_UNKNOWN_CONTAINER == nWhich ) &&
2050 ( SfxItemState::SET == aIter.GetItemState( true, &pItem ) ) )
2051 rSet.Put( *pItem );
2052 nWhich = aIter.NextWhich();
2055 else
2056 rSet.Put( rAttr );
2059 static void lcl_MergeAttr_ExpandChrFormat( SfxItemSet& rSet, const SfxPoolItem& rAttr )
2061 if( RES_TXTATR_CHARFMT == rAttr.Which() ||
2062 RES_TXTATR_INETFMT == rAttr.Which() ||
2063 RES_TXTATR_AUTOFMT == rAttr.Which() )
2065 const SfxItemSet* pCFSet = CharFormat::GetItemSet( rAttr );
2067 if ( pCFSet )
2069 SfxWhichIter aIter( *pCFSet );
2070 sal_uInt16 nWhich = aIter.FirstWhich();
2071 while( nWhich )
2073 const SfxPoolItem* pItem = nullptr;
2074 if( ( nWhich < RES_CHRATR_END ||
2075 ( RES_TXTATR_AUTOFMT == rAttr.Which() && RES_TXTATR_UNKNOWN_CONTAINER == nWhich ) ) &&
2076 ( SfxItemState::SET == aIter.GetItemState( true, &pItem ) ) )
2077 rSet.Put( *pItem );
2078 nWhich = aIter.NextWhich();
2083 /* If multiple attributes overlap, the last one wins!
2084 Probably this can only happen between a RES_TXTATR_INETFMT and one of the
2085 other hints, because BuildPortions ensures that CHARFMT/AUTOFMT don't
2086 overlap. But there may be multiple CHARFMT/AUTOFMT with exactly the same
2087 start/end, sorted by BuildPortions, in which case the same logic applies.
2089 1234567890123456789
2090 |------------| Font1
2091 |------| Font2
2093 |--| query range: -> Font2
2095 // merge into set
2096 rSet.Put( rAttr );
2099 namespace {
2101 struct SwPoolItemEndPair
2103 public:
2104 const SfxPoolItem* mpItem;
2105 sal_Int32 mnEndPos;
2107 SwPoolItemEndPair() : mpItem( nullptr ), mnEndPos( 0 ) {};
2112 static void lcl_MergeListLevelIndentAsLRSpaceItem( const SwTextNode& rTextNode,
2113 SfxItemSet& rSet )
2115 ::sw::ListLevelIndents const indents(rTextNode.AreListLevelIndentsApplicable());
2116 if (indents == ::sw::ListLevelIndents::No)
2117 return;
2119 const SwNumRule* pRule = rTextNode.GetNumRule();
2120 if ( pRule && rTextNode.GetActualListLevel() >= 0 )
2122 const SwNumFormat& rFormat = pRule->Get(o3tl::narrowing<sal_uInt16>(rTextNode.GetActualListLevel()));
2123 if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
2125 if (indents & ::sw::ListLevelIndents::FirstLine)
2127 SvxFirstLineIndentItem const firstLine(static_cast<short>(rFormat.GetFirstLineIndent()), RES_MARGIN_FIRSTLINE);
2128 rSet.Put(firstLine);
2130 if (indents & ::sw::ListLevelIndents::LeftMargin)
2132 SvxTextLeftMarginItem const leftMargin(rFormat.GetIndentAt(), RES_MARGIN_TEXTLEFT);
2133 rSet.Put(leftMargin);
2139 // request the attributes of the TextNode at the range
2140 bool SwTextNode::GetParaAttr(SfxItemSet& rSet, sal_Int32 nStt, sal_Int32 nEnd,
2141 const bool bOnlyTextAttr, const bool bGetFromChrFormat,
2142 const bool bMergeIndentValuesOfNumRule,
2143 SwRootFrame const*const pLayout) const
2145 assert(!rSet.Count()); // handled inconsistently, typically an error?
2147 if (pLayout && pLayout->HasMergedParas())
2149 if (GetRedlineMergeFlag() == SwNode::Merge::Hidden)
2151 return false; // ignore deleted node
2155 // get the node's automatic attributes
2156 SfxItemSet aFormatSet( *rSet.GetPool(), rSet.GetRanges() );
2157 if (!bOnlyTextAttr)
2159 SwTextNode const& rParaPropsNode(
2160 sw::GetAttrMerged(aFormatSet, *this, pLayout));
2161 if (bMergeIndentValuesOfNumRule)
2163 lcl_MergeListLevelIndentAsLRSpaceItem(rParaPropsNode, aFormatSet);
2167 if( HasHints() )
2169 // First, check which text attributes are valid in the range.
2170 // cases:
2171 // Ambiguous, if
2172 // * the attribute is wholly contained in the range
2173 // * the attribute end is in the range
2174 // * the attribute start is in the range
2175 // Unambiguous (merge into set), if
2176 // * the attribute wholly contains the range
2177 // Ignored, if
2178 // * the attribute is wholly outside the range
2180 void (*fnMergeAttr)( SfxItemSet&, const SfxPoolItem& )
2181 = bGetFromChrFormat ? &lcl_MergeAttr_ExpandChrFormat
2182 : &lcl_MergeAttr;
2184 const size_t nSize = m_pSwpHints->Count();
2186 if (nStt == nEnd) // no range:
2188 for (size_t n = 0; n < nSize; ++n)
2190 const SwTextAttr* pHt = m_pSwpHints->Get(n);
2191 const sal_Int32 nAttrStart = pHt->GetStart();
2192 if (nAttrStart > nEnd) // behind the range
2193 break;
2195 const sal_Int32* pAttrEnd = pHt->End();
2196 if ( ! pAttrEnd ) // no attributes without end
2197 continue;
2199 if( ( nAttrStart < nStt &&
2200 ( pHt->DontExpand() ? nStt < *pAttrEnd
2201 : nStt <= *pAttrEnd )) ||
2202 ( nStt == nAttrStart &&
2203 ( nAttrStart == *pAttrEnd || !nStt )))
2204 (*fnMergeAttr)( rSet, pHt->GetAttr() );
2207 else // a query range is defined
2209 // #i75299#
2210 std::optional< std::vector< SwPoolItemEndPair > > pAttrArr;
2212 const size_t coArrSz = RES_TXTATR_WITHEND_END - RES_CHRATR_BEGIN;
2214 for (size_t n = 0; n < nSize; ++n)
2216 const SwTextAttr* pHt = m_pSwpHints->Get(n);
2217 const sal_Int32 nAttrStart = pHt->GetStart();
2218 if (nAttrStart > nEnd) // outside, behind
2219 break;
2221 const sal_Int32* pAttrEnd = pHt->End();
2222 if ( ! pAttrEnd ) // no attributes without end
2223 continue;
2225 bool bChkInvalid = false;
2226 if (nAttrStart <= nStt) // before or exactly Start
2228 if (*pAttrEnd <= nStt) // outside, before
2229 continue;
2231 if (nEnd <= *pAttrEnd) // behind or exactly End
2232 (*fnMergeAttr)( aFormatSet, pHt->GetAttr() );
2233 else
2234 // else if( pHt->GetAttr() != aFormatSet.Get( pHt->Which() ) )
2235 // ambiguous
2236 bChkInvalid = true;
2238 else if (nAttrStart < nEnd // starts in the range
2239 )// && pHt->GetAttr() != aFormatSet.Get( pHt->Which() ) )
2240 bChkInvalid = true;
2242 if( bChkInvalid )
2244 // ambiguous?
2245 std::optional< SfxItemIter > oItemIter;
2246 const SfxPoolItem* pItem = nullptr;
2248 if ( RES_TXTATR_AUTOFMT == pHt->Which() )
2250 const SfxItemSet* pAutoSet = CharFormat::GetItemSet( pHt->GetAttr() );
2251 if ( pAutoSet )
2253 oItemIter.emplace( *pAutoSet );
2254 pItem = oItemIter->GetCurItem();
2257 else
2258 pItem = &pHt->GetAttr();
2260 const sal_Int32 nHintEnd = *pAttrEnd;
2262 for (; pItem; pItem = oItemIter ? oItemIter->NextItem() : nullptr)
2264 const sal_uInt16 nHintWhich = pItem->Which();
2265 OSL_ENSURE(!isUNKNOWNATR(nHintWhich),
2266 "SwTextNode::GetAttr(): unknown attribute?");
2268 if (!pAttrArr)
2270 pAttrArr = std::vector< SwPoolItemEndPair >(coArrSz);
2273 std::vector< SwPoolItemEndPair >::iterator pPrev = pAttrArr->begin();
2274 if (isCHRATR(nHintWhich) ||
2275 isTXTATR_WITHEND(nHintWhich))
2277 pPrev += nHintWhich - RES_CHRATR_BEGIN;
2279 else
2281 pPrev = pAttrArr->end();
2284 if( pPrev != pAttrArr->end() )
2286 if( !pPrev->mpItem )
2288 if ( bOnlyTextAttr || *pItem != aFormatSet.Get( nHintWhich ) )
2290 if( nAttrStart > nStt )
2292 rSet.InvalidateItem( nHintWhich );
2293 pPrev->mpItem = INVALID_POOL_ITEM;
2295 else
2297 pPrev->mpItem = pItem;
2298 pPrev->mnEndPos = nHintEnd;
2302 else if( !IsInvalidItem(pPrev->mpItem) )
2304 if( pPrev->mnEndPos == nAttrStart &&
2305 *pPrev->mpItem == *pItem )
2307 pPrev->mpItem = pItem;
2308 pPrev->mnEndPos = nHintEnd;
2310 else
2312 rSet.InvalidateItem( nHintWhich );
2313 pPrev->mpItem = INVALID_POOL_ITEM;
2317 } // end while
2321 if (pAttrArr)
2323 for (size_t n = 0; n < coArrSz; ++n)
2325 const SwPoolItemEndPair& rItemPair = (*pAttrArr)[ n ];
2326 if( rItemPair.mpItem && !IsInvalidItem(rItemPair.mpItem) )
2328 const sal_uInt16 nWh =
2329 o3tl::narrowing<sal_uInt16>(n + RES_CHRATR_BEGIN);
2331 if (nEnd <= rItemPair.mnEndPos) // behind or exactly end
2333 if( *rItemPair.mpItem != aFormatSet.Get( nWh ) )
2334 (*fnMergeAttr)( rSet, *rItemPair.mpItem );
2336 else
2337 // ambiguous
2338 rSet.InvalidateItem( nWh );
2343 if( aFormatSet.Count() )
2345 // remove all from the format-set that are also set in the text-set
2346 aFormatSet.Differentiate( rSet );
2350 if (aFormatSet.Count())
2352 // now "merge" everything
2353 rSet.Put( aFormatSet );
2356 return rSet.Count() != 0;
2359 namespace
2362 typedef std::pair<sal_Int32, sal_Int32> AttrSpan_t;
2363 typedef std::multimap<AttrSpan_t, const SwTextAttr*> AttrSpanMap_t;
2365 struct IsAutoStyle
2367 bool
2368 operator()(const AttrSpanMap_t::value_type& i_rAttrSpan)
2369 const
2371 return i_rAttrSpan.second && i_rAttrSpan.second->Which() == RES_TXTATR_AUTOFMT;
2375 /** Removes from io_rAttrSet all items that are set by style on the
2376 given span.
2378 struct RemovePresentAttrs
2380 explicit RemovePresentAttrs(SfxItemSet& io_rAttrSet)
2381 : m_rAttrSet(io_rAttrSet)
2385 void
2386 operator()(const AttrSpanMap_t::value_type& i_rAttrSpan)
2387 const
2389 if (!i_rAttrSpan.second)
2391 return;
2394 const SwTextAttr* const pAutoStyle(i_rAttrSpan.second);
2395 SfxItemIter aIter(m_rAttrSet);
2396 for (const SfxPoolItem* pItem(aIter.GetCurItem()); pItem; pItem = aIter.NextItem())
2398 const sal_uInt16 nWhich(pItem->Which());
2399 if (CharFormat::IsItemIncluded(nWhich, pAutoStyle))
2401 aIter.ClearItem();
2406 private:
2407 SfxItemSet& m_rAttrSet;
2410 /** Collects all style-covered spans from i_rHints to o_rSpanMap. In
2411 addition inserts dummy spans with pointer to format equal to 0 for
2412 all gaps (i.e. spans not covered by any style). This simplifies
2413 creation of autostyles for all needed spans, but it means all code
2414 that tries to access the pointer has to check if it's non-null!
2416 void
2417 lcl_CollectHintSpans(const SwpHints& i_rHints, const sal_Int32 nLength,
2418 AttrSpanMap_t& o_rSpanMap)
2420 sal_Int32 nLastEnd(0);
2422 for (size_t i = 0; i < i_rHints.Count(); ++i)
2424 const SwTextAttr* pHint = i_rHints.Get(i);
2425 const sal_uInt16 nWhich(pHint->Which());
2426 if (nWhich == RES_TXTATR_CHARFMT || nWhich == RES_TXTATR_AUTOFMT)
2428 const AttrSpan_t aSpan(pHint->GetStart(), *pHint->End());
2429 o_rSpanMap.emplace(aSpan, pHint);
2431 // < not != because there may be multiple CHARFMT at same range
2432 if (nLastEnd < aSpan.first)
2434 // insert dummy span covering the gap
2435 o_rSpanMap.emplace( AttrSpan_t(nLastEnd, aSpan.first), nullptr );
2438 nLastEnd = aSpan.second;
2442 // no hints at the end (special case: no hints at all in i_rHints)
2443 if (nLastEnd != nLength && nLength != 0)
2445 o_rSpanMap.emplace(AttrSpan_t(nLastEnd, nLength), nullptr);
2449 void
2450 lcl_FillWhichIds(const SfxItemSet& i_rAttrSet, std::vector<sal_uInt16>& o_rClearIds)
2452 o_rClearIds.reserve(i_rAttrSet.Count());
2453 SfxItemIter aIter(i_rAttrSet);
2454 for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem())
2456 o_rClearIds.push_back(pItem->Which());
2460 struct SfxItemSetClearer
2462 SfxItemSet & m_rItemSet;
2463 explicit SfxItemSetClearer(SfxItemSet & rItemSet) : m_rItemSet(rItemSet) { }
2464 void operator()(sal_uInt16 const nWhich) { m_rItemSet.ClearItem(nWhich); }
2469 /** Does the hard work of SwTextNode::FormatToTextAttr: the real conversion
2470 of items to automatic styles.
2472 void
2473 SwTextNode::impl_FormatToTextAttr(const SfxItemSet& i_rAttrSet)
2475 typedef AttrSpanMap_t::iterator AttrSpanMap_iterator_t;
2476 AttrSpanMap_t aAttrSpanMap;
2478 if (i_rAttrSet.Count() == 0)
2480 return;
2483 // 1. Identify all spans in hints' array
2485 lcl_CollectHintSpans(*m_pSwpHints, m_Text.getLength(), aAttrSpanMap);
2487 // 2. Go through all spans and insert new attrs
2489 AttrSpanMap_iterator_t aCurRange(aAttrSpanMap.begin());
2490 const AttrSpanMap_iterator_t aEnd(aAttrSpanMap.end());
2491 while (aCurRange != aEnd)
2493 typedef std::pair<AttrSpanMap_iterator_t, AttrSpanMap_iterator_t>
2494 AttrSpanMapRange_t;
2495 AttrSpanMapRange_t aRange(aAttrSpanMap.equal_range(aCurRange->first));
2497 // 2a. Collect attributes to insert
2499 SfxItemSet aCurSet(i_rAttrSet);
2500 std::for_each(aRange.first, aRange.second, RemovePresentAttrs(aCurSet));
2502 // 2b. Insert automatic style containing the collected attributes
2504 if (aCurSet.Count() != 0)
2506 AttrSpanMap_iterator_t aAutoStyleIt(
2507 std::find_if(aRange.first, aRange.second, IsAutoStyle()));
2508 if (aAutoStyleIt != aRange.second)
2510 // there already is an automatic style on that span:
2511 // create new one and remove the original one
2512 SwTextAttr* const pAutoStyle(const_cast<SwTextAttr*>(aAutoStyleIt->second));
2513 const std::shared_ptr<SfxItemSet> pOldStyle(
2514 static_cast<const SwFormatAutoFormat&>(
2515 pAutoStyle->GetAttr()).GetStyleHandle());
2516 aCurSet.Put(*pOldStyle);
2518 // remove the old hint
2519 m_pSwpHints->Delete(pAutoStyle);
2520 DestroyAttr(pAutoStyle);
2522 m_pSwpHints->Insert(
2523 MakeTextAttr(GetDoc(), aCurSet,
2524 aCurRange->first.first, aCurRange->first.second));
2527 aCurRange = aRange.second;
2530 // hints were directly inserted, so need to fix the Ignore flags now
2531 m_pSwpHints->MergePortions(*this);
2533 // 3. Clear items from the node
2534 std::vector<sal_uInt16> aClearedIds;
2535 lcl_FillWhichIds(i_rAttrSet, aClearedIds);
2536 ClearItemsFromAttrSet(aClearedIds);
2539 void SwTextNode::FormatToTextAttr( SwTextNode* pNd )
2541 SfxItemSet aThisSet( GetDoc().GetAttrPool(), aCharFormatSetRange );
2542 if( HasSwAttrSet() && GetpSwAttrSet()->Count() )
2543 aThisSet.Put( *GetpSwAttrSet() );
2545 GetOrCreateSwpHints();
2547 if( pNd == this )
2549 impl_FormatToTextAttr(aThisSet);
2551 else
2553 // There are five possible combinations of items from this and
2554 // pNd (pNd is the 'main' node):
2556 // case pNd this action
2558 // 1 - - do nothing
2559 // 2 - a convert item to attr of this
2560 // 3 a - convert item to attr of pNd
2561 // 4 a a clear item in this
2562 // 5 a b convert item to attr of this
2564 SfxItemSet aNdSet( pNd->GetDoc().GetAttrPool(), aCharFormatSetRange );
2565 if( pNd->HasSwAttrSet() && pNd->GetpSwAttrSet()->Count() )
2566 aNdSet.Put( *pNd->GetpSwAttrSet() );
2568 pNd->GetOrCreateSwpHints();
2570 std::vector<sal_uInt16> aProcessedIds;
2572 if( aThisSet.Count() )
2574 SfxItemIter aIter( aThisSet );
2575 const SfxPoolItem* pItem = aIter.GetCurItem(), *pNdItem = nullptr;
2576 SfxItemSet aConvertSet( GetDoc().GetAttrPool(), aCharFormatSetRange );
2577 std::vector<sal_uInt16> aClearWhichIds;
2581 if( SfxItemState::SET == aNdSet.GetItemState( pItem->Which(), false, &pNdItem ) )
2583 if (*pItem == *pNdItem) // 4
2585 aClearWhichIds.push_back( pItem->Which() );
2587 else // 5
2589 aConvertSet.Put(*pItem);
2591 aProcessedIds.push_back(pItem->Which());
2593 else // 2
2595 aConvertSet.Put(*pItem);
2598 pItem = aIter.NextItem();
2599 } while (pItem);
2601 // 4/ clear items of this that are set with the same value on pNd
2602 ClearItemsFromAttrSet( aClearWhichIds );
2604 // 2, 5/ convert all other items to attrs
2605 impl_FormatToTextAttr(aConvertSet);
2609 std::for_each(aProcessedIds.begin(), aProcessedIds.end(),
2610 SfxItemSetClearer(aNdSet));
2612 // 3/ convert items to attrs
2613 pNd->impl_FormatToTextAttr(aNdSet);
2615 if( aNdSet.Count() )
2617 SwFormatChg aTmp1( pNd->GetFormatColl() );
2618 pNd->CallSwClientNotify(sw::LegacyModifyHint(&aTmp1, &aTmp1));
2623 SetCalcHiddenCharFlags();
2625 pNd->TryDeleteSwpHints();
2628 void SwpHints::CalcFlags()
2630 m_bDDEFields = m_bFootnote = false;
2631 const size_t nSize = Count();
2632 for( size_t nPos = 0; nPos < nSize; ++nPos )
2634 const SwTextAttr* pAttr = Get( nPos );
2635 switch( pAttr->Which() )
2637 case RES_TXTATR_FTN:
2638 m_bFootnote = true;
2639 if ( m_bDDEFields )
2640 return;
2641 break;
2642 case RES_TXTATR_FIELD:
2644 const SwField* pField = pAttr->GetFormatField().GetField();
2645 if( SwFieldIds::Dde == pField->GetTyp()->Which() )
2647 m_bDDEFields = true;
2648 if ( m_bFootnote )
2649 return;
2652 break;
2657 bool SwpHints::CalcHiddenParaField() const
2659 m_bCalcHiddenParaField = false;
2660 const bool bOldHiddenByParaField = m_bHiddenByParaField;
2661 bool bNewHiddenByParaField = false;
2662 int nNewResultWeight = 0;
2663 const size_t nSize = Count();
2664 const SwTextAttr* pTextHt;
2666 for (size_t nPos = 0; nPos < nSize; ++nPos)
2668 pTextHt = Get(nPos);
2669 const sal_uInt16 nWhich = pTextHt->Which();
2671 if (RES_TXTATR_FIELD == nWhich)
2673 // see also SwTextFrame::IsHiddenNow()
2674 const SwFormatField& rField = pTextHt->GetFormatField();
2675 int nCurWeight = m_rParent.GetDoc().FieldCanHideParaWeight(rField.GetField()->GetTyp()->Which());
2676 if (nCurWeight > nNewResultWeight)
2678 nNewResultWeight = nCurWeight;
2679 bNewHiddenByParaField = m_rParent.GetDoc().FieldHidesPara(*rField.GetField());
2681 else if (nCurWeight == nNewResultWeight && bNewHiddenByParaField)
2683 // Currently, for both supported hiding types (HiddenPara, Database), "Don't hide"
2684 // takes precedence - i.e., if there's a "Don't hide" field of that weight, we only
2685 // care about fields of higher weight.
2686 bNewHiddenByParaField = m_rParent.GetDoc().FieldHidesPara(*rField.GetField());
2690 SetHiddenByParaField(bNewHiddenByParaField);
2691 return bOldHiddenByParaField != bNewHiddenByParaField;
2694 void SwpHints::NoteInHistory( SwTextAttr *pAttr, const bool bNew )
2696 if ( m_pHistory ) { m_pHistory->AddHint( pAttr, bNew ); }
2699 namespace {
2700 struct Portion {
2701 SwTextAttr* pTextAttr;
2702 sal_Int32 nKey;
2703 bool isRsidOnlyAutoFormat;
2705 typedef std::vector< Portion > PortionMap;
2706 enum MergeResult { MATCH, DIFFER_ONLY_RSID, DIFFER };
2709 static MergeResult lcl_Compare_Attributes(
2710 int i, int j,
2711 const std::pair< PortionMap::const_iterator, PortionMap::const_iterator >& aRange1,
2712 const std::pair< PortionMap::const_iterator, PortionMap::const_iterator >& aRange2,
2713 std::vector<bool>& RsidOnlyAutoFormatFlagMap);
2715 bool SwpHints::MergePortions( SwTextNode& rNode )
2717 if ( !Count() )
2718 return false;
2720 // sort before merging
2721 Resort();
2723 bool bRet = false;
2724 PortionMap aPortionMap;
2725 aPortionMap.reserve(Count() + 1);
2726 std::vector<bool> RsidOnlyAutoFormatFlagMap;
2727 RsidOnlyAutoFormatFlagMap.resize(Count() + 1);
2728 sal_Int32 nLastPorStart = COMPLETE_STRING;
2729 sal_Int32 nKey = 0;
2731 // get portions by start position:
2732 for ( size_t i = 0; i < Count(); ++i )
2734 SwTextAttr *pHt = Get( i );
2735 if ( RES_TXTATR_CHARFMT != pHt->Which() &&
2736 RES_TXTATR_AUTOFMT != pHt->Which() )
2737 //&&
2738 //RES_TXTATR_INETFMT != pHt->Which() )
2739 continue;
2741 bool isRsidOnlyAutoFormat(false);
2742 // check for RSID-only AUTOFMT
2743 if (RES_TXTATR_AUTOFMT == pHt->Which())
2745 std::shared_ptr<SfxItemSet> const & pSet(
2746 pHt->GetAutoFormat().GetStyleHandle());
2747 if ((pSet->Count() == 1) && pSet->GetItem(RES_CHRATR_RSID, false))
2749 // fdo#70201: eliminate no-extent RSID-only AUTOFMT
2750 // could be produced by ReplaceText or (maybe?) RstAttr
2751 if (pHt->GetStart() == *pHt->GetEnd())
2753 DeleteAtPos(i); // kill it without History!
2754 SwTextAttr::Destroy(pHt, rNode.GetDoc().GetAttrPool());
2755 --i;
2756 continue;
2758 // fdo#52028: this one has _only_ RSID => ignore it completely
2759 if (!pHt->IsFormatIgnoreStart() || !pHt->IsFormatIgnoreEnd())
2761 NoteInHistory(pHt);
2762 pHt->SetFormatIgnoreStart(true);
2763 pHt->SetFormatIgnoreEnd (true);
2764 NoteInHistory(pHt, true);
2766 isRsidOnlyAutoFormat = true;
2770 if (pHt->GetStart() == *pHt->GetEnd())
2772 // no-length hints are a disease. ignore them here.
2773 // the SwAttrIter::SeekFwd will not call Rst/Chg for them.
2774 continue;
2777 const sal_Int32 nPorStart = pHt->GetStart();
2778 if (nPorStart != nLastPorStart)
2779 ++nKey;
2780 nLastPorStart = nPorStart;
2781 aPortionMap.push_back(Portion {pHt, nKey, isRsidOnlyAutoFormat});
2782 RsidOnlyAutoFormatFlagMap[nKey] = isRsidOnlyAutoFormat;
2785 // we add data strictly in-order, so we can forward-search the vector
2786 auto equal_range = [](PortionMap::const_iterator startIt, PortionMap::const_iterator endIt, int i)
2788 auto it1 = startIt;
2789 while (it1 != endIt && it1->nKey < i)
2790 ++it1;
2791 auto it2 = it1;
2792 while (it2 != endIt && it2->nKey == i)
2793 ++it2;
2794 return std::pair<PortionMap::const_iterator, PortionMap::const_iterator>{ it1, it2 };
2797 // check if portion i can be merged with portion i+1:
2798 // note: need to include i=0 to set IgnoreStart and j=nKey+1 to reset
2799 // IgnoreEnd at first / last portion
2800 int i = 0;
2801 int j = i + 1;
2802 // Store this outside the loop, because we limit the search area on subsequent searches.
2803 std::pair< PortionMap::const_iterator, PortionMap::const_iterator > aRange1 { aPortionMap.begin(), aPortionMap.begin() + aPortionMap.size() };
2804 while ( i <= nKey )
2806 aRange1 = equal_range( aRange1.first, aPortionMap.begin() + aPortionMap.size(), i );
2807 // start the search for this one from where the first search ended.
2808 std::pair< PortionMap::const_iterator, PortionMap::const_iterator > aRange2
2809 = equal_range( aRange1.second, aPortionMap.begin() + aPortionMap.size(), j );
2811 MergeResult eMerge = lcl_Compare_Attributes(i, j, aRange1, aRange2, RsidOnlyAutoFormatFlagMap);
2813 if (MATCH == eMerge)
2815 // important: delete second range so any IgnoreStart on the first
2816 // range is still valid
2817 // erase all elements with key i + 1
2818 sal_Int32 nNewPortionEnd = 0;
2819 for ( auto aIter2 = aRange2.first; aIter2 != aRange2.second; ++aIter2 )
2821 SwTextAttr *const p2 = aIter2->pTextAttr;
2822 nNewPortionEnd = *p2->GetEnd();
2824 const size_t nCountBeforeDelete = Count();
2825 Delete( p2 );
2827 // robust: check if deletion actually took place before destroying attribute:
2828 if ( Count() < nCountBeforeDelete )
2829 rNode.DestroyAttr( p2 );
2831 aPortionMap.erase( aRange2.first, aRange2.second );
2832 ++j;
2834 // change all attributes with key i
2835 aRange1 = equal_range( aRange1.first, aPortionMap.begin() + aPortionMap.size(), i );
2836 for ( auto aIter1 = aRange1.first; aIter1 != aRange1.second; ++aIter1 )
2838 SwTextAttr *const p1 = aIter1->pTextAttr;
2839 NoteInHistory( p1 );
2840 p1->SetEnd(nNewPortionEnd);
2841 NoteInHistory( p1, true );
2842 bRet = true;
2845 if (bRet)
2847 Resort();
2850 else
2852 // when not merging the ignore flags need to be either set or reset
2853 // (reset too in case one of the autofmts was recently changed)
2854 bool const bSetIgnoreFlag(DIFFER_ONLY_RSID == eMerge);
2855 for (auto aIter1 = aRange1.first; aIter1 != aRange1.second; ++aIter1)
2857 if (!aIter1->isRsidOnlyAutoFormat) // already set above, don't change
2859 SwTextAttr *const pCurrent(aIter1->pTextAttr);
2860 if (pCurrent->IsFormatIgnoreEnd() != bSetIgnoreFlag)
2862 NoteInHistory(pCurrent);
2863 pCurrent->SetFormatIgnoreEnd(bSetIgnoreFlag);
2864 NoteInHistory(pCurrent, true);
2868 for (auto aIter2 = aRange2.first; aIter2 != aRange2.second; ++aIter2)
2870 if (!aIter2->isRsidOnlyAutoFormat) // already set above, don't change
2872 SwTextAttr *const pCurrent(aIter2->pTextAttr);
2873 if (pCurrent->IsFormatIgnoreStart() != bSetIgnoreFlag)
2875 NoteInHistory(pCurrent);
2876 pCurrent->SetFormatIgnoreStart(bSetIgnoreFlag);
2877 NoteInHistory(pCurrent, true);
2881 i = j; // ++i not enough: i + 1 may have been deleted (MATCH)!
2882 ++j;
2886 return bRet;
2890 static MergeResult lcl_Compare_Attributes(
2891 int i, int j,
2892 const std::pair< PortionMap::const_iterator, PortionMap::const_iterator >& aRange1,
2893 const std::pair< PortionMap::const_iterator, PortionMap::const_iterator >& aRange2,
2894 std::vector<bool>& RsidOnlyAutoFormatFlagMap)
2896 PortionMap::const_iterator aIter1 = aRange1.first;
2897 PortionMap::const_iterator aIter2 = aRange2.first;
2899 size_t const nAttributesInPor1 = std::distance(aRange1.first, aRange1.second);
2900 size_t const nAttributesInPor2 = std::distance(aRange2.first, aRange2.second);
2901 bool const isRsidOnlyAutoFormat1 = i < sal_Int32(RsidOnlyAutoFormatFlagMap.size()) && RsidOnlyAutoFormatFlagMap[i];
2902 bool const isRsidOnlyAutoFormat2 = j < sal_Int32(RsidOnlyAutoFormatFlagMap.size()) && RsidOnlyAutoFormatFlagMap[j];
2904 // if both have one they could be equal, but not if only one has it
2905 bool const bSkipRsidOnlyAutoFormat(nAttributesInPor1 != nAttributesInPor2);
2907 // this loop needs to handle the case where one has a CHARFMT and the
2908 // other CHARFMT + RSID-only AUTOFMT, so...
2909 // want to skip over RSID-only AUTOFMT here, hence the -1
2910 if (!((nAttributesInPor1 - (isRsidOnlyAutoFormat1 ? 1 : 0)) ==
2911 (nAttributesInPor2 - (isRsidOnlyAutoFormat2 ? 1 : 0))
2912 && (nAttributesInPor1 != 0 || nAttributesInPor2 != 0)))
2914 return DIFFER;
2917 MergeResult eMerge(MATCH);
2919 // _if_ there is one element more either in aRange1 or aRange2
2920 // it _must_ be an RSID-only AUTOFMT, which can be ignored here...
2921 // But if both have RSID-only AUTOFMT they could be equal, no skip!
2922 while (aIter1 != aRange1.second || aIter2 != aRange2.second)
2924 // first of all test if there's no gap (before skipping stuff!)
2925 if (aIter1 != aRange1.second && aIter2 != aRange2.second &&
2926 *aIter1->pTextAttr->End() < aIter2->pTextAttr->GetStart())
2928 return DIFFER;
2930 // skip it - cannot be equal if bSkipRsidOnlyAutoFormat is set
2931 if (bSkipRsidOnlyAutoFormat
2932 && aIter1 != aRange1.second && aIter1->isRsidOnlyAutoFormat)
2934 assert(DIFFER != eMerge);
2935 eMerge = DIFFER_ONLY_RSID;
2936 ++aIter1;
2937 continue;
2939 if (bSkipRsidOnlyAutoFormat
2940 && aIter2 != aRange2.second && aIter2->isRsidOnlyAutoFormat)
2942 assert(DIFFER != eMerge);
2943 eMerge = DIFFER_ONLY_RSID;
2944 ++aIter2;
2945 continue;
2947 assert(aIter1 != aRange1.second && aIter2 != aRange2.second);
2948 SwTextAttr const*const p1 = aIter1->pTextAttr;
2949 SwTextAttr const*const p2 = aIter2->pTextAttr;
2950 if (p1->Which() != p2->Which())
2952 return DIFFER;
2954 if (!(*p1 == *p2))
2956 // fdo#52028: for auto styles, check if they differ only
2957 // in the RSID, which should have no effect on text layout
2958 if (RES_TXTATR_AUTOFMT != p1->Which())
2959 return DIFFER;
2961 const SfxItemSet& rSet1 = *p1->GetAutoFormat().GetStyleHandle();
2962 const SfxItemSet& rSet2 = *p2->GetAutoFormat().GetStyleHandle();
2964 // sadly SfxItemSet::operator== does not seem to work?
2965 SfxItemIter iter1(rSet1);
2966 SfxItemIter iter2(rSet2);
2967 for (SfxPoolItem const* pItem1 = iter1.GetCurItem(),
2968 * pItem2 = iter2.GetCurItem();;)
2970 if (pItem1 && pItem1->Which() == RES_CHRATR_RSID)
2971 pItem1 = iter1.NextItem();
2972 if (pItem2 && pItem2->Which() == RES_CHRATR_RSID)
2973 pItem2 = iter2.NextItem();
2974 if (!pItem1 && !pItem2)
2976 eMerge = DIFFER_ONLY_RSID;
2977 break;
2979 if (!pItem1 || !pItem2)
2981 return DIFFER;
2983 if (pItem1 != pItem2) // all are poolable
2985 assert(IsInvalidItem(pItem1) || IsInvalidItem(pItem2) || pItem1->Which() != pItem2->Which() || *pItem1 != *pItem2);
2986 return DIFFER;
2988 pItem1 = iter1.NextItem();
2989 pItem2 = iter2.NextItem();
2992 ++aIter1;
2993 ++aIter2;
2995 return eMerge;
2999 // check if there is already a character format and adjust the sort numbers
3000 static void lcl_CheckSortNumber( const SwpHints& rHints, SwTextCharFormat& rNewCharFormat )
3002 const sal_Int32 nHtStart = rNewCharFormat.GetStart();
3003 const sal_Int32 nHtEnd = *rNewCharFormat.GetEnd();
3004 sal_uInt16 nSortNumber = 0;
3006 for ( size_t i = 0; i < rHints.Count(); ++i )
3008 const SwTextAttr* pOtherHt = rHints.Get(i);
3010 const sal_Int32 nOtherStart = pOtherHt->GetStart();
3012 if ( nOtherStart > nHtStart )
3013 break;
3015 if ( RES_TXTATR_CHARFMT == pOtherHt->Which() )
3017 const sal_Int32 nOtherEnd = *pOtherHt->End();
3019 if ( nOtherStart == nHtStart && nOtherEnd == nHtEnd )
3021 nSortNumber = static_txtattr_cast<const SwTextCharFormat*>(pOtherHt)->GetSortNumber() + 1;
3026 if ( nSortNumber > 0 )
3027 rNewCharFormat.SetSortNumber( nSortNumber );
3031 * Try to insert the new hint.
3032 * Depending on the type of the hint, this either always succeeds, or may fail.
3033 * Depending on the type of the hint, other hints may be deleted or
3034 * overwritten.
3035 * The return value indicates successful insertion.
3037 bool SwpHints::TryInsertHint(
3038 SwTextAttr* const pHint,
3039 SwTextNode &rNode,
3040 const SetAttrMode nMode )
3042 if ( MAX_HINTS <= Count() ) // we're sorry, this flight is overbooked...
3044 OSL_FAIL("hints array full :-(");
3045 rNode.DestroyAttr(pHint);
3046 return false;
3049 const sal_Int32 *pHtEnd = pHint->GetEnd();
3050 const sal_uInt16 nWhich = pHint->Which();
3051 std::vector<sal_uInt16> aWhichSublist;
3053 switch( nWhich )
3055 case RES_TXTATR_CHARFMT:
3057 // Check if character format contains hidden attribute:
3058 const SwCharFormat* pFormat = pHint->GetCharFormat().GetCharFormat();
3059 if ( SfxItemState::SET == pFormat->GetItemState( RES_CHRATR_HIDDEN ) )
3060 rNode.SetCalcHiddenCharFlags();
3062 static_txtattr_cast<SwTextCharFormat*>(pHint)->ChgTextNode( &rNode );
3063 break;
3065 // #i75430# Recalc hidden flags if necessary
3066 case RES_TXTATR_AUTOFMT:
3068 std::shared_ptr<SfxItemSet> const & pSet( pHint->GetAutoFormat().GetStyleHandle() );
3069 if (pHint->GetStart() == *pHint->GetEnd())
3071 if (pSet->Count() == 1 && pSet->GetItem(RES_CHRATR_RSID, false))
3072 { // empty range RSID-only hints could cause trouble, there's no
3073 rNode.DestroyAttr(pHint); // need for them so don't insert
3074 return false;
3077 // Check if auto style contains hidden attribute:
3078 const SfxPoolItem* pHiddenItem = CharFormat::GetItem( *pHint, RES_CHRATR_HIDDEN );
3079 if ( pHiddenItem )
3080 rNode.SetCalcHiddenCharFlags();
3082 // fdo#71556: populate aWhichFormatAttr member of SwMsgPoolItem
3083 const WhichRangesContainer& pRanges = pSet->GetRanges();
3084 for(auto const & rPair : pRanges)
3086 const sal_uInt16 nBeg = rPair.first;
3087 const sal_uInt16 nEnd = rPair.second;
3088 for( sal_uInt16 nSubElem = nBeg; nSubElem <= nEnd; ++nSubElem )
3089 if( pSet->HasItem( nSubElem ) )
3090 aWhichSublist.push_back( nSubElem );
3092 break;
3094 case RES_TXTATR_INETFMT:
3095 static_txtattr_cast<SwTextINetFormat*>(pHint)->InitINetFormat(rNode);
3096 break;
3098 case RES_TXTATR_FIELD:
3099 case RES_TXTATR_ANNOTATION:
3100 case RES_TXTATR_INPUTFIELD:
3102 SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pHint));
3103 bool bDelFirst = nullptr != pTextField->GetpTextNode();
3104 pTextField->ChgTextNode( &rNode );
3105 SwDoc& rDoc = rNode.GetDoc();
3106 const SwField* pField = pTextField->GetFormatField().GetField();
3108 if( !rDoc.getIDocumentFieldsAccess().IsNewFieldLst() )
3110 // certain fields must update the SwDoc's calculation flags
3111 switch( pField->GetTyp()->Which() )
3113 case SwFieldIds::Database:
3114 case SwFieldIds::SetExp:
3115 case SwFieldIds::HiddenPara:
3116 case SwFieldIds::HiddenText:
3117 case SwFieldIds::DbNumSet:
3118 case SwFieldIds::DbNextSet:
3120 if( bDelFirst )
3121 rDoc.getIDocumentFieldsAccess().InsDelFieldInFieldLst(false, *pTextField);
3122 if( rNode.GetNodes().IsDocNodes() )
3123 rDoc.getIDocumentFieldsAccess().InsDelFieldInFieldLst(true, *pTextField);
3125 break;
3126 case SwFieldIds::Dde:
3127 if( rNode.GetNodes().IsDocNodes() )
3128 static_cast<SwDDEFieldType*>(pField->GetTyp())->IncRefCnt();
3129 break;
3130 default: break;
3134 // insert into real document's nodes-array?
3135 if( rNode.GetNodes().IsDocNodes() )
3137 bool bInsFieldType = false;
3138 switch( pField->GetTyp()->Which() )
3140 case SwFieldIds::SetExp:
3141 bInsFieldType = static_cast<SwSetExpFieldType*>(pField->GetTyp())->IsDeleted();
3142 if( nsSwGetSetExpType::GSE_SEQ & static_cast<SwSetExpFieldType*>(pField->GetTyp())->GetType() )
3144 // register the field at its FieldType before setting
3145 // the sequence reference number!
3146 SwSetExpFieldType* pFieldType = static_cast<SwSetExpFieldType*>(
3147 rDoc.getIDocumentFieldsAccess().InsertFieldType( *pField->GetTyp() ) );
3148 if( pFieldType != pField->GetTyp() )
3150 SwFormatField* pFormatField = const_cast<SwFormatField*>(&pTextField->GetFormatField());
3151 pFormatField->RegisterToFieldType( *pFieldType );
3152 pFormatField->GetField()->ChgTyp( pFieldType );
3154 pFieldType->SetSeqRefNo( *const_cast<SwSetExpField*>(static_cast<const SwSetExpField*>(pField)) );
3156 break;
3157 case SwFieldIds::User:
3158 bInsFieldType = static_cast<SwUserFieldType*>(pField->GetTyp())->IsDeleted();
3159 break;
3161 case SwFieldIds::Dde:
3162 if( rDoc.getIDocumentFieldsAccess().IsNewFieldLst() )
3163 static_cast<SwDDEFieldType*>(pField->GetTyp())->IncRefCnt();
3164 bInsFieldType = static_cast<SwDDEFieldType*>(pField->GetTyp())->IsDeleted();
3165 break;
3167 case SwFieldIds::Postit:
3168 if ( rDoc.GetDocShell() )
3170 rDoc.GetDocShell()->Broadcast( SwFormatFieldHint(
3171 &pTextField->GetFormatField(), SwFormatFieldHintWhich::INSERTED));
3173 break;
3174 default: break;
3176 if( bInsFieldType )
3177 rDoc.getIDocumentFieldsAccess().InsDeletedFieldType( *pField->GetTyp() );
3180 break;
3181 case RES_TXTATR_FTN :
3182 static_cast<SwTextFootnote*>(pHint)->ChgTextNode( &rNode );
3183 break;
3184 case RES_TXTATR_REFMARK:
3185 static_txtattr_cast<SwTextRefMark*>(pHint)->ChgTextNode( &rNode );
3186 if( rNode.GetNodes().IsDocNodes() )
3188 // search for a reference with the same name
3189 SwTextAttr* pTmpHt;
3190 for( size_t n = 0, nEnd = Count(); n < nEnd; ++n )
3192 const sal_Int32 *pTmpHtEnd;
3193 const sal_Int32 *pTmpHintEnd;
3194 if (RES_TXTATR_REFMARK == (pTmpHt = Get(n))->Which() &&
3195 pHint->GetAttr() == pTmpHt->GetAttr() &&
3196 nullptr != ( pTmpHtEnd = pTmpHt->GetEnd() ) &&
3197 nullptr != ( pTmpHintEnd = pHint->GetEnd() ) )
3199 SwComparePosition eCmp = ::ComparePosition(
3200 pTmpHt->GetStart(), *pTmpHtEnd,
3201 pHint->GetStart(), *pTmpHintEnd );
3202 bool bDelOld = true, bChgStart = false, bChgEnd = false;
3203 switch( eCmp )
3205 case SwComparePosition::Before:
3206 case SwComparePosition::Behind: bDelOld = false; break;
3208 case SwComparePosition::Outside: bChgStart = bChgEnd = true; break;
3210 case SwComparePosition::CollideEnd:
3211 case SwComparePosition::OverlapBefore: bChgStart = true; break;
3212 case SwComparePosition::CollideStart:
3213 case SwComparePosition::OverlapBehind: bChgEnd = true; break;
3214 default: break;
3217 if( bChgStart )
3219 pHint->SetStart( pTmpHt->GetStart() );
3221 if( bChgEnd )
3222 pHint->SetEnd(*pTmpHtEnd);
3224 if( bDelOld )
3226 NoteInHistory( pTmpHt );
3227 rNode.DestroyAttr( Cut( n-- ) );
3228 --nEnd;
3233 break;
3234 case RES_TXTATR_TOXMARK:
3235 static_txtattr_cast<SwTextTOXMark*>(pHint)->ChgTextNode( &rNode );
3236 break;
3238 case RES_TXTATR_CJK_RUBY:
3239 static_txtattr_cast<SwTextRuby*>(pHint)->InitRuby(rNode);
3240 break;
3242 case RES_TXTATR_META:
3243 case RES_TXTATR_METAFIELD:
3244 static_txtattr_cast<SwTextMeta *>(pHint)->ChgTextNode( &rNode );
3245 break;
3247 case RES_TXTATR_CONTENTCONTROL:
3248 static_txtattr_cast<SwTextContentControl*>(pHint)->ChgTextNode( &rNode );
3249 break;
3251 case RES_CHRATR_HIDDEN:
3252 rNode.SetCalcHiddenCharFlags();
3253 break;
3256 if( SetAttrMode::DONTEXPAND & nMode )
3257 pHint->SetDontExpand( true );
3259 // special handling for SwTextAttrs without end:
3260 // 1) they cannot overlap
3261 // 2) if two fields are adjacent, they must not be merged into one
3262 // this is guaranteed by inserting a CH_TXTATR_* into the paragraph text!
3263 sal_Int32 nHtStart = pHint->GetStart();
3264 if( !pHtEnd )
3266 Insert( pHint );
3267 NoteInHistory(pHint, true);
3268 CalcFlags();
3269 #ifdef DBG_UTIL
3270 if( !rNode.GetDoc().IsInReading() )
3271 CHECK;
3272 #endif
3273 // ... and notify listeners
3274 if(rNode.HasWriterListeners())
3276 SwUpdateAttr aHint(
3277 nHtStart,
3278 nHtStart,
3279 nWhich);
3281 rNode.TriggerNodeUpdate(sw::LegacyModifyHint(&aHint, &aHint));
3284 return true;
3287 // from here on, pHint is known to have an end index!
3289 if( *pHtEnd < nHtStart )
3291 assert(*pHtEnd >= nHtStart);
3293 // just swap the nonsense:
3294 pHint->SetStart(*pHtEnd);
3295 pHint->SetEnd(nHtStart);
3296 nHtStart = pHint->GetStart();
3299 // I need this value later on for notification but the pointer may become invalid
3300 const sal_Int32 nHintEnd = *pHtEnd;
3301 const bool bNoHintAdjustMode = bool(SetAttrMode::NOHINTADJUST & nMode);
3303 // handle nesting attributes: inserting may fail due to overlap!
3304 if (pHint->IsNesting())
3306 const bool bRet(
3307 TryInsertNesting(rNode, *static_txtattr_cast<SwTextAttrNesting*>(pHint)));
3308 if (!bRet) return false;
3310 // Currently REFMARK and TOXMARK have OverlapAllowed set to true.
3311 // These attributes may be inserted directly.
3312 // Also attributes without length may be inserted directly.
3313 // SETATTR_NOHINTADJUST is set e.g., during undo.
3314 // Portion building in not necessary during XML import.
3315 else if ( !bNoHintAdjustMode &&
3316 !pHint->IsOverlapAllowedAttr() &&
3317 !rNode.GetDoc().IsInXMLImport() &&
3318 ( RES_TXTATR_AUTOFMT == nWhich ||
3319 RES_TXTATR_CHARFMT == nWhich ) )
3321 assert( nWhich != RES_TXTATR_AUTOFMT ||
3322 static_cast<const SwFormatAutoFormat&>(pHint->GetAttr()).GetStyleHandle()->GetPool() ==
3323 &rNode.GetDoc().GetAttrPool());
3325 BuildPortions( rNode, *pHint, nMode );
3327 if ( nHtStart < nHintEnd ) // skip merging for 0-length attributes
3328 MergePortions( rNode );
3330 else
3332 // There may be more than one character style at the current position.
3333 // Take care of the sort number.
3334 // FME 2007-11-08 #i82989# in NOHINTADJUST mode, we want to insert
3335 // character attributes directly
3336 if (!bNoHintAdjustMode
3337 && ( (RES_TXTATR_CHARFMT == nWhich)
3338 // tdf#149978 also for import of automatic styles, could be produced by non-LO application
3339 || (RES_TXTATR_AUTOFMT == nWhich && rNode.GetDoc().IsInXMLImport())))
3341 BuildPortions( rNode, *pHint, nMode );
3343 else
3345 // #i82989# Check sort numbers in NoHintAdjustMode
3346 if ( RES_TXTATR_CHARFMT == nWhich )
3347 lcl_CheckSortNumber(*this, *static_txtattr_cast<SwTextCharFormat*>(pHint));
3349 Insert( pHint );
3350 NoteInHistory( pHint, true );
3354 // ... and notify listeners
3355 if ( rNode.HasWriterListeners() )
3357 const SwUpdateAttr aHint(nHtStart, nHintEnd, nWhich, std::move(aWhichSublist));
3358 rNode.TriggerNodeUpdate(sw::LegacyModifyHint(&aHint, &aHint));
3361 #ifdef DBG_UTIL
3362 if( !bNoHintAdjustMode && !rNode.GetDoc().IsInReading() )
3363 CHECK;
3364 #endif
3366 return true;
3369 void SwpHints::DeleteAtPos( const size_t nPos )
3371 assert(!m_bStartMapNeedsSorting && "deleting at pos and the list needs sorting?");
3373 SwTextAttr *pHint = Get(nPos);
3374 assert( pHint->m_pHints == this );
3375 // ChainDelete( pHint );
3376 NoteInHistory( pHint );
3378 // optimization: nPos is the position in the Starts array
3379 SwTextAttr *pHt = m_HintsByStart[ nPos ];
3380 m_HintsByStart.erase( m_HintsByStart.begin() + nPos );
3382 if (m_bStartMapNeedsSorting)
3383 ResortStartMap();
3384 if (m_bEndMapNeedsSorting)
3385 ResortEndMap();
3386 if (m_bWhichMapNeedsSorting)
3387 ResortWhichMap();
3389 auto findIt = std::lower_bound(m_HintsByEnd.begin(), m_HintsByEnd.end(), pHt, CompareSwpHtEnd());
3390 assert(*findIt == pHt);
3391 m_HintsByEnd.erase(findIt);
3393 auto findIt2 = std::lower_bound(m_HintsByWhichAndStart.begin(), m_HintsByWhichAndStart.end(), pHt, CompareSwpHtWhichStart());
3394 assert(*findIt2 == pHt);
3395 m_HintsByWhichAndStart.erase(findIt2);
3397 pHt->m_pHints = nullptr;
3399 if( pHint->Which() == RES_TXTATR_FIELD )
3401 SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pHint));
3402 const SwFieldType* pFieldTyp = pTextField->GetFormatField().GetField()->GetTyp();
3403 if( SwFieldIds::Dde == pFieldTyp->Which() )
3405 const SwTextNode* pNd = pTextField->GetpTextNode();
3406 if( pNd && pNd->GetNodes().IsDocNodes() )
3407 const_cast<SwDDEFieldType*>(static_cast<const SwDDEFieldType*>(pFieldTyp))->DecRefCnt();
3408 pTextField->ChgTextNode(nullptr);
3410 else if (m_bHiddenByParaField
3411 && m_rParent.GetDoc().FieldCanHideParaWeight(pFieldTyp->Which()))
3413 m_bCalcHiddenParaField = true;
3416 else if ( pHint->Which() == RES_TXTATR_ANNOTATION )
3418 SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pHint));
3419 const_cast<SwFormatField&>(pTextField->GetFormatField()).Broadcast(
3420 SwFormatFieldHint(&pTextField->GetFormatField(), SwFormatFieldHintWhich::REMOVED));
3423 CalcFlags();
3424 CHECK_NOTMERGED; // called from BuildPortions
3427 /// delete the hint
3428 /// precondition: pTextHt must be in this array
3429 void SwpHints::Delete( SwTextAttr const * pTextHt )
3431 const size_t nPos = GetIndexOf( pTextHt );
3432 assert(SAL_MAX_SIZE != nPos);
3433 if( SAL_MAX_SIZE != nPos )
3434 DeleteAtPos( nPos );
3437 void SwTextNode::ClearSwpHintsArr( bool bDelFields )
3439 if ( !HasHints() )
3440 return;
3442 size_t nPos = 0;
3443 while ( nPos < m_pSwpHints->Count() )
3445 SwTextAttr* pDel = m_pSwpHints->Get( nPos );
3446 bool bDel = false;
3448 switch( pDel->Which() )
3450 case RES_TXTATR_FLYCNT:
3451 case RES_TXTATR_FTN:
3452 break;
3454 case RES_TXTATR_FIELD:
3455 case RES_TXTATR_ANNOTATION:
3456 case RES_TXTATR_INPUTFIELD:
3457 if( bDelFields )
3458 bDel = true;
3459 break;
3460 default:
3461 bDel = true; break;
3464 if( bDel )
3466 m_pSwpHints->DeleteAtPos( nPos );
3467 DestroyAttr( pDel );
3469 else
3470 ++nPos;
3474 LanguageType SwTextNode::GetLang( const sal_Int32 nBegin, const sal_Int32 nLen,
3475 sal_uInt16 nScript ) const
3477 LanguageType nRet = LANGUAGE_DONTKNOW;
3479 if ( ! nScript )
3481 nScript = g_pBreakIt->GetRealScriptOfText( m_Text, nBegin );
3484 // #i91465# Consider nScript if pSwpHints == 0
3485 const sal_uInt16 nWhichId = GetWhichOfScript( RES_CHRATR_LANGUAGE, nScript );
3487 if ( HasHints() )
3489 const sal_Int32 nEnd = nBegin + nLen;
3490 const size_t nSize = m_pSwpHints->Count();
3491 for ( size_t i = 0; i < nSize; ++i )
3493 const SwTextAttr *pHt = m_pSwpHints->Get(i);
3494 const sal_Int32 nAttrStart = pHt->GetStart();
3495 if( nEnd < nAttrStart )
3496 break;
3498 const sal_uInt16 nWhich = pHt->Which();
3500 if( nWhichId == nWhich ||
3501 ( ( pHt->IsCharFormatAttr() || RES_TXTATR_AUTOFMT == nWhich ) && CharFormat::IsItemIncluded( nWhichId, pHt ) ) )
3503 const sal_Int32 *pEndIdx = pHt->End();
3504 // do the attribute and the range overlap?
3505 if( !pEndIdx )
3506 continue;
3507 if( nLen )
3509 if( nAttrStart >= nEnd || nBegin >= *pEndIdx )
3510 continue;
3512 else if( nBegin != nAttrStart || ( nAttrStart != *pEndIdx && nBegin ))
3514 if( nAttrStart >= nBegin )
3515 continue;
3516 if( pHt->DontExpand() ? nBegin >= *pEndIdx : nBegin > *pEndIdx)
3517 continue;
3519 const SfxPoolItem* pItem = CharFormat::GetItem( *pHt, nWhichId );
3520 const LanguageType nLng = static_cast<const SvxLanguageItem*>(pItem)->GetLanguage();
3522 // does the attribute completely cover the range?
3523 if( nAttrStart <= nBegin && nEnd <= *pEndIdx )
3524 nRet = nLng;
3525 else if( LANGUAGE_DONTKNOW == nRet )
3526 nRet = nLng; // partial overlap, the first one wins
3530 if( LANGUAGE_DONTKNOW == nRet )
3532 nRet = static_cast<const SvxLanguageItem&>(GetSwAttrSet().Get( nWhichId )).GetLanguage();
3533 if( LANGUAGE_DONTKNOW == nRet )
3534 nRet = GetAppLanguage();
3536 return nRet;
3539 sal_Unicode GetCharOfTextAttr( const SwTextAttr& rAttr )
3541 sal_Unicode cRet = CH_TXTATR_BREAKWORD;
3542 switch ( rAttr.Which() )
3544 case RES_TXTATR_REFMARK:
3545 case RES_TXTATR_TOXMARK:
3546 case RES_TXTATR_ANNOTATION:
3547 cRet = CH_TXTATR_INWORD;
3548 break;
3550 case RES_TXTATR_FIELD:
3551 case RES_TXTATR_FLYCNT:
3552 case RES_TXTATR_FTN:
3553 case RES_TXTATR_META:
3554 case RES_TXTATR_METAFIELD:
3555 case RES_TXTATR_CONTENTCONTROL:
3557 cRet = CH_TXTATR_BREAKWORD;
3559 break;
3560 case RES_TXTATR_LINEBREAK:
3562 cRet = CH_TXTATR_NEWLINE;
3564 break;
3566 default:
3567 assert(!"GetCharOfTextAttr: unknown attr");
3568 break;
3570 return cRet;
3573 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */