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 <memory>
21 #include "textdoc.hxx"
22 #include <osl/diagnose.h>
23 #include <sal/log.hxx>
24 
25 // compare function called by QuickSort
CompareStart(const std::unique_ptr<TextCharAttrib> & pFirst,const std::unique_ptr<TextCharAttrib> & pSecond)26 static bool CompareStart( const std::unique_ptr<TextCharAttrib>& pFirst, const std::unique_ptr<TextCharAttrib>& pSecond )
27 {
28     return pFirst->GetStart() < pSecond->GetStart();
29 }
30 
TextCharAttrib(const TextAttrib & rAttr,sal_Int32 nStart,sal_Int32 nEnd)31 TextCharAttrib::TextCharAttrib( const TextAttrib& rAttr, sal_Int32 nStart, sal_Int32 nEnd )
32     : mpAttr(rAttr.Clone())
33     , mnStart(nStart)
34     , mnEnd(nEnd)
35 {
36 }
37 
TextCharAttrib(const TextCharAttrib & rTextCharAttrib)38 TextCharAttrib::TextCharAttrib( const TextCharAttrib& rTextCharAttrib )
39     : mpAttr(rTextCharAttrib.mpAttr->Clone())
40     , mnStart(rTextCharAttrib.mnStart)
41     , mnEnd(rTextCharAttrib.mnEnd)
42 {
43 }
44 
TextCharAttribList()45 TextCharAttribList::TextCharAttribList()
46     : mbHasEmptyAttribs(false)
47 {
48 }
49 
~TextCharAttribList()50 TextCharAttribList::~TextCharAttribList()
51 {
52     // PTRARR_DEL
53 }
54 
Clear()55 void TextCharAttribList::Clear()
56 {
57     maAttribs.clear();
58 }
59 
InsertAttrib(std::unique_ptr<TextCharAttrib> pAttrib)60 void TextCharAttribList::InsertAttrib( std::unique_ptr<TextCharAttrib> pAttrib )
61 {
62     if ( pAttrib->IsEmpty() )
63         mbHasEmptyAttribs = true;
64 
65     const sal_Int32 nStart = pAttrib->GetStart(); // maybe better for Comp.Opt.
66     bool bInserted = false;
67     auto it = std::find_if(maAttribs.begin(), maAttribs.end(),
68         [nStart](std::unique_ptr<TextCharAttrib>& rAttrib) { return rAttrib->GetStart() > nStart; });
69     if (it != maAttribs.end())
70     {
71         maAttribs.insert( it, std::move(pAttrib) );
72         bInserted = true;
73     }
74     if ( !bInserted )
75         maAttribs.push_back( std::move(pAttrib) );
76 }
77 
ResortAttribs()78 void TextCharAttribList::ResortAttribs()
79 {
80     std::sort( maAttribs.begin(), maAttribs.end(), CompareStart );
81 }
82 
FindAttrib(sal_uInt16 nWhich,sal_Int32 nPos)83 TextCharAttrib* TextCharAttribList::FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos )
84 {
85     for (std::vector<std::unique_ptr<TextCharAttrib> >::reverse_iterator it = maAttribs.rbegin(); it != maAttribs.rend(); ++it)
86     {
87         if ( (*it)->GetEnd() < nPos )
88             return nullptr;
89 
90         if ( ( (*it)->Which() == nWhich ) && (*it)->IsIn(nPos) )
91             return it->get();
92     }
93     return nullptr;
94 }
95 
HasBoundingAttrib(sal_Int32 nBound)96 bool TextCharAttribList::HasBoundingAttrib( sal_Int32 nBound )
97 {
98     for (std::vector<std::unique_ptr<TextCharAttrib> >::reverse_iterator it = maAttribs.rbegin(); it != maAttribs.rend(); ++it)
99     {
100         if ( (*it)->GetEnd() < nBound )
101             return false;
102 
103         if ( ( (*it)->GetStart() == nBound ) || ( (*it)->GetEnd() == nBound ) )
104             return true;
105     }
106     return false;
107 }
108 
FindEmptyAttrib(sal_uInt16 nWhich,sal_Int32 nPos)109 TextCharAttrib* TextCharAttribList::FindEmptyAttrib( sal_uInt16 nWhich, sal_Int32 nPos )
110 {
111     if ( !mbHasEmptyAttribs )
112         return nullptr;
113 
114     for (auto const& attrib : maAttribs)
115     {
116         if ( attrib->GetStart() > nPos )
117             return nullptr;
118 
119         if ( ( attrib->GetStart() == nPos ) && ( attrib->GetEnd() == nPos ) && ( attrib->Which() == nWhich ) )
120             return attrib.get();
121     }
122     return nullptr;
123 }
124 
DeleteEmptyAttribs()125 void TextCharAttribList::DeleteEmptyAttribs()
126 {
127     maAttribs.erase(
128         std::remove_if( maAttribs.begin(), maAttribs.end(),
129             [] (const std::unique_ptr<TextCharAttrib>& rAttrib) { return rAttrib->IsEmpty(); } ),
130         maAttribs.end() );
131     mbHasEmptyAttribs = false;
132 }
133 
TextNode(const OUString & rText)134 TextNode::TextNode( const OUString& rText ) :
135     maText( rText )
136 {
137 }
138 
ExpandAttribs(sal_Int32 nIndex,sal_Int32 nNew)139 void TextNode::ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNew )
140 {
141     if ( !nNew )
142         return;
143 
144     bool bResort = false;
145     sal_uInt16 nAttribs = maCharAttribs.Count();
146     for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
147     {
148         TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
149         if ( rAttrib.GetEnd() >= nIndex )
150         {
151             // move all attributes that are behind the cursor
152             if ( rAttrib.GetStart() > nIndex )
153             {
154                 rAttrib.MoveForward( nNew );
155             }
156             // 0: expand empty attribute, if at cursor
157             else if ( rAttrib.IsEmpty() )
158             {
159                 // Do not check the index; empty one may only be here.
160                 // If checking later anyway, special case:
161                 // Start == 0; AbsLen == 1, nNew = 1 => Expand due to new paragraph!
162                 // Start <= nIndex, End >= nIndex => Start=End=nIndex!
163                 rAttrib.Expand( nNew );
164             }
165             // 1: attribute starts before and reaches up to index
166             else if ( rAttrib.GetEnd() == nIndex ) // start must be before
167             {
168                 // Only expand if no feature and not in Exclude list!
169                 // Otherwise e.g. an UL would go until the new ULDB, thus expand both.
170                 if ( !maCharAttribs.FindEmptyAttrib( rAttrib.Which(), nIndex ) )
171                 {
172                     rAttrib.Expand( nNew );
173                 }
174                 else
175                     bResort = true;
176             }
177             // 2: attribute starts before and reaches past the index
178             else if ( ( rAttrib.GetStart() < nIndex ) && ( rAttrib.GetEnd() > nIndex ) )
179             {
180                 rAttrib.Expand( nNew );
181             }
182             // 3: attribute starts at Index
183             else if ( rAttrib.GetStart() == nIndex )
184             {
185                 if ( nIndex == 0 )
186                 {
187                     rAttrib.Expand( nNew );
188                 }
189                 else
190                     rAttrib.MoveForward( nNew );
191             }
192         }
193 
194         SAL_WARN_IF( rAttrib.GetStart() > rAttrib.GetEnd(), "vcl", "Expand: attribute twisted!" );
195         SAL_WARN_IF( ( rAttrib.GetEnd() > maText.getLength() ), "vcl", "Expand: attribute greater than paragraph!" );
196         SAL_WARN_IF( rAttrib.IsEmpty(), "vcl", "Empty attribute after ExpandAttribs?" );
197     }
198 
199     if ( bResort )
200         maCharAttribs.ResortAttribs();
201 }
202 
CollapseAttribs(sal_Int32 nIndex,sal_Int32 nDeleted)203 void TextNode::CollapseAttribs( sal_Int32 nIndex, sal_Int32 nDeleted )
204 {
205     if ( !nDeleted )
206         return;
207 
208     bool bResort = false;
209     const sal_Int32 nEndChanges = nIndex+nDeleted;
210 
211     for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
212     {
213         TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
214         bool bDelAttr = false;
215         if ( rAttrib.GetEnd() >= nIndex )
216         {
217             // move all attributes that are behind the cursor
218             if ( rAttrib.GetStart() >= nEndChanges )
219             {
220                 rAttrib.MoveBackward( nDeleted );
221             }
222             // 1. delete inner attributes
223             else if ( ( rAttrib.GetStart() >= nIndex ) && ( rAttrib.GetEnd() <= nEndChanges ) )
224             {
225                 // special case: attribute covers the region exactly
226                 // => keep as an empty attribute
227                 if ( ( rAttrib.GetStart() == nIndex ) && ( rAttrib.GetEnd() == nEndChanges ) )
228                     rAttrib.SetEnd(nIndex); // empty
229                 else
230                     bDelAttr = true;
231             }
232             // 2. attribute starts before, ends inside or after
233             else if ( ( rAttrib.GetStart() <= nIndex ) && ( rAttrib.GetEnd() > nIndex ) )
234             {
235                 if ( rAttrib.GetEnd() <= nEndChanges ) // ends inside
236                     rAttrib.SetEnd(nIndex);
237                 else
238                     rAttrib.Collaps( nDeleted );       // ends after
239             }
240             // 3. attribute starts inside, ends after
241             else if ( ( rAttrib.GetStart() >= nIndex ) && ( rAttrib.GetEnd() > nEndChanges ) )
242             {
243                 // features are not allowed to expand!
244                 rAttrib.SetStart(nEndChanges);
245                 rAttrib.MoveBackward( nDeleted );
246             }
247         }
248 
249         SAL_WARN_IF( rAttrib.GetStart() > rAttrib.GetEnd(), "vcl", "Collaps: attribute twisted!" );
250         SAL_WARN_IF( ( rAttrib.GetEnd() > maText.getLength()) && !bDelAttr, "vcl", "Collaps: attribute greater than paragraph!" );
251         if ( bDelAttr /* || rAttrib.IsEmpty() */ )
252         {
253             bResort = true;
254             maCharAttribs.RemoveAttrib( nAttr );
255             nAttr--;
256         }
257         else if ( rAttrib.IsEmpty() )
258             maCharAttribs.HasEmptyAttribs() = true;
259     }
260 
261     if ( bResort )
262         maCharAttribs.ResortAttribs();
263 }
264 
InsertText(sal_Int32 nPos,const OUString & rText)265 void TextNode::InsertText( sal_Int32 nPos, const OUString& rText )
266 {
267     maText = maText.replaceAt( nPos, 0, rText );
268     ExpandAttribs( nPos, rText.getLength() );
269 }
270 
InsertText(sal_Int32 nPos,sal_Unicode c)271 void TextNode::InsertText( sal_Int32 nPos, sal_Unicode c )
272 {
273     maText = maText.replaceAt( nPos, 0, OUString(c) );
274     ExpandAttribs( nPos, 1 );
275 }
276 
RemoveText(sal_Int32 nPos,sal_Int32 nChars)277 void TextNode::RemoveText( sal_Int32 nPos, sal_Int32 nChars )
278 {
279     maText = maText.replaceAt( nPos, nChars, "" );
280     CollapseAttribs( nPos, nChars );
281 }
282 
Split(sal_Int32 nPos)283 std::unique_ptr<TextNode> TextNode::Split( sal_Int32 nPos )
284 {
285     OUString aNewText;
286     if ( nPos < maText.getLength() )
287     {
288         aNewText = maText.copy( nPos );
289         maText = maText.copy(0, nPos);
290     }
291     std::unique_ptr<TextNode> pNew(new TextNode( aNewText ));
292 
293     for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
294     {
295         TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
296         if ( rAttrib.GetEnd() < nPos )
297         {
298             // no change
299             ;
300         }
301         else if ( rAttrib.GetEnd() == nPos )
302         {
303             // must be copied as an empty attribute
304             // !FindAttrib only sensible if traversing backwards through the list!
305             if ( !pNew->maCharAttribs.FindAttrib( rAttrib.Which(), 0 ) )
306             {
307                 std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
308                 pNewAttrib->SetStart(0);
309                 pNewAttrib->SetEnd(0);
310                 pNew->maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
311             }
312         }
313         else if ( rAttrib.IsInside( nPos ) || ( !nPos && !rAttrib.GetStart() ) )
314         {
315             // If cutting at the very beginning, the attribute has to be
316             // copied and changed
317             std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
318             pNewAttrib->SetStart(0);
319             pNewAttrib->SetEnd(rAttrib.GetEnd()-nPos);
320             pNew->maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
321             // trim
322             rAttrib.SetEnd(nPos);
323         }
324         else
325         {
326             SAL_WARN_IF( rAttrib.GetStart() < nPos, "vcl", "Start < nPos!" );
327             SAL_WARN_IF( rAttrib.GetEnd() < nPos, "vcl", "End < nPos!" );
328             // move all into the new node (this)
329             pNew->maCharAttribs.InsertAttrib(maCharAttribs.RemoveAttrib(nAttr));
330             rAttrib.SetStart( rAttrib.GetStart() - nPos );
331             rAttrib.SetEnd( rAttrib.GetEnd() - nPos );
332             nAttr--;
333         }
334     }
335     return pNew;
336 }
337 
Append(const TextNode & rNode)338 void TextNode::Append( const TextNode& rNode )
339 {
340     sal_Int32 nOldLen = maText.getLength();
341 
342     maText += rNode.GetText();
343 
344     const sal_uInt16 nAttribs = rNode.GetCharAttribs().Count();
345     for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
346     {
347         const TextCharAttrib& rAttrib = rNode.GetCharAttrib( nAttr );
348         bool bMelted = false;
349         if ( rAttrib.GetStart() == 0 )
350         {
351             // potentially merge attributes
352             sal_uInt16 nTmpAttribs = maCharAttribs.Count();
353             for ( sal_uInt16 nTmpAttr = 0; nTmpAttr < nTmpAttribs; nTmpAttr++ )
354             {
355                 TextCharAttrib& rTmpAttrib = maCharAttribs.GetAttrib( nTmpAttr );
356 
357                 if ( rTmpAttrib.GetEnd() == nOldLen )
358                 {
359                     if ( ( rTmpAttrib.Which() == rAttrib.Which() ) &&
360                          ( rTmpAttrib.GetAttr() == rAttrib.GetAttr() ) )
361                     {
362                         rTmpAttrib.SetEnd( rTmpAttrib.GetEnd() + rAttrib.GetLen() );
363                         bMelted = true;
364                         break;  // there can be only one of this type at this position
365                     }
366                 }
367             }
368         }
369 
370         if ( !bMelted )
371         {
372             std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
373             pNewAttrib->SetStart( pNewAttrib->GetStart() + nOldLen );
374             pNewAttrib->SetEnd( pNewAttrib->GetEnd() + nOldLen );
375             maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
376         }
377     }
378 }
379 
TextDoc()380 TextDoc::TextDoc()
381     : mnLeftMargin(0)
382 {
383 };
384 
~TextDoc()385 TextDoc::~TextDoc()
386 {
387     DestroyTextNodes();
388 }
389 
Clear()390 void TextDoc::Clear()
391 {
392     DestroyTextNodes();
393 }
394 
DestroyTextNodes()395 void TextDoc::DestroyTextNodes()
396 {
397     maTextNodes.clear();
398 }
399 
GetText(const sal_Unicode * pSep) const400 OUString TextDoc::GetText( const sal_Unicode* pSep ) const
401 {
402     sal_uInt32 nNodes = static_cast<sal_uInt32>(maTextNodes.size());
403 
404     OUStringBuffer aASCIIText;
405     const sal_uInt32 nLastNode = nNodes-1;
406     for ( sal_uInt32 nNode = 0; nNode < nNodes; ++nNode )
407     {
408         TextNode* pNode = maTextNodes[ nNode ].get();
409         aASCIIText.append(pNode->GetText());
410         if ( pSep && ( nNode != nLastNode ) )
411             aASCIIText.append(pSep);
412     }
413 
414     return aASCIIText.makeStringAndClear();
415 }
416 
GetText(sal_uInt32 nPara) const417 OUString TextDoc::GetText( sal_uInt32 nPara ) const
418 {
419     TextNode* pNode = ( nPara < maTextNodes.size() ) ? maTextNodes[ nPara ].get() : nullptr;
420     if ( pNode )
421         return pNode->GetText();
422 
423     return OUString();
424 }
425 
GetTextLen(const sal_Unicode * pSep,const TextSelection * pSel) const426 sal_Int32 TextDoc::GetTextLen( const sal_Unicode* pSep, const TextSelection* pSel ) const
427 {
428     sal_Int32 nLen = 0;
429     sal_uInt32 nNodes = static_cast<sal_uInt32>(maTextNodes.size());
430     if ( nNodes )
431     {
432         sal_uInt32 nStartNode = 0;
433         sal_uInt32 nEndNode = nNodes-1;
434         if ( pSel )
435         {
436             nStartNode = pSel->GetStart().GetPara();
437             nEndNode = pSel->GetEnd().GetPara();
438         }
439 
440         for ( sal_uInt32 nNode = nStartNode; nNode <= nEndNode; ++nNode )
441         {
442             TextNode* pNode = maTextNodes[ nNode ].get();
443 
444             sal_Int32 nS = 0;
445             sal_Int32 nE = pNode->GetText().getLength();
446             if ( pSel && ( nNode == pSel->GetStart().GetPara() ) )
447                 nS = pSel->GetStart().GetIndex();
448             if ( pSel && ( nNode == pSel->GetEnd().GetPara() ) )
449                 nE = pSel->GetEnd().GetIndex();
450 
451             nLen += ( nE - nS );
452         }
453 
454         if ( pSep )
455             nLen += (nEndNode-nStartNode) * rtl_ustr_getLength(pSep);
456     }
457 
458     return nLen;
459 }
460 
InsertText(const TextPaM & rPaM,sal_Unicode c)461 TextPaM TextDoc::InsertText( const TextPaM& rPaM, sal_Unicode c )
462 {
463     SAL_WARN_IF( c == 0x0A, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
464     SAL_WARN_IF( c == 0x0D, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
465 
466     TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
467     pNode->InsertText( rPaM.GetIndex(), c );
468 
469     TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+1 );
470     return aPaM;
471 }
472 
InsertText(const TextPaM & rPaM,const OUString & rStr)473 TextPaM TextDoc::InsertText( const TextPaM& rPaM, const OUString& rStr )
474 {
475     SAL_WARN_IF( rStr.indexOf( 0x0A ) != -1, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
476     SAL_WARN_IF( rStr.indexOf( 0x0D ) != -1, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
477 
478     TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
479     pNode->InsertText( rPaM.GetIndex(), rStr );
480 
481     TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+rStr.getLength() );
482     return aPaM;
483 }
484 
InsertParaBreak(const TextPaM & rPaM)485 TextPaM TextDoc::InsertParaBreak( const TextPaM& rPaM )
486 {
487     TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
488     std::unique_ptr<TextNode> pNew = pNode->Split( rPaM.GetIndex() );
489 
490     SAL_WARN_IF( maTextNodes.size()>=SAL_MAX_UINT32, "vcl", "InsertParaBreak: more than 4Gi paragraphs!" );
491     maTextNodes.insert( maTextNodes.begin() + rPaM.GetPara() + 1, std::move(pNew) );
492 
493     TextPaM aPaM( rPaM.GetPara()+1, 0 );
494     return aPaM;
495 }
496 
ConnectParagraphs(TextNode * pLeft,const TextNode * pRight)497 TextPaM TextDoc::ConnectParagraphs( TextNode* pLeft, const TextNode* pRight )
498 {
499     sal_Int32 nPrevLen = pLeft->GetText().getLength();
500     pLeft->Append( *pRight );
501 
502     // the paragraph on the right vanishes
503     maTextNodes.erase( std::find_if( maTextNodes.begin(), maTextNodes.end(),
504                                      [&] (std::unique_ptr<TextNode> const & p) { return p.get() == pRight; } ) );
505 
506     sal_uLong nLeft = ::std::find_if( maTextNodes.begin(), maTextNodes.end(),
507                                       [&] (std::unique_ptr<TextNode> const & p) { return p.get() == pLeft; } )
508                         - maTextNodes.begin();
509     TextPaM aPaM( nLeft, nPrevLen );
510     return aPaM;
511 }
512 
RemoveChars(const TextPaM & rPaM,sal_Int32 nChars)513 void TextDoc::RemoveChars( const TextPaM& rPaM, sal_Int32 nChars )
514 {
515     TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
516     pNode->RemoveText( rPaM.GetIndex(), nChars );
517 }
518 
IsValidPaM(const TextPaM & rPaM)519 bool TextDoc::IsValidPaM( const TextPaM& rPaM )
520 {
521     if ( rPaM.GetPara() >= maTextNodes.size() )
522     {
523         OSL_FAIL( "PaM: Para out of range" );
524         return false;
525     }
526     TextNode * pNode = maTextNodes[ rPaM.GetPara() ].get();
527     if ( rPaM.GetIndex() > pNode->GetText().getLength() )
528     {
529         OSL_FAIL( "PaM: Index out of range" );
530         return false;
531     }
532     return true;
533 }
534 
535 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
536