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