xref: /core/editeng/source/editeng/editdoc.cxx (revision 39214c54)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <editeng/tstpitem.hxx>
21 #include <editeng/colritem.hxx>
22 #include <editeng/fontitem.hxx>
23 #include <editeng/crossedoutitem.hxx>
24 #include <editeng/fhgtitem.hxx>
25 #include <editeng/flditem.hxx>
26 #include <editeng/postitem.hxx>
27 #include <editeng/kernitem.hxx>
28 #include <editeng/wrlmitem.hxx>
29 #include <editeng/wghtitem.hxx>
30 #include <editeng/udlnitem.hxx>
31 #include <editeng/cmapitem.hxx>
32 #include <editeng/contouritem.hxx>
33 #include <editeng/escapementitem.hxx>
34 #include <editeng/shdditem.hxx>
35 #include <editeng/autokernitem.hxx>
36 #include <editeng/langitem.hxx>
37 #include <editeng/emphasismarkitem.hxx>
38 #include <editeng/charscaleitem.hxx>
39 #include <editeng/charreliefitem.hxx>
40 #include <editeng/xmlcnitm.hxx>
41 #include <editeng/editids.hrc>
42 #include <editeng/editdata.hxx>
43 #include <editeng/lrspitem.hxx>
44 #include <editeng/ulspitem.hxx>
45 #include <editeng/lspcitem.hxx>
46 
47 #include <editdoc.hxx>
48 #include "editdbg.hxx"
49 #include <editeng/eerdll.hxx>
50 #include <eerdll2.hxx>
51 #include "impedit.hxx"
52 
53 #include <rtl/ustrbuf.hxx>
54 #include <sal/log.hxx>
55 #include <osl/diagnose.h>
56 
57 #include <svl/grabbagitem.hxx>
58 #include <tools/stream.hxx>
59 #include <tools/debug.hxx>
60 #include <com/sun/star/i18n/ScriptType.hpp>
61 #include <libxml/xmlwriter.h>
62 
63 #include <algorithm>
64 #include <cassert>
65 #include <limits>
66 #include <memory>
67 #include <set>
68 #include <string_view>
69 
70 using namespace ::com::sun::star;
71 
72 
73 sal_uInt16 GetScriptItemId( sal_uInt16 nItemId, SvtScriptType nScriptType )
74 {
75     sal_uInt16 nId = nItemId;
76 
77     if ( ( nScriptType == SvtScriptType::ASIAN ) ||
78          ( nScriptType == SvtScriptType::COMPLEX ) )
79     {
80         switch ( nItemId )
81         {
82             case EE_CHAR_LANGUAGE:
83                 nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_LANGUAGE_CJK : EE_CHAR_LANGUAGE_CTL;
84             break;
85             case EE_CHAR_FONTINFO:
86                 nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_FONTINFO_CJK : EE_CHAR_FONTINFO_CTL;
87             break;
88             case EE_CHAR_FONTHEIGHT:
89                 nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_FONTHEIGHT_CJK : EE_CHAR_FONTHEIGHT_CTL;
90             break;
91             case EE_CHAR_WEIGHT:
92                 nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_WEIGHT_CJK : EE_CHAR_WEIGHT_CTL;
93             break;
94             case EE_CHAR_ITALIC:
95                 nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_ITALIC_CJK : EE_CHAR_ITALIC_CTL;
96             break;
97         }
98     }
99 
100     return nId;
101 }
102 
103 bool IsScriptItemValid( sal_uInt16 nItemId, short nScriptType )
104 {
105     bool bValid = true;
106 
107     switch ( nItemId )
108     {
109         case EE_CHAR_LANGUAGE:
110             bValid = nScriptType == i18n::ScriptType::LATIN;
111         break;
112         case EE_CHAR_LANGUAGE_CJK:
113             bValid = nScriptType == i18n::ScriptType::ASIAN;
114         break;
115         case EE_CHAR_LANGUAGE_CTL:
116             bValid = nScriptType == i18n::ScriptType::COMPLEX;
117         break;
118         case EE_CHAR_FONTINFO:
119             bValid = nScriptType == i18n::ScriptType::LATIN;
120         break;
121         case EE_CHAR_FONTINFO_CJK:
122             bValid = nScriptType == i18n::ScriptType::ASIAN;
123         break;
124         case EE_CHAR_FONTINFO_CTL:
125             bValid = nScriptType == i18n::ScriptType::COMPLEX;
126         break;
127         case EE_CHAR_FONTHEIGHT:
128             bValid = nScriptType == i18n::ScriptType::LATIN;
129         break;
130         case EE_CHAR_FONTHEIGHT_CJK:
131             bValid = nScriptType == i18n::ScriptType::ASIAN;
132         break;
133         case EE_CHAR_FONTHEIGHT_CTL:
134             bValid = nScriptType == i18n::ScriptType::COMPLEX;
135         break;
136         case EE_CHAR_WEIGHT:
137             bValid = nScriptType == i18n::ScriptType::LATIN;
138         break;
139         case EE_CHAR_WEIGHT_CJK:
140             bValid = nScriptType == i18n::ScriptType::ASIAN;
141         break;
142         case EE_CHAR_WEIGHT_CTL:
143             bValid = nScriptType == i18n::ScriptType::COMPLEX;
144         break;
145         case EE_CHAR_ITALIC:
146             bValid = nScriptType == i18n::ScriptType::LATIN;
147         break;
148         case EE_CHAR_ITALIC_CJK:
149             bValid = nScriptType == i18n::ScriptType::ASIAN;
150         break;
151         case EE_CHAR_ITALIC_CTL:
152             bValid = nScriptType == i18n::ScriptType::COMPLEX;
153         break;
154     }
155 
156     return bValid;
157 }
158 
159 const SfxItemInfo aItemInfos[EDITITEMCOUNT] = {
160         { SID_ATTR_FRAMEDIRECTION, true },         // EE_PARA_WRITINGDIR
161         { 0, true },                               // EE_PARA_XMLATTRIBS
162         { SID_ATTR_PARA_HANGPUNCTUATION, true },   // EE_PARA_HANGINGPUNCTUATION
163         { SID_ATTR_PARA_FORBIDDEN_RULES, true },   // EE_PARA_FORBIDDENRULES
164         { SID_ATTR_PARA_SCRIPTSPACE, true },       // EE_PARA_ASIANCJKSPACING
165         { SID_ATTR_NUMBERING_RULE, true },         // EE_PARA_NUMBULL
166         { 0, true },                               // EE_PARA_HYPHENATE
167         { 0, true },                               // EE_PARA_BULLETSTATE
168         { 0, true },                               // EE_PARA_OUTLLRSPACE
169         { SID_ATTR_PARA_OUTLLEVEL, true },         // EE_PARA_OUTLLEVEL
170         { SID_ATTR_PARA_BULLET, true },            // EE_PARA_BULLET
171         { SID_ATTR_LRSPACE, true },                // EE_PARA_LRSPACE
172         { SID_ATTR_ULSPACE, true },                // EE_PARA_ULSPACE
173         { SID_ATTR_PARA_LINESPACE, true },         // EE_PARA_SBL
174         { SID_ATTR_PARA_ADJUST, true },            // EE_PARA_JUST
175         { SID_ATTR_TABSTOP, true },                // EE_PARA_TABS
176         { SID_ATTR_ALIGN_HOR_JUSTIFY_METHOD, true }, // EE_PARA_JUST_METHOD
177         { SID_ATTR_ALIGN_VER_JUSTIFY, true },      // EE_PARA_VER_JUST
178         { SID_ATTR_CHAR_COLOR, true },         // EE_CHAR_COLOR
179         { SID_ATTR_CHAR_FONT, true },          // EE_CHAR_FONTINFO
180         { SID_ATTR_CHAR_FONTHEIGHT, true },    // EE_CHAR_FONTHEIGHT
181         { SID_ATTR_CHAR_SCALEWIDTH, true },    // EE_CHAR_FONTWIDTH
182         { SID_ATTR_CHAR_WEIGHT, true },        // EE_CHAR_WEIGHT
183         { SID_ATTR_CHAR_UNDERLINE, true },     // EE_CHAR_UNDERLINE
184         { SID_ATTR_CHAR_STRIKEOUT, true },     // EE_CHAR_STRIKEOUT
185         { SID_ATTR_CHAR_POSTURE, true },       // EE_CHAR_ITALIC
186         { SID_ATTR_CHAR_CONTOUR, true },       // EE_CHAR_OUTLINE
187         { SID_ATTR_CHAR_SHADOWED, true },      // EE_CHAR_SHADOW
188         { SID_ATTR_CHAR_ESCAPEMENT, true },    // EE_CHAR_ESCAPEMENT
189         { SID_ATTR_CHAR_AUTOKERN, true },      // EE_CHAR_PAIRKERNING
190         { SID_ATTR_CHAR_KERNING, true },       // EE_CHAR_KERNING
191         { SID_ATTR_CHAR_WORDLINEMODE, true },  // EE_CHAR_WLM
192         { SID_ATTR_CHAR_LANGUAGE, true },      // EE_CHAR_LANGUAGE
193         { SID_ATTR_CHAR_CJK_LANGUAGE, true },  // EE_CHAR_LANGUAGE_CJK
194         { SID_ATTR_CHAR_CTL_LANGUAGE, true },  // EE_CHAR_LANGUAGE_CTL
195         { SID_ATTR_CHAR_CJK_FONT, true },      // EE_CHAR_FONTINFO_CJK
196         { SID_ATTR_CHAR_CTL_FONT, true },      // EE_CHAR_FONTINFO_CTL
197         { SID_ATTR_CHAR_CJK_FONTHEIGHT, true }, // EE_CHAR_FONTHEIGHT_CJK
198         { SID_ATTR_CHAR_CTL_FONTHEIGHT, true }, // EE_CHAR_FONTHEIGHT_CTL
199         { SID_ATTR_CHAR_CJK_WEIGHT, true },    // EE_CHAR_WEIGHT_CJK
200         { SID_ATTR_CHAR_CTL_WEIGHT, true },    // EE_CHAR_WEIGHT_CTL
201         { SID_ATTR_CHAR_CJK_POSTURE, true },   // EE_CHAR_ITALIC_CJK
202         { SID_ATTR_CHAR_CTL_POSTURE, true },   // EE_CHAR_ITALIC_CTL
203         { SID_ATTR_CHAR_EMPHASISMARK, true },  // EE_CHAR_EMPHASISMARK
204         { SID_ATTR_CHAR_RELIEF, true },        // EE_CHAR_RELIEF
205         { 0, true },                           // EE_CHAR_RUBI_DUMMY
206         { 0, true },                           // EE_CHAR_XMLATTRIBS
207         { SID_ATTR_CHAR_OVERLINE, true },      // EE_CHAR_OVERLINE
208         { SID_ATTR_CHAR_CASEMAP, true },       // EE_CHAR_CASEMAP
209         { SID_ATTR_CHAR_GRABBAG, true },       // EE_CHAR_GRABBAG
210         { SID_ATTR_CHAR_BACK_COLOR, true },    // EE_CHAR_BKGCOLOR
211         { 0, true },                           // EE_FEATURE_TAB
212         { 0, true },                           // EE_FEATURE_LINEBR
213         { SID_ATTR_CHAR_CHARSETCOLOR, true },  // EE_FEATURE_NOTCONV
214         { SID_FIELD, false },                  // EE_FEATURE_FIELD
215 };
216 
217 EditCharAttrib* MakeCharAttrib( SfxItemPool& rPool, const SfxPoolItem& rAttr, sal_Int32 nS, sal_Int32 nE )
218 {
219     // Create a new attribute in the pool
220     const SfxPoolItem& rNew = rPool.Put( rAttr );
221 
222     EditCharAttrib* pNew = nullptr;
223     switch( rNew.Which() )
224     {
225         case EE_CHAR_LANGUAGE:
226         case EE_CHAR_LANGUAGE_CJK:
227         case EE_CHAR_LANGUAGE_CTL:
228         {
229             pNew = new EditCharAttribLanguage( static_cast<const SvxLanguageItem&>(rNew), nS, nE );
230         }
231         break;
232         case EE_CHAR_COLOR:
233         {
234             pNew = new EditCharAttribColor( static_cast<const SvxColorItem&>(rNew), nS, nE );
235         }
236         break;
237         case EE_CHAR_FONTINFO:
238         case EE_CHAR_FONTINFO_CJK:
239         case EE_CHAR_FONTINFO_CTL:
240         {
241             pNew = new EditCharAttribFont( static_cast<const SvxFontItem&>(rNew), nS, nE );
242         }
243         break;
244         case EE_CHAR_FONTHEIGHT:
245         case EE_CHAR_FONTHEIGHT_CJK:
246         case EE_CHAR_FONTHEIGHT_CTL:
247         {
248             pNew = new EditCharAttribFontHeight( static_cast<const SvxFontHeightItem&>(rNew), nS, nE );
249         }
250         break;
251         case EE_CHAR_FONTWIDTH:
252         {
253             pNew = new EditCharAttribFontWidth( static_cast<const SvxCharScaleWidthItem&>(rNew), nS, nE );
254         }
255         break;
256         case EE_CHAR_WEIGHT:
257         case EE_CHAR_WEIGHT_CJK:
258         case EE_CHAR_WEIGHT_CTL:
259         {
260             pNew = new EditCharAttribWeight( static_cast<const SvxWeightItem&>(rNew), nS, nE );
261         }
262         break;
263         case EE_CHAR_UNDERLINE:
264         {
265             pNew = new EditCharAttribUnderline( static_cast<const SvxUnderlineItem&>(rNew), nS, nE );
266         }
267         break;
268         case EE_CHAR_OVERLINE:
269         {
270             pNew = new EditCharAttribOverline( static_cast<const SvxOverlineItem&>(rNew), nS, nE );
271         }
272         break;
273         case EE_CHAR_EMPHASISMARK:
274         {
275             pNew = new EditCharAttribEmphasisMark( static_cast<const SvxEmphasisMarkItem&>(rNew), nS, nE );
276         }
277         break;
278         case EE_CHAR_RELIEF:
279         {
280             pNew = new EditCharAttribRelief( static_cast<const SvxCharReliefItem&>(rNew), nS, nE );
281         }
282         break;
283         case EE_CHAR_STRIKEOUT:
284         {
285             pNew = new EditCharAttribStrikeout( static_cast<const SvxCrossedOutItem&>(rNew), nS, nE );
286         }
287         break;
288         case EE_CHAR_ITALIC:
289         case EE_CHAR_ITALIC_CJK:
290         case EE_CHAR_ITALIC_CTL:
291         {
292             pNew = new EditCharAttribItalic( static_cast<const SvxPostureItem&>(rNew), nS, nE );
293         }
294         break;
295         case EE_CHAR_OUTLINE:
296         {
297             pNew = new EditCharAttribOutline( static_cast<const SvxContourItem&>(rNew), nS, nE );
298         }
299         break;
300         case EE_CHAR_SHADOW:
301         {
302             pNew = new EditCharAttribShadow( static_cast<const SvxShadowedItem&>(rNew), nS, nE );
303         }
304         break;
305         case EE_CHAR_ESCAPEMENT:
306         {
307             pNew = new EditCharAttribEscapement( static_cast<const SvxEscapementItem&>(rNew), nS, nE );
308         }
309         break;
310         case EE_CHAR_PAIRKERNING:
311         {
312             pNew = new EditCharAttribPairKerning( static_cast<const SvxAutoKernItem&>(rNew), nS, nE );
313         }
314         break;
315         case EE_CHAR_KERNING:
316         {
317             pNew = new EditCharAttribKerning( static_cast<const SvxKerningItem&>(rNew), nS, nE );
318         }
319         break;
320         case EE_CHAR_WLM:
321         {
322             pNew = new EditCharAttribWordLineMode( static_cast<const SvxWordLineModeItem&>(rNew), nS, nE );
323         }
324         break;
325         case EE_CHAR_XMLATTRIBS:
326         {
327             pNew = new EditCharAttrib( rNew, nS, nE );  // Attribute is only for holding XML information...
328         }
329         break;
330         case EE_CHAR_CASEMAP:
331         {
332             pNew = new EditCharAttribCaseMap( static_cast<const SvxCaseMapItem&>(rNew), nS, nE );
333         }
334         break;
335         case EE_CHAR_GRABBAG:
336         {
337             pNew = new EditCharAttribGrabBag( static_cast<const SfxGrabBagItem&>(rNew), nS, nE );
338         }
339         break;
340         case EE_FEATURE_TAB:
341         {
342             pNew = new EditCharAttribTab( static_cast<const SfxVoidItem&>(rNew), nS );
343         }
344         break;
345         case EE_FEATURE_LINEBR:
346         {
347             pNew = new EditCharAttribLineBreak( static_cast<const SfxVoidItem&>(rNew), nS );
348         }
349         break;
350         case EE_FEATURE_FIELD:
351         {
352             pNew = new EditCharAttribField( static_cast<const SvxFieldItem&>(rNew), nS );
353         }
354         break;
355         case EE_CHAR_BKGCOLOR:
356         {
357             pNew = new EditCharAttribBackgroundColor( static_cast<const SvxBackgroundColorItem&>(rNew), nS, nE );
358         }
359         break;
360         default:
361         {
362             OSL_FAIL( "Invalid Attribute!" );
363         }
364     }
365     return pNew;
366 }
367 
368 TextPortionList::TextPortionList()
369 {
370 }
371 
372 TextPortionList::~TextPortionList()
373 {
374     Reset();
375 }
376 
377 void TextPortionList::Reset()
378 {
379     maPortions.clear();
380 }
381 
382 void TextPortionList::DeleteFromPortion(sal_Int32 nDelFrom)
383 {
384     assert((nDelFrom < static_cast<sal_Int32>(maPortions.size())) || ((nDelFrom == 0) && maPortions.empty()));
385     PortionsType::iterator it = maPortions.begin();
386     std::advance(it, nDelFrom);
387     maPortions.erase(it, maPortions.end());
388 }
389 
390 sal_Int32 TextPortionList::Count() const
391 {
392     return static_cast<sal_Int32>(maPortions.size());
393 }
394 
395 const TextPortion& TextPortionList::operator[](sal_Int32 nPos) const
396 {
397     return *maPortions[nPos].get();
398 }
399 
400 TextPortion& TextPortionList::operator[](sal_Int32 nPos)
401 {
402     return *maPortions[nPos].get();
403 }
404 
405 void TextPortionList::Append(TextPortion* p)
406 {
407     maPortions.push_back(std::unique_ptr<TextPortion>(p));
408 }
409 
410 void TextPortionList::Insert(sal_Int32 nPos, TextPortion* p)
411 {
412     maPortions.insert(maPortions.begin()+nPos, std::unique_ptr<TextPortion>(p));
413 }
414 
415 void TextPortionList::Remove(sal_Int32 nPos)
416 {
417     maPortions.erase(maPortions.begin()+nPos);
418 }
419 
420 namespace {
421 
422 class FindTextPortionByAddress
423 {
424     const TextPortion* mp;
425 public:
426     explicit FindTextPortionByAddress(const TextPortion* p) : mp(p) {}
427     bool operator() (const std::unique_ptr<TextPortion>& v) const
428     {
429         return v.get() == mp;
430     }
431 };
432 
433 }
434 
435 sal_Int32 TextPortionList::GetPos(const TextPortion* p) const
436 {
437     PortionsType::const_iterator it =
438         std::find_if(maPortions.begin(), maPortions.end(), FindTextPortionByAddress(p));
439 
440     if (it == maPortions.end())
441         return std::numeric_limits<sal_Int32>::max(); // not found.
442 
443     return std::distance(maPortions.begin(), it);
444 }
445 
446 sal_Int32 TextPortionList::FindPortion(
447     sal_Int32 nCharPos, sal_Int32& nPortionStart, bool bPreferStartingPortion) const
448 {
449     // When nCharPos at portion limit, the left portion is found
450     sal_Int32 nTmpPos = 0;
451     sal_Int32 n = maPortions.size();
452     for (sal_Int32 i = 0; i < n; ++i)
453     {
454         const TextPortion& rPortion = *maPortions[i].get();
455         nTmpPos = nTmpPos + rPortion.GetLen();
456         if ( nTmpPos >= nCharPos )
457         {
458             // take this one if we don't prefer the starting portion, or if it's the last one
459             if ( ( nTmpPos != nCharPos ) || !bPreferStartingPortion || ( i == n-1 ) )
460             {
461                 nPortionStart = nTmpPos - rPortion.GetLen();
462                 return i;
463             }
464         }
465     }
466     OSL_FAIL( "FindPortion: Not found!" );
467     return n - 1;
468 }
469 
470 sal_Int32 TextPortionList::GetStartPos(sal_Int32 nPortion)
471 {
472     sal_Int32 nPos = 0;
473     for (sal_Int32 i = 0; i < nPortion; ++i)
474     {
475         const TextPortion& rPortion = *maPortions[i].get();
476         nPos = nPos + rPortion.GetLen();
477     }
478     return nPos;
479 }
480 
481 ExtraPortionInfo::ExtraPortionInfo()
482 : nOrgWidth(0)
483 , nWidthFullCompression(0)
484 , nPortionOffsetX(0)
485 , nMaxCompression100thPercent(0)
486 , nAsianCompressionTypes(AsianCompressionFlags::Normal)
487 , bFirstCharIsRightPunktuation(false)
488 , bCompressed(false)
489 , lineBreaksList()
490 {
491 }
492 
493 ExtraPortionInfo::~ExtraPortionInfo()
494 {
495 }
496 
497 void ExtraPortionInfo::SaveOrgDXArray( const long* pDXArray, sal_Int32 nLen )
498 {
499     if (pDXArray)
500     {
501         pOrgDXArray.reset(new long[nLen]);
502         memcpy( pOrgDXArray.get(), pDXArray, nLen * sizeof(long) );
503     }
504     else
505         pOrgDXArray.reset();
506 }
507 
508 ParaPortion::ParaPortion( ContentNode* pN ) :
509     pNode(pN),
510     nHeight(0),
511     nInvalidPosStart(0),
512     nFirstLineOffset(0),
513     nBulletX(0),
514     nInvalidDiff(0),
515     bInvalid(true),
516     bSimple(false),
517     bVisible(true),
518     bForceRepaint(false)
519 {
520 }
521 
522 ParaPortion::~ParaPortion()
523 {
524 }
525 
526 void ParaPortion::MarkInvalid( sal_Int32 nStart, sal_Int32 nDiff )
527 {
528     if ( !bInvalid )
529     {
530 //      nInvalidPosEnd = nStart;    // ??? => CreateLines
531         nInvalidPosStart = ( nDiff >= 0 ) ? nStart : ( nStart + nDiff );
532         nInvalidDiff = nDiff;
533     }
534     else
535     {
536         // Simple tap in succession
537         if ( ( nDiff > 0 ) && ( nInvalidDiff > 0 ) &&
538              ( ( nInvalidPosStart+nInvalidDiff ) == nStart ) )
539         {
540             nInvalidDiff = nInvalidDiff + nDiff;
541         }
542         // Simple delete in succession
543         else if ( ( nDiff < 0 ) && ( nInvalidDiff < 0 ) && ( nInvalidPosStart == nStart ) )
544         {
545             nInvalidPosStart = nInvalidPosStart + nDiff;
546             nInvalidDiff = nInvalidDiff + nDiff;
547         }
548         else
549         {
550 //          nInvalidPosEnd = pNode->Len();
551             DBG_ASSERT( ( nDiff >= 0 ) || ( (nStart+nDiff) >= 0 ), "MarkInvalid: Diff out of Range" );
552             nInvalidPosStart = std::min( nInvalidPosStart, ( nDiff < 0 ? nStart+nDiff : nDiff ) );
553             nInvalidDiff = 0;
554             bSimple = false;
555         }
556     }
557     bInvalid = true;
558     aScriptInfos.clear();
559     aWritingDirectionInfos.clear();
560 }
561 
562 void ParaPortion::MarkSelectionInvalid( sal_Int32 nStart )
563 {
564     if ( !bInvalid )
565     {
566         nInvalidPosStart = nStart;
567     }
568     else
569     {
570         nInvalidPosStart = std::min( nInvalidPosStart, nStart );
571     }
572     nInvalidDiff = 0;
573     bInvalid = true;
574     bSimple = false;
575     aScriptInfos.clear();
576     aWritingDirectionInfos.clear();
577 }
578 
579 sal_Int32 ParaPortion::GetLineNumber( sal_Int32 nIndex ) const
580 {
581     SAL_WARN_IF( !aLineList.Count(), "editeng", "Empty ParaPortion in GetLine!" );
582     DBG_ASSERT( bVisible, "Why GetLine() on an invisible paragraph?" );
583 
584     for ( sal_Int32 nLine = 0; nLine < aLineList.Count(); nLine++ )
585     {
586         if ( aLineList[nLine].IsIn( nIndex ) )
587             return nLine;
588     }
589 
590     // Then it should be at the end of the last line!
591     DBG_ASSERT( nIndex == aLineList[ aLineList.Count() - 1 ].GetEnd(), "Index dead wrong!" );
592     return (aLineList.Count()-1);
593 }
594 
595 void ParaPortion::SetVisible( bool bMakeVisible )
596 {
597     bVisible = bMakeVisible;
598 }
599 
600 void ParaPortion::CorrectValuesBehindLastFormattedLine( sal_Int32 nLastFormattedLine )
601 {
602     sal_Int32 nLines = aLineList.Count();
603     DBG_ASSERT( nLines, "CorrectPortionNumbersFromLine: Empty Portion?" );
604     if ( nLastFormattedLine < ( nLines - 1 ) )
605     {
606         const EditLine& rLastFormatted = aLineList[ nLastFormattedLine ];
607         const EditLine& rUnformatted = aLineList[ nLastFormattedLine+1 ];
608         sal_Int32 nPortionDiff = rUnformatted.GetStartPortion() - rLastFormatted.GetEndPortion();
609         sal_Int32 nTextDiff = rUnformatted.GetStart() - rLastFormatted.GetEnd();
610         nTextDiff++;    // LastFormatted->GetEnd() was included => 1 deducted too much!
611 
612         // The first unformatted must begin exactly one Portion behind the last
613         // of the formatted:
614         // If the modified line was split into one portion, can
615         // nLastEnd > nNextStart!
616         int nPDiff = -( nPortionDiff-1 );
617         int nTDiff = -( nTextDiff-1 );
618         if ( nPDiff || nTDiff )
619         {
620             for ( sal_Int32 nL = nLastFormattedLine+1; nL < nLines; nL++ )
621             {
622                 EditLine& rLine = aLineList[ nL ];
623 
624                 rLine.GetStartPortion() = rLine.GetStartPortion() + nPDiff;
625                 rLine.GetEndPortion() = rLine.GetEndPortion() + nPDiff;
626 
627                 rLine.GetStart() = rLine.GetStart() + nTDiff;
628                 rLine.GetEnd() = rLine.GetEnd() + nTDiff;
629 
630                 rLine.SetValid();
631             }
632         }
633     }
634     DBG_ASSERT( aLineList[ aLineList.Count()-1 ].GetEnd() == pNode->Len(), "CorrectLines: The end is not right!" );
635 }
636 
637 // Shared reverse lookup acceleration pieces ...
638 
639 namespace {
640 
641 template<typename Array, typename Val>
642 sal_Int32 FastGetPos(const Array& rArray, const Val* p, sal_Int32& rLastPos)
643 {
644     sal_Int32 nArrayLen = rArray.size();
645 
646     // Through certain filter code-paths we do a lot of appends, which in
647     // turn call GetPos - creating some N^2 nightmares. If we have a
648     // non-trivially large list, do a few checks from the end first.
649     if (rLastPos > 16 && nArrayLen > 16)
650     {
651         sal_Int32 nEnd;
652         if (rLastPos > nArrayLen - 2)
653             nEnd = nArrayLen;
654         else
655             nEnd = rLastPos + 2;
656 
657         for (sal_Int32 nIdx = rLastPos - 2; nIdx < nEnd; ++nIdx)
658         {
659             if (rArray.at(nIdx).get() == p)
660             {
661                 rLastPos = nIdx;
662                 return nIdx;
663             }
664         }
665     }
666     // The world's lamest linear search from svarray...
667     for (sal_Int32 nIdx = 0; nIdx < nArrayLen; ++nIdx)
668         if (rArray.at(nIdx).get() == p)
669             return rLastPos = nIdx;
670 
671     // XXX "not found" condition for sal_Int32 indexes
672     return EE_PARA_NOT_FOUND;
673 }
674 
675 }
676 
677 ParaPortionList::ParaPortionList() : nLastCache( 0 )
678 {
679 }
680 
681 ParaPortionList::~ParaPortionList()
682 {
683 }
684 
685 sal_Int32 ParaPortionList::GetPos(const ParaPortion* p) const
686 {
687     return FastGetPos(maPortions, p, nLastCache);
688 }
689 
690 ParaPortion* ParaPortionList::operator [](sal_Int32 nPos)
691 {
692     return 0 <= nPos && nPos < static_cast<sal_Int32>(maPortions.size()) ? maPortions[nPos].get() : nullptr;
693 }
694 
695 const ParaPortion* ParaPortionList::operator [](sal_Int32 nPos) const
696 {
697     return 0 <= nPos && nPos < static_cast<sal_Int32>(maPortions.size()) ? maPortions[nPos].get() : nullptr;
698 }
699 
700 std::unique_ptr<ParaPortion> ParaPortionList::Release(sal_Int32 nPos)
701 {
702     if (nPos < 0 || static_cast<sal_Int32>(maPortions.size()) <= nPos)
703     {
704         SAL_WARN( "editeng", "ParaPortionList::Release - out of bounds pos " << nPos);
705         return nullptr;
706     }
707     std::unique_ptr<ParaPortion> p = std::move(maPortions[nPos]);
708     maPortions.erase(maPortions.begin()+nPos);
709     return p;
710 }
711 
712 void ParaPortionList::Remove(sal_Int32 nPos)
713 {
714     if (nPos < 0 || static_cast<sal_Int32>(maPortions.size()) <= nPos)
715     {
716         SAL_WARN( "editeng", "ParaPortionList::Remove - out of bounds pos " << nPos);
717         return;
718     }
719     maPortions.erase(maPortions.begin()+nPos);
720 }
721 
722 void ParaPortionList::Insert(sal_Int32 nPos, std::unique_ptr<ParaPortion> p)
723 {
724     if (nPos < 0 || static_cast<sal_Int32>(maPortions.size()) < nPos)
725     {
726         SAL_WARN( "editeng", "ParaPortionList::Insert - out of bounds pos " << nPos);
727         return;
728     }
729     maPortions.insert(maPortions.begin()+nPos, std::move(p));
730 }
731 
732 void ParaPortionList::Append(std::unique_ptr<ParaPortion> p)
733 {
734     maPortions.push_back(std::move(p));
735 }
736 
737 sal_Int32 ParaPortionList::Count() const
738 {
739     size_t nSize = maPortions.size();
740     if (nSize > SAL_MAX_INT32)
741     {
742         SAL_WARN( "editeng", "ParaPortionList::Count - overflow " << nSize);
743         return SAL_MAX_INT32;
744     }
745     return nSize;
746 }
747 
748 void ParaPortionList::Reset()
749 {
750     maPortions.clear();
751 }
752 
753 long ParaPortionList::GetYOffset(const ParaPortion* pPPortion) const
754 {
755     long nHeight = 0;
756     for (const auto & rPortion : maPortions)
757     {
758         const ParaPortion* pTmpPortion = rPortion.get();
759         if ( pTmpPortion == pPPortion )
760             return nHeight;
761         nHeight += pTmpPortion->GetHeight();
762     }
763     OSL_FAIL( "GetYOffset: Portion not found" );
764     return nHeight;
765 }
766 
767 sal_Int32 ParaPortionList::FindParagraph(long nYOffset) const
768 {
769     long nY = 0;
770     for (size_t i = 0, n = maPortions.size(); i < n; ++i)
771     {
772         nY += maPortions[i]->GetHeight(); // should also be correct even in bVisible!
773         if ( nY > nYOffset )
774             return i <= SAL_MAX_INT32 ? static_cast<sal_Int32>(i) : SAL_MAX_INT32;
775     }
776     return EE_PARA_NOT_FOUND;
777 }
778 
779 const ParaPortion* ParaPortionList::SafeGetObject(sal_Int32 nPos) const
780 {
781     return 0 <= nPos && nPos < static_cast<sal_Int32>(maPortions.size()) ? maPortions[nPos].get() : nullptr;
782 }
783 
784 ParaPortion* ParaPortionList::SafeGetObject(sal_Int32 nPos)
785 {
786     return 0 <= nPos && nPos < static_cast<sal_Int32>(maPortions.size()) ? maPortions[nPos].get() : nullptr;
787 }
788 
789 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
790 void
791 ParaPortionList::DbgCheck(ParaPortionList const& rParas, EditDoc const& rDoc)
792 {
793     assert(rParas.Count() == rDoc.Count());
794     for (sal_Int32 i = 0; i < rParas.Count(); ++i)
795     {
796         assert(rParas.SafeGetObject(i) != nullptr);
797         assert(rParas.SafeGetObject(i)->GetNode() != nullptr);
798         assert(rParas.SafeGetObject(i)->GetNode() == rDoc.GetObject(i));
799     }
800 }
801 #endif
802 
803 ContentAttribsInfo::ContentAttribsInfo( const SfxItemSet& rParaAttribs ) :
804         aPrevParaAttribs( rParaAttribs)
805 {
806 }
807 
808 void ContentAttribsInfo::RemoveAllCharAttribsFromPool(SfxItemPool& rPool) const
809 {
810     for (const std::unique_ptr<EditCharAttrib>& rAttrib : aPrevCharAttribs)
811         rPool.Remove(*rAttrib->GetItem());
812 }
813 
814 void ContentAttribsInfo::AppendCharAttrib(EditCharAttrib* pNew)
815 {
816     aPrevCharAttribs.push_back(std::unique_ptr<EditCharAttrib>(pNew));
817 }
818 
819 void ConvertItem( std::unique_ptr<SfxPoolItem>& rPoolItem, MapUnit eSourceUnit, MapUnit eDestUnit )
820 {
821     DBG_ASSERT( eSourceUnit != eDestUnit, "ConvertItem - Why?!" );
822 
823     switch ( rPoolItem->Which() )
824     {
825         case EE_PARA_LRSPACE:
826         {
827             assert(dynamic_cast<const SvxLRSpaceItem *>(rPoolItem.get()) != nullptr);
828             SvxLRSpaceItem& rItem = static_cast<SvxLRSpaceItem&>(*rPoolItem);
829             rItem.SetTextFirstLineOfst( sal::static_int_cast< short >( OutputDevice::LogicToLogic( rItem.GetTextFirstLineOfst(), eSourceUnit, eDestUnit ) ) );
830             rItem.SetTextLeft( OutputDevice::LogicToLogic( rItem.GetTextLeft(), eSourceUnit, eDestUnit ) );
831             rItem.SetRight( OutputDevice::LogicToLogic( rItem.GetRight(), eSourceUnit, eDestUnit ) );
832         }
833         break;
834         case EE_PARA_ULSPACE:
835         {
836             assert(dynamic_cast<const SvxULSpaceItem *>(rPoolItem.get()) != nullptr);
837             SvxULSpaceItem& rItem = static_cast<SvxULSpaceItem&>(*rPoolItem);
838             rItem.SetUpper( sal::static_int_cast< sal_uInt16 >( OutputDevice::LogicToLogic( rItem.GetUpper(), eSourceUnit, eDestUnit ) ) );
839             rItem.SetLower( sal::static_int_cast< sal_uInt16 >( OutputDevice::LogicToLogic( rItem.GetLower(), eSourceUnit, eDestUnit ) ) );
840         }
841         break;
842         case EE_PARA_SBL:
843         {
844             assert(dynamic_cast<const SvxLineSpacingItem *>(rPoolItem.get()) != nullptr);
845             SvxLineSpacingItem& rItem = static_cast<SvxLineSpacingItem&>(*rPoolItem);
846             // SetLineHeight changes also eLineSpace!
847             if ( rItem.GetLineSpaceRule() == SvxLineSpaceRule::Min )
848                 rItem.SetLineHeight( sal::static_int_cast< sal_uInt16 >( OutputDevice::LogicToLogic( rItem.GetLineHeight(), eSourceUnit, eDestUnit ) ) );
849         }
850         break;
851         case EE_PARA_TABS:
852         {
853             assert(dynamic_cast<const SvxTabStopItem *>(rPoolItem.get()) != nullptr);
854             SvxTabStopItem& rItem = static_cast<SvxTabStopItem&>(*rPoolItem);
855             SvxTabStopItem* pNewItem(new SvxTabStopItem(EE_PARA_TABS));
856             for ( sal_uInt16 i = 0; i < rItem.Count(); i++ )
857             {
858                 const SvxTabStop& rTab = rItem[i];
859                 SvxTabStop aNewStop( OutputDevice::LogicToLogic( rTab.GetTabPos(), eSourceUnit, eDestUnit ), rTab.GetAdjustment(), rTab.GetDecimal(), rTab.GetFill() );
860                 pNewItem->Insert( aNewStop );
861             }
862             rPoolItem.reset(pNewItem);
863         }
864         break;
865         case EE_CHAR_FONTHEIGHT:
866         case EE_CHAR_FONTHEIGHT_CJK:
867         case EE_CHAR_FONTHEIGHT_CTL:
868         {
869             assert(dynamic_cast<const SvxFontHeightItem *>(rPoolItem.get()) != nullptr);
870             SvxFontHeightItem& rItem = static_cast<SvxFontHeightItem&>(*rPoolItem);
871             rItem.SetHeight( OutputDevice::LogicToLogic( rItem.GetHeight(), eSourceUnit, eDestUnit ) );
872         }
873         break;
874     }
875 }
876 
877 void ConvertAndPutItems( SfxItemSet& rDest, const SfxItemSet& rSource, const MapUnit* pSourceUnit, const MapUnit* pDestUnit )
878 {
879     const SfxItemPool* pSourcePool = rSource.GetPool();
880     const SfxItemPool* pDestPool = rDest.GetPool();
881 
882     for ( sal_uInt16 nWhich = EE_PARA_START; nWhich <= EE_CHAR_END; nWhich++ )
883     {
884         // If possible go through SlotID ...
885 
886         sal_uInt16 nSourceWhich = nWhich;
887         sal_uInt16 nSlot = pDestPool->GetTrueSlotId( nWhich );
888         if ( nSlot )
889         {
890             sal_uInt16 nW = pSourcePool->GetTrueWhich( nSlot );
891             if ( nW )
892                 nSourceWhich = nW;
893         }
894 
895         if ( rSource.GetItemState( nSourceWhich, false ) == SfxItemState::SET )
896         {
897             MapUnit eSourceUnit = pSourceUnit ? *pSourceUnit : pSourcePool->GetMetric( nSourceWhich );
898             MapUnit eDestUnit = pDestUnit ? *pDestUnit : pDestPool->GetMetric( nWhich );
899             if ( eSourceUnit != eDestUnit )
900             {
901                 std::unique_ptr<SfxPoolItem> pItem(rSource.Get( nSourceWhich ).Clone());
902                 ConvertItem( pItem, eSourceUnit, eDestUnit );
903                 pItem->SetWhich(nWhich);
904                 rDest.Put( std::move(pItem) );
905             }
906             else
907             {
908                 rDest.Put( rSource.Get( nSourceWhich ).CloneSetWhich(nWhich) );
909             }
910         }
911     }
912 }
913 
914 EditLine::EditLine() :
915     nTxtWidth(0),
916     nStartPosX(0),
917     nStart(0),
918     nEnd(0),
919     nStartPortion(0),   // to be able to tell the difference between a line
920                         // without Portions from one with the Portion number 0
921     nEndPortion(0),
922     nHeight(0),
923     nTxtHeight(0),
924     nMaxAscent(0),
925     bHangingPunctuation(false),
926     bInvalid(true)
927 {
928 }
929 
930 EditLine::EditLine( const EditLine& r ) :
931     nTxtWidth(0),
932     nStartPosX(0),
933     nStart(r.nStart),
934     nEnd(r.nEnd),
935     nStartPortion(r.nStartPortion),
936     nEndPortion(r.nEndPortion),
937     nHeight(0),
938     nTxtHeight(0),
939     nMaxAscent(0),
940     bHangingPunctuation(r.bHangingPunctuation),
941     bInvalid(true)
942 {
943 }
944 
945 EditLine::~EditLine()
946 {
947 }
948 
949 
950 EditLine* EditLine::Clone() const
951 {
952     EditLine* pL = new EditLine;
953     pL->aPositions = aPositions;
954     pL->nStartPosX      = nStartPosX;
955     pL->nStart          = nStart;
956     pL->nEnd            = nEnd;
957     pL->nStartPortion   = nStartPortion;
958     pL->nEndPortion     = nEndPortion;
959     pL->nHeight         = nHeight;
960     pL->nTxtWidth       = nTxtWidth;
961     pL->nTxtHeight      = nTxtHeight;
962     pL->nMaxAscent      = nMaxAscent;
963 
964     return pL;
965 }
966 
967 bool operator == ( const EditLine& r1,  const EditLine& r2  )
968 {
969     if ( r1.nStart != r2.nStart )
970         return false;
971 
972     if ( r1.nEnd != r2.nEnd )
973         return false;
974 
975     if ( r1.nStartPortion != r2.nStartPortion )
976         return false;
977 
978     if ( r1.nEndPortion != r2.nEndPortion )
979         return false;
980 
981     return true;
982 }
983 
984 EditLine& EditLine::operator = ( const EditLine& r )
985 {
986     nEnd = r.nEnd;
987     nStart = r.nStart;
988     nEndPortion = r.nEndPortion;
989     nStartPortion = r.nStartPortion;
990     return *this;
991 }
992 
993 
994 void EditLine::SetHeight( sal_uInt16 nH, sal_uInt16 nTxtH )
995 {
996     nHeight = nH;
997     nTxtHeight = ( nTxtH ? nTxtH : nH );
998 }
999 
1000 void EditLine::SetStartPosX( long start )
1001 {
1002     if (start > 0)
1003         nStartPosX = start;
1004     else
1005         nStartPosX = 0;
1006 }
1007 
1008 Size EditLine::CalcTextSize( ParaPortion& rParaPortion )
1009 {
1010     Size aSz;
1011     Size aTmpSz;
1012 
1013     DBG_ASSERT( rParaPortion.GetTextPortions().Count(), "GetTextSize before CreatePortions !" );
1014 
1015     for ( sal_Int32 n = nStartPortion; n <= nEndPortion; n++ )
1016     {
1017         TextPortion& rPortion = rParaPortion.GetTextPortions()[n];
1018         switch ( rPortion.GetKind() )
1019         {
1020             case PortionKind::TEXT:
1021             case PortionKind::FIELD:
1022             case PortionKind::HYPHENATOR:
1023             {
1024                 aTmpSz = rPortion.GetSize();
1025                 aSz.AdjustWidth(aTmpSz.Width() );
1026                 if ( aSz.Height() < aTmpSz.Height() )
1027                     aSz.setHeight( aTmpSz.Height() );
1028             }
1029             break;
1030             case PortionKind::TAB:
1031             {
1032                 aSz.AdjustWidth(rPortion.GetSize().Width() );
1033             }
1034             break;
1035             case PortionKind::LINEBREAK: break;
1036         }
1037     }
1038 
1039     SetHeight( static_cast<sal_uInt16>(aSz.Height()) );
1040     return aSz;
1041 }
1042 
1043 EditLineList::EditLineList()
1044 {
1045 }
1046 
1047 EditLineList::~EditLineList()
1048 {
1049     Reset();
1050 }
1051 
1052 void EditLineList::Reset()
1053 {
1054     maLines.clear();
1055 }
1056 
1057 void EditLineList::DeleteFromLine(sal_Int32 nDelFrom)
1058 {
1059     assert(nDelFrom <= (static_cast<sal_Int32>(maLines.size()) - 1));
1060     LinesType::iterator it = maLines.begin();
1061     std::advance(it, nDelFrom);
1062     maLines.erase(it, maLines.end());
1063 }
1064 
1065 sal_Int32 EditLineList::FindLine(sal_Int32 nChar, bool bInclEnd)
1066 {
1067     sal_Int32 n = maLines.size();
1068     for (sal_Int32 i = 0; i < n; ++i)
1069     {
1070         const EditLine& rLine = *maLines[i].get();
1071         if ( (bInclEnd && (rLine.GetEnd() >= nChar)) ||
1072              (rLine.GetEnd() > nChar) )
1073         {
1074             return i;
1075         }
1076     }
1077 
1078     DBG_ASSERT( !bInclEnd, "Line not found: FindLine" );
1079     return n - 1;
1080 }
1081 
1082 sal_Int32 EditLineList::Count() const
1083 {
1084     return maLines.size();
1085 }
1086 
1087 const EditLine& EditLineList::operator[](sal_Int32 nPos) const
1088 {
1089     return *maLines[nPos].get();
1090 }
1091 
1092 EditLine& EditLineList::operator[](sal_Int32 nPos)
1093 {
1094     return *maLines[nPos].get();
1095 }
1096 
1097 void EditLineList::Append(EditLine* p)
1098 {
1099     maLines.push_back(std::unique_ptr<EditLine>(p));
1100 }
1101 
1102 void EditLineList::Insert(sal_Int32 nPos, EditLine* p)
1103 {
1104     maLines.insert(maLines.begin()+nPos, std::unique_ptr<EditLine>(p));
1105 }
1106 
1107 EditPaM::EditPaM() : pNode(nullptr), nIndex(0) {}
1108 EditPaM::EditPaM(ContentNode* p, sal_Int32 n) : pNode(p), nIndex(n) {}
1109 
1110 
1111 void EditPaM::SetNode(ContentNode* p)
1112 {
1113     pNode = p;
1114 }
1115 
1116 bool EditPaM::DbgIsBuggy( EditDoc const & rDoc ) const
1117 {
1118     return !pNode ||
1119            rDoc.GetPos( pNode ) >= rDoc.Count() ||
1120            nIndex > pNode->Len();
1121 }
1122 
1123 bool EditSelection::DbgIsBuggy( EditDoc const & rDoc ) const
1124 {
1125     return aStartPaM.DbgIsBuggy( rDoc ) || aEndPaM.DbgIsBuggy( rDoc );
1126 }
1127 
1128 EditSelection::EditSelection()
1129 {
1130 }
1131 
1132 EditSelection::EditSelection( const EditPaM& rStartAndAnd ) :
1133     aStartPaM(rStartAndAnd),
1134     aEndPaM(rStartAndAnd)
1135 {
1136 }
1137 
1138 EditSelection::EditSelection( const EditPaM& rStart, const EditPaM& rEnd ) :
1139     aStartPaM(rStart),
1140     aEndPaM(rEnd)
1141 {
1142 }
1143 
1144 EditSelection& EditSelection::operator = ( const EditPaM& rPaM )
1145 {
1146     aStartPaM = rPaM;
1147     aEndPaM = rPaM;
1148     return *this;
1149 }
1150 
1151 void EditSelection::Adjust( const EditDoc& rNodes )
1152 {
1153     DBG_ASSERT( aStartPaM.GetIndex() <= aStartPaM.GetNode()->Len(), "Index out of range in Adjust(1)" );
1154     DBG_ASSERT( aEndPaM.GetIndex() <= aEndPaM.GetNode()->Len(), "Index out of range in Adjust(2)" );
1155 
1156     const ContentNode* pStartNode = aStartPaM.GetNode();
1157     const ContentNode* pEndNode = aEndPaM.GetNode();
1158 
1159     sal_Int32 nStartNode = rNodes.GetPos( pStartNode );
1160     sal_Int32 nEndNode = rNodes.GetPos( pEndNode );
1161 
1162     DBG_ASSERT( nStartNode != SAL_MAX_INT32, "Node out of range in Adjust(1)" );
1163     DBG_ASSERT( nEndNode != SAL_MAX_INT32, "Node out of range in Adjust(2)" );
1164 
1165     const bool bSwap = ( nStartNode > nEndNode ) ||
1166                        ( ( nStartNode == nEndNode ) &&
1167                          ( aStartPaM.GetIndex() > aEndPaM.GetIndex() ) );
1168 
1169     if ( bSwap )
1170     {
1171         EditPaM aTmpPaM( aStartPaM );
1172         aStartPaM = aEndPaM;
1173         aEndPaM = aTmpPaM;
1174     }
1175 }
1176 
1177 bool operator == ( const EditPaM& r1, const EditPaM& r2 )
1178 {
1179     return ( r1.GetNode() == r2.GetNode() ) &&
1180            ( r1.GetIndex() == r2.GetIndex() );
1181 }
1182 
1183 bool operator != ( const EditPaM& r1, const EditPaM& r2 )
1184 {
1185     return !( r1 == r2 );
1186 }
1187 
1188 ContentNode::ContentNode( SfxItemPool& rPool ) : aContentAttribs( rPool )
1189 {
1190 }
1191 
1192 ContentNode::ContentNode( const OUString& rStr, const ContentAttribs& rContentAttribs ) :
1193     maString(rStr), aContentAttribs(rContentAttribs)
1194 {
1195 }
1196 
1197 ContentNode::~ContentNode()
1198 {
1199 }
1200 
1201 void ContentNode::ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNew, SfxItemPool& rItemPool )
1202 {
1203     if ( !nNew )
1204         return;
1205 
1206 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
1207     CharAttribList::DbgCheckAttribs(aCharAttribList);
1208 #endif
1209 
1210     // Since features are treated differently than normal character attributes,
1211     // but can also affect the order of the start list.    // In every if ...,  in the next (n) opportunities due to bFeature or
1212     // an existing special case, must (n-1) opportunities be provided with
1213     // bResort. The most likely possibility receives no bResort, so that is
1214     // not sorted anew when all attributes are the same.
1215     bool bResort = false;
1216     bool bExpandedEmptyAtIndexNull = false;
1217 
1218     sal_Int32 nAttr = 0;
1219     CharAttribList::AttribsType& rAttribs = aCharAttribList.GetAttribs();
1220     EditCharAttrib* pAttrib = GetAttrib(rAttribs, nAttr);
1221     while ( pAttrib )
1222     {
1223         if ( pAttrib->GetEnd() >= nIndex )
1224         {
1225             // Move all attributes behind the insertion point...
1226             if ( pAttrib->GetStart() > nIndex )
1227             {
1228                 pAttrib->MoveForward( nNew );
1229             }
1230             // 0: Expand empty attribute, if at insertion point
1231             else if ( pAttrib->IsEmpty() )
1232             {
1233                 // Do not check Index, an empty one could only be there
1234                 // When later checking it anyhow:
1235                 //   Special case: Start == 0; AbsLen == 1, nNew = 1
1236                 // => Expand, because of paragraph break!
1237                 // Start <= nIndex, End >= nIndex => Start=End=nIndex!
1238 //              if ( pAttrib->GetStart() == nIndex )
1239                 pAttrib->Expand( nNew );
1240                 bResort = true;
1241                 if ( pAttrib->GetStart() == 0 )
1242                     bExpandedEmptyAtIndexNull = true;
1243             }
1244             // 1: Attribute starts before, goes to index ...
1245             else if ( pAttrib->GetEnd() == nIndex ) // Start must be before
1246             {
1247                 // Only expand when there is no feature
1248                 // and if not in exclude list!
1249                 // Otherwise, a UL will go on until a new ULDB, expanding both
1250 //              if ( !pAttrib->IsFeature() && !rExclList.FindAttrib( pAttrib->Which() ) )
1251                 if ( !pAttrib->IsFeature() && !aCharAttribList.FindEmptyAttrib( pAttrib->Which(), nIndex ) )
1252                 {
1253                     if ( !pAttrib->IsEdge() )
1254                         pAttrib->Expand( nNew );
1255                 }
1256                 else
1257                     bResort = true;
1258             }
1259             // 2: Attribute starts before, goes past the Index...
1260             else if ( ( pAttrib->GetStart() < nIndex ) && ( pAttrib->GetEnd() > nIndex ) )
1261             {
1262                 DBG_ASSERT( !pAttrib->IsFeature(), "Large Feature?!" );
1263                 pAttrib->Expand( nNew );
1264             }
1265             // 3: Attribute starts on index...
1266             else if ( pAttrib->GetStart() == nIndex )
1267             {
1268                 if ( pAttrib->IsFeature() )
1269                 {
1270                     pAttrib->MoveForward( nNew );
1271                     bResort = true;
1272                 }
1273                 else
1274                 {
1275                     bool bExpand = false;
1276                     if ( nIndex == 0 )
1277                     {
1278                         bExpand = true;
1279                         if( bExpandedEmptyAtIndexNull )
1280                         {
1281                             // Check if this kind of attribute was empty and expanded here...
1282                             sal_uInt16 nW = pAttrib->GetItem()->Which();
1283                             for ( sal_Int32 nA = 0; nA < nAttr; nA++ )
1284                             {
1285                                 const EditCharAttrib& r = *aCharAttribList.GetAttribs()[nA].get();
1286                                 if ( ( r.GetStart() == 0 ) && ( r.GetItem()->Which() == nW ) )
1287                                 {
1288                                     bExpand = false;
1289                                     break;
1290                                 }
1291                             }
1292 
1293                         }
1294                     }
1295                     if ( bExpand )
1296                     {
1297                         pAttrib->Expand( nNew );
1298                         bResort = true;
1299                     }
1300                     else
1301                     {
1302                         pAttrib->MoveForward( nNew );
1303                     }
1304                 }
1305             }
1306         }
1307 
1308         if ( pAttrib->IsEdge() )
1309             pAttrib->SetEdge(false);
1310 
1311         DBG_ASSERT( !pAttrib->IsFeature() || ( pAttrib->GetLen() == 1 ), "Expand: FeaturesLen != 1" );
1312 
1313         DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Expand: Attribute distorted!" );
1314         DBG_ASSERT( ( pAttrib->GetEnd() <= Len() ), "Expand: Attribute larger than paragraph!" );
1315         if ( pAttrib->IsEmpty() )
1316         {
1317             OSL_FAIL( "Empty Attribute after ExpandAttribs?" );
1318             bResort = true;
1319             rItemPool.Remove( *pAttrib->GetItem() );
1320             rAttribs.erase(rAttribs.begin()+nAttr);
1321             --nAttr;
1322         }
1323         ++nAttr;
1324         pAttrib = GetAttrib(rAttribs, nAttr);
1325     }
1326 
1327     if ( bResort )
1328         aCharAttribList.ResortAttribs();
1329 
1330     if (mpWrongList)
1331     {
1332         bool bSep = ( maString[ nIndex ] == ' ' ) || IsFeature( nIndex );
1333         mpWrongList->TextInserted( nIndex, nNew, bSep );
1334     }
1335 
1336 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
1337     CharAttribList::DbgCheckAttribs(aCharAttribList);
1338 #endif
1339 }
1340 
1341 void ContentNode::CollapseAttribs( sal_Int32 nIndex, sal_Int32 nDeleted, SfxItemPool& rItemPool )
1342 {
1343     if ( !nDeleted )
1344         return;
1345 
1346 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
1347     CharAttribList::DbgCheckAttribs(aCharAttribList);
1348 #endif
1349 
1350     // Since features are treated differently than normal character attributes,
1351     // but can also affect the order of the start list
1352     bool bResort = false;
1353     sal_Int32 nEndChanges = nIndex+nDeleted;
1354 
1355     sal_Int32 nAttr = 0;
1356     CharAttribList::AttribsType& rAttribs = aCharAttribList.GetAttribs();
1357     EditCharAttrib* pAttrib = GetAttrib(rAttribs, nAttr);
1358     while ( pAttrib )
1359     {
1360         bool bDelAttr = false;
1361         if ( pAttrib->GetEnd() >= nIndex )
1362         {
1363             // Move all Attribute behind the insert point...
1364             if ( pAttrib->GetStart() >= nEndChanges )
1365             {
1366                 pAttrib->MoveBackward( nDeleted );
1367             }
1368             // 1. Delete Internal attributes...
1369             else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() <= nEndChanges ) )
1370             {
1371                 // Special case: Attribute covers the area exactly
1372                 // => keep as empty Attribute.
1373                 if ( !pAttrib->IsFeature() && ( pAttrib->GetStart() == nIndex ) && ( pAttrib->GetEnd() == nEndChanges ) )
1374                 {
1375                     pAttrib->GetEnd() = nIndex; // empty
1376                     bResort = true;
1377                 }
1378                 else
1379                     bDelAttr = true;
1380             }
1381             // 2. Attribute starts earlier, ends inside or behind it ...
1382             else if ( ( pAttrib->GetStart() <= nIndex ) && ( pAttrib->GetEnd() > nIndex ) )
1383             {
1384                 DBG_ASSERT( !pAttrib->IsFeature(), "Collapsing Feature!" );
1385                 if ( pAttrib->GetEnd() <= nEndChanges ) // ends inside
1386                     pAttrib->GetEnd() = nIndex;
1387                 else
1388                     pAttrib->Collaps( nDeleted );       // ends behind
1389             }
1390             // 3. Attribute starts inside, ending behind ...
1391             else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() > nEndChanges ) )
1392             {
1393                 // Features not allowed to expand!
1394                 if ( pAttrib->IsFeature() )
1395                 {
1396                     pAttrib->MoveBackward( nDeleted );
1397                     bResort = true;
1398                 }
1399                 else
1400                 {
1401                     pAttrib->GetStart() = nEndChanges;
1402                     pAttrib->MoveBackward( nDeleted );
1403                 }
1404             }
1405         }
1406         DBG_ASSERT( !pAttrib->IsFeature() || ( pAttrib->GetLen() == 1 ), "Expand: FeaturesLen != 1" );
1407 
1408         DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Collapse: Attribute distorted!" );
1409         DBG_ASSERT( ( pAttrib->GetEnd() <= Len()) || bDelAttr, "Collapse: Attribute larger than paragraph!" );
1410         if ( bDelAttr )
1411         {
1412             bResort = true;
1413             rItemPool.Remove( *pAttrib->GetItem() );
1414             rAttribs.erase(rAttribs.begin()+nAttr);
1415             nAttr--;
1416         }
1417         else if ( pAttrib->IsEmpty() )
1418             aCharAttribList.SetHasEmptyAttribs(true);
1419 
1420         nAttr++;
1421         pAttrib = GetAttrib(rAttribs, nAttr);
1422     }
1423 
1424     if ( bResort )
1425         aCharAttribList.ResortAttribs();
1426 
1427     if (mpWrongList)
1428         mpWrongList->TextDeleted(nIndex, nDeleted);
1429 
1430 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
1431     CharAttribList::DbgCheckAttribs(aCharAttribList);
1432 #endif
1433 }
1434 
1435 void ContentNode::CopyAndCutAttribs( ContentNode* pPrevNode, SfxItemPool& rPool, bool bKeepEndingAttribs )
1436 {
1437     assert(pPrevNode);
1438 
1439 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
1440     CharAttribList::DbgCheckAttribs(aCharAttribList);
1441     CharAttribList::DbgCheckAttribs(pPrevNode->aCharAttribList);
1442 #endif
1443 
1444     sal_Int32 nCut = pPrevNode->Len();
1445 
1446     sal_Int32 nAttr = 0;
1447     CharAttribList::AttribsType& rPrevAttribs = pPrevNode->GetCharAttribs().GetAttribs();
1448     EditCharAttrib* pAttrib = GetAttrib(rPrevAttribs, nAttr);
1449     while ( pAttrib )
1450     {
1451         if ( pAttrib->GetEnd() < nCut )
1452         {
1453             // remain unchanged...
1454             ;
1455         }
1456         else if ( pAttrib->GetEnd() == nCut )
1457         {
1458             // must be copied as an empty attributes.
1459             if ( bKeepEndingAttribs && !pAttrib->IsFeature() && !aCharAttribList.FindAttrib( pAttrib->GetItem()->Which(), 0 ) )
1460             {
1461                 EditCharAttrib* pNewAttrib = MakeCharAttrib( rPool, *(pAttrib->GetItem()), 0, 0 );
1462                 assert(pNewAttrib);
1463                 aCharAttribList.InsertAttrib( pNewAttrib );
1464             }
1465         }
1466         else if ( pAttrib->IsInside( nCut ) || ( !nCut && !pAttrib->GetStart() && !pAttrib->IsFeature() ) )
1467         {
1468             // If cut is done right at the front then the attribute must be
1469             // kept! Has to be copied and changed.
1470             EditCharAttrib* pNewAttrib = MakeCharAttrib( rPool, *(pAttrib->GetItem()), 0, pAttrib->GetEnd()-nCut );
1471             assert(pNewAttrib);
1472             aCharAttribList.InsertAttrib( pNewAttrib );
1473             pAttrib->GetEnd() = nCut;
1474         }
1475         else
1476         {
1477             // Move all attributes in the current node (this)
1478             CharAttribList::AttribsType::iterator it = rPrevAttribs.begin() + nAttr;
1479             aCharAttribList.InsertAttrib(it->release());
1480             rPrevAttribs.erase(it);
1481             pAttrib->MoveBackward( nCut );
1482             nAttr--;
1483         }
1484         nAttr++;
1485         pAttrib = GetAttrib(rPrevAttribs, nAttr);
1486     }
1487 
1488 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
1489     CharAttribList::DbgCheckAttribs(aCharAttribList);
1490     CharAttribList::DbgCheckAttribs(pPrevNode->aCharAttribList);
1491 #endif
1492 }
1493 
1494 void ContentNode::AppendAttribs( ContentNode* pNextNode )
1495 {
1496     assert(pNextNode);
1497 
1498     sal_Int32 nNewStart = maString.getLength();
1499 
1500 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
1501     CharAttribList::DbgCheckAttribs(aCharAttribList);
1502     CharAttribList::DbgCheckAttribs(pNextNode->aCharAttribList);
1503 #endif
1504 
1505     sal_Int32 nAttr = 0;
1506     CharAttribList::AttribsType& rNextAttribs = pNextNode->GetCharAttribs().GetAttribs();
1507     EditCharAttrib* pAttrib = GetAttrib(rNextAttribs, nAttr);
1508     while ( pAttrib )
1509     {
1510         // Move all attributes in the current node (this)
1511         bool bMelted = false;
1512         if ( ( pAttrib->GetStart() == 0 ) && ( !pAttrib->IsFeature() ) )
1513         {
1514             // Attributes can possibly be summarized as:
1515             sal_Int32 nTmpAttr = 0;
1516             EditCharAttrib* pTmpAttrib = GetAttrib( aCharAttribList.GetAttribs(), nTmpAttr );
1517             while ( !bMelted && pTmpAttrib )
1518             {
1519                 if ( pTmpAttrib->GetEnd() == nNewStart )
1520                 {
1521                     if (pTmpAttrib->Which() == pAttrib->Which())
1522                     {
1523                         // prevent adding 2 0-length attributes at same position
1524                         if ((*(pTmpAttrib->GetItem()) == *(pAttrib->GetItem()))
1525                                 || (0 == pAttrib->GetLen()))
1526                         {
1527                             pTmpAttrib->GetEnd() =
1528                                 pTmpAttrib->GetEnd() + pAttrib->GetLen();
1529                             rNextAttribs.erase(rNextAttribs.begin()+nAttr);
1530                             // Unsubscribe from the pool?!
1531                             bMelted = true;
1532                         }
1533                         else if (0 == pTmpAttrib->GetLen())
1534                         {
1535                             aCharAttribList.Remove(nTmpAttr);
1536                             --nTmpAttr; // to cancel later increment...
1537                         }
1538                     }
1539                 }
1540                 ++nTmpAttr;
1541                 pTmpAttrib = GetAttrib( aCharAttribList.GetAttribs(), nTmpAttr );
1542             }
1543         }
1544 
1545         if ( !bMelted )
1546         {
1547             pAttrib->GetStart() = pAttrib->GetStart() + nNewStart;
1548             pAttrib->GetEnd() = pAttrib->GetEnd() + nNewStart;
1549             CharAttribList::AttribsType::iterator it = rNextAttribs.begin() + nAttr;
1550             aCharAttribList.InsertAttrib(it->release());
1551             rNextAttribs.erase(it);
1552         }
1553         pAttrib = GetAttrib(rNextAttribs, nAttr);
1554     }
1555     // For the Attributes that just moved over:
1556     rNextAttribs.clear();
1557 
1558 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
1559     CharAttribList::DbgCheckAttribs(aCharAttribList);
1560     CharAttribList::DbgCheckAttribs(pNextNode->aCharAttribList);
1561 #endif
1562 }
1563 
1564 void ContentNode::CreateDefFont()
1565 {
1566     // First use the information from the style ...
1567     SfxStyleSheet* pS = aContentAttribs.GetStyleSheet();
1568     if ( pS )
1569         CreateFont( GetCharAttribs().GetDefFont(), pS->GetItemSet() );
1570 
1571     // ... then iron out the hard paragraph formatting...
1572     CreateFont( GetCharAttribs().GetDefFont(),
1573         GetContentAttribs().GetItems(), pS == nullptr );
1574 }
1575 
1576 void ContentNode::SetStyleSheet( SfxStyleSheet* pS, const SvxFont& rFontFromStyle )
1577 {
1578     aContentAttribs.SetStyleSheet( pS );
1579 
1580 
1581     // First use the information from the style ...
1582     GetCharAttribs().GetDefFont() = rFontFromStyle;
1583     // ... then iron out the hard paragraph formatting...
1584     CreateFont( GetCharAttribs().GetDefFont(),
1585                 GetContentAttribs().GetItems(), pS == nullptr );
1586 }
1587 
1588 void ContentNode::SetStyleSheet( SfxStyleSheet* pS, bool bRecalcFont )
1589 {
1590     aContentAttribs.SetStyleSheet( pS );
1591     if ( bRecalcFont )
1592         CreateDefFont();
1593 }
1594 
1595 bool ContentNode::IsFeature( sal_Int32 nPos ) const
1596 {
1597     return maString[nPos] == CH_FEATURE;
1598 }
1599 
1600 sal_Int32 ContentNode::Len() const
1601 {
1602     return maString.getLength();
1603 }
1604 
1605 sal_uLong ContentNode::GetExpandedLen() const
1606 {
1607     sal_uLong nLen = maString.getLength();
1608 
1609     // Fields can be longer than the placeholder in the Node
1610     const CharAttribList::AttribsType& rAttrs = GetCharAttribs().GetAttribs();
1611     for (sal_Int32 nAttr = rAttrs.size(); nAttr; )
1612     {
1613         const EditCharAttrib& rAttr = *rAttrs[--nAttr].get();
1614         if (rAttr.Which() == EE_FEATURE_FIELD)
1615         {
1616             nLen += static_cast<const EditCharAttribField&>(rAttr).GetFieldValue().getLength();
1617             --nLen; // Standalone, to avoid corner cases when previous getLength() returns 0
1618         }
1619     }
1620 
1621     return nLen;
1622 }
1623 
1624 OUString ContentNode::GetExpandedText(sal_Int32 nStartPos, sal_Int32 nEndPos) const
1625 {
1626     if ( nEndPos < 0 || nEndPos > Len() )
1627         nEndPos = Len();
1628 
1629     DBG_ASSERT( nStartPos <= nEndPos, "Start and End reversed?" );
1630 
1631     sal_Int32 nIndex = nStartPos;
1632     OUStringBuffer aStr;
1633     const EditCharAttrib* pNextFeature = GetCharAttribs().FindFeature( nIndex );
1634     while ( nIndex < nEndPos )
1635     {
1636         sal_Int32 nEnd = nEndPos;
1637         if ( pNextFeature && ( pNextFeature->GetStart() < nEnd ) )
1638             nEnd = pNextFeature->GetStart();
1639         else
1640             pNextFeature = nullptr;   // Feature does not interest the below
1641 
1642         DBG_ASSERT( nEnd >= nIndex, "End in front of the index?" );
1643         //!! beware of sub string length  of -1
1644         if (nEnd > nIndex)
1645             aStr.append( std::u16string_view(GetString()).substr(nIndex, nEnd - nIndex) );
1646 
1647         if ( pNextFeature )
1648         {
1649             switch ( pNextFeature->GetItem()->Which() )
1650             {
1651                 case EE_FEATURE_TAB:    aStr.append( "\t" );
1652                 break;
1653                 case EE_FEATURE_LINEBR: aStr.append( "\x0A" );
1654                 break;
1655                 case EE_FEATURE_FIELD:
1656                     aStr.append( static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue() );
1657                 break;
1658                 default:    OSL_FAIL( "What feature?" );
1659             }
1660             pNextFeature = GetCharAttribs().FindFeature( ++nEnd );
1661         }
1662         nIndex = nEnd;
1663     }
1664     return aStr.makeStringAndClear();
1665 }
1666 
1667 void ContentNode::UnExpandPosition( sal_Int32 &rPos, bool bBiasStart )
1668 {
1669     sal_Int32 nOffset = 0;
1670 
1671     const CharAttribList::AttribsType& rAttrs = GetCharAttribs().GetAttribs();
1672     for (size_t nAttr = 0; nAttr < rAttrs.size(); ++nAttr )
1673     {
1674         const EditCharAttrib& rAttr = *rAttrs[nAttr].get();
1675         assert (!(nAttr < rAttrs.size() - 1) ||
1676                 rAttrs[nAttr]->GetStart() <= rAttrs[nAttr + 1]->GetStart());
1677 
1678         nOffset = rAttr.GetStart();
1679 
1680         if (nOffset >= rPos) // happens after the position
1681             return;
1682 
1683         sal_Int32 nChunk = 0;
1684         if (rAttr.Which() == EE_FEATURE_FIELD)
1685         {
1686             nChunk += static_cast<const EditCharAttribField&>(rAttr).GetFieldValue().getLength();
1687             nChunk--; // Character representing the field in the string
1688 
1689             if (nOffset + nChunk >= rPos) // we're inside the field
1690             {
1691                 if (bBiasStart)
1692                     rPos = rAttr.GetStart();
1693                 else
1694                     rPos = rAttr.GetEnd();
1695                 return;
1696             }
1697             // Adjust for the position
1698             rPos -= nChunk;
1699         }
1700     }
1701     assert (rPos <= Len());
1702 }
1703 
1704 /*
1705  * Fields are represented by a single character in the underlying string
1706  * and/or selection, however, they can be expanded to the full value of
1707  * the field. When we're dealing with selection / offsets however we need
1708  * to deal in character positions inside the real (unexpanded) string.
1709  * This method maps us back to character offsets.
1710  */
1711 void ContentNode::UnExpandPositions( sal_Int32 &rStartPos, sal_Int32 &rEndPos )
1712 {
1713     UnExpandPosition( rStartPos, true );
1714     UnExpandPosition( rEndPos, false );
1715 }
1716 
1717 void ContentNode::SetChar(sal_Int32 nPos, sal_Unicode c)
1718 {
1719     maString = maString.replaceAt(nPos, 1, OUString(c));
1720 }
1721 
1722 void ContentNode::Insert(const OUString& rStr, sal_Int32 nPos)
1723 {
1724     maString = maString.replaceAt(nPos, 0, rStr);
1725 }
1726 
1727 void ContentNode::Append(const OUString& rStr)
1728 {
1729     maString += rStr;
1730 }
1731 
1732 void ContentNode::Erase(sal_Int32 nPos)
1733 {
1734     maString = maString.copy(0, nPos);
1735 }
1736 
1737 void ContentNode::Erase(sal_Int32 nPos, sal_Int32 nCount)
1738 {
1739     maString = maString.replaceAt(nPos, nCount, "");
1740 }
1741 
1742 OUString ContentNode::Copy(sal_Int32 nPos) const
1743 {
1744     return maString.copy(nPos);
1745 }
1746 
1747 OUString ContentNode::Copy(sal_Int32 nPos, sal_Int32 nCount) const
1748 {
1749     return maString.copy(nPos, nCount);
1750 }
1751 
1752 sal_Unicode ContentNode::GetChar(sal_Int32 nPos) const
1753 {
1754     return maString[nPos];
1755 }
1756 
1757 void ContentNode::EnsureWrongList()
1758 {
1759     if (!mpWrongList)
1760         CreateWrongList();
1761 }
1762 
1763 WrongList* ContentNode::GetWrongList()
1764 {
1765     return mpWrongList.get();
1766 }
1767 
1768 const WrongList* ContentNode::GetWrongList() const
1769 {
1770     return mpWrongList.get();
1771 }
1772 
1773 void ContentNode::SetWrongList( WrongList* p )
1774 {
1775     mpWrongList.reset(p);
1776 }
1777 
1778 void ContentNode::CreateWrongList()
1779 {
1780     SAL_WARN_IF( mpWrongList && !mpWrongList->empty(), "editeng", "WrongList already exist!");
1781     if (!mpWrongList || !mpWrongList->empty())
1782         mpWrongList.reset(new WrongList);
1783 }
1784 
1785 void ContentNode::DestroyWrongList()
1786 {
1787     mpWrongList.reset();
1788 }
1789 
1790 void ContentNode::dumpAsXml(xmlTextWriterPtr pWriter) const
1791 {
1792     xmlTextWriterStartElement(pWriter, BAD_CAST("ContentNode"));
1793     xmlTextWriterWriteAttribute(pWriter, BAD_CAST("maString"), BAD_CAST(maString.toUtf8().getStr()));
1794     aContentAttribs.dumpAsXml(pWriter);
1795     aCharAttribList.dumpAsXml(pWriter);
1796     xmlTextWriterEndElement(pWriter);
1797 }
1798 
1799 
1800 ContentAttribs::ContentAttribs( SfxItemPool& rPool )
1801 : pStyle(nullptr)
1802 , aAttribSet( rPool, svl::Items<EE_PARA_START, EE_CHAR_END>{} )
1803 {
1804 }
1805 
1806 
1807 SvxTabStop ContentAttribs::FindTabStop( sal_Int32 nCurPos, sal_uInt16 nDefTab )
1808 {
1809     const SvxTabStopItem& rTabs = GetItem( EE_PARA_TABS );
1810     for ( sal_uInt16 i = 0; i < rTabs.Count(); i++ )
1811     {
1812         const SvxTabStop& rTab = rTabs[i];
1813         if ( rTab.GetTabPos() > nCurPos  )
1814             return rTab;
1815     }
1816 
1817     // Determine DefTab ...
1818     SvxTabStop aTabStop;
1819     const sal_Int32 x = nCurPos / nDefTab + 1;
1820     aTabStop.GetTabPos() = nDefTab * x;
1821     return aTabStop;
1822 }
1823 
1824 void ContentAttribs::SetStyleSheet( SfxStyleSheet* pS )
1825 {
1826     bool bStyleChanged = ( pStyle != pS );
1827     pStyle = pS;
1828     // Only when other style sheet, not when current style sheet modified
1829     if ( pStyle && bStyleChanged )
1830     {
1831         // Selectively remove the attributes from the paragraph formatting
1832         // which are specified in the style, so that the attributes of the
1833         // style can have an affect.
1834         const SfxItemSet& rStyleAttribs = pStyle->GetItemSet();
1835         for ( sal_uInt16 nWhich = EE_PARA_START; nWhich <= EE_CHAR_END; nWhich++ )
1836         {
1837             // Don't change bullet on/off
1838             if ( ( nWhich != EE_PARA_BULLETSTATE ) && ( rStyleAttribs.GetItemState( nWhich ) == SfxItemState::SET ) )
1839                 aAttribSet.ClearItem( nWhich );
1840         }
1841     }
1842 }
1843 
1844 const SfxPoolItem& ContentAttribs::GetItem( sal_uInt16 nWhich ) const
1845 {
1846     // Hard paragraph attributes take precedence!
1847     const SfxItemSet* pTakeFrom = &aAttribSet;
1848     if ( pStyle && ( aAttribSet.GetItemState( nWhich, false ) != SfxItemState::SET  ) )
1849         pTakeFrom = &pStyle->GetItemSet();
1850 
1851     return pTakeFrom->Get( nWhich );
1852 }
1853 
1854 bool ContentAttribs::HasItem( sal_uInt16 nWhich ) const
1855 {
1856     bool bHasItem = false;
1857     if ( aAttribSet.GetItemState( nWhich, false ) == SfxItemState::SET  )
1858         bHasItem = true;
1859     else if ( pStyle && pStyle->GetItemSet().GetItemState( nWhich ) == SfxItemState::SET )
1860         bHasItem = true;
1861 
1862     return bHasItem;
1863 }
1864 
1865 void ContentAttribs::dumpAsXml(xmlTextWriterPtr pWriter) const
1866 {
1867     xmlTextWriterStartElement(pWriter, BAD_CAST("ContentAttribs"));
1868     xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("style"), "%s", pStyle->GetName().toUtf8().getStr());
1869     aAttribSet.dumpAsXml(pWriter);
1870     xmlTextWriterEndElement(pWriter);
1871 }
1872 
1873 
1874 ItemList::ItemList() : CurrentItem( 0 )
1875 {
1876 }
1877 
1878 const SfxPoolItem* ItemList::First()
1879 {
1880     CurrentItem = 0;
1881     return aItemPool.empty() ? nullptr : aItemPool[ 0 ];
1882 }
1883 
1884 const SfxPoolItem* ItemList::Next()
1885 {
1886     if ( CurrentItem + 1 < static_cast<sal_Int32>(aItemPool.size()) )
1887     {
1888         ++CurrentItem;
1889         return aItemPool[ CurrentItem ];
1890     }
1891     return nullptr;
1892 }
1893 
1894 void ItemList::Insert( const SfxPoolItem* pItem )
1895 {
1896     aItemPool.push_back( pItem );
1897     CurrentItem = aItemPool.size() - 1;
1898 }
1899 
1900 
1901 EditDoc::EditDoc( SfxItemPool* pPool ) :
1902     nLastCache(0),
1903     pItemPool(pPool ? pPool : new EditEngineItemPool()),
1904     nDefTab(DEFTAB),
1905     bIsVertical(false),
1906     bIsTopToBottomVert(false),
1907     bIsFixedCellHeight(false),
1908     bOwnerOfPool(pPool == nullptr),
1909     bModified(false)
1910 {
1911     // Don't create an empty node, Clear() will be called in EditEngine-CTOR
1912 };
1913 
1914 EditDoc::~EditDoc()
1915 {
1916     ImplDestroyContents();
1917     if ( bOwnerOfPool )
1918         SfxItemPool::Free(pItemPool);
1919 }
1920 
1921 namespace {
1922 
1923 class RemoveEachItemFromPool
1924 {
1925     EditDoc& mrDoc;
1926 public:
1927     explicit RemoveEachItemFromPool(EditDoc& rDoc) : mrDoc(rDoc) {}
1928     void operator() (const std::unique_ptr<ContentNode>& rNode)
1929     {
1930         mrDoc.RemoveItemsFromPool(*rNode);
1931     }
1932 };
1933 
1934 struct ClearSpellErrorsHandler
1935 {
1936     void operator() (std::unique_ptr<ContentNode> const & rNode)
1937     {
1938         rNode->DestroyWrongList();
1939     }
1940 };
1941 
1942 }
1943 
1944 void EditDoc::ImplDestroyContents()
1945 {
1946     std::for_each(maContents.begin(), maContents.end(), RemoveEachItemFromPool(*this));
1947     maContents.clear();
1948 }
1949 
1950 void EditDoc::RemoveItemsFromPool(const ContentNode& rNode)
1951 {
1952     for (sal_Int32 nAttr = 0; nAttr < rNode.GetCharAttribs().Count(); ++nAttr)
1953     {
1954         const EditCharAttrib& rAttr = *rNode.GetCharAttribs().GetAttribs()[nAttr].get();
1955         GetItemPool().Remove(*rAttr.GetItem());
1956     }
1957 }
1958 
1959 void CreateFont( SvxFont& rFont, const SfxItemSet& rSet, bool bSearchInParent, SvtScriptType nScriptType )
1960 {
1961     vcl::Font aPrevFont( rFont );
1962     rFont.SetAlignment( ALIGN_BASELINE );
1963     rFont.SetTransparent( true );
1964 
1965     sal_uInt16 nWhich_FontInfo = GetScriptItemId( EE_CHAR_FONTINFO, nScriptType );
1966     sal_uInt16 nWhich_Language = GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType );
1967     sal_uInt16 nWhich_FontHeight = GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType );
1968     sal_uInt16 nWhich_Weight = GetScriptItemId( EE_CHAR_WEIGHT, nScriptType );
1969     sal_uInt16 nWhich_Italic = GetScriptItemId( EE_CHAR_ITALIC, nScriptType );
1970 
1971     if ( bSearchInParent || ( rSet.GetItemState( nWhich_FontInfo ) == SfxItemState::SET ) )
1972     {
1973         const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(rSet.Get( nWhich_FontInfo ));
1974         rFont.SetFamilyName( rFontItem.GetFamilyName() );
1975         rFont.SetFamily( rFontItem.GetFamily() );
1976         rFont.SetPitch( rFontItem.GetPitch() );
1977         rFont.SetCharSet( rFontItem.GetCharSet() );
1978     }
1979     if ( bSearchInParent || ( rSet.GetItemState( nWhich_Language ) == SfxItemState::SET ) )
1980         rFont.SetLanguage( static_cast<const SvxLanguageItem&>(rSet.Get( nWhich_Language )).GetLanguage() );
1981     if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_COLOR ) == SfxItemState::SET ) )
1982         rFont.SetColor( rSet.Get( EE_CHAR_COLOR ).GetValue() );
1983     if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_BKGCOLOR ) == SfxItemState::SET ) )
1984         rFont.SetFillColor( rSet.Get( EE_CHAR_BKGCOLOR ).GetValue() );
1985     if ( bSearchInParent || ( rSet.GetItemState( nWhich_FontHeight ) == SfxItemState::SET ) )
1986         rFont.SetFontSize( Size( rFont.GetFontSize().Width(), static_cast<const SvxFontHeightItem&>(rSet.Get( nWhich_FontHeight ) ).GetHeight() ) );
1987     if ( bSearchInParent || ( rSet.GetItemState( nWhich_Weight ) == SfxItemState::SET ) )
1988         rFont.SetWeight( static_cast<const SvxWeightItem&>(rSet.Get( nWhich_Weight )).GetWeight() );
1989     if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_UNDERLINE ) == SfxItemState::SET ) )
1990         rFont.SetUnderline( rSet.Get( EE_CHAR_UNDERLINE ).GetLineStyle() );
1991     if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_OVERLINE ) == SfxItemState::SET ) )
1992         rFont.SetOverline( rSet.Get( EE_CHAR_OVERLINE ).GetLineStyle() );
1993     if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_STRIKEOUT ) == SfxItemState::SET ) )
1994         rFont.SetStrikeout( rSet.Get( EE_CHAR_STRIKEOUT ).GetStrikeout() );
1995     if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_CASEMAP ) == SfxItemState::SET ) )
1996         rFont.SetCaseMap( rSet.Get( EE_CHAR_CASEMAP ).GetCaseMap() );
1997     if ( bSearchInParent || ( rSet.GetItemState( nWhich_Italic ) == SfxItemState::SET ) )
1998         rFont.SetItalic( static_cast<const SvxPostureItem&>(rSet.Get( nWhich_Italic )).GetPosture() );
1999     if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_OUTLINE ) == SfxItemState::SET ) )
2000         rFont.SetOutline( rSet.Get( EE_CHAR_OUTLINE ).GetValue() );
2001     if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_SHADOW ) == SfxItemState::SET ) )
2002         rFont.SetShadow( rSet.Get( EE_CHAR_SHADOW ).GetValue() );
2003     if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_ESCAPEMENT ) == SfxItemState::SET ) )
2004     {
2005         const SvxEscapementItem& rEsc = rSet.Get( EE_CHAR_ESCAPEMENT );
2006 
2007         sal_uInt16 const nProp = rEsc.GetProportionalHeight();
2008         rFont.SetPropr( static_cast<sal_uInt8>(nProp) );
2009 
2010         short nEsc = rEsc.GetEsc();
2011         if ( nEsc == DFLT_ESC_AUTO_SUPER )
2012             nEsc = 100 - nProp;
2013         else if ( nEsc == DFLT_ESC_AUTO_SUB )
2014             nEsc = sal::static_int_cast< short >( -( 100 - nProp ) );
2015         rFont.SetEscapement( nEsc );
2016     }
2017     if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_PAIRKERNING ) == SfxItemState::SET ) )
2018         rFont.SetKerning( rSet.Get( EE_CHAR_PAIRKERNING ).GetValue() ? FontKerning::FontSpecific : FontKerning::NONE );
2019     if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_KERNING ) == SfxItemState::SET ) )
2020         rFont.SetFixKerning( rSet.Get( EE_CHAR_KERNING ).GetValue() );
2021     if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_WLM ) == SfxItemState::SET ) )
2022         rFont.SetWordLineMode( rSet.Get( EE_CHAR_WLM ).GetValue() );
2023     if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_EMPHASISMARK ) == SfxItemState::SET ) )
2024         rFont.SetEmphasisMark( rSet.Get( EE_CHAR_EMPHASISMARK ).GetEmphasisMark() );
2025     if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_RELIEF ) == SfxItemState::SET ) )
2026         rFont.SetRelief( rSet.Get( EE_CHAR_RELIEF ).GetValue() );
2027 
2028     // Operator == compares the individual members of the font if the impl pointer is
2029     // not equal. If all members are the same, this assignment makes
2030     // sure that both also point to the same internal instance of the font.
2031     // To avoid this assignment, you would need to check in
2032     // every if statement above whether or not the new value differs from the
2033     // old value before making an assignment.
2034     if ( rFont == aPrevFont  )
2035         rFont = aPrevFont;  // => The same ImpPointer for IsSameInstance
2036 }
2037 
2038 void EditDoc::CreateDefFont( bool bUseStyles )
2039 {
2040     SfxItemSet aTmpSet( GetItemPool(), svl::Items<EE_PARA_START, EE_CHAR_END>{} );
2041     CreateFont( aDefFont, aTmpSet );
2042     aDefFont.SetVertical( IsVertical() );
2043     aDefFont.SetOrientation( IsVertical() ? (IsTopToBottom() ? 2700 : 900) : 0 );
2044 
2045     for ( sal_Int32 nNode = 0; nNode < Count(); nNode++ )
2046     {
2047         ContentNode* pNode = GetObject( nNode );
2048         pNode->GetCharAttribs().GetDefFont() = aDefFont;
2049         if ( bUseStyles )
2050             pNode->CreateDefFont();
2051     }
2052 }
2053 
2054 sal_Int32 EditDoc::GetPos(const ContentNode* p) const
2055 {
2056     return FastGetPos(maContents, p, nLastCache);
2057 }
2058 
2059 const ContentNode* EditDoc::GetObject(sal_Int32 nPos) const
2060 {
2061     return 0 <= nPos && nPos < static_cast<sal_Int32>(maContents.size()) ? maContents[nPos].get() : nullptr;
2062 }
2063 
2064 ContentNode* EditDoc::GetObject(sal_Int32 nPos)
2065 {
2066     return 0 <= nPos && nPos < static_cast<sal_Int32>(maContents.size()) ? maContents[nPos].get() : nullptr;
2067 }
2068 
2069 const ContentNode* EditDoc::operator[](sal_Int32 nPos) const
2070 {
2071     return GetObject(nPos);
2072 }
2073 
2074 ContentNode* EditDoc::operator[](sal_Int32 nPos)
2075 {
2076     return GetObject(nPos);
2077 }
2078 
2079 void EditDoc::Insert(sal_Int32 nPos, ContentNode* p)
2080 {
2081     if (nPos < 0 || nPos == SAL_MAX_INT32)
2082     {
2083         SAL_WARN( "editeng", "EditDoc::Insert - overflow pos " << nPos);
2084         return;
2085     }
2086     maContents.insert(maContents.begin()+nPos, std::unique_ptr<ContentNode>(p));
2087 }
2088 
2089 void EditDoc::Remove(sal_Int32 nPos)
2090 {
2091     if (nPos < 0 || nPos >= static_cast<sal_Int32>(maContents.size()))
2092     {
2093         SAL_WARN( "editeng", "EditDoc::Remove - out of bounds pos " << nPos);
2094         return;
2095     }
2096     maContents.erase(maContents.begin() + nPos);
2097 }
2098 
2099 void EditDoc::Release(sal_Int32 nPos)
2100 {
2101     if (nPos < 0 || nPos >= static_cast<sal_Int32>(maContents.size()))
2102     {
2103         SAL_WARN( "editeng", "EditDoc::Release - out of bounds pos " << nPos);
2104         return;
2105     }
2106     (void)maContents[nPos].release();
2107     maContents.erase(maContents.begin() + nPos);
2108 }
2109 
2110 sal_Int32 EditDoc::Count() const
2111 {
2112     size_t nSize = maContents.size();
2113     if (nSize > SAL_MAX_INT32)
2114     {
2115         SAL_WARN( "editeng", "EditDoc::Count - overflow " << nSize);
2116         return SAL_MAX_INT32;
2117     }
2118     return nSize;
2119 }
2120 
2121 OUString EditDoc::GetSepStr( LineEnd eEnd )
2122 {
2123     if ( eEnd == LINEEND_CR )
2124         return "\015"; // 0x0d
2125     if ( eEnd == LINEEND_LF )
2126         return "\012"; // 0x0a
2127     return "\015\012"; // 0x0d, 0x0a
2128 }
2129 
2130 OUString EditDoc::GetText( LineEnd eEnd ) const
2131 {
2132     const sal_Int32 nNodes = Count();
2133     if (nNodes == 0)
2134         return OUString();
2135 
2136     const OUString aSep = EditDoc::GetSepStr( eEnd );
2137     const sal_Int32 nSepSize = aSep.getLength();
2138     const sal_uInt32 nLen = GetTextLen() + (nNodes - 1)*nSepSize;
2139 
2140     OUStringBuffer aBuffer(nLen + 16); // leave some slack
2141 
2142     for ( sal_Int32 nNode = 0; nNode < nNodes; nNode++ )
2143     {
2144         if ( nSepSize && nNode>0 )
2145         {
2146             aBuffer.append(aSep);
2147         }
2148         aBuffer.append(GetParaAsString( GetObject(nNode) ));
2149     }
2150 
2151     return aBuffer.makeStringAndClear();
2152 }
2153 
2154 OUString EditDoc::GetParaAsString( sal_Int32 nNode ) const
2155 {
2156     return GetParaAsString( GetObject( nNode ) );
2157 }
2158 
2159 OUString EditDoc::GetParaAsString(
2160     const ContentNode* pNode, sal_Int32 nStartPos, sal_Int32 nEndPos)
2161 {
2162     return pNode->GetExpandedText(nStartPos, nEndPos);
2163 }
2164 
2165 EditPaM EditDoc::GetStartPaM() const
2166 {
2167     ContentNode* p = const_cast<ContentNode*>(GetObject(0));
2168     return EditPaM(p, 0);
2169 }
2170 
2171 EditPaM EditDoc::GetEndPaM() const
2172 {
2173     ContentNode* pLastNode = const_cast<ContentNode*>(GetObject(Count()-1));
2174     return EditPaM( pLastNode, pLastNode->Len() );
2175 }
2176 
2177 sal_uLong EditDoc::GetTextLen() const
2178 {
2179     sal_uLong nLen = 0;
2180     for ( sal_Int32 nNode = 0; nNode < Count(); nNode++ )
2181     {
2182         const ContentNode* pNode = GetObject( nNode );
2183         nLen += pNode->GetExpandedLen();
2184     }
2185     return nLen;
2186 }
2187 
2188 EditPaM EditDoc::Clear()
2189 {
2190     ImplDestroyContents();
2191 
2192     ContentNode* pNode = new ContentNode( GetItemPool() );
2193     Insert(0, pNode);
2194 
2195     CreateDefFont(false);
2196 
2197     SetModified(false);
2198 
2199     return EditPaM( pNode, 0 );
2200 }
2201 
2202 void EditDoc::ClearSpellErrors()
2203 {
2204     std::for_each(maContents.begin(), maContents.end(), ClearSpellErrorsHandler());
2205 }
2206 
2207 void EditDoc::SetModified( bool b )
2208 {
2209     bModified = b;
2210     if ( bModified )
2211     {
2212         aModifyHdl.Call( nullptr );
2213     }
2214 }
2215 
2216 EditPaM EditDoc::RemoveText()
2217 {
2218     // Keep the old ItemSet, to keep the chart Font.
2219     ContentNode* pPrevFirstNode = GetObject(0);
2220     SfxStyleSheet* pPrevStyle = pPrevFirstNode->GetStyleSheet();
2221     SfxItemSet aPrevSet( pPrevFirstNode->GetContentAttribs().GetItems() );
2222     vcl::Font aPrevFont( pPrevFirstNode->GetCharAttribs().GetDefFont() );
2223 
2224     ImplDestroyContents();
2225 
2226     ContentNode* pNode = new ContentNode( GetItemPool() );
2227     Insert(0, pNode);
2228 
2229     pNode->SetStyleSheet(pPrevStyle, false);
2230     pNode->GetContentAttribs().GetItems().Set( aPrevSet );
2231     pNode->GetCharAttribs().GetDefFont() = aPrevFont;
2232 
2233     SetModified(true);
2234 
2235     return EditPaM( pNode, 0 );
2236 }
2237 
2238 EditPaM EditDoc::InsertText( EditPaM aPaM, const OUString& rStr )
2239 {
2240     DBG_ASSERT( rStr.indexOf( 0x0A ) == -1, "EditDoc::InsertText: Newlines prohibited in paragraph!" );
2241     DBG_ASSERT( rStr.indexOf( 0x0D ) == -1, "EditDoc::InsertText: Newlines prohibited in paragraph!" );
2242     DBG_ASSERT( rStr.indexOf( '\t' ) == -1, "EditDoc::InsertText: Newlines prohibited in paragraph!" );
2243     assert(aPaM.GetNode());
2244 
2245     aPaM.GetNode()->Insert( rStr, aPaM.GetIndex() );
2246     aPaM.GetNode()->ExpandAttribs( aPaM.GetIndex(), rStr.getLength(), GetItemPool() );
2247     aPaM.SetIndex( aPaM.GetIndex() + rStr.getLength() );
2248 
2249     SetModified( true );
2250 
2251     return aPaM;
2252 }
2253 
2254 EditPaM EditDoc::InsertParaBreak( EditPaM aPaM, bool bKeepEndingAttribs )
2255 {
2256     assert(aPaM.GetNode());
2257     ContentNode* pCurNode = aPaM.GetNode();
2258     sal_Int32 nPos = GetPos( pCurNode );
2259     OUString aStr = aPaM.GetNode()->Copy( aPaM.GetIndex() );
2260     aPaM.GetNode()->Erase( aPaM.GetIndex() );
2261 
2262     // the paragraph attributes...
2263     ContentAttribs aContentAttribs( aPaM.GetNode()->GetContentAttribs() );
2264 
2265     // for a new paragraph we like to have the bullet/numbering visible by default
2266     aContentAttribs.GetItems().Put( SfxBoolItem( EE_PARA_BULLETSTATE, true) );
2267 
2268     // ContentNode constructor copies also the paragraph attributes
2269     ContentNode* pNode = new ContentNode( aStr, aContentAttribs );
2270 
2271     // Copy the Default Font
2272     pNode->GetCharAttribs().GetDefFont() = aPaM.GetNode()->GetCharAttribs().GetDefFont();
2273     SfxStyleSheet* pStyle = aPaM.GetNode()->GetStyleSheet();
2274     if ( pStyle )
2275     {
2276         OUString aFollow( pStyle->GetFollow() );
2277         if ( !aFollow.isEmpty() && ( aFollow != pStyle->GetName() ) )
2278         {
2279             SfxStyleSheetBase* pNext = pStyle->GetPool()->Find( aFollow, pStyle->GetFamily() );
2280             pNode->SetStyleSheet( static_cast<SfxStyleSheet*>(pNext) );
2281         }
2282     }
2283 
2284     // Character attributes may need to be copied or trimmed:
2285     pNode->CopyAndCutAttribs( aPaM.GetNode(), GetItemPool(), bKeepEndingAttribs );
2286 
2287     Insert(nPos+1, pNode);
2288 
2289     SetModified(true);
2290 
2291     aPaM.SetNode( pNode );
2292     aPaM.SetIndex( 0 );
2293     return aPaM;
2294 }
2295 
2296 EditPaM EditDoc::InsertFeature( EditPaM aPaM, const SfxPoolItem& rItem  )
2297 {
2298     assert(aPaM.GetNode());
2299 
2300     aPaM.GetNode()->Insert( OUString(CH_FEATURE), aPaM.GetIndex() );
2301     aPaM.GetNode()->ExpandAttribs( aPaM.GetIndex(), 1, GetItemPool() );
2302 
2303     // Create a feature-attribute for the feature...
2304     EditCharAttrib* pAttrib = MakeCharAttrib( GetItemPool(), rItem, aPaM.GetIndex(), aPaM.GetIndex()+1 );
2305     assert(pAttrib);
2306     aPaM.GetNode()->GetCharAttribs().InsertAttrib( pAttrib );
2307 
2308     SetModified( true );
2309 
2310     aPaM.SetIndex( aPaM.GetIndex() + 1 );
2311     return aPaM;
2312 }
2313 
2314 EditPaM EditDoc::ConnectParagraphs( ContentNode* pLeft, ContentNode* pRight )
2315 {
2316     const EditPaM aPaM( pLeft, pLeft->Len() );
2317 
2318     // First the attributes, otherwise nLen will not be correct!
2319     pLeft->AppendAttribs( pRight );
2320     // then the Text...
2321     pLeft->Append(pRight->GetString());
2322 
2323     // the one to the right disappears.
2324     RemoveItemsFromPool(*pRight);
2325     sal_Int32 nRight = GetPos( pRight );
2326     Remove( nRight );
2327 
2328     SetModified(true);
2329 
2330     return aPaM;
2331 }
2332 
2333 void EditDoc::RemoveChars( EditPaM aPaM, sal_Int32 nChars )
2334 {
2335     // Maybe remove Features!
2336     aPaM.GetNode()->Erase( aPaM.GetIndex(), nChars );
2337     aPaM.GetNode()->CollapseAttribs( aPaM.GetIndex(), nChars, GetItemPool() );
2338 
2339     SetModified( true );
2340 }
2341 
2342 void EditDoc::InsertAttribInSelection( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, const SfxPoolItem& rPoolItem )
2343 {
2344     assert(pNode);
2345     DBG_ASSERT( nEnd <= pNode->Len(), "InsertAttrib: Attribute too large!" );
2346 
2347     // for Optimization:
2348     // This ends at the beginning of the selection => can be expanded
2349     EditCharAttrib* pEndingAttrib = nullptr;
2350     // This starts at the end of the selection => can be expanded
2351     EditCharAttrib* pStartingAttrib = nullptr;
2352 
2353     DBG_ASSERT( nStart <= nEnd, "Small miscalculations in InsertAttribInSelection" );
2354 
2355     RemoveAttribs( pNode, nStart, nEnd, pStartingAttrib, pEndingAttrib, rPoolItem.Which() );
2356 
2357     if ( pStartingAttrib && pEndingAttrib &&
2358          ( *(pStartingAttrib->GetItem()) == rPoolItem ) &&
2359          ( *(pEndingAttrib->GetItem()) == rPoolItem ) )
2360     {
2361         // Will become a large Attribute.
2362         pEndingAttrib->GetEnd() = pStartingAttrib->GetEnd();
2363         GetItemPool().Remove( *(pStartingAttrib->GetItem()) );
2364         pNode->GetCharAttribs().Remove(pStartingAttrib);
2365     }
2366     else if ( pStartingAttrib && ( *(pStartingAttrib->GetItem()) == rPoolItem ) )
2367         pStartingAttrib->GetStart() = nStart;
2368     else if ( pEndingAttrib && ( *(pEndingAttrib->GetItem()) == rPoolItem ) )
2369         pEndingAttrib->GetEnd() = nEnd;
2370     else
2371         InsertAttrib( rPoolItem, pNode, nStart, nEnd );
2372 
2373     if ( pStartingAttrib )
2374         pNode->GetCharAttribs().ResortAttribs();
2375 
2376     SetModified(true);
2377 }
2378 
2379 bool EditDoc::RemoveAttribs( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, sal_uInt16 nWhich )
2380 {
2381     EditCharAttrib* pStarting;
2382     EditCharAttrib* pEnding;
2383     return RemoveAttribs( pNode, nStart, nEnd, pStarting, pEnding, nWhich );
2384 }
2385 
2386 bool EditDoc::RemoveAttribs( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, EditCharAttrib*& rpStarting, EditCharAttrib*& rpEnding, sal_uInt16 nWhich )
2387 {
2388 
2389     assert(pNode);
2390     DBG_ASSERT( nEnd <= pNode->Len(), "InsertAttrib: Attribute too large!" );
2391 
2392     // This ends at the beginning of the selection => can be expanded
2393     rpEnding = nullptr;
2394     // This starts at the end of the selection => can be expanded
2395     rpStarting = nullptr;
2396 
2397     bool bChanged = false;
2398 
2399     DBG_ASSERT( nStart <= nEnd, "Small miscalculations in InsertAttribInSelection" );
2400 
2401 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
2402     CharAttribList::DbgCheckAttribs(pNode->GetCharAttribs());
2403 #endif
2404 
2405     // iterate over the attributes ...
2406     sal_Int32 nAttr = 0;
2407     CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs();
2408     EditCharAttrib* pAttr = GetAttrib(rAttribs, nAttr);
2409     while ( pAttr )
2410     {
2411         bool bRemoveAttrib = false;
2412         sal_uInt16 nAttrWhich = pAttr->Which();
2413         if ( ( nAttrWhich < EE_FEATURE_START ) && ( !nWhich || ( nAttrWhich == nWhich ) ) )
2414         {
2415             // Attribute starts in Selection
2416             if ( ( pAttr->GetStart() >= nStart ) && ( pAttr->GetStart() <= nEnd ) )
2417             {
2418                 bChanged = true;
2419                 if ( pAttr->GetEnd() > nEnd )
2420                 {
2421                     pAttr->GetStart() = nEnd;   // then it starts after this
2422                     rpStarting = pAttr;
2423                     if ( nWhich )
2424                         break;  // There can be no further attributes here
2425                 }
2426                 else if ( !pAttr->IsFeature() || ( pAttr->GetStart() == nStart ) )
2427                 {
2428                     // Delete feature only if on the exact spot
2429                     bRemoveAttrib = true;
2430                 }
2431             }
2432 
2433             // Attribute ends in Selection
2434             else if ( ( pAttr->GetEnd() >= nStart ) && ( pAttr->GetEnd() <= nEnd ) )
2435             {
2436                 bChanged = true;
2437                 if ( ( pAttr->GetStart() < nStart ) && !pAttr->IsFeature() )
2438                 {
2439                     pAttr->GetEnd() = nStart;   // then it ends here
2440                     rpEnding = pAttr;
2441                 }
2442                 else if ( !pAttr->IsFeature() || ( pAttr->GetStart() == nStart ) )
2443                 {
2444                     // Delete feature only if on the exact spot
2445                     bRemoveAttrib = true;
2446                 }
2447             }
2448             // Attribute overlaps the selection
2449             else if ( ( pAttr->GetStart() <= nStart ) && ( pAttr->GetEnd() >= nEnd ) )
2450             {
2451                 bChanged = true;
2452                 if ( pAttr->GetStart() == nStart )
2453                 {
2454                     pAttr->GetStart() = nEnd;
2455                     rpStarting = pAttr;
2456                     if ( nWhich )
2457                         break;  // There can be further attributes!
2458                 }
2459                 else if ( pAttr->GetEnd() == nEnd )
2460                 {
2461                     pAttr->GetEnd() = nStart;
2462                     rpEnding = pAttr;
2463                     if ( nWhich )
2464                         break;  // There can be further attributes!
2465                 }
2466                 else // Attribute must be split ...
2467                 {
2468                     sal_Int32 nOldEnd = pAttr->GetEnd();
2469                     pAttr->GetEnd() = nStart;
2470                     rpEnding = pAttr;
2471                     InsertAttrib( *pAttr->GetItem(), pNode, nEnd, nOldEnd );
2472                     if ( nWhich )
2473                         break;  // There can be further attributes!
2474                 }
2475             }
2476         }
2477         if ( bRemoveAttrib )
2478         {
2479             DBG_ASSERT( ( pAttr != rpStarting ) && ( pAttr != rpEnding ), "Delete and retain the same attribute?" );
2480             DBG_ASSERT( !pAttr->IsFeature(), "RemoveAttribs: Remove a feature?!" );
2481             GetItemPool().Remove( *pAttr->GetItem() );
2482             rAttribs.erase(rAttribs.begin()+nAttr);
2483             nAttr--;
2484         }
2485         nAttr++;
2486         pAttr = GetAttrib(rAttribs, nAttr);
2487     }
2488 
2489     if ( bChanged )
2490     {
2491         // char attributes need to be sorted by start again
2492         pNode->GetCharAttribs().ResortAttribs();
2493         SetModified(true);
2494     }
2495 
2496 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
2497     CharAttribList::DbgCheckAttribs(pNode->GetCharAttribs());
2498 #endif
2499 
2500     return bChanged;
2501 }
2502 
2503 void EditDoc::InsertAttrib( const SfxPoolItem& rPoolItem, ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd )
2504 {
2505     // This method no longer checks whether a corresponding attribute already
2506     // exists at this place!
2507     EditCharAttrib* pAttrib = MakeCharAttrib( GetItemPool(), rPoolItem, nStart, nEnd );
2508     assert(pAttrib);
2509     pNode->GetCharAttribs().InsertAttrib( pAttrib );
2510 
2511     SetModified( true );
2512 }
2513 
2514 void EditDoc::InsertAttrib( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, const SfxPoolItem& rPoolItem )
2515 {
2516     if ( nStart != nEnd )
2517     {
2518         InsertAttribInSelection( pNode, nStart, nEnd, rPoolItem );
2519     }
2520     else
2521     {
2522         // Check whether already a new attribute with WhichId exists at this place:
2523         CharAttribList& rAttrList = pNode->GetCharAttribs();
2524         EditCharAttrib* pAttr = rAttrList.FindEmptyAttrib( rPoolItem.Which(), nStart );
2525         if ( pAttr )
2526         {
2527             // Remove attribute...
2528             rAttrList.Remove(pAttr);
2529         }
2530 
2531         // check whether 'the same' attribute exist at this place.
2532         pAttr = rAttrList.FindAttrib( rPoolItem.Which(), nStart );
2533         if ( pAttr )
2534         {
2535             if ( pAttr->IsInside( nStart ) )    // split
2536             {
2537                 // check again if really splitting, or return !
2538                 sal_Int32 nOldEnd = pAttr->GetEnd();
2539                 pAttr->GetEnd() = nStart;
2540                 EditCharAttrib* pNew = MakeCharAttrib( GetItemPool(), *(pAttr->GetItem()), nStart, nOldEnd );
2541                 rAttrList.InsertAttrib(pNew);
2542             }
2543             else if ( pAttr->GetEnd() == nStart )
2544             {
2545                 DBG_ASSERT( !pAttr->IsEmpty(), "Still an empty attribute?" );
2546                 // Check if exactly the same attribute
2547                 if ( *(pAttr->GetItem()) == rPoolItem )
2548                     return;
2549             }
2550         }
2551         InsertAttrib( rPoolItem, pNode, nStart, nStart );
2552     }
2553 
2554     SetModified( true );
2555 }
2556 
2557 void EditDoc::FindAttribs( ContentNode* pNode, sal_Int32 nStartPos, sal_Int32 nEndPos, SfxItemSet& rCurSet )
2558 {
2559     assert(pNode);
2560     DBG_ASSERT( nStartPos <= nEndPos, "Invalid region!" );
2561 
2562     sal_uInt16 nAttr = 0;
2563     EditCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
2564     // No Selection...
2565     if ( nStartPos == nEndPos )
2566     {
2567         while ( pAttr && ( pAttr->GetStart() <= nEndPos) )
2568         {
2569             const SfxPoolItem* pItem = nullptr;
2570             // Attribute is about...
2571             if ( ( pAttr->GetStart() < nStartPos ) && ( pAttr->GetEnd() > nStartPos ) )
2572                 pItem = pAttr->GetItem();
2573             // Attribute ending here is not empty
2574             else if ( ( pAttr->GetStart() < nStartPos ) && ( pAttr->GetEnd() == nStartPos ) )
2575             {
2576                 if ( !pNode->GetCharAttribs().FindEmptyAttrib( pAttr->GetItem()->Which(), nStartPos ) )
2577                     pItem = pAttr->GetItem();
2578             }
2579             // Attribute ending here is empty
2580             else if ( ( pAttr->GetStart() == nStartPos ) && ( pAttr->GetEnd() == nStartPos ) )
2581             {
2582                 pItem = pAttr->GetItem();
2583             }
2584             // Attribute starts here
2585             else if ( ( pAttr->GetStart() == nStartPos ) && ( pAttr->GetEnd() > nStartPos ) )
2586             {
2587                 if ( nStartPos == 0 )   // special case
2588                     pItem = pAttr->GetItem();
2589             }
2590 
2591             if ( pItem )
2592             {
2593                 sal_uInt16 nWhich = pItem->Which();
2594                 if ( rCurSet.GetItemState( nWhich ) == SfxItemState::DEFAULT )
2595                 {
2596                     rCurSet.Put( *pItem );
2597                 }
2598                 else if ( rCurSet.GetItemState( nWhich ) == SfxItemState::SET )
2599                 {
2600                     const SfxPoolItem& rItem = rCurSet.Get( nWhich );
2601                     if ( rItem != *pItem )
2602                     {
2603                         rCurSet.InvalidateItem( nWhich );
2604                     }
2605                 }
2606             }
2607             nAttr++;
2608             pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
2609         }
2610     }
2611     else    // Selection
2612     {
2613         while ( pAttr && ( pAttr->GetStart() < nEndPos) )
2614         {
2615             const SfxPoolItem* pItem = nullptr;
2616             // Attribute is about...
2617             if ( ( pAttr->GetStart() <= nStartPos ) && ( pAttr->GetEnd() >= nEndPos ) )
2618                 pItem = pAttr->GetItem();
2619             // Attribute starts right in the middle ...
2620             else if ( pAttr->GetStart() >= nStartPos )
2621             {
2622                 // !!! pItem = pAttr->GetItem();
2623                 // PItem is simply not enough, since one for example in case
2624                 // of Shadow, would never find an unequal item, since such a
2625                 // item represents its presence by absence!
2626                 // If (...)
2627                 // It needs to be examined on exactly the same attribute at the
2628                 // break point, which is quite expensive.
2629                 // Since optimization is done when inserting the attributes
2630                 // this case does not appear so fast...
2631                 // So based on the need for speed:
2632                 rCurSet.InvalidateItem( pAttr->GetItem()->Which() );
2633 
2634             }
2635             // Attribute ends in the middle of it ...
2636             else if ( pAttr->GetEnd() > nStartPos )
2637             {
2638                 rCurSet.InvalidateItem( pAttr->GetItem()->Which() );
2639             }
2640 
2641             if ( pItem )
2642             {
2643                 sal_uInt16 nWhich = pItem->Which();
2644                 if ( rCurSet.GetItemState( nWhich ) == SfxItemState::DEFAULT )
2645                 {
2646                     rCurSet.Put( *pItem );
2647                 }
2648                 else if ( rCurSet.GetItemState( nWhich ) == SfxItemState::SET )
2649                 {
2650                     const SfxPoolItem& rItem = rCurSet.Get( nWhich );
2651                     if ( rItem != *pItem )
2652                     {
2653                         rCurSet.InvalidateItem( nWhich );
2654                     }
2655                 }
2656             }
2657             nAttr++;
2658             pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
2659         }
2660     }
2661 }
2662 
2663 void EditDoc::dumpAsXml(xmlTextWriterPtr pWriter) const
2664 {
2665     bool bOwns = false;
2666     if (!pWriter)
2667     {
2668         pWriter = xmlNewTextWriterFilename("editdoc.xml", 0);
2669         xmlTextWriterSetIndent(pWriter,1);
2670         xmlTextWriterSetIndentString(pWriter, BAD_CAST("  "));
2671         xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr);
2672         bOwns = true;
2673     }
2674 
2675     xmlTextWriterStartElement(pWriter, BAD_CAST("EditDoc"));
2676     for (auto const & i : maContents)
2677     {
2678         i->dumpAsXml(pWriter);
2679     }
2680     xmlTextWriterEndElement(pWriter);
2681 
2682     if (bOwns)
2683     {
2684        xmlTextWriterEndDocument(pWriter);
2685        xmlFreeTextWriter(pWriter);
2686     }
2687 }
2688 
2689 
2690 namespace {
2691 
2692 struct LessByStart
2693 {
2694     bool operator() (const std::unique_ptr<EditCharAttrib>& left, const std::unique_ptr<EditCharAttrib>& right) const
2695     {
2696         return left->GetStart() < right->GetStart();
2697     }
2698 };
2699 
2700 }
2701 
2702 CharAttribList::CharAttribList()
2703 : aAttribs()
2704 , aDefFont()
2705 , bHasEmptyAttribs(false)
2706 {
2707 }
2708 
2709 CharAttribList::~CharAttribList()
2710 {
2711 }
2712 
2713 void CharAttribList::InsertAttrib( EditCharAttrib* pAttrib )
2714 {
2715 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!
2716 // optimize: binary search?    !
2717 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!
2718 
2719     // Maybe just simply iterate backwards:
2720     // The most common and critical case: Attributes are already sorted
2721     // (InsertBinTextObject!) binary search would not be optimal here.
2722     // => Would bring something!
2723 
2724     const sal_Int32 nStart = pAttrib->GetStart(); // may be better for Comp.Opt.
2725 
2726 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
2727     CharAttribList::DbgCheckAttribs(*this);
2728 #endif
2729 
2730     if ( pAttrib->IsEmpty() )
2731         bHasEmptyAttribs = true;
2732 
2733     bool bInsert(true);
2734     for (sal_Int32 i = 0, n = aAttribs.size(); i < n; ++i)
2735     {
2736         const EditCharAttrib& rCurAttrib = *aAttribs[i].get();
2737         if (rCurAttrib.GetStart() > nStart)
2738         {
2739             aAttribs.insert(aAttribs.begin()+i, std::unique_ptr<EditCharAttrib>(pAttrib));
2740             bInsert = false;
2741             break;
2742         }
2743     }
2744 
2745     if (bInsert) aAttribs.push_back(std::unique_ptr<EditCharAttrib>(pAttrib));
2746 
2747 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
2748     CharAttribList::DbgCheckAttribs(*this);
2749 #endif
2750 }
2751 
2752 void CharAttribList::ResortAttribs()
2753 {
2754     std::sort(aAttribs.begin(), aAttribs.end(), LessByStart());
2755 
2756 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
2757     CharAttribList::DbgCheckAttribs(*this);
2758 #endif
2759 }
2760 
2761 void CharAttribList::OptimizeRanges( SfxItemPool& rItemPool )
2762 {
2763 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
2764     CharAttribList::DbgCheckAttribs(*this);
2765 #endif
2766     for (sal_Int32 i = 0; i < static_cast<sal_Int32>(aAttribs.size()); ++i)
2767     {
2768         EditCharAttrib& rAttr = *aAttribs[i].get();
2769         for (sal_Int32 nNext = i+1; nNext < static_cast<sal_Int32>(aAttribs.size()); ++nNext)
2770         {
2771             EditCharAttrib& rNext = *aAttribs[nNext].get();
2772             if (!rAttr.IsFeature() && rNext.GetStart() == rAttr.GetEnd() && rNext.Which() == rAttr.Which())
2773             {
2774                 if (*rNext.GetItem() == *rAttr.GetItem())
2775                 {
2776                     rAttr.GetEnd() = rNext.GetEnd();
2777                     rItemPool.Remove(*rNext.GetItem());
2778                     aAttribs.erase(aAttribs.begin()+nNext);
2779                 }
2780                 break;  // only 1 attr with same which can start here.
2781             }
2782             else if (rNext.GetStart() > rAttr.GetEnd())
2783             {
2784                 break;
2785             }
2786         }
2787     }
2788 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
2789     CharAttribList::DbgCheckAttribs(*this);
2790 #endif
2791 }
2792 
2793 sal_Int32 CharAttribList::Count() const
2794 {
2795     return aAttribs.size();
2796 }
2797 
2798 const EditCharAttrib* CharAttribList::FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos ) const
2799 {
2800     // Backwards, if one ends where the next starts.
2801     // => The starting one is the valid one ...
2802     AttribsType::const_reverse_iterator it = std::find_if(aAttribs.rbegin(), aAttribs.rend(),
2803         [&nWhich, &nPos](const AttribsType::value_type& rxAttr) {
2804             return rxAttr->Which() == nWhich && rxAttr->IsIn(nPos); });
2805     if (it != aAttribs.rend())
2806     {
2807         const EditCharAttrib& rAttr = **it;
2808         return &rAttr;
2809     }
2810     return nullptr;
2811 }
2812 
2813 EditCharAttrib* CharAttribList::FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos )
2814 {
2815     // Backwards, if one ends where the next starts.
2816     // => The starting one is the valid one ...
2817     AttribsType::reverse_iterator it = std::find_if(aAttribs.rbegin(), aAttribs.rend(),
2818         [&nWhich, &nPos](AttribsType::value_type& rxAttr) {
2819             return rxAttr->Which() == nWhich && rxAttr->IsIn(nPos); });
2820     if (it != aAttribs.rend())
2821     {
2822         EditCharAttrib& rAttr = **it;
2823         return &rAttr;
2824     }
2825     return nullptr;
2826 }
2827 
2828 const EditCharAttrib* CharAttribList::FindNextAttrib( sal_uInt16 nWhich, sal_Int32 nFromPos ) const
2829 {
2830     assert(nWhich);
2831     for (auto const& attrib : aAttribs)
2832     {
2833         const EditCharAttrib& rAttr = *attrib;
2834         if (rAttr.GetStart() >= nFromPos && rAttr.Which() == nWhich)
2835             return &rAttr;
2836     }
2837     return nullptr;
2838 }
2839 
2840 bool CharAttribList::HasAttrib( sal_Int32 nStartPos, sal_Int32 nEndPos ) const
2841 {
2842     return std::any_of(aAttribs.rbegin(), aAttribs.rend(),
2843         [&nStartPos, &nEndPos](const AttribsType::value_type& rxAttr) {
2844             return rxAttr->GetStart() < nEndPos && rxAttr->GetEnd() > nStartPos; });
2845 }
2846 
2847 
2848 namespace {
2849 
2850 class FindByAddress
2851 {
2852     const EditCharAttrib* mpAttr;
2853 public:
2854     explicit FindByAddress(const EditCharAttrib* p) : mpAttr(p) {}
2855     bool operator() (const std::unique_ptr<EditCharAttrib>& r) const
2856     {
2857         return r.get() == mpAttr;
2858     }
2859 };
2860 
2861 }
2862 
2863 void CharAttribList::Remove(const EditCharAttrib* p)
2864 {
2865     AttribsType::iterator it = std::find_if(aAttribs.begin(), aAttribs.end(), FindByAddress(p));
2866     if (it != aAttribs.end())
2867         aAttribs.erase(it);
2868 }
2869 
2870 void CharAttribList::Remove(sal_Int32 nPos)
2871 {
2872     if (nPos >= static_cast<sal_Int32>(aAttribs.size()))
2873         return;
2874 
2875     aAttribs.erase(aAttribs.begin()+nPos);
2876 }
2877 
2878 void CharAttribList::SetHasEmptyAttribs(bool b)
2879 {
2880     bHasEmptyAttribs = b;
2881 }
2882 
2883 bool CharAttribList::HasBoundingAttrib( sal_Int32 nBound ) const
2884 {
2885     // Backwards, if one ends where the next starts.
2886     // => The starting one is the valid one ...
2887     AttribsType::const_reverse_iterator it = aAttribs.rbegin(), itEnd = aAttribs.rend();
2888     for (; it != itEnd; ++it)
2889     {
2890         const EditCharAttrib& rAttr = **it;
2891         if (rAttr.GetEnd() < nBound)
2892             return false;
2893 
2894         if (rAttr.GetStart() == nBound || rAttr.GetEnd() == nBound)
2895             return true;
2896     }
2897     return false;
2898 }
2899 
2900 EditCharAttrib* CharAttribList::FindEmptyAttrib( sal_uInt16 nWhich, sal_Int32 nPos )
2901 {
2902     if ( !bHasEmptyAttribs )
2903         return nullptr;
2904 
2905     for (const std::unique_ptr<EditCharAttrib>& rAttr : aAttribs)
2906     {
2907         if (rAttr->GetStart() == nPos && rAttr->GetEnd() == nPos && rAttr->Which() == nWhich)
2908             return rAttr.get();
2909     }
2910     return nullptr;
2911 }
2912 
2913 namespace {
2914 
2915 class FindByStartPos
2916 {
2917     sal_Int32 mnPos;
2918 public:
2919     explicit FindByStartPos(sal_Int32 nPos) : mnPos(nPos) {}
2920     bool operator() (const std::unique_ptr<EditCharAttrib>& r) const
2921     {
2922         return r->GetStart() >= mnPos;
2923     }
2924 };
2925 
2926 }
2927 
2928 const EditCharAttrib* CharAttribList::FindFeature( sal_Int32 nPos ) const
2929 {
2930     // First, find the first attribute that starts at or after specified position.
2931     AttribsType::const_iterator it =
2932         std::find_if(aAttribs.begin(), aAttribs.end(), FindByStartPos(nPos));
2933 
2934     if (it == aAttribs.end())
2935         // All attributes are before the specified position.
2936         return nullptr;
2937 
2938     // And find the first attribute with feature.
2939     it = std::find_if(it, aAttribs.end(), [](const std::unique_ptr<EditCharAttrib>& aAttrib) { return aAttrib->IsFeature(); } );
2940     return it == aAttribs.end() ? nullptr : it->get();
2941 }
2942 
2943 namespace {
2944 
2945 class RemoveEmptyAttrItem
2946 {
2947     SfxItemPool& mrItemPool;
2948 public:
2949     explicit RemoveEmptyAttrItem(SfxItemPool& rPool) : mrItemPool(rPool) {}
2950     void operator() (const std::unique_ptr<EditCharAttrib>& r)
2951     {
2952         if (r->IsEmpty())
2953             mrItemPool.Remove(*r->GetItem());
2954     }
2955 };
2956 
2957 }
2958 
2959 void CharAttribList::DeleteEmptyAttribs( SfxItemPool& rItemPool )
2960 {
2961     std::for_each(aAttribs.begin(), aAttribs.end(), RemoveEmptyAttrItem(rItemPool));
2962     aAttribs.erase( std::remove_if(aAttribs.begin(), aAttribs.end(), [](const std::unique_ptr<EditCharAttrib>& aAttrib) { return aAttrib->IsEmpty(); } ), aAttribs.end() );
2963     bHasEmptyAttribs = false;
2964 }
2965 
2966 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
2967 void CharAttribList::DbgCheckAttribs(CharAttribList const& rAttribs)
2968 {
2969     std::set<std::pair<sal_Int32, sal_uInt16>> zero_set;
2970     for (const std::unique_ptr<EditCharAttrib>& rAttr : rAttribs.aAttribs)
2971     {
2972         assert(rAttr->GetStart() <= rAttr->GetEnd());
2973         assert(!rAttr->IsFeature() || rAttr->GetLen() == 1);
2974         if (0 == rAttr->GetLen())
2975         {
2976             // not sure if 0-length attributes allowed at all in non-empty para?
2977             assert(zero_set.insert(std::make_pair(rAttr->GetStart(), rAttr->Which())).second && "duplicate 0-length attribute detected");
2978         }
2979     }
2980     CheckOrderedList(rAttribs.GetAttribs());
2981 }
2982 #endif
2983 
2984 void CharAttribList::dumpAsXml(xmlTextWriterPtr pWriter) const
2985 {
2986     xmlTextWriterStartElement(pWriter, BAD_CAST("CharAttribList"));
2987     for (auto const & i : aAttribs) {
2988         i->dumpAsXml(pWriter);
2989     }
2990     xmlTextWriterEndElement(pWriter);
2991 }
2992 
2993 EditEngineItemPool::EditEngineItemPool()
2994     : SfxItemPool( "EditEngineItemPool", EE_ITEMS_START, EE_ITEMS_END,
2995                     aItemInfos, nullptr )
2996 {
2997     m_xDefItems = EditDLL::Get().GetGlobalData()->GetDefItems();
2998     SetDefaults(m_xDefItems->getDefaults());
2999 }
3000 
3001 EditEngineItemPool::~EditEngineItemPool()
3002 {
3003     ClearDefaults();
3004 }
3005 
3006 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
3007