1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include "textdoc.hxx"
22 #include <osl/diagnose.h>
23 #include <sal/log.hxx>
24 #include <rtl/ustrbuf.hxx>
27 // compare function called by QuickSort
28 static bool CompareStart( const std::unique_ptr
<TextCharAttrib
>& pFirst
, const std::unique_ptr
<TextCharAttrib
>& pSecond
)
30 return pFirst
->GetStart() < pSecond
->GetStart();
33 TextCharAttrib::TextCharAttrib( const TextAttrib
& rAttr
, sal_Int32 nStart
, sal_Int32 nEnd
)
34 : mpAttr(rAttr
.Clone())
40 TextCharAttrib::TextCharAttrib( const TextCharAttrib
& rTextCharAttrib
)
41 : mpAttr(rTextCharAttrib
.mpAttr
->Clone())
42 , mnStart(rTextCharAttrib
.mnStart
)
43 , mnEnd(rTextCharAttrib
.mnEnd
)
47 TextCharAttribList::TextCharAttribList()
48 : mbHasEmptyAttribs(false)
52 TextCharAttribList::~TextCharAttribList()
57 void TextCharAttribList::Clear()
62 void TextCharAttribList::InsertAttrib( std::unique_ptr
<TextCharAttrib
> pAttrib
)
64 if ( pAttrib
->IsEmpty() )
65 mbHasEmptyAttribs
= true;
67 const sal_Int32 nStart
= pAttrib
->GetStart(); // maybe better for Comp.Opt.
68 bool bInserted
= false;
69 auto it
= std::find_if(maAttribs
.begin(), maAttribs
.end(),
70 [nStart
](std::unique_ptr
<TextCharAttrib
>& rAttrib
) { return rAttrib
->GetStart() > nStart
; });
71 if (it
!= maAttribs
.end())
73 maAttribs
.insert( it
, std::move(pAttrib
) );
77 maAttribs
.push_back( std::move(pAttrib
) );
80 void TextCharAttribList::ResortAttribs()
82 std::sort( maAttribs
.begin(), maAttribs
.end(), CompareStart
);
85 TextCharAttrib
* TextCharAttribList::FindAttrib( sal_uInt16 nWhich
, sal_Int32 nPos
)
87 for (std::vector
<std::unique_ptr
<TextCharAttrib
> >::reverse_iterator it
= maAttribs
.rbegin(); it
!= maAttribs
.rend(); ++it
)
89 if ( (*it
)->GetEnd() < nPos
)
92 if ( ( (*it
)->Which() == nWhich
) && (*it
)->IsIn(nPos
) )
98 bool TextCharAttribList::HasBoundingAttrib( sal_Int32 nBound
)
100 for (std::vector
<std::unique_ptr
<TextCharAttrib
> >::reverse_iterator it
= maAttribs
.rbegin(); it
!= maAttribs
.rend(); ++it
)
102 if ( (*it
)->GetEnd() < nBound
)
105 if ( ( (*it
)->GetStart() == nBound
) || ( (*it
)->GetEnd() == nBound
) )
111 TextCharAttrib
* TextCharAttribList::FindEmptyAttrib( sal_uInt16 nWhich
, sal_Int32 nPos
)
113 if ( !mbHasEmptyAttribs
)
116 for (auto const& attrib
: maAttribs
)
118 if ( attrib
->GetStart() > nPos
)
121 if ( ( attrib
->GetStart() == nPos
) && ( attrib
->GetEnd() == nPos
) && ( attrib
->Which() == nWhich
) )
127 void TextCharAttribList::DeleteEmptyAttribs()
130 std::remove_if( maAttribs
.begin(), maAttribs
.end(),
131 [] (const std::unique_ptr
<TextCharAttrib
>& rAttrib
) { return rAttrib
->IsEmpty(); } ),
133 mbHasEmptyAttribs
= false;
136 TextNode::TextNode( OUString aText
) :
137 maText(std::move( aText
))
141 void TextNode::ExpandAttribs( sal_Int32 nIndex
, sal_Int32 nNew
)
146 bool bResort
= false;
147 sal_uInt16 nAttribs
= maCharAttribs
.Count();
148 for ( sal_uInt16 nAttr
= 0; nAttr
< nAttribs
; nAttr
++ )
150 TextCharAttrib
& rAttrib
= maCharAttribs
.GetAttrib( nAttr
);
151 if ( rAttrib
.GetEnd() >= nIndex
)
153 // move all attributes that are behind the cursor
154 if ( rAttrib
.GetStart() > nIndex
)
156 rAttrib
.MoveForward( nNew
);
158 // 0: expand empty attribute, if at cursor
159 else if ( rAttrib
.IsEmpty() )
161 // Do not check the index; empty one may only be here.
162 // If checking later anyway, special case:
163 // Start == 0; AbsLen == 1, nNew = 1 => Expand due to new paragraph!
164 // Start <= nIndex, End >= nIndex => Start=End=nIndex!
165 rAttrib
.Expand( nNew
);
167 // 1: attribute starts before and reaches up to index
168 else if ( rAttrib
.GetEnd() == nIndex
) // start must be before
170 // Only expand if no feature and not in Exclude list!
171 // Otherwise e.g. an UL would go until the new ULDB, thus expand both.
172 if ( !maCharAttribs
.FindEmptyAttrib( rAttrib
.Which(), nIndex
) )
174 rAttrib
.Expand( nNew
);
179 // 2: attribute starts before and reaches past the index
180 else if ( ( rAttrib
.GetStart() < nIndex
) && ( rAttrib
.GetEnd() > nIndex
) )
182 rAttrib
.Expand( nNew
);
184 // 3: attribute starts at Index
185 else if ( rAttrib
.GetStart() == nIndex
)
189 rAttrib
.Expand( nNew
);
192 rAttrib
.MoveForward( nNew
);
196 SAL_WARN_IF( rAttrib
.GetStart() > rAttrib
.GetEnd(), "vcl", "Expand: attribute twisted!" );
197 SAL_WARN_IF( ( rAttrib
.GetEnd() > maText
.getLength() ), "vcl", "Expand: attribute greater than paragraph!" );
198 SAL_WARN_IF( rAttrib
.IsEmpty(), "vcl", "Empty attribute after ExpandAttribs?" );
202 maCharAttribs
.ResortAttribs();
205 void TextNode::CollapseAttribs( sal_Int32 nIndex
, sal_Int32 nDeleted
)
210 bool bResort
= false;
211 const sal_Int32 nEndChanges
= nIndex
+nDeleted
;
213 for ( sal_uInt16 nAttr
= 0; nAttr
< maCharAttribs
.Count(); nAttr
++ )
215 TextCharAttrib
& rAttrib
= maCharAttribs
.GetAttrib( nAttr
);
216 bool bDelAttr
= false;
217 if ( rAttrib
.GetEnd() >= nIndex
)
219 // move all attributes that are behind the cursor
220 if ( rAttrib
.GetStart() >= nEndChanges
)
222 rAttrib
.MoveBackward( nDeleted
);
224 // 1. delete inner attributes
225 else if ( ( rAttrib
.GetStart() >= nIndex
) && ( rAttrib
.GetEnd() <= nEndChanges
) )
227 // special case: attribute covers the region exactly
228 // => keep as an empty attribute
229 if ( ( rAttrib
.GetStart() == nIndex
) && ( rAttrib
.GetEnd() == nEndChanges
) )
230 rAttrib
.SetEnd(nIndex
); // empty
234 // 2. attribute starts before, ends inside or after
235 else if ( ( rAttrib
.GetStart() <= nIndex
) && ( rAttrib
.GetEnd() > nIndex
) )
237 if ( rAttrib
.GetEnd() <= nEndChanges
) // ends inside
238 rAttrib
.SetEnd(nIndex
);
240 rAttrib
.Collaps( nDeleted
); // ends after
242 // 3. attribute starts inside, ends after
243 else if ( ( rAttrib
.GetStart() >= nIndex
) && ( rAttrib
.GetEnd() > nEndChanges
) )
245 // features are not allowed to expand!
246 rAttrib
.SetStart(nEndChanges
);
247 rAttrib
.MoveBackward( nDeleted
);
251 SAL_WARN_IF( rAttrib
.GetStart() > rAttrib
.GetEnd(), "vcl", "Collaps: attribute twisted!" );
252 SAL_WARN_IF( ( rAttrib
.GetEnd() > maText
.getLength()) && !bDelAttr
, "vcl", "Collaps: attribute greater than paragraph!" );
253 if ( bDelAttr
/* || rAttrib.IsEmpty() */ )
256 maCharAttribs
.RemoveAttrib( nAttr
);
259 else if ( rAttrib
.IsEmpty() )
260 maCharAttribs
.HasEmptyAttribs() = true;
264 maCharAttribs
.ResortAttribs();
267 void TextNode::InsertText( sal_Int32 nPos
, std::u16string_view rText
)
269 maText
= maText
.replaceAt( nPos
, 0, rText
);
270 ExpandAttribs( nPos
, rText
.size() );
273 void TextNode::InsertText( sal_Int32 nPos
, sal_Unicode c
)
275 maText
= maText
.replaceAt( nPos
, 0, rtl::OUStringChar(c
) );
276 ExpandAttribs( nPos
, 1 );
279 void TextNode::RemoveText( sal_Int32 nPos
, sal_Int32 nChars
)
281 maText
= maText
.replaceAt( nPos
, nChars
, u
"" );
282 CollapseAttribs( nPos
, nChars
);
285 std::unique_ptr
<TextNode
> TextNode::Split( sal_Int32 nPos
)
288 if ( nPos
< maText
.getLength() )
290 aNewText
= maText
.copy( nPos
);
291 maText
= maText
.copy(0, nPos
);
293 std::unique_ptr
<TextNode
> pNew(new TextNode( aNewText
));
295 for ( sal_uInt16 nAttr
= 0; nAttr
< maCharAttribs
.Count(); nAttr
++ )
297 TextCharAttrib
& rAttrib
= maCharAttribs
.GetAttrib( nAttr
);
298 if ( rAttrib
.GetEnd() < nPos
)
303 else if ( rAttrib
.GetEnd() == nPos
)
305 // must be copied as an empty attribute
306 // !FindAttrib only sensible if traversing backwards through the list!
307 if ( !pNew
->maCharAttribs
.FindAttrib( rAttrib
.Which(), 0 ) )
309 std::unique_ptr
<TextCharAttrib
> pNewAttrib(new TextCharAttrib( rAttrib
));
310 pNewAttrib
->SetStart(0);
311 pNewAttrib
->SetEnd(0);
312 pNew
->maCharAttribs
.InsertAttrib( std::move(pNewAttrib
) );
315 else if ( rAttrib
.IsInside( nPos
) || ( !nPos
&& !rAttrib
.GetStart() ) )
317 // If cutting at the very beginning, the attribute has to be
318 // copied and changed
319 std::unique_ptr
<TextCharAttrib
> pNewAttrib(new TextCharAttrib( rAttrib
));
320 pNewAttrib
->SetStart(0);
321 pNewAttrib
->SetEnd(rAttrib
.GetEnd()-nPos
);
322 pNew
->maCharAttribs
.InsertAttrib( std::move(pNewAttrib
) );
324 rAttrib
.SetEnd(nPos
);
328 SAL_WARN_IF( rAttrib
.GetStart() < nPos
, "vcl", "Start < nPos!" );
329 SAL_WARN_IF( rAttrib
.GetEnd() < nPos
, "vcl", "End < nPos!" );
330 // move all into the new node (this)
331 pNew
->maCharAttribs
.InsertAttrib(maCharAttribs
.RemoveAttrib(nAttr
));
332 rAttrib
.SetStart( rAttrib
.GetStart() - nPos
);
333 rAttrib
.SetEnd( rAttrib
.GetEnd() - nPos
);
340 void TextNode::Append( const TextNode
& rNode
)
342 sal_Int32 nOldLen
= maText
.getLength();
344 maText
+= rNode
.GetText();
346 const sal_uInt16 nAttribs
= rNode
.GetCharAttribs().Count();
347 for ( sal_uInt16 nAttr
= 0; nAttr
< nAttribs
; nAttr
++ )
349 const TextCharAttrib
& rAttrib
= rNode
.GetCharAttrib( nAttr
);
350 bool bMelted
= false;
351 if ( rAttrib
.GetStart() == 0 )
353 // potentially merge attributes
354 sal_uInt16 nTmpAttribs
= maCharAttribs
.Count();
355 for ( sal_uInt16 nTmpAttr
= 0; nTmpAttr
< nTmpAttribs
; nTmpAttr
++ )
357 TextCharAttrib
& rTmpAttrib
= maCharAttribs
.GetAttrib( nTmpAttr
);
359 if ( rTmpAttrib
.GetEnd() == nOldLen
)
361 if ( ( rTmpAttrib
.Which() == rAttrib
.Which() ) &&
362 ( rTmpAttrib
.GetAttr() == rAttrib
.GetAttr() ) )
364 rTmpAttrib
.SetEnd( rTmpAttrib
.GetEnd() + rAttrib
.GetLen() );
366 break; // there can be only one of this type at this position
374 std::unique_ptr
<TextCharAttrib
> pNewAttrib(new TextCharAttrib( rAttrib
));
375 pNewAttrib
->SetStart( pNewAttrib
->GetStart() + nOldLen
);
376 pNewAttrib
->SetEnd( pNewAttrib
->GetEnd() + nOldLen
);
377 maCharAttribs
.InsertAttrib( std::move(pNewAttrib
) );
392 void TextDoc::Clear()
397 void TextDoc::DestroyTextNodes()
402 OUString
TextDoc::GetText( const sal_Unicode
* pSep
) const
404 sal_uInt32 nNodes
= static_cast<sal_uInt32
>(maTextNodes
.size());
406 OUStringBuffer aASCIIText
;
407 const sal_uInt32 nLastNode
= nNodes
-1;
408 for ( sal_uInt32 nNode
= 0; nNode
< nNodes
; ++nNode
)
410 TextNode
* pNode
= maTextNodes
[ nNode
].get();
411 aASCIIText
.append(pNode
->GetText());
412 if ( pSep
&& ( nNode
!= nLastNode
) )
413 aASCIIText
.append(pSep
);
416 return aASCIIText
.makeStringAndClear();
419 OUString
TextDoc::GetText( sal_uInt32 nPara
) const
421 TextNode
* pNode
= ( nPara
< maTextNodes
.size() ) ? maTextNodes
[ nPara
].get() : nullptr;
423 return pNode
->GetText();
428 sal_Int32
TextDoc::GetTextLen( const sal_Unicode
* pSep
, const TextSelection
* pSel
) const
431 sal_uInt32 nNodes
= static_cast<sal_uInt32
>(maTextNodes
.size());
434 sal_uInt32 nStartNode
= 0;
435 sal_uInt32 nEndNode
= nNodes
-1;
438 nStartNode
= pSel
->GetStart().GetPara();
439 nEndNode
= pSel
->GetEnd().GetPara();
442 for ( sal_uInt32 nNode
= nStartNode
; nNode
<= nEndNode
; ++nNode
)
444 TextNode
* pNode
= maTextNodes
[ nNode
].get();
447 sal_Int32 nE
= pNode
->GetText().getLength();
448 if ( pSel
&& ( nNode
== pSel
->GetStart().GetPara() ) )
449 nS
= pSel
->GetStart().GetIndex();
450 if ( pSel
&& ( nNode
== pSel
->GetEnd().GetPara() ) )
451 nE
= pSel
->GetEnd().GetIndex();
457 nLen
+= (nEndNode
-nStartNode
) * rtl_ustr_getLength(pSep
);
463 TextPaM
TextDoc::InsertText( const TextPaM
& rPaM
, sal_Unicode c
)
465 SAL_WARN_IF( c
== 0x0A, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
466 SAL_WARN_IF( c
== 0x0D, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
468 TextNode
* pNode
= maTextNodes
[ rPaM
.GetPara() ].get();
469 pNode
->InsertText( rPaM
.GetIndex(), c
);
471 TextPaM
aPaM( rPaM
.GetPara(), rPaM
.GetIndex()+1 );
475 TextPaM
TextDoc::InsertText( const TextPaM
& rPaM
, std::u16string_view rStr
)
477 SAL_WARN_IF( rStr
.find( 0x0A ) != std::u16string_view::npos
, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
478 SAL_WARN_IF( rStr
.find( 0x0D ) != std::u16string_view::npos
, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
480 TextNode
* pNode
= maTextNodes
[ rPaM
.GetPara() ].get();
481 pNode
->InsertText( rPaM
.GetIndex(), rStr
);
483 TextPaM
aPaM( rPaM
.GetPara(), rPaM
.GetIndex()+rStr
.size() );
487 TextPaM
TextDoc::InsertParaBreak( const TextPaM
& rPaM
)
489 TextNode
* pNode
= maTextNodes
[ rPaM
.GetPara() ].get();
490 std::unique_ptr
<TextNode
> pNew
= pNode
->Split( rPaM
.GetIndex() );
492 SAL_WARN_IF( maTextNodes
.size()>=SAL_MAX_UINT32
, "vcl", "InsertParaBreak: more than 4Gi paragraphs!" );
493 maTextNodes
.insert( maTextNodes
.begin() + rPaM
.GetPara() + 1, std::move(pNew
) );
495 TextPaM
aPaM( rPaM
.GetPara()+1, 0 );
499 TextPaM
TextDoc::ConnectParagraphs( TextNode
* pLeft
, const TextNode
* pRight
)
501 sal_Int32 nPrevLen
= pLeft
->GetText().getLength();
502 pLeft
->Append( *pRight
);
504 // the paragraph on the right vanishes
505 maTextNodes
.erase( std::find_if( maTextNodes
.begin(), maTextNodes
.end(),
506 [&] (std::unique_ptr
<TextNode
> const & p
) { return p
.get() == pRight
; } ) );
508 sal_Int32 nLeft
= ::std::find_if( maTextNodes
.begin(), maTextNodes
.end(),
509 [&] (std::unique_ptr
<TextNode
> const & p
) { return p
.get() == pLeft
; } )
510 - maTextNodes
.begin();
511 TextPaM
aPaM( nLeft
, nPrevLen
);
515 void TextDoc::RemoveChars( const TextPaM
& rPaM
, sal_Int32 nChars
)
517 TextNode
* pNode
= maTextNodes
[ rPaM
.GetPara() ].get();
518 pNode
->RemoveText( rPaM
.GetIndex(), nChars
);
521 bool TextDoc::IsValidPaM( const TextPaM
& rPaM
)
523 if ( rPaM
.GetPara() >= maTextNodes
.size() )
525 OSL_FAIL( "PaM: Para out of range" );
528 TextNode
* pNode
= maTextNodes
[ rPaM
.GetPara() ].get();
529 if ( rPaM
.GetIndex() > pNode
->GetText().getLength() )
531 OSL_FAIL( "PaM: Index out of range" );
537 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */