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 <tools/stream.hxx>
21
22 #include <vcl/texteng.hxx>
23 #include <vcl/textview.hxx>
24 #include <vcl/commandevent.hxx>
25 #include <vcl/inputctx.hxx>
26 #include "textdoc.hxx"
27 #include "textdat2.hxx"
28 #include "textundo.hxx"
29 #include "textund2.hxx"
30 #include <svl/ctloptions.hxx>
31 #include <vcl/window.hxx>
32 #include <vcl/settings.hxx>
33 #include <vcl/toolkit/edit.hxx>
34 #include <vcl/virdev.hxx>
35 #include <sal/log.hxx>
36 #include <osl/diagnose.h>
37
38 #include <com/sun/star/i18n/XBreakIterator.hpp>
39
40 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
41
42 #include <com/sun/star/i18n/WordType.hpp>
43
44 #include <com/sun/star/i18n/InputSequenceChecker.hpp>
45 #include <com/sun/star/i18n/InputSequenceCheckMode.hpp>
46 #include <com/sun/star/i18n/ScriptType.hpp>
47
48 #include <comphelper/processfactory.hxx>
49
50 #include <unotools/localedatawrapper.hxx>
51 #include <vcl/unohelp.hxx>
52
53 #include <vcl/svapp.hxx>
54
55 #include <unicode/ubidi.h>
56
57 #include <algorithm>
58 #include <cstddef>
59 #include <cstdlib>
60 #include <memory>
61 #include <o3tl/sorted_vector.hxx>
62 #include <string_view>
63 #include <vector>
64
65 using namespace ::com::sun::star;
66 using namespace ::com::sun::star::uno;
67
TextEngine()68 TextEngine::TextEngine()
69 : mpActiveView {nullptr}
70 , maTextColor {COL_BLACK}
71 , mnMaxTextLen {0}
72 , mnMaxTextWidth {0}
73 , mnCharHeight {0}
74 , mnCurTextWidth {-1}
75 , mnCurTextHeight {0}
76 , mnDefTab {0}
77 , meAlign {TxtAlign::Left}
78 , mbIsFormatting {false}
79 , mbFormatted {false}
80 , mbUpdate {true}
81 , mbModified {false}
82 , mbUndoEnabled {false}
83 , mbIsInUndo {false}
84 , mbDowning {false}
85 , mbRightToLeft {false}
86 , mbHasMultiLineParas {false}
87 {
88 mpViews.reset( new TextViews );
89
90 mpIdleFormatter.reset( new IdleFormatter );
91 mpIdleFormatter->SetInvokeHandler( LINK( this, TextEngine, IdleFormatHdl ) );
92
93 mpRefDev = VclPtr<VirtualDevice>::Create();
94
95 ImpInitLayoutMode( mpRefDev );
96
97 ImpInitDoc();
98
99 vcl::Font aFont(mpRefDev->GetFont().GetFamilyName(), Size(0, 0));
100 aFont.SetTransparent( false );
101 Color aFillColor( aFont.GetFillColor() );
102 aFillColor.SetAlpha( 255 );
103 aFont.SetFillColor( aFillColor );
104 SetFont( aFont );
105 }
106
~TextEngine()107 TextEngine::~TextEngine()
108 {
109 mbDowning = true;
110
111 mpIdleFormatter.reset();
112 mpDoc.reset();
113 mpTEParaPortions.reset();
114 mpViews.reset(); // only the list, not the Views
115 mpRefDev.disposeAndClear();
116 mpUndoManager.reset();
117 mpIMEInfos.reset();
118 mpLocaleDataWrapper.reset();
119 }
120
InsertView(TextView * pTextView)121 void TextEngine::InsertView( TextView* pTextView )
122 {
123 mpViews->push_back( pTextView );
124 pTextView->SetSelection( TextSelection() );
125
126 if ( !GetActiveView() )
127 SetActiveView( pTextView );
128 }
129
RemoveView(TextView * pTextView)130 void TextEngine::RemoveView( TextView* pTextView )
131 {
132 TextViews::iterator it = std::find( mpViews->begin(), mpViews->end(), pTextView );
133 if( it != mpViews->end() )
134 {
135 pTextView->HideCursor();
136 mpViews->erase( it );
137 if ( pTextView == GetActiveView() )
138 SetActiveView( nullptr );
139 }
140 }
141
GetViewCount() const142 sal_uInt16 TextEngine::GetViewCount() const
143 {
144 return mpViews->size();
145 }
146
GetView(sal_uInt16 nView) const147 TextView* TextEngine::GetView( sal_uInt16 nView ) const
148 {
149 return (*mpViews)[ nView ];
150 }
151
152
SetActiveView(TextView * pTextView)153 void TextEngine::SetActiveView( TextView* pTextView )
154 {
155 if ( pTextView != mpActiveView )
156 {
157 if ( mpActiveView )
158 mpActiveView->HideSelection();
159
160 mpActiveView = pTextView;
161
162 if ( mpActiveView )
163 mpActiveView->ShowSelection();
164 }
165 }
166
SetFont(const vcl::Font & rFont)167 void TextEngine::SetFont( const vcl::Font& rFont )
168 {
169 if ( rFont == maFont )
170 return;
171
172 maFont = rFont;
173 // #i40221# As the font's color now defaults to transparent (since i35764)
174 // we have to choose a useful textcolor in this case.
175 // Otherwise maTextColor and maFont.GetColor() are both transparent...
176 if( rFont.GetColor() == COL_TRANSPARENT )
177 maTextColor = COL_BLACK;
178 else
179 maTextColor = rFont.GetColor();
180
181 // Do not allow transparent fonts because of selection
182 // (otherwise delete the background in ImplPaint later differently)
183 maFont.SetTransparent( false );
184 // Tell VCL not to use the font color, use text color from OutputDevice
185 maFont.SetColor( COL_TRANSPARENT );
186 Color aFillColor( maFont.GetFillColor() );
187 aFillColor.SetAlpha( 255 );
188 maFont.SetFillColor( aFillColor );
189
190 maFont.SetAlignment( ALIGN_TOP );
191 mpRefDev->SetFont( maFont );
192 mnDefTab = mpRefDev->GetTextWidth(u" "_ustr);
193 if ( !mnDefTab )
194 mnDefTab = mpRefDev->GetTextWidth(u"XXXX"_ustr);
195 if ( !mnDefTab )
196 mnDefTab = 1;
197 mnCharHeight = mpRefDev->GetTextHeight();
198
199 FormatFullDoc();
200 UpdateViews();
201
202 for ( auto nView = mpViews->size(); nView; )
203 {
204 TextView* pView = (*mpViews)[ --nView ];
205 pView->GetWindow()->SetInputContext( InputContext( GetFont(), !pView->IsReadOnly() ? InputContextFlags::Text|InputContextFlags::ExtText : InputContextFlags::NONE ) );
206 }
207
208 }
209
SetMaxTextLen(sal_Int32 nLen)210 void TextEngine::SetMaxTextLen( sal_Int32 nLen )
211 {
212 mnMaxTextLen = nLen>=0 ? nLen : EDIT_NOLIMIT;
213 }
214
SetMaxTextWidth(tools::Long nMaxWidth)215 void TextEngine::SetMaxTextWidth( tools::Long nMaxWidth )
216 {
217 if ( nMaxWidth>=0 && nMaxWidth != mnMaxTextWidth )
218 {
219 mnMaxTextWidth = nMaxWidth;
220 FormatFullDoc();
221 UpdateViews();
222 }
223 }
224
225 const sal_Unicode static_aLFText[] = { '\n', 0 };
226 const sal_Unicode static_aCRText[] = { '\r', 0 };
227 const sal_Unicode static_aCRLFText[] = { '\r', '\n', 0 };
228
static_getLineEndText(LineEnd aLineEnd)229 static const sal_Unicode* static_getLineEndText( LineEnd aLineEnd )
230 {
231 const sal_Unicode* pRet = nullptr;
232
233 switch( aLineEnd )
234 {
235 case LINEEND_LF:
236 pRet = static_aLFText;
237 break;
238 case LINEEND_CR:
239 pRet = static_aCRText;
240 break;
241 case LINEEND_CRLF:
242 pRet = static_aCRLFText;
243 break;
244 }
245 return pRet;
246 }
247
ReplaceText(const TextSelection & rSel,const OUString & rText)248 void TextEngine::ReplaceText(const TextSelection& rSel, const OUString& rText)
249 {
250 ImpInsertText( rSel, rText );
251 }
252
GetText(LineEnd aSeparator) const253 OUString TextEngine::GetText( LineEnd aSeparator ) const
254 {
255 return mpDoc->GetText( static_getLineEndText( aSeparator ) );
256 }
257
GetTextLines(LineEnd aSeparator) const258 OUString TextEngine::GetTextLines( LineEnd aSeparator ) const
259 {
260 OUStringBuffer aText;
261 const sal_uInt32 nParas = mpTEParaPortions->Count();
262 const sal_Unicode* pSep = static_getLineEndText( aSeparator );
263 for ( sal_uInt32 nP = 0; nP < nParas; ++nP )
264 {
265 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nP );
266
267 const size_t nLines = pTEParaPortion->GetLines().size();
268 for ( size_t nL = 0; nL < nLines; ++nL )
269 {
270 TextLine& rLine = pTEParaPortion->GetLines()[nL];
271 aText.append( pTEParaPortion->GetNode()->GetText().subView(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()) );
272 if ( pSep && ( ( (nP+1) < nParas ) || ( (nL+1) < nLines ) ) )
273 aText.append(pSep);
274 }
275 }
276 return aText.makeStringAndClear();
277 }
278
GetText(sal_uInt32 nPara) const279 OUString TextEngine::GetText( sal_uInt32 nPara ) const
280 {
281 return mpDoc->GetText( nPara );
282 }
283
GetTextLen() const284 sal_Int32 TextEngine::GetTextLen() const
285 {
286 return mpDoc->GetTextLen( static_getLineEndText( LINEEND_LF ) );
287 }
288
GetTextLen(const TextSelection & rSel) const289 sal_Int32 TextEngine::GetTextLen( const TextSelection& rSel ) const
290 {
291 TextSelection aSel( rSel );
292 aSel.Justify();
293 ValidateSelection( aSel );
294 return mpDoc->GetTextLen( static_getLineEndText( LINEEND_LF ), &aSel );
295 }
296
GetTextLen(const sal_uInt32 nPara) const297 sal_Int32 TextEngine::GetTextLen( const sal_uInt32 nPara ) const
298 {
299 return mpDoc->GetNodes()[ nPara ]->GetText().getLength();
300 }
301
SetUpdateMode(bool bUpdate)302 void TextEngine::SetUpdateMode( bool bUpdate )
303 {
304 if ( bUpdate != mbUpdate )
305 {
306 mbUpdate = bUpdate;
307 if ( mbUpdate )
308 {
309 FormatAndUpdate( GetActiveView() );
310 if ( GetActiveView() )
311 GetActiveView()->ShowCursor();
312 }
313 }
314 }
315
DoesKeyChangeText(const KeyEvent & rKeyEvent)316 bool TextEngine::DoesKeyChangeText( const KeyEvent& rKeyEvent )
317 {
318 bool bDoesChange = false;
319
320 KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction();
321 if ( eFunc != KeyFuncType::DONTKNOW )
322 {
323 switch ( eFunc )
324 {
325 case KeyFuncType::UNDO:
326 case KeyFuncType::REDO:
327 case KeyFuncType::CUT:
328 case KeyFuncType::PASTE:
329 bDoesChange = true;
330 break;
331 default:
332 // might get handled below
333 eFunc = KeyFuncType::DONTKNOW;
334 }
335 }
336 if ( eFunc == KeyFuncType::DONTKNOW )
337 {
338 switch ( rKeyEvent.GetKeyCode().GetCode() )
339 {
340 case KEY_DELETE:
341 case KEY_BACKSPACE:
342 if ( !rKeyEvent.GetKeyCode().IsMod2() )
343 bDoesChange = true;
344 break;
345 case KEY_RETURN:
346 case KEY_TAB:
347 if ( !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() )
348 bDoesChange = true;
349 break;
350 default:
351 bDoesChange = TextEngine::IsSimpleCharInput( rKeyEvent );
352 }
353 }
354 return bDoesChange;
355 }
356
IsSimpleCharInput(const KeyEvent & rKeyEvent)357 bool TextEngine::IsSimpleCharInput( const KeyEvent& rKeyEvent )
358 {
359 return rKeyEvent.GetCharCode() >= 32 && rKeyEvent.GetCharCode() != 127 &&
360 KEY_MOD1 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT) && // (ssa) #i45714#:
361 KEY_MOD2 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT); // check for Ctrl and Alt separately
362 }
363
ImpInitDoc()364 void TextEngine::ImpInitDoc()
365 {
366 if ( mpDoc )
367 mpDoc->Clear();
368 else
369 mpDoc.reset( new TextDoc );
370
371 mpTEParaPortions.reset(new TEParaPortions);
372
373 std::unique_ptr<TextNode> pNode(new TextNode( OUString() ));
374 mpDoc->GetNodes().insert( mpDoc->GetNodes().begin(), std::move(pNode) );
375
376 TEParaPortion* pIniPortion = new TEParaPortion( mpDoc->GetNodes().begin()->get() );
377 mpTEParaPortions->Insert( pIniPortion, 0 );
378
379 mbFormatted = false;
380
381 ImpParagraphRemoved( TEXT_PARA_ALL );
382 ImpParagraphInserted( 0 );
383 }
384
GetText(const TextSelection & rSel,LineEnd aSeparator) const385 OUString TextEngine::GetText( const TextSelection& rSel, LineEnd aSeparator ) const
386 {
387 if ( !rSel.HasRange() )
388 return OUString();
389
390 TextSelection aSel( rSel );
391 aSel.Justify();
392
393 OUStringBuffer aText;
394 const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
395 const sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
396 const sal_Unicode* pSep = static_getLineEndText( aSeparator );
397 for ( sal_uInt32 nNode = aSel.GetStart().GetPara(); nNode <= nEndPara; ++nNode )
398 {
399 TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
400
401 sal_Int32 nStartPos = 0;
402 sal_Int32 nEndPos = pNode->GetText().getLength();
403 if ( nNode == nStartPara )
404 nStartPos = aSel.GetStart().GetIndex();
405 if ( nNode == nEndPara ) // may also be == nStart!
406 nEndPos = aSel.GetEnd().GetIndex();
407
408 aText.append(pNode->GetText().subView(nStartPos, nEndPos-nStartPos));
409 if ( nNode < nEndPara )
410 aText.append(pSep);
411 }
412 return aText.makeStringAndClear();
413 }
414
ImpRemoveText()415 void TextEngine::ImpRemoveText()
416 {
417 ImpInitDoc();
418
419 const TextSelection aEmptySel;
420 for (TextView* pView : *mpViews)
421 {
422 pView->ImpSetSelection( aEmptySel );
423 }
424 ResetUndo();
425 }
426
SetText(const OUString & rText)427 void TextEngine::SetText( const OUString& rText )
428 {
429 ImpRemoveText();
430
431 const bool bUndoCurrentlyEnabled = IsUndoEnabled();
432 // the manually inserted text cannot be reversed by the user
433 EnableUndo( false );
434
435 const TextSelection aEmptySel;
436
437 TextPaM aPaM;
438 if ( !rText.isEmpty() )
439 aPaM = ImpInsertText( aEmptySel, rText );
440
441 for (TextView* pView : *mpViews)
442 {
443 pView->ImpSetSelection( aEmptySel );
444
445 // if no text, then no Format&Update => the text remains
446 if ( rText.isEmpty() && GetUpdateMode() )
447 pView->Invalidate();
448 }
449
450 if( rText.isEmpty() ) // otherwise needs invalidation later; !bFormatted is sufficient
451 mnCurTextHeight = 0;
452
453 FormatAndUpdate();
454
455 EnableUndo( bUndoCurrentlyEnabled );
456 SAL_WARN_IF( HasUndoManager() && GetUndoManager().GetUndoActionCount(), "vcl", "SetText: Undo!" );
457 }
458
CursorMoved(sal_uInt32 nNode)459 void TextEngine::CursorMoved( sal_uInt32 nNode )
460 {
461 // delete empty attribute; but only if paragraph is not empty!
462 TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
463 if ( pNode && pNode->GetCharAttribs().HasEmptyAttribs() && !pNode->GetText().isEmpty() )
464 pNode->GetCharAttribs().DeleteEmptyAttribs();
465 }
466
ImpRemoveChars(const TextPaM & rPaM,sal_Int32 nChars)467 void TextEngine::ImpRemoveChars( const TextPaM& rPaM, sal_Int32 nChars )
468 {
469 SAL_WARN_IF( !nChars, "vcl", "ImpRemoveChars: 0 Chars?!" );
470 if ( IsUndoEnabled() && !IsInUndo() )
471 {
472 // attributes have to be saved for UNDO before RemoveChars!
473 TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
474 OUString aStr( pNode->GetText().copy( rPaM.GetIndex(), nChars ) );
475
476 // check if attributes are being deleted or changed
477 const sal_Int32 nStart = rPaM.GetIndex();
478 const sal_Int32 nEnd = nStart + nChars;
479 for ( sal_uInt16 nAttr = pNode->GetCharAttribs().Count(); nAttr; )
480 {
481 TextCharAttrib& rAttr = pNode->GetCharAttribs().GetAttrib( --nAttr );
482 if ( ( rAttr.GetEnd() >= nStart ) && ( rAttr.GetStart() < nEnd ) )
483 {
484 break; // for
485 }
486 }
487 InsertUndo( std::make_unique<TextUndoRemoveChars>( this, rPaM, aStr ) );
488 }
489
490 mpDoc->RemoveChars( rPaM, nChars );
491 ImpCharsRemoved( rPaM.GetPara(), rPaM.GetIndex(), nChars );
492 }
493
ImpConnectParagraphs(sal_uInt32 nLeft,sal_uInt32 nRight)494 TextPaM TextEngine::ImpConnectParagraphs( sal_uInt32 nLeft, sal_uInt32 nRight )
495 {
496 SAL_WARN_IF( nLeft == nRight, "vcl", "ImpConnectParagraphs: connect the very same paragraph ?" );
497
498 TextNode* pLeft = mpDoc->GetNodes()[ nLeft ].get();
499 TextNode* pRight = mpDoc->GetNodes()[ nRight ].get();
500
501 if ( IsUndoEnabled() && !IsInUndo() )
502 InsertUndo( std::make_unique<TextUndoConnectParas>( this, nLeft, pLeft->GetText().getLength() ) );
503
504 // first lookup Portions, as pRight is gone after ConnectParagraphs
505 TEParaPortion* pLeftPortion = mpTEParaPortions->GetObject( nLeft );
506 TEParaPortion* pRightPortion = mpTEParaPortions->GetObject( nRight );
507 SAL_WARN_IF( !pLeft || !pLeftPortion, "vcl", "ImpConnectParagraphs(1): Hidden Portion" );
508 SAL_WARN_IF( !pRight || !pRightPortion, "vcl", "ImpConnectParagraphs(2): Hidden Portion" );
509
510 TextPaM aPaM = mpDoc->ConnectParagraphs( pLeft, pRight );
511 ImpParagraphRemoved( nRight );
512
513 pLeftPortion->MarkSelectionInvalid( aPaM.GetIndex() );
514
515 mpTEParaPortions->Remove( nRight );
516 // the right Node is deleted by EditDoc::ConnectParagraphs()
517
518 return aPaM;
519 }
520
ImpDeleteText(const TextSelection & rSel)521 TextPaM TextEngine::ImpDeleteText( const TextSelection& rSel )
522 {
523 if ( !rSel.HasRange() )
524 return rSel.GetStart();
525
526 TextSelection aSel( rSel );
527 aSel.Justify();
528 TextPaM aStartPaM( aSel.GetStart() );
529 TextPaM aEndPaM( aSel.GetEnd() );
530
531 CursorMoved( aStartPaM.GetPara() ); // so that newly-adjusted attributes vanish
532 CursorMoved( aEndPaM.GetPara() ); // so that newly-adjusted attributes vanish
533
534 SAL_WARN_IF( !mpDoc->IsValidPaM( aStartPaM ), "vcl", "ImpDeleteText(1): bad Index" );
535 SAL_WARN_IF( !mpDoc->IsValidPaM( aEndPaM ), "vcl", "ImpDeleteText(2): bad Index" );
536
537 const sal_uInt32 nStartNode = aStartPaM.GetPara();
538 sal_uInt32 nEndNode = aEndPaM.GetPara();
539
540 // remove all Nodes inbetween
541 for ( sal_uInt32 z = nStartNode+1; z < nEndNode; ++z )
542 {
543 // always nStartNode+1, because of Remove()!
544 ImpRemoveParagraph( nStartNode+1 );
545 }
546
547 if ( nStartNode != nEndNode )
548 {
549 // the remainder of StartNodes...
550 TextNode* pLeft = mpDoc->GetNodes()[ nStartNode ].get();
551 sal_Int32 nChars = pLeft->GetText().getLength() - aStartPaM.GetIndex();
552 if ( nChars )
553 {
554 ImpRemoveChars( aStartPaM, nChars );
555 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nStartNode );
556 SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(3): bad Index" );
557 pPortion->MarkSelectionInvalid( aStartPaM.GetIndex() );
558 }
559
560 // the beginning of EndNodes...
561 nEndNode = nStartNode+1; // the other paragraphs were deleted
562 nChars = aEndPaM.GetIndex();
563 if ( nChars )
564 {
565 aEndPaM.GetPara() = nEndNode;
566 aEndPaM.GetIndex() = 0;
567 ImpRemoveChars( aEndPaM, nChars );
568 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nEndNode );
569 SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(4): bad Index" );
570 pPortion->MarkSelectionInvalid( 0 );
571 }
572
573 // connect...
574 aStartPaM = ImpConnectParagraphs( nStartNode, nEndNode );
575 }
576 else
577 {
578 const sal_Int32 nChars = aEndPaM.GetIndex() - aStartPaM.GetIndex();
579 ImpRemoveChars( aStartPaM, nChars );
580 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nStartNode );
581 SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(5): bad Index" );
582 pPortion->MarkInvalid( aEndPaM.GetIndex(), aStartPaM.GetIndex() - aEndPaM.GetIndex() );
583 }
584
585 // UpdateSelections();
586 TextModified();
587 return aStartPaM;
588 }
589
ImpRemoveParagraph(sal_uInt32 nPara)590 void TextEngine::ImpRemoveParagraph( sal_uInt32 nPara )
591 {
592 std::unique_ptr<TextNode> pNode = std::move(mpDoc->GetNodes()[ nPara ]);
593
594 // the Node is handled by Undo and is deleted if appropriate
595 mpDoc->GetNodes().erase( mpDoc->GetNodes().begin() + nPara );
596 if ( IsUndoEnabled() && !IsInUndo() )
597 InsertUndo( std::make_unique<TextUndoDelPara>( this, pNode.release(), nPara ) );
598
599 mpTEParaPortions->Remove( nPara );
600
601 ImpParagraphRemoved( nPara );
602 }
603
GetInputSequenceChecker()604 uno::Reference < i18n::XExtendedInputSequenceChecker > const & TextEngine::GetInputSequenceChecker()
605 {
606 if ( !mxISC.is() )
607 {
608 mxISC = i18n::InputSequenceChecker::create( ::comphelper::getProcessComponentContext() );
609 }
610 return mxISC;
611 }
612
IsInputSequenceCheckingRequired(sal_Unicode c,const TextSelection & rCurSel) const613 bool TextEngine::IsInputSequenceCheckingRequired( sal_Unicode c, const TextSelection& rCurSel ) const
614 {
615 // get the index that really is first
616 const sal_Int32 nFirstPos = std::min(rCurSel.GetStart().GetIndex(), rCurSel.GetEnd().GetIndex());
617
618 bool bIsSequenceChecking =
619 SvtCTLOptions::IsCTLFontEnabled() &&
620 SvtCTLOptions::IsCTLSequenceChecking() &&
621 nFirstPos != 0; /* first char needs not to be checked */
622
623 if (bIsSequenceChecking)
624 {
625 uno::Reference< i18n::XBreakIterator > xBI = const_cast<TextEngine *>(this)->GetBreakIterator();
626 bIsSequenceChecking = xBI.is() && i18n::ScriptType::COMPLEX == xBI->getScriptType( OUString( c ), 0 );
627 }
628
629 return bIsSequenceChecking;
630 }
631
ImpInsertText(const TextSelection & rCurSel,sal_Unicode c,bool bOverwrite)632 TextPaM TextEngine::ImpInsertText( const TextSelection& rCurSel, sal_Unicode c, bool bOverwrite )
633 {
634 return ImpInsertText( c, rCurSel, bOverwrite );
635 }
636
ImpInsertText(sal_Unicode c,const TextSelection & rCurSel,bool bOverwrite,bool bIsUserInput)637 TextPaM TextEngine::ImpInsertText( sal_Unicode c, const TextSelection& rCurSel, bool bOverwrite, bool bIsUserInput )
638 {
639 SAL_WARN_IF( c == '\n', "vcl", "InsertText: NewLine!" );
640 SAL_WARN_IF( c == '\r', "vcl", "InsertText: NewLine!" );
641
642 TextPaM aPaM( rCurSel.GetStart() );
643 TextNode* pNode = mpDoc->GetNodes()[ aPaM.GetPara() ].get();
644
645 bool bDoOverwrite = bOverwrite && ( aPaM.GetIndex() < pNode->GetText().getLength() );
646
647 bool bUndoAction = rCurSel.HasRange() || bDoOverwrite;
648
649 if ( bUndoAction )
650 UndoActionStart();
651
652 if ( rCurSel.HasRange() )
653 {
654 aPaM = ImpDeleteText( rCurSel );
655 }
656 else if ( bDoOverwrite )
657 {
658 // if selection, then don't overwrite a character
659 TextSelection aTmpSel( aPaM );
660 ++aTmpSel.GetEnd().GetIndex();
661 ImpDeleteText( aTmpSel );
662 }
663
664 if (bIsUserInput && IsInputSequenceCheckingRequired( c, rCurSel ))
665 {
666 uno::Reference < i18n::XExtendedInputSequenceChecker > xISC = GetInputSequenceChecker();
667
668 if (xISC.is())
669 {
670 sal_Int32 nTmpPos = aPaM.GetIndex();
671 sal_Int16 nCheckMode = SvtCTLOptions::IsCTLSequenceCheckingRestricted() ?
672 i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC;
673
674 // the text that needs to be checked is only the one
675 // before the current cursor position
676 OUString aOldText( mpDoc->GetText( aPaM.GetPara() ).copy(0, nTmpPos) );
677 if (SvtCTLOptions::IsCTLSequenceCheckingTypeAndReplace())
678 {
679 OUString aNewText( aOldText );
680 xISC->correctInputSequence( aNewText, nTmpPos - 1, c, nCheckMode );
681
682 // find position of first character that has changed
683 const sal_Int32 nOldLen = aOldText.getLength();
684 const sal_Int32 nNewLen = aNewText.getLength();
685 const sal_Unicode *pOldTxt = aOldText.getStr();
686 const sal_Unicode *pNewTxt = aNewText.getStr();
687 sal_Int32 nChgPos = 0;
688 while ( nChgPos < nOldLen && nChgPos < nNewLen &&
689 pOldTxt[nChgPos] == pNewTxt[nChgPos] )
690 ++nChgPos;
691
692 OUString aChgText( aNewText.copy( nChgPos ) );
693
694 // select text from first pos to be changed to current pos
695 TextSelection aSel( TextPaM( aPaM.GetPara(), nChgPos ), aPaM );
696
697 if (!aChgText.isEmpty())
698 // ImpInsertText implicitly handles undo...
699 return ImpInsertText( aSel, aChgText );
700 else
701 return aPaM;
702 }
703 else
704 {
705 // should the character be ignored (i.e. not get inserted) ?
706 if (!xISC->checkInputSequence( aOldText, nTmpPos - 1, c, nCheckMode ))
707 return aPaM; // nothing to be done -> no need for undo
708 }
709 }
710
711 // at this point now we will insert the character 'normally' some lines below...
712 }
713
714 if ( IsUndoEnabled() && !IsInUndo() )
715 {
716 std::unique_ptr<TextUndoInsertChars> pNewUndo(new TextUndoInsertChars( this, aPaM, OUString(c) ));
717 bool bTryMerge = !bDoOverwrite && ( c != ' ' );
718 InsertUndo( std::move(pNewUndo), bTryMerge );
719 }
720
721 TEParaPortion* pPortion = mpTEParaPortions->GetObject( aPaM.GetPara() );
722 pPortion->MarkInvalid( aPaM.GetIndex(), 1 );
723 if ( c == '\t' )
724 pPortion->SetNotSimpleInvalid();
725 aPaM = mpDoc->InsertText( aPaM, c );
726 ImpCharsInserted( aPaM.GetPara(), aPaM.GetIndex()-1, 1 );
727
728 TextModified();
729
730 if ( bUndoAction )
731 UndoActionEnd();
732
733 return aPaM;
734 }
735
ImpInsertText(const TextSelection & rCurSel,const OUString & rStr)736 TextPaM TextEngine::ImpInsertText( const TextSelection& rCurSel, const OUString& rStr )
737 {
738 UndoActionStart();
739
740 TextPaM aPaM;
741
742 if ( rCurSel.HasRange() )
743 aPaM = ImpDeleteText( rCurSel );
744 else
745 aPaM = rCurSel.GetEnd();
746
747 OUString aText(convertLineEnd(rStr, LINEEND_LF));
748
749 sal_Int32 nStart = 0;
750 while ( nStart < aText.getLength() )
751 {
752 sal_Int32 nEnd = aText.indexOf( LINE_SEP, nStart );
753 if (nEnd == -1)
754 nEnd = aText.getLength(); // do not dereference!
755
756 // Start == End => empty line
757 if ( nEnd > nStart )
758 {
759 OUString aLine(aText.copy(nStart, nEnd-nStart));
760 if ( IsUndoEnabled() && !IsInUndo() )
761 InsertUndo( std::make_unique<TextUndoInsertChars>( this, aPaM, aLine ) );
762
763 TEParaPortion* pPortion = mpTEParaPortions->GetObject( aPaM.GetPara() );
764 pPortion->MarkInvalid( aPaM.GetIndex(), aLine.getLength() );
765 if (aLine.indexOf( '\t' ) != -1)
766 pPortion->SetNotSimpleInvalid();
767
768 aPaM = mpDoc->InsertText( aPaM, aLine );
769 ImpCharsInserted( aPaM.GetPara(), aPaM.GetIndex()-aLine.getLength(), aLine.getLength() );
770
771 }
772 if ( nEnd < aText.getLength() )
773 aPaM = ImpInsertParaBreak( aPaM );
774
775 if ( nEnd == aText.getLength() ) // #108611# prevent overflow in "nStart = nEnd+1" calculation
776 break;
777
778 nStart = nEnd+1;
779 }
780
781 UndoActionEnd();
782
783 TextModified();
784 return aPaM;
785 }
786
ImpInsertParaBreak(const TextSelection & rCurSel)787 TextPaM TextEngine::ImpInsertParaBreak( const TextSelection& rCurSel )
788 {
789 TextPaM aPaM;
790 if ( rCurSel.HasRange() )
791 aPaM = ImpDeleteText( rCurSel );
792 else
793 aPaM = rCurSel.GetEnd();
794
795 return ImpInsertParaBreak( aPaM );
796 }
797
ImpInsertParaBreak(const TextPaM & rPaM)798 TextPaM TextEngine::ImpInsertParaBreak( const TextPaM& rPaM )
799 {
800 if ( IsUndoEnabled() && !IsInUndo() )
801 InsertUndo( std::make_unique<TextUndoSplitPara>( this, rPaM.GetPara(), rPaM.GetIndex() ) );
802
803 TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
804 bool bFirstParaContentChanged = rPaM.GetIndex() < pNode->GetText().getLength();
805
806 TextPaM aPaM( mpDoc->InsertParaBreak( rPaM ) );
807
808 TEParaPortion* pPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
809 SAL_WARN_IF( !pPortion, "vcl", "ImpInsertParaBreak: Hidden Portion" );
810 pPortion->MarkInvalid( rPaM.GetIndex(), 0 );
811
812 TextNode* pNewNode = mpDoc->GetNodes()[ aPaM.GetPara() ].get();
813 TEParaPortion* pNewPortion = new TEParaPortion( pNewNode );
814 mpTEParaPortions->Insert( pNewPortion, aPaM.GetPara() );
815 ImpParagraphInserted( aPaM.GetPara() );
816
817 CursorMoved( rPaM.GetPara() ); // if empty attribute created
818 TextModified();
819
820 if ( bFirstParaContentChanged )
821 Broadcast( TextHint( SfxHintId::TextParaContentChanged, rPaM.GetPara() ) );
822
823 return aPaM;
824 }
825
PaMtoEditCursor(const TextPaM & rPaM,bool bSpecial)826 tools::Rectangle TextEngine::PaMtoEditCursor( const TextPaM& rPaM, bool bSpecial )
827 {
828 SAL_WARN_IF( !GetUpdateMode(), "vcl", "PaMtoEditCursor: GetUpdateMode()" );
829
830 tools::Rectangle aEditCursor;
831 tools::Long nY = 0;
832
833 if ( !mbHasMultiLineParas )
834 {
835 nY = rPaM.GetPara() * mnCharHeight;
836 }
837 else
838 {
839 for ( sal_uInt32 nPortion = 0; nPortion < rPaM.GetPara(); ++nPortion )
840 {
841 TEParaPortion* pPortion = mpTEParaPortions->GetObject(nPortion);
842 nY += pPortion->GetLines().size() * mnCharHeight;
843 }
844 }
845
846 aEditCursor = GetEditCursor( rPaM, bSpecial );
847 aEditCursor.AdjustTop(nY );
848 aEditCursor.AdjustBottom(nY );
849 return aEditCursor;
850 }
851
GetEditCursor(const TextPaM & rPaM,bool bSpecial,bool bPreferPortionStart)852 tools::Rectangle TextEngine::GetEditCursor( const TextPaM& rPaM, bool bSpecial, bool bPreferPortionStart )
853 {
854 if ( !IsFormatted() && !IsFormatting() )
855 FormatAndUpdate();
856
857 TEParaPortion* pPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
858 //TextNode* pNode = mpDoc->GetNodes().GetObject( rPaM.GetPara() );
859
860 /*
861 bSpecial: If behind the last character of a made up line, stay at the
862 end of the line, not at the start of the next line.
863 Purpose: - really END = > behind the last character
864 - to selection...
865
866 */
867
868 tools::Long nY = 0;
869 sal_Int32 nCurIndex = 0;
870 TextLine* pLine = nullptr;
871 for (TextLine & rTmpLine : pPortion->GetLines())
872 {
873 if ( ( rTmpLine.GetStart() == rPaM.GetIndex() ) || ( rTmpLine.IsIn( rPaM.GetIndex(), bSpecial ) ) )
874 {
875 pLine = &rTmpLine;
876 break;
877 }
878
879 nCurIndex = nCurIndex + rTmpLine.GetLen();
880 nY += mnCharHeight;
881 }
882 if ( !pLine )
883 {
884 // Cursor at end of paragraph
885 SAL_WARN_IF( rPaM.GetIndex() != nCurIndex, "vcl", "GetEditCursor: Bad Index!" );
886
887 pLine = & ( pPortion->GetLines().back() );
888 nY -= mnCharHeight;
889 }
890
891 tools::Rectangle aEditCursor;
892
893 aEditCursor.SetTop( nY );
894 nY += mnCharHeight;
895 aEditCursor.SetBottom( nY-1 );
896
897 // search within the line
898 tools::Long nX = ImpGetXPos( rPaM.GetPara(), pLine, rPaM.GetIndex(), bPreferPortionStart );
899 aEditCursor.SetLeft(nX);
900 aEditCursor.SetRight(nX);
901 return aEditCursor;
902 }
903
ImpGetXPos(sal_uInt32 nPara,TextLine * pLine,sal_Int32 nIndex,bool bPreferPortionStart)904 tools::Long TextEngine::ImpGetXPos( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart )
905 {
906 SAL_WARN_IF( ( nIndex < pLine->GetStart() ) || ( nIndex > pLine->GetEnd() ) , "vcl", "ImpGetXPos: Bad parameters!" );
907
908 bool bDoPreferPortionStart = bPreferPortionStart;
909 // Assure that the portion belongs to this line
910 if ( nIndex == pLine->GetStart() )
911 bDoPreferPortionStart = true;
912 else if ( nIndex == pLine->GetEnd() )
913 bDoPreferPortionStart = false;
914
915 TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
916
917 sal_Int32 nTextPortionStart = 0;
918 std::size_t nTextPortion = pParaPortion->GetTextPortions().FindPortion( nIndex, nTextPortionStart, bDoPreferPortionStart );
919
920 SAL_WARN_IF( ( nTextPortion < pLine->GetStartPortion() ) || ( nTextPortion > pLine->GetEndPortion() ), "vcl", "GetXPos: Portion not in current line!" );
921
922 TETextPortion& rPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
923
924 tools::Long nX = ImpGetPortionXOffset( nPara, pLine, nTextPortion );
925
926 tools::Long nPortionTextWidth = rPortion.GetWidth();
927
928 if ( nTextPortionStart != nIndex )
929 {
930 // Search within portion...
931 if ( nIndex == ( nTextPortionStart + rPortion.GetLen() ) )
932 {
933 // End of Portion
934 if ( ( rPortion.GetKind() == PORTIONKIND_TAB ) ||
935 ( !IsRightToLeft() && !rPortion.IsRightToLeft() ) ||
936 ( IsRightToLeft() && rPortion.IsRightToLeft() ) )
937 {
938 nX += nPortionTextWidth;
939 if ( ( rPortion.GetKind() == PORTIONKIND_TAB ) && ( (nTextPortion+1) < pParaPortion->GetTextPortions().size() ) )
940 {
941 TETextPortion& rNextPortion = pParaPortion->GetTextPortions()[ nTextPortion+1 ];
942 if (rNextPortion.GetKind() != PORTIONKIND_TAB && IsRightToLeft() != rNextPortion.IsRightToLeft())
943 {
944 // End of the tab portion, use start of next for cursor pos
945 SAL_WARN_IF( bPreferPortionStart, "vcl", "ImpGetXPos: How can we get here!" );
946 nX = ImpGetXPos( nPara, pLine, nIndex, true );
947 }
948
949 }
950 }
951 }
952 else if ( rPortion.GetKind() == PORTIONKIND_TEXT )
953 {
954 SAL_WARN_IF( nIndex == pLine->GetStart(), "vcl", "ImpGetXPos: Strange behavior" );
955
956 tools::Long nPosInPortion = CalcTextWidth( nPara, nTextPortionStart, nIndex-nTextPortionStart );
957
958 if (IsRightToLeft() == rPortion.IsRightToLeft())
959 {
960 nX += nPosInPortion;
961 }
962 else
963 {
964 nX += nPortionTextWidth - nPosInPortion;
965 }
966 }
967 }
968 else // if ( nIndex == pLine->GetStart() )
969 {
970 if (rPortion.GetKind() != PORTIONKIND_TAB && IsRightToLeft() != rPortion.IsRightToLeft())
971 {
972 nX += nPortionTextWidth;
973 }
974 }
975
976 return nX;
977 }
978
FindAttrib(const TextPaM & rPaM,sal_uInt16 nWhich) const979 const TextAttrib* TextEngine::FindAttrib( const TextPaM& rPaM, sal_uInt16 nWhich ) const
980 {
981 const TextAttrib* pAttr = nullptr;
982 const TextCharAttrib* pCharAttr = FindCharAttrib( rPaM, nWhich );
983 if ( pCharAttr )
984 pAttr = &pCharAttr->GetAttr();
985 return pAttr;
986 }
987
FindCharAttrib(const TextPaM & rPaM,sal_uInt16 nWhich) const988 const TextCharAttrib* TextEngine::FindCharAttrib( const TextPaM& rPaM, sal_uInt16 nWhich ) const
989 {
990 const TextCharAttrib* pAttr = nullptr;
991 TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
992 if (pNode && (rPaM.GetIndex() <= pNode->GetText().getLength()))
993 pAttr = pNode->GetCharAttribs().FindAttrib( nWhich, rPaM.GetIndex() );
994 return pAttr;
995 }
996
GetPaM(const Point & rDocPos)997 TextPaM TextEngine::GetPaM( const Point& rDocPos )
998 {
999 SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetPaM: GetUpdateMode()" );
1000
1001 tools::Long nY = 0;
1002 for ( sal_uInt32 nPortion = 0; nPortion < mpTEParaPortions->Count(); ++nPortion )
1003 {
1004 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
1005 tools::Long nTmpHeight = pPortion->GetLines().size() * mnCharHeight;
1006 nY += nTmpHeight;
1007 if ( nY > rDocPos.Y() )
1008 {
1009 nY -= nTmpHeight;
1010 Point aPosInPara( rDocPos );
1011 aPosInPara.AdjustY( -nY );
1012
1013 TextPaM aPaM( nPortion, 0 );
1014 aPaM.GetIndex() = ImpFindIndex( nPortion, aPosInPara );
1015 return aPaM;
1016 }
1017 }
1018
1019 // not found - go to last visible
1020 const sal_uInt32 nLastNode = static_cast<sal_uInt32>(mpDoc->GetNodes().size() - 1);
1021 TextNode* pLast = mpDoc->GetNodes()[ nLastNode ].get();
1022 return TextPaM( nLastNode, pLast->GetText().getLength() );
1023 }
1024
ImpFindIndex(sal_uInt32 nPortion,const Point & rPosInPara)1025 sal_Int32 TextEngine::ImpFindIndex( sal_uInt32 nPortion, const Point& rPosInPara )
1026 {
1027 SAL_WARN_IF( !IsFormatted(), "vcl", "GetPaM: Not formatted" );
1028 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
1029
1030 sal_Int32 nCurIndex = 0;
1031
1032 tools::Long nY = 0;
1033 TextLine* pLine = nullptr;
1034 std::vector<TextLine>::size_type nLine;
1035 for ( nLine = 0; nLine < pPortion->GetLines().size(); nLine++ )
1036 {
1037 TextLine& rmpLine = pPortion->GetLines()[ nLine ];
1038 nY += mnCharHeight;
1039 if ( nY > rPosInPara.Y() ) // that's it
1040 {
1041 pLine = &rmpLine;
1042 break; // correct Y-Position not needed
1043 }
1044 }
1045
1046 assert(pLine && "ImpFindIndex: pLine ?");
1047
1048 nCurIndex = GetCharPos( nPortion, nLine, rPosInPara.X() );
1049
1050 if ( nCurIndex && ( nCurIndex == pLine->GetEnd() ) &&
1051 ( pLine != &( pPortion->GetLines().back() ) ) )
1052 {
1053 uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
1054 sal_Int32 nCount = 1;
1055 nCurIndex = xBI->previousCharacters( pPortion->GetNode()->GetText(), nCurIndex, GetLocale(), i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount );
1056 }
1057 return nCurIndex;
1058 }
1059
GetCharPos(sal_uInt32 nPortion,std::vector<TextLine>::size_type nLine,tools::Long nXPos)1060 sal_Int32 TextEngine::GetCharPos( sal_uInt32 nPortion, std::vector<TextLine>::size_type nLine, tools::Long nXPos )
1061 {
1062
1063 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
1064 TextLine& rLine = pPortion->GetLines()[ nLine ];
1065
1066 sal_Int32 nCurIndex = rLine.GetStart();
1067
1068 tools::Long nTmpX = rLine.GetStartX();
1069 if ( nXPos <= nTmpX )
1070 return nCurIndex;
1071
1072 for ( std::size_t i = rLine.GetStartPortion(); i <= rLine.GetEndPortion(); i++ )
1073 {
1074 TETextPortion& rTextPortion = pPortion->GetTextPortions()[ i ];
1075 nTmpX += rTextPortion.GetWidth();
1076
1077 if ( nTmpX > nXPos )
1078 {
1079 if( rTextPortion.GetLen() > 1 )
1080 {
1081 nTmpX -= rTextPortion.GetWidth(); // position before Portion
1082 // TODO: Optimize: no GetTextBreak if fixed-width Font
1083 vcl::Font aFont;
1084 SeekCursor( nPortion, nCurIndex+1, aFont, nullptr );
1085 mpRefDev->SetFont( aFont);
1086 tools::Long nPosInPortion = nXPos-nTmpX;
1087 if ( IsRightToLeft() != rTextPortion.IsRightToLeft() )
1088 nPosInPortion = rTextPortion.GetWidth() - nPosInPortion;
1089 nCurIndex = mpRefDev->GetTextBreak( pPortion->GetNode()->GetText(), nPosInPortion, nCurIndex );
1090 // MT: GetTextBreak should assure that we are not within a CTL cell...
1091 }
1092 return nCurIndex;
1093 }
1094 nCurIndex += rTextPortion.GetLen();
1095 }
1096 return nCurIndex;
1097 }
1098
GetTextHeight() const1099 tools::Long TextEngine::GetTextHeight() const
1100 {
1101 SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetTextHeight: GetUpdateMode()" );
1102
1103 if ( !IsFormatted() && !IsFormatting() )
1104 const_cast<TextEngine*>(this)->FormatAndUpdate();
1105
1106 return mnCurTextHeight;
1107 }
1108
GetTextHeight(sal_uInt32 nParagraph) const1109 tools::Long TextEngine::GetTextHeight( sal_uInt32 nParagraph ) const
1110 {
1111 SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetTextHeight: GetUpdateMode()" );
1112
1113 if ( !IsFormatted() && !IsFormatting() )
1114 const_cast<TextEngine*>(this)->FormatAndUpdate();
1115
1116 return CalcParaHeight( nParagraph );
1117 }
1118
CalcTextWidth(sal_uInt32 nPara)1119 tools::Long TextEngine::CalcTextWidth( sal_uInt32 nPara )
1120 {
1121 tools::Long nParaWidth = 0;
1122 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
1123 for ( auto nLine = pPortion->GetLines().size(); nLine; )
1124 {
1125 tools::Long nLineWidth = 0;
1126 TextLine& rLine = pPortion->GetLines()[ --nLine ];
1127 for ( std::size_t nTP = rLine.GetStartPortion(); nTP <= rLine.GetEndPortion(); nTP++ )
1128 {
1129 TETextPortion& rTextPortion = pPortion->GetTextPortions()[ nTP ];
1130 nLineWidth += rTextPortion.GetWidth();
1131 }
1132 if ( nLineWidth > nParaWidth )
1133 nParaWidth = nLineWidth;
1134 }
1135 return nParaWidth;
1136 }
1137
CalcTextWidth()1138 tools::Long TextEngine::CalcTextWidth()
1139 {
1140 if ( !IsFormatted() && !IsFormatting() )
1141 FormatAndUpdate();
1142
1143 if ( mnCurTextWidth < 0 )
1144 {
1145 mnCurTextWidth = 0;
1146 for ( sal_uInt32 nPara = mpTEParaPortions->Count(); nPara; )
1147 {
1148 const tools::Long nParaWidth = CalcTextWidth( --nPara );
1149 if ( nParaWidth > mnCurTextWidth )
1150 mnCurTextWidth = nParaWidth;
1151 }
1152 }
1153 return mnCurTextWidth+1;// wider by 1, as CreateLines breaks at >=
1154 }
1155
CalcTextHeight() const1156 tools::Long TextEngine::CalcTextHeight() const
1157 {
1158 SAL_WARN_IF( !GetUpdateMode(), "vcl", "CalcTextHeight: GetUpdateMode()" );
1159
1160 tools::Long nY = 0;
1161 for ( auto nPortion = mpTEParaPortions->Count(); nPortion; )
1162 nY += CalcParaHeight( --nPortion );
1163 return nY;
1164 }
1165
CalcTextWidth(sal_uInt32 nPara,sal_Int32 nPortionStart,sal_Int32 nLen)1166 tools::Long TextEngine::CalcTextWidth( sal_uInt32 nPara, sal_Int32 nPortionStart, sal_Int32 nLen )
1167 {
1168 #ifdef DBG_UTIL
1169 // within the text there must not be a Portion change (attribute/tab)!
1170 sal_Int32 nTabPos = mpDoc->GetNodes()[ nPara ]->GetText().indexOf( '\t', nPortionStart );
1171 SAL_WARN_IF( nTabPos != -1 && nTabPos < (nPortionStart+nLen), "vcl", "CalcTextWidth: Tab!" );
1172 #endif
1173
1174 vcl::Font aFont;
1175 SeekCursor( nPara, nPortionStart+1, aFont, nullptr );
1176 mpRefDev->SetFont( aFont );
1177 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
1178 tools::Long nWidth = mpRefDev->GetTextWidth( pNode->GetText(), nPortionStart, nLen );
1179 return nWidth;
1180 }
1181
GetTextPortionRange(const TextPaM & rPaM,sal_Int32 & nStart,sal_Int32 & nEnd)1182 void TextEngine::GetTextPortionRange(const TextPaM& rPaM, sal_Int32& nStart, sal_Int32& nEnd)
1183 {
1184 nStart = 0;
1185 nEnd = 0;
1186 TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
1187 for ( std::size_t i = 0; i < pParaPortion->GetTextPortions().size(); ++i )
1188 {
1189 TETextPortion& rTextPortion = pParaPortion->GetTextPortions()[ i ];
1190 if (nStart + rTextPortion.GetLen() > rPaM.GetIndex())
1191 {
1192 nEnd = nStart + rTextPortion.GetLen();
1193 return;
1194 }
1195 else
1196 {
1197 nStart += rTextPortion.GetLen();
1198 }
1199 }
1200 }
1201
GetLineCount(sal_uInt32 nParagraph) const1202 sal_uInt16 TextEngine::GetLineCount( sal_uInt32 nParagraph ) const
1203 {
1204 SAL_WARN_IF( nParagraph >= mpTEParaPortions->Count(), "vcl", "GetLineCount: Out of range" );
1205
1206 TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
1207 if ( pPPortion )
1208 return pPPortion->GetLines().size();
1209
1210 return 0;
1211 }
1212
GetLineLen(sal_uInt32 nParagraph,sal_uInt16 nLine) const1213 sal_Int32 TextEngine::GetLineLen( sal_uInt32 nParagraph, sal_uInt16 nLine ) const
1214 {
1215 SAL_WARN_IF( nParagraph >= mpTEParaPortions->Count(), "vcl", "GetLineCount: Out of range" );
1216
1217 TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
1218 if ( pPPortion && ( nLine < pPPortion->GetLines().size() ) )
1219 {
1220 return pPPortion->GetLines()[ nLine ].GetLen();
1221 }
1222
1223 return 0;
1224 }
1225
CalcParaHeight(sal_uInt32 nParagraph) const1226 tools::Long TextEngine::CalcParaHeight( sal_uInt32 nParagraph ) const
1227 {
1228 tools::Long nHeight = 0;
1229
1230 TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
1231 SAL_WARN_IF( !pPPortion, "vcl", "GetParaHeight: paragraph not found" );
1232 if ( pPPortion )
1233 nHeight = pPPortion->GetLines().size() * mnCharHeight;
1234
1235 return nHeight;
1236 }
1237
GetInvalidYOffsets(sal_uInt32 nPortion)1238 Range TextEngine::GetInvalidYOffsets( sal_uInt32 nPortion )
1239 {
1240 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPortion );
1241 sal_uInt16 nLines = pTEParaPortion->GetLines().size();
1242 sal_uInt16 nLastInvalid, nFirstInvalid = 0;
1243 sal_uInt16 nLine;
1244 for ( nLine = 0; nLine < nLines; nLine++ )
1245 {
1246 TextLine& rL = pTEParaPortion->GetLines()[ nLine ];
1247 if ( rL.IsInvalid() )
1248 {
1249 nFirstInvalid = nLine;
1250 break;
1251 }
1252 }
1253
1254 for ( nLastInvalid = nFirstInvalid; nLastInvalid < nLines; nLastInvalid++ )
1255 {
1256 TextLine& rL = pTEParaPortion->GetLines()[ nLine ];
1257 if ( rL.IsValid() )
1258 break;
1259 }
1260
1261 if ( nLastInvalid >= nLines )
1262 nLastInvalid = nLines-1;
1263
1264 return Range( nFirstInvalid*mnCharHeight, ((nLastInvalid+1)*mnCharHeight)-1 );
1265 }
1266
GetParagraphCount() const1267 sal_uInt32 TextEngine::GetParagraphCount() const
1268 {
1269 return static_cast<sal_uInt32>(mpDoc->GetNodes().size());
1270 }
1271
EnableUndo(bool bEnable)1272 void TextEngine::EnableUndo( bool bEnable )
1273 {
1274 // delete list when switching mode
1275 if ( bEnable != IsUndoEnabled() )
1276 ResetUndo();
1277
1278 mbUndoEnabled = bEnable;
1279 }
1280
GetUndoManager()1281 SfxUndoManager& TextEngine::GetUndoManager()
1282 {
1283 if ( !mpUndoManager )
1284 mpUndoManager.reset( new TextUndoManager( this ) );
1285 return *mpUndoManager;
1286 }
1287
UndoActionStart(sal_uInt16 nId)1288 void TextEngine::UndoActionStart( sal_uInt16 nId )
1289 {
1290 if ( IsUndoEnabled() && !IsInUndo() )
1291 {
1292 GetUndoManager().EnterListAction( OUString(), OUString(), nId, ViewShellId(-1) );
1293 }
1294 }
1295
UndoActionEnd()1296 void TextEngine::UndoActionEnd()
1297 {
1298 if ( IsUndoEnabled() && !IsInUndo() )
1299 GetUndoManager().LeaveListAction();
1300 }
1301
InsertUndo(std::unique_ptr<TextUndo> pUndo,bool bTryMerge)1302 void TextEngine::InsertUndo( std::unique_ptr<TextUndo> pUndo, bool bTryMerge )
1303 {
1304 SAL_WARN_IF( IsInUndo(), "vcl", "InsertUndo: in Undo mode!" );
1305 GetUndoManager().AddUndoAction( std::move(pUndo), bTryMerge );
1306 }
1307
ResetUndo()1308 void TextEngine::ResetUndo()
1309 {
1310 if ( mpUndoManager )
1311 mpUndoManager->Clear();
1312 }
1313
InsertContent(std::unique_ptr<TextNode> pNode,sal_uInt32 nPara)1314 void TextEngine::InsertContent( std::unique_ptr<TextNode> pNode, sal_uInt32 nPara )
1315 {
1316 SAL_WARN_IF( !pNode, "vcl", "InsertContent: NULL-Pointer!" );
1317 SAL_WARN_IF( !IsInUndo(), "vcl", "InsertContent: only in Undo()!" );
1318 TEParaPortion* pNew = new TEParaPortion( pNode.get() );
1319 mpTEParaPortions->Insert( pNew, nPara );
1320 mpDoc->GetNodes().insert( mpDoc->GetNodes().begin() + nPara, std::move(pNode) );
1321 ImpParagraphInserted( nPara );
1322 }
1323
SplitContent(sal_uInt32 nNode,sal_Int32 nSepPos)1324 TextPaM TextEngine::SplitContent( sal_uInt32 nNode, sal_Int32 nSepPos )
1325 {
1326 #ifdef DBG_UTIL
1327 TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
1328 assert(pNode && "SplitContent: Invalid Node!");
1329 SAL_WARN_IF( !IsInUndo(), "vcl", "SplitContent: only in Undo()!" );
1330 SAL_WARN_IF( nSepPos > pNode->GetText().getLength(), "vcl", "SplitContent: Bad index" );
1331 #endif
1332 TextPaM aPaM( nNode, nSepPos );
1333 return ImpInsertParaBreak( aPaM );
1334 }
1335
ConnectContents(sal_uInt32 nLeftNode)1336 TextPaM TextEngine::ConnectContents( sal_uInt32 nLeftNode )
1337 {
1338 SAL_WARN_IF( !IsInUndo(), "vcl", "ConnectContent: only in Undo()!" );
1339 return ImpConnectParagraphs( nLeftNode, nLeftNode+1 );
1340 }
1341
SeekCursor(sal_uInt32 nPara,sal_Int32 nPos,vcl::Font & rFont,OutputDevice * pOutDev)1342 void TextEngine::SeekCursor( sal_uInt32 nPara, sal_Int32 nPos, vcl::Font& rFont, OutputDevice* pOutDev )
1343 {
1344 rFont = maFont;
1345 if ( pOutDev )
1346 pOutDev->SetTextColor( maTextColor );
1347
1348 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
1349 sal_uInt16 nAttribs = pNode->GetCharAttribs().Count();
1350 for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
1351 {
1352 TextCharAttrib& rAttrib = pNode->GetCharAttribs().GetAttrib( nAttr );
1353 if ( rAttrib.GetStart() > nPos )
1354 break;
1355
1356 // When seeking don't use Attr that start there!
1357 // Do not use empty attributes:
1358 // - If just being setup and empty => no effect on Font
1359 // - Characters that are setup in an empty paragraph become visible right away.
1360 if ( ( ( rAttrib.GetStart() < nPos ) && ( rAttrib.GetEnd() >= nPos ) )
1361 || pNode->GetText().isEmpty() )
1362 {
1363 if ( rAttrib.Which() != TEXTATTR_FONTCOLOR )
1364 {
1365 rAttrib.GetAttr().SetFont(rFont);
1366 }
1367 else
1368 {
1369 if ( pOutDev )
1370 pOutDev->SetTextColor( static_cast<const TextAttribFontColor&>(rAttrib.GetAttr()).GetColor() );
1371 }
1372 }
1373 }
1374
1375 if ( !(mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetPara() == nPara ) &&
1376 ( nPos > mpIMEInfos->aPos.GetIndex() ) && ( nPos <= ( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen ) )) )
1377 return;
1378
1379 ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[ nPos - mpIMEInfos->aPos.GetIndex() - 1 ];
1380 if ( nAttr & ExtTextInputAttr::Underline )
1381 rFont.SetUnderline( LINESTYLE_SINGLE );
1382 else if ( nAttr & ExtTextInputAttr::DoubleUnderline )
1383 rFont.SetUnderline( LINESTYLE_DOUBLE );
1384 else if ( nAttr & ExtTextInputAttr::BoldUnderline )
1385 rFont.SetUnderline( LINESTYLE_BOLD );
1386 else if ( nAttr & ExtTextInputAttr::DottedUnderline )
1387 rFont.SetUnderline( LINESTYLE_DOTTED );
1388 else if ( nAttr & ExtTextInputAttr::DashDotUnderline )
1389 rFont.SetUnderline( LINESTYLE_DOTTED );
1390 if ( nAttr & ExtTextInputAttr::RedText )
1391 rFont.SetColor( COL_RED );
1392 else if ( nAttr & ExtTextInputAttr::HalfToneText )
1393 rFont.SetColor( COL_LIGHTGRAY );
1394 if ( nAttr & ExtTextInputAttr::Highlight )
1395 {
1396 const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
1397 rFont.SetColor( rStyleSettings.GetHighlightTextColor() );
1398 rFont.SetFillColor( rStyleSettings.GetHighlightColor() );
1399 rFont.SetTransparent( false );
1400 }
1401 else if ( nAttr & ExtTextInputAttr::GrayWaveline )
1402 {
1403 rFont.SetUnderline( LINESTYLE_WAVE );
1404 // if( pOut )
1405 // pOut->SetTextLineColor( COL_LIGHTGRAY );
1406 }
1407 }
1408
FormatAndUpdate(TextView * pCurView)1409 void TextEngine::FormatAndUpdate( TextView* pCurView )
1410 {
1411 if ( mbDowning )
1412 return;
1413
1414 if ( IsInUndo() )
1415 IdleFormatAndUpdate( pCurView );
1416 else
1417 {
1418 FormatDoc();
1419 UpdateViews( pCurView );
1420 }
1421 }
1422
IdleFormatAndUpdate(TextView * pCurView,sal_uInt16 nMaxTimerRestarts)1423 void TextEngine::IdleFormatAndUpdate( TextView* pCurView, sal_uInt16 nMaxTimerRestarts )
1424 {
1425 mpIdleFormatter->DoIdleFormat( pCurView, nMaxTimerRestarts );
1426 }
1427
TextModified()1428 void TextEngine::TextModified()
1429 {
1430 mbFormatted = false;
1431 mbModified = true;
1432 }
1433
UpdateViews(TextView * pCurView)1434 void TextEngine::UpdateViews( TextView* pCurView )
1435 {
1436 if ( !GetUpdateMode() || IsFormatting() || maInvalidRect.IsEmpty() )
1437 return;
1438
1439 SAL_WARN_IF( !IsFormatted(), "vcl", "UpdateViews: Doc not formatted!" );
1440
1441 for (TextView* pView : *mpViews)
1442 {
1443 pView->HideCursor();
1444
1445 tools::Rectangle aClipRect( maInvalidRect );
1446 const Size aOutSz = pView->GetWindow()->GetOutputSizePixel();
1447 const tools::Rectangle aVisArea( pView->GetStartDocPos(), aOutSz );
1448 aClipRect.Intersection( aVisArea );
1449 if ( !aClipRect.IsEmpty() )
1450 {
1451 // translate into window coordinates
1452 Point aNewPos = pView->GetWindowPos( aClipRect.TopLeft() );
1453 if ( IsRightToLeft() )
1454 aNewPos.AdjustX( -(aOutSz.Width() - 1) );
1455 aClipRect.SetPos( aNewPos );
1456
1457 pView->GetWindow()->Invalidate( aClipRect );
1458 }
1459 }
1460
1461 if ( pCurView )
1462 {
1463 pCurView->ShowCursor( pCurView->IsAutoScroll() );
1464 }
1465
1466 maInvalidRect = tools::Rectangle();
1467 }
1468
IMPL_LINK_NOARG(TextEngine,IdleFormatHdl,Timer *,void)1469 IMPL_LINK_NOARG(TextEngine, IdleFormatHdl, Timer *, void)
1470 {
1471 FormatAndUpdate( mpIdleFormatter->GetView() );
1472 }
1473
CheckIdleFormatter()1474 void TextEngine::CheckIdleFormatter()
1475 {
1476 mpIdleFormatter->ForceTimeout();
1477 }
1478
FormatFullDoc()1479 void TextEngine::FormatFullDoc()
1480 {
1481 for ( sal_uInt32 nPortion = 0; nPortion < mpTEParaPortions->Count(); ++nPortion )
1482 {
1483 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPortion );
1484 pTEParaPortion->MarkSelectionInvalid( 0 );
1485 }
1486 mbFormatted = false;
1487 FormatDoc();
1488 }
1489
FormatDoc()1490 void TextEngine::FormatDoc()
1491 {
1492 if ( IsFormatted() || !GetUpdateMode() || IsFormatting() )
1493 return;
1494
1495 mbIsFormatting = true;
1496 mbHasMultiLineParas = false;
1497
1498 tools::Long nY = 0;
1499 bool bGrow = false;
1500
1501 maInvalidRect = tools::Rectangle(); // clear
1502 for ( sal_uInt32 nPara = 0; nPara < mpTEParaPortions->Count(); ++nPara )
1503 {
1504 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1505 if ( pTEParaPortion->IsInvalid() )
1506 {
1507 const tools::Long nOldParaWidth = mnCurTextWidth >= 0 ? CalcTextWidth( nPara ) : -1;
1508
1509 Broadcast( TextHint( SfxHintId::TextFormatPara, nPara ) );
1510
1511 if ( CreateLines( nPara ) )
1512 bGrow = true;
1513
1514 // set InvalidRect only once
1515 if ( maInvalidRect.IsEmpty() )
1516 {
1517 // otherwise remains Empty() for Paperwidth 0 (AutoPageSize)
1518 const tools::Long nWidth = mnMaxTextWidth
1519 ? mnMaxTextWidth
1520 : std::numeric_limits<tools::Long>::max();
1521 const Range aInvRange( GetInvalidYOffsets( nPara ) );
1522 maInvalidRect = tools::Rectangle( Point( 0, nY+aInvRange.Min() ),
1523 Size( nWidth, aInvRange.Len() ) );
1524 }
1525 else
1526 {
1527 maInvalidRect.SetBottom( nY + CalcParaHeight( nPara ) );
1528 }
1529
1530 if ( mnCurTextWidth >= 0 )
1531 {
1532 const tools::Long nNewParaWidth = CalcTextWidth( nPara );
1533 if ( nNewParaWidth >= mnCurTextWidth )
1534 mnCurTextWidth = nNewParaWidth;
1535 else if ( nOldParaWidth >= mnCurTextWidth )
1536 mnCurTextWidth = -1;
1537 }
1538 }
1539 else if ( bGrow )
1540 {
1541 maInvalidRect.SetBottom( nY + CalcParaHeight( nPara ) );
1542 }
1543 nY += CalcParaHeight( nPara );
1544 if ( !mbHasMultiLineParas && pTEParaPortion->GetLines().size() > 1 )
1545 mbHasMultiLineParas = true;
1546 }
1547
1548 if ( !maInvalidRect.IsEmpty() )
1549 {
1550 const tools::Long nNewHeight = CalcTextHeight();
1551 const tools::Long nDiff = nNewHeight - mnCurTextHeight;
1552 if ( nNewHeight < mnCurTextHeight )
1553 {
1554 maInvalidRect.SetBottom( std::max( nNewHeight, mnCurTextHeight ) );
1555 if ( maInvalidRect.IsEmpty() )
1556 {
1557 maInvalidRect.SetTop( 0 );
1558 // Left and Right are not evaluated, but set because of IsEmpty
1559 maInvalidRect.SetLeft( 0 );
1560 maInvalidRect.SetRight( mnMaxTextWidth );
1561 }
1562 }
1563
1564 mnCurTextHeight = nNewHeight;
1565 if ( nDiff )
1566 {
1567 mbFormatted = true;
1568 Broadcast( TextHint( SfxHintId::TextHeightChanged ) );
1569 }
1570 }
1571
1572 mbIsFormatting = false;
1573 mbFormatted = true;
1574
1575 Broadcast( TextHint( SfxHintId::TextFormatted ) );
1576 }
1577
CreateAndInsertEmptyLine(sal_uInt32 nPara)1578 void TextEngine::CreateAndInsertEmptyLine( sal_uInt32 nPara )
1579 {
1580 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
1581 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1582
1583 TextLine aTmpLine;
1584 aTmpLine.SetStart( pNode->GetText().getLength() );
1585 aTmpLine.SetEnd( aTmpLine.GetStart() );
1586
1587 if ( ImpGetAlign() == TxtAlign::Center )
1588 aTmpLine.SetStartX( static_cast<short>(mnMaxTextWidth / 2) );
1589 else if ( ImpGetAlign() == TxtAlign::Right )
1590 aTmpLine.SetStartX( static_cast<short>(mnMaxTextWidth) );
1591 else
1592 aTmpLine.SetStartX( mpDoc->GetLeftMargin() );
1593
1594 bool bLineBreak = !pNode->GetText().isEmpty();
1595
1596 TETextPortion aDummyPortion( 0 );
1597 aDummyPortion.GetWidth() = 0;
1598 pTEParaPortion->GetTextPortions().push_back( aDummyPortion );
1599
1600 if ( bLineBreak )
1601 {
1602 // -2: The new one is already inserted.
1603 const std::size_t nPos = pTEParaPortion->GetTextPortions().size() - 1;
1604 aTmpLine.SetStartPortion( nPos );
1605 aTmpLine.SetEndPortion( nPos );
1606 }
1607 pTEParaPortion->GetLines().push_back( aTmpLine );
1608 }
1609
ImpBreakLine(sal_uInt32 nPara,TextLine * pLine,sal_Int32 nPortionStart,tools::Long nRemainingWidth)1610 void TextEngine::ImpBreakLine( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nPortionStart, tools::Long nRemainingWidth )
1611 {
1612 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
1613
1614 // Font still should be adjusted
1615 sal_Int32 nMaxBreakPos = mpRefDev->GetTextBreak( pNode->GetText(), nRemainingWidth, nPortionStart );
1616
1617 SAL_WARN_IF( nMaxBreakPos >= pNode->GetText().getLength(), "vcl", "ImpBreakLine: Break?!" );
1618
1619 if ( nMaxBreakPos == -1 ) // GetTextBreak() != GetTextSize()
1620 nMaxBreakPos = pNode->GetText().getLength() - 1;
1621
1622 uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
1623 i18n::LineBreakHyphenationOptions aHyphOptions( nullptr, uno::Sequence< beans::PropertyValue >(), 1 );
1624
1625 i18n::LineBreakUserOptions aUserOptions;
1626 aUserOptions.forbiddenBeginCharacters = ImpGetLocaleDataWrapper()->getForbiddenCharacters().beginLine;
1627 aUserOptions.forbiddenEndCharacters = ImpGetLocaleDataWrapper()->getForbiddenCharacters().endLine;
1628 aUserOptions.applyForbiddenRules = true;
1629 aUserOptions.allowPunctuationOutsideMargin = false;
1630 aUserOptions.allowHyphenateEnglish = false;
1631
1632 static const css::lang::Locale aDefLocale;
1633 i18n::LineBreakResults aLBR = xBI->getLineBreak( pNode->GetText(), nMaxBreakPos, aDefLocale, pLine->GetStart(), aHyphOptions, aUserOptions );
1634 sal_Int32 nBreakPos = aLBR.breakIndex;
1635 if ( nBreakPos <= pLine->GetStart() )
1636 {
1637 nBreakPos = nMaxBreakPos;
1638 if ( nBreakPos <= pLine->GetStart() )
1639 nBreakPos = pLine->GetStart() + 1; // infinite loop otherwise!
1640 }
1641
1642 // the damaged Portion is the End Portion
1643 pLine->SetEnd( nBreakPos );
1644 const std::size_t nEndPortion = SplitTextPortion( nPara, nBreakPos );
1645
1646 if ( nBreakPos >= pLine->GetStart() &&
1647 nBreakPos < pNode->GetText().getLength() &&
1648 pNode->GetText()[ nBreakPos ] == ' ' )
1649 {
1650 // generally suppress blanks at the end of line
1651 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1652 TETextPortion& rTP = pTEParaPortion->GetTextPortions()[ nEndPortion ];
1653 SAL_WARN_IF( nBreakPos <= pLine->GetStart(), "vcl", "ImpBreakLine: SplitTextPortion at beginning of line?" );
1654 rTP.GetWidth() = CalcTextWidth( nPara, nBreakPos-rTP.GetLen(), rTP.GetLen()-1 );
1655 }
1656 pLine->SetEndPortion( nEndPortion );
1657 }
1658
SplitTextPortion(sal_uInt32 nPara,sal_Int32 nPos)1659 std::size_t TextEngine::SplitTextPortion( sal_uInt32 nPara, sal_Int32 nPos )
1660 {
1661
1662 // the Portion at nPos is being split, unless there is already a switch at nPos
1663 if ( nPos == 0 )
1664 return 0;
1665
1666 std::size_t nSplitPortion;
1667 sal_Int32 nTmpPos = 0;
1668 TETextPortion* pTextPortion = nullptr;
1669 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1670 const std::size_t nPortions = pTEParaPortion->GetTextPortions().size();
1671 for ( nSplitPortion = 0; nSplitPortion < nPortions; nSplitPortion++ )
1672 {
1673 TETextPortion& rTP = pTEParaPortion->GetTextPortions()[nSplitPortion];
1674 nTmpPos += rTP.GetLen();
1675 if ( nTmpPos >= nPos )
1676 {
1677 if ( nTmpPos == nPos ) // nothing needs splitting
1678 return nSplitPortion;
1679 pTextPortion = &rTP;
1680 break;
1681 }
1682 }
1683
1684 assert(pTextPortion && "SplitTextPortion: position outside of region!");
1685
1686 const sal_Int32 nOverlapp = nTmpPos - nPos;
1687 pTextPortion->GetLen() -= nOverlapp;
1688 pTextPortion->GetWidth() = CalcTextWidth( nPara, nPos-pTextPortion->GetLen(), pTextPortion->GetLen() );
1689 TETextPortion aNewPortion( nOverlapp );
1690 pTEParaPortion->GetTextPortions().insert( pTEParaPortion->GetTextPortions().begin() + nSplitPortion + 1, aNewPortion );
1691
1692 return nSplitPortion;
1693 }
1694
CreateTextPortions(sal_uInt32 nPara,sal_Int32 nStartPos)1695 void TextEngine::CreateTextPortions( sal_uInt32 nPara, sal_Int32 nStartPos )
1696 {
1697 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1698 TextNode* pNode = pTEParaPortion->GetNode();
1699 SAL_WARN_IF( pNode->GetText().isEmpty(), "vcl", "CreateTextPortions: should not be used for empty paragraphs!" );
1700
1701 o3tl::sorted_vector<sal_Int32> aPositions;
1702 o3tl::sorted_vector<sal_Int32>::const_iterator aPositionsIt;
1703 aPositions.insert(0);
1704
1705 const sal_uInt16 nAttribs = pNode->GetCharAttribs().Count();
1706 for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
1707 {
1708 TextCharAttrib& rAttrib = pNode->GetCharAttribs().GetAttrib( nAttr );
1709
1710 aPositions.insert( rAttrib.GetStart() );
1711 aPositions.insert( rAttrib.GetEnd() );
1712 }
1713 aPositions.insert( pNode->GetText().getLength() );
1714
1715 const std::vector<TEWritingDirectionInfo>& rWritingDirections = pTEParaPortion->GetWritingDirectionInfos();
1716 for ( const auto& rWritingDirection : rWritingDirections )
1717 aPositions.insert( rWritingDirection.nStartPos );
1718
1719 if ( mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetPara() == nPara ) )
1720 {
1721 ExtTextInputAttr nLastAttr = ExtTextInputAttr(0xffff);
1722 for( sal_Int32 n = 0; n < mpIMEInfos->nLen; n++ )
1723 {
1724 if ( mpIMEInfos->pAttribs[n] != nLastAttr )
1725 {
1726 aPositions.insert( mpIMEInfos->aPos.GetIndex() + n );
1727 nLastAttr = mpIMEInfos->pAttribs[n];
1728 }
1729 }
1730 }
1731
1732 sal_Int32 nTabPos = pNode->GetText().indexOf( '\t' );
1733 while ( nTabPos != -1 )
1734 {
1735 aPositions.insert( nTabPos );
1736 aPositions.insert( nTabPos + 1 );
1737 nTabPos = pNode->GetText().indexOf( '\t', nTabPos+1 );
1738 }
1739
1740 // Delete starting with...
1741 // Unfortunately, the number of TextPortions does not have to be
1742 // equal to aPositions.Count(), because of linebreaks
1743 sal_Int32 nPortionStart = 0;
1744 std::size_t nInvPortion = 0;
1745 std::size_t nP;
1746 for ( nP = 0; nP < pTEParaPortion->GetTextPortions().size(); nP++ )
1747 {
1748 TETextPortion& rTmpPortion = pTEParaPortion->GetTextPortions()[nP];
1749 nPortionStart += rTmpPortion.GetLen();
1750 if ( nPortionStart >= nStartPos )
1751 {
1752 nPortionStart -= rTmpPortion.GetLen();
1753 nInvPortion = nP;
1754 break;
1755 }
1756 }
1757 OSL_ENSURE(nP < pTEParaPortion->GetTextPortions().size()
1758 || pTEParaPortion->GetTextPortions().empty(),
1759 "CreateTextPortions: Nothing to delete!");
1760 if ( nInvPortion && ( nPortionStart+pTEParaPortion->GetTextPortions()[nInvPortion].GetLen() > nStartPos ) )
1761 {
1762 // better one before...
1763 // But only if it was within the Portion; otherwise it might be
1764 // the only one in the previous line!
1765 nInvPortion--;
1766 nPortionStart -= pTEParaPortion->GetTextPortions()[nInvPortion].GetLen();
1767 }
1768 pTEParaPortion->GetTextPortions().DeleteFromPortion( nInvPortion );
1769
1770 // a Portion might have been created by a line break
1771 aPositions.insert( nPortionStart );
1772
1773 aPositionsIt = aPositions.find( nPortionStart );
1774 SAL_WARN_IF( aPositionsIt == aPositions.end(), "vcl", "CreateTextPortions: nPortionStart not found" );
1775
1776 if ( aPositionsIt != aPositions.end() )
1777 {
1778 o3tl::sorted_vector<sal_Int32>::const_iterator nextIt = aPositionsIt;
1779 for ( ++nextIt; nextIt != aPositions.end(); ++aPositionsIt, ++nextIt )
1780 {
1781 TETextPortion aNew( *nextIt - *aPositionsIt );
1782 pTEParaPortion->GetTextPortions().push_back( aNew );
1783 }
1784 }
1785 OSL_ENSURE(pTEParaPortion->GetTextPortions().size(), "CreateTextPortions: No Portions?!");
1786 }
1787
RecalcTextPortion(sal_uInt32 nPara,sal_Int32 nStartPos,sal_Int32 nNewChars)1788 void TextEngine::RecalcTextPortion( sal_uInt32 nPara, sal_Int32 nStartPos, sal_Int32 nNewChars )
1789 {
1790 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1791 OSL_ENSURE(pTEParaPortion->GetTextPortions().size(), "RecalcTextPortion: no Portions!");
1792 OSL_ENSURE(nNewChars, "RecalcTextPortion: Diff == 0");
1793
1794 TextNode* const pNode = pTEParaPortion->GetNode();
1795 if ( nNewChars > 0 )
1796 {
1797 // If an Attribute is starting/ending at nStartPos, or there is a tab
1798 // before nStartPos => a new Portion starts.
1799 // Otherwise the Portion is extended at nStartPos.
1800 // Or if at the very beginning ( StartPos 0 ) followed by a tab...
1801 if ( ( pNode->GetCharAttribs().HasBoundingAttrib( nStartPos ) ) ||
1802 ( nStartPos && ( pNode->GetText()[ nStartPos - 1 ] == '\t' ) ) ||
1803 ( !nStartPos && ( nNewChars < pNode->GetText().getLength() ) && pNode->GetText()[ nNewChars ] == '\t' ) )
1804 {
1805 std::size_t nNewPortionPos = 0;
1806 if ( nStartPos )
1807 nNewPortionPos = SplitTextPortion( nPara, nStartPos ) + 1;
1808
1809 // Here could be an empty Portion if the paragraph was empty,
1810 // or a new line was created by a hard line-break.
1811 if ( ( nNewPortionPos < pTEParaPortion->GetTextPortions().size() ) &&
1812 !pTEParaPortion->GetTextPortions()[nNewPortionPos].GetLen() )
1813 {
1814 // use the empty Portion
1815 pTEParaPortion->GetTextPortions()[nNewPortionPos].GetLen() = nNewChars;
1816 }
1817 else
1818 {
1819 TETextPortion aNewPortion( nNewChars );
1820 pTEParaPortion->GetTextPortions().insert( pTEParaPortion->GetTextPortions().begin() + nNewPortionPos, aNewPortion );
1821 }
1822 }
1823 else
1824 {
1825 sal_Int32 nPortionStart {0};
1826 const std::size_t nTP = pTEParaPortion->GetTextPortions().FindPortion( nStartPos, nPortionStart );
1827 TETextPortion& rTP = pTEParaPortion->GetTextPortions()[ nTP ];
1828 rTP.GetLen() += nNewChars;
1829 rTP.GetWidth() = -1;
1830 }
1831 }
1832 else
1833 {
1834 // Shrink or remove Portion
1835 // Before calling this function, ensure that no Portions were in the deleted range!
1836
1837 // There must be no Portion reaching into or starting within,
1838 // thus: nStartPos <= nPos <= nStartPos - nNewChars(neg.)
1839 std::size_t nPortion = 0;
1840 sal_Int32 nPos = 0;
1841 const sal_Int32 nEnd = nStartPos-nNewChars;
1842 const std::size_t nPortions = pTEParaPortion->GetTextPortions().size();
1843 TETextPortion* pTP = nullptr;
1844 for ( nPortion = 0; nPortion < nPortions; nPortion++ )
1845 {
1846 pTP = &pTEParaPortion->GetTextPortions()[ nPortion ];
1847 if ( ( nPos+pTP->GetLen() ) > nStartPos )
1848 {
1849 SAL_WARN_IF( nPos > nStartPos, "vcl", "RecalcTextPortion: Bad Start!" );
1850 SAL_WARN_IF( nPos+pTP->GetLen() < nEnd, "vcl", "RecalcTextPortion: Bad End!" );
1851 break;
1852 }
1853 nPos += pTP->GetLen();
1854 }
1855 SAL_WARN_IF( !pTP, "vcl", "RecalcTextPortion: Portion not found!" );
1856 if ( ( nPos == nStartPos ) && ( (nPos+pTP->GetLen()) == nEnd ) )
1857 {
1858 // remove Portion
1859 pTEParaPortion->GetTextPortions().erase( pTEParaPortion->GetTextPortions().begin() + nPortion );
1860 }
1861 else
1862 {
1863 SAL_WARN_IF( pTP->GetLen() <= (-nNewChars), "vcl", "RecalcTextPortion: Portion too small to shrink!" );
1864 pTP->GetLen() += nNewChars;
1865 }
1866 OSL_ENSURE( pTEParaPortion->GetTextPortions().size(),
1867 "RecalcTextPortion: none are left!" );
1868 }
1869 }
1870
ImpPaint(OutputDevice * pOutDev,const Point & rStartPos,tools::Rectangle const * pPaintArea,TextSelection const * pSelection)1871 void TextEngine::ImpPaint( OutputDevice* pOutDev, const Point& rStartPos, tools::Rectangle const* pPaintArea, TextSelection const* pSelection )
1872 {
1873 if ( !GetUpdateMode() )
1874 return;
1875
1876 if ( !IsFormatted() )
1877 FormatDoc();
1878
1879 vcl::Window* const pOutWin = pOutDev->GetOwnerWindow();
1880 const bool bTransparent = (pOutWin && pOutWin->IsPaintTransparent());
1881
1882 tools::Long nY = rStartPos.Y();
1883
1884 TextPaM const* pSelStart = nullptr;
1885 TextPaM const* pSelEnd = nullptr;
1886 if ( pSelection && pSelection->HasRange() )
1887 {
1888 const bool bInvers = pSelection->GetEnd() < pSelection->GetStart();
1889 pSelStart = !bInvers ? &pSelection->GetStart() : &pSelection->GetEnd();
1890 pSelEnd = bInvers ? &pSelection->GetStart() : &pSelection->GetEnd();
1891 }
1892
1893 const StyleSettings& rStyleSettings = pOutDev->GetSettings().GetStyleSettings();
1894
1895 // for all paragraphs
1896 for ( sal_uInt32 nPara = 0; nPara < mpTEParaPortions->Count(); ++nPara )
1897 {
1898 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
1899 // in case while typing Idle-Formatting, asynchronous Paint
1900 if ( pPortion->IsInvalid() )
1901 return;
1902
1903 const tools::Long nParaHeight = CalcParaHeight( nPara );
1904 if ( !pPaintArea || ( ( nY + nParaHeight ) > pPaintArea->Top() ) )
1905 {
1906 // for all lines of the paragraph
1907 sal_Int32 nIndex = 0;
1908 for ( auto & rLine : pPortion->GetLines() )
1909 {
1910 Point aTmpPos( rStartPos.X() + rLine.GetStartX(), nY );
1911
1912 if ( !pPaintArea || ( ( nY + mnCharHeight ) > pPaintArea->Top() ) )
1913 {
1914 // for all Portions of the line
1915 nIndex = rLine.GetStart();
1916 for ( std::size_t y = rLine.GetStartPortion(); y <= rLine.GetEndPortion(); y++ )
1917 {
1918 OSL_ENSURE(pPortion->GetTextPortions().size(),
1919 "ImpPaint: Line without Textportion!");
1920 TETextPortion& rTextPortion = pPortion->GetTextPortions()[ y ];
1921
1922 ImpInitLayoutMode( pOutDev /*, pTextPortion->IsRightToLeft() */);
1923
1924 const tools::Long nTxtWidth = rTextPortion.GetWidth();
1925 aTmpPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nIndex, nIndex ) );
1926
1927 // only print if starting in the visible region
1928 if ( ( aTmpPos.X() + nTxtWidth ) >= 0 )
1929 {
1930 switch ( rTextPortion.GetKind() )
1931 {
1932 case PORTIONKIND_TEXT:
1933 {
1934 vcl::Font aFont;
1935 SeekCursor( nPara, nIndex+1, aFont, pOutDev );
1936 if( bTransparent )
1937 aFont.SetTransparent( true );
1938 else if ( pSelection )
1939 aFont.SetTransparent( false );
1940 pOutDev->SetFont( aFont );
1941
1942 sal_Int32 nTmpIndex = nIndex;
1943 sal_Int32 nEnd = nTmpIndex + rTextPortion.GetLen();
1944 Point aPos = aTmpPos;
1945
1946 bool bDone = false;
1947 if ( pSelStart )
1948 {
1949 // is a part of it in the selection?
1950 const TextPaM aTextStart( nPara, nTmpIndex );
1951 const TextPaM aTextEnd( nPara, nEnd );
1952 if ( ( aTextStart < *pSelEnd ) && ( aTextEnd > *pSelStart ) )
1953 {
1954 // 1) vcl::Region before Selection
1955 if ( aTextStart < *pSelStart )
1956 {
1957 const sal_Int32 nL = pSelStart->GetIndex() - nTmpIndex;
1958 pOutDev->SetFont( aFont);
1959 pOutDev->SetTextFillColor();
1960 aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
1961 pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nL );
1962 nTmpIndex = nTmpIndex + nL;
1963
1964 }
1965 // 2) vcl::Region with Selection
1966 sal_Int32 nL = nEnd - nTmpIndex;
1967 if ( aTextEnd > *pSelEnd )
1968 nL = pSelEnd->GetIndex() - nTmpIndex;
1969 if ( nL )
1970 {
1971 const Color aOldTextColor = pOutDev->GetTextColor();
1972 pOutDev->SetTextColor( rStyleSettings.GetHighlightTextColor() );
1973 pOutDev->SetTextFillColor( rStyleSettings.GetHighlightColor() );
1974 aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
1975 pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nL );
1976 pOutDev->SetTextColor( aOldTextColor );
1977 pOutDev->SetTextFillColor();
1978 nTmpIndex = nTmpIndex + nL;
1979 }
1980
1981 // 3) vcl::Region after Selection
1982 if ( nTmpIndex < nEnd )
1983 {
1984 nL = nEnd-nTmpIndex;
1985 pOutDev->SetTextFillColor();
1986 aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
1987 pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nEnd-nTmpIndex );
1988 }
1989 bDone = true;
1990 }
1991 }
1992 if ( !bDone )
1993 {
1994 pOutDev->SetTextFillColor();
1995 aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nEnd ) );
1996 pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nEnd-nTmpIndex );
1997 }
1998 }
1999 break;
2000 case PORTIONKIND_TAB:
2001 // for HideSelection() only Range, pSelection = 0.
2002 if ( pSelStart ) // also implies pSelEnd
2003 {
2004 const tools::Rectangle aTabArea( aTmpPos, Point( aTmpPos.X()+nTxtWidth, aTmpPos.Y()+mnCharHeight-1 ) );
2005 // is the Tab in the Selection???
2006 const TextPaM aTextStart(nPara, nIndex);
2007 const TextPaM aTextEnd(nPara, nIndex + 1);
2008 if ((aTextStart < *pSelEnd) && (aTextEnd > *pSelStart))
2009 {
2010 const Color aOldColor = pOutDev->GetFillColor();
2011 pOutDev->SetFillColor(
2012 rStyleSettings.GetHighlightColor());
2013 pOutDev->DrawRect(aTabArea);
2014 pOutDev->SetFillColor(aOldColor);
2015 }
2016 else
2017 {
2018 pOutDev->Erase( aTabArea );
2019 }
2020 }
2021 break;
2022 default:
2023 OSL_FAIL( "ImpPaint: Unknown Portion-Type !" );
2024 }
2025 }
2026
2027 nIndex += rTextPortion.GetLen();
2028 }
2029 }
2030
2031 nY += mnCharHeight;
2032
2033 if ( pPaintArea && ( nY >= pPaintArea->Bottom() ) )
2034 break; // no more visible actions
2035 }
2036 }
2037 else
2038 {
2039 nY += nParaHeight;
2040 }
2041
2042 if ( pPaintArea && ( nY > pPaintArea->Bottom() ) )
2043 break; // no more visible actions
2044 }
2045 }
2046
CreateLines(sal_uInt32 nPara)2047 bool TextEngine::CreateLines( sal_uInt32 nPara )
2048 {
2049 // bool: changing Height of Paragraph Yes/No - true/false
2050
2051 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
2052 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
2053 SAL_WARN_IF( !pTEParaPortion->IsInvalid(), "vcl", "CreateLines: Portion not invalid!" );
2054
2055 const auto nOldLineCount = pTEParaPortion->GetLines().size();
2056
2057 // fast special case for empty paragraphs
2058 if ( pTEParaPortion->GetNode()->GetText().isEmpty() )
2059 {
2060 if ( !pTEParaPortion->GetTextPortions().empty() )
2061 pTEParaPortion->GetTextPortions().Reset();
2062 pTEParaPortion->GetLines().clear();
2063 CreateAndInsertEmptyLine( nPara );
2064 pTEParaPortion->SetValid();
2065 return nOldLineCount != pTEParaPortion->GetLines().size();
2066 }
2067
2068 // initialization
2069 if ( pTEParaPortion->GetLines().empty() )
2070 {
2071 pTEParaPortion->GetLines().emplace_back( );
2072 }
2073
2074 const sal_Int32 nInvalidDiff = pTEParaPortion->GetInvalidDiff();
2075 const sal_Int32 nInvalidStart = pTEParaPortion->GetInvalidPosStart();
2076 const sal_Int32 nInvalidEnd = nInvalidStart + std::abs( nInvalidDiff );
2077 bool bQuickFormat = false;
2078
2079 if ( pTEParaPortion->GetWritingDirectionInfos().empty() )
2080 ImpInitWritingDirections( nPara );
2081
2082 if ( pTEParaPortion->GetWritingDirectionInfos().size() == 1 && pTEParaPortion->IsSimpleInvalid() )
2083 {
2084 bQuickFormat = nInvalidDiff != 0;
2085 if ( nInvalidDiff < 0 )
2086 {
2087 // check if deleting across Portion border
2088 sal_Int32 nPos = 0;
2089 for ( const auto & rTP : pTEParaPortion->GetTextPortions() )
2090 {
2091 // there must be no Start/End in the deleted region
2092 nPos += rTP.GetLen();
2093 if ( nPos > nInvalidStart && nPos < nInvalidEnd )
2094 {
2095 bQuickFormat = false;
2096 break;
2097 }
2098 }
2099 }
2100 }
2101
2102 if ( bQuickFormat )
2103 RecalcTextPortion( nPara, nInvalidStart, nInvalidDiff );
2104 else
2105 CreateTextPortions( nPara, nInvalidStart );
2106
2107 // search for line with InvalidPos; start a line prior
2108 // flag lines => do not remove!
2109
2110 sal_uInt16 nLine = pTEParaPortion->GetLines().size()-1;
2111 for ( sal_uInt16 nL = 0; nL <= nLine; nL++ )
2112 {
2113 TextLine& rLine = pTEParaPortion->GetLines()[ nL ];
2114 if ( rLine.GetEnd() > nInvalidStart )
2115 {
2116 nLine = nL;
2117 break;
2118 }
2119 rLine.SetValid();
2120 }
2121 // start a line before...
2122 // if typing at the end, the line before cannot change
2123 if ( nLine && ( !pTEParaPortion->IsSimpleInvalid() || ( nInvalidEnd < pNode->GetText().getLength() ) || ( nInvalidDiff <= 0 ) ) )
2124 nLine--;
2125
2126 TextLine* pLine = &( pTEParaPortion->GetLines()[ nLine ] );
2127
2128 // format all lines starting here
2129 std::size_t nDelFromLine = TETextPortionList::npos;
2130
2131 sal_Int32 nIndex = pLine->GetStart();
2132 TextLine aSaveLine( *pLine );
2133
2134 while ( nIndex < pNode->GetText().getLength() )
2135 {
2136 bool bEOL = false;
2137 sal_Int32 nPortionStart = 0;
2138 sal_Int32 nPortionEnd = 0;
2139
2140 sal_Int32 nTmpPos = nIndex;
2141 std::size_t nTmpPortion = pLine->GetStartPortion();
2142 tools::Long nTmpWidth = mpDoc->GetLeftMargin();
2143 // do not subtract margin; it is included in TmpWidth
2144 tools::Long nXWidth = std::max(
2145 mnMaxTextWidth ? mnMaxTextWidth : std::numeric_limits<tools::Long>::max(), nTmpWidth);
2146
2147 // search for Portion that does not fit anymore into line
2148 TETextPortion* pPortion = nullptr;
2149 bool bBrokenLine = false;
2150
2151 while ( ( nTmpWidth <= nXWidth ) && !bEOL && ( nTmpPortion < pTEParaPortion->GetTextPortions().size() ) )
2152 {
2153 nPortionStart = nTmpPos;
2154 pPortion = &pTEParaPortion->GetTextPortions()[ nTmpPortion ];
2155 SAL_WARN_IF( !pPortion->GetLen(), "vcl", "CreateLines: Empty Portion!" );
2156 if ( pNode->GetText()[ nTmpPos ] == '\t' )
2157 {
2158 tools::Long nCurPos = nTmpWidth-mpDoc->GetLeftMargin();
2159 nTmpWidth = ((nCurPos/mnDefTab)+1)*mnDefTab+mpDoc->GetLeftMargin();
2160 pPortion->GetWidth() = nTmpWidth - nCurPos - mpDoc->GetLeftMargin();
2161 // infinite loop, if this is the first token of the line and nTmpWidth > aPaperSize.Width !!!
2162 if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
2163 {
2164 // adjust Tab
2165 pPortion->GetWidth() = nXWidth-1;
2166 nTmpWidth = pPortion->GetWidth();
2167 bEOL = true;
2168 bBrokenLine = true;
2169 }
2170 pPortion->GetKind() = PORTIONKIND_TAB;
2171 }
2172 else
2173 {
2174
2175 pPortion->GetWidth() = CalcTextWidth( nPara, nTmpPos, pPortion->GetLen() );
2176 nTmpWidth += pPortion->GetWidth();
2177
2178 pPortion->SetRightToLeft( ImpGetRightToLeft( nPara, nTmpPos+1 ) );
2179 pPortion->GetKind() = PORTIONKIND_TEXT;
2180 }
2181
2182 nTmpPos += pPortion->GetLen();
2183 nPortionEnd = nTmpPos;
2184 nTmpPortion++;
2185 }
2186
2187 // this was perhaps one Portion too far
2188 bool bFixedEnd = false;
2189 if ( nTmpWidth > nXWidth )
2190 {
2191 assert(pPortion);
2192
2193 nPortionEnd = nTmpPos;
2194 nTmpPos -= pPortion->GetLen();
2195 nPortionStart = nTmpPos;
2196 nTmpPortion--;
2197 bEOL = false;
2198
2199 nTmpWidth -= pPortion->GetWidth();
2200 if ( pPortion->GetKind() == PORTIONKIND_TAB )
2201 {
2202 bEOL = true;
2203 bFixedEnd = true;
2204 }
2205 }
2206 else
2207 {
2208 bEOL = true;
2209 pLine->SetEnd( nPortionEnd );
2210 OSL_ENSURE(pTEParaPortion->GetTextPortions().size(),
2211 "CreateLines: No TextPortions?");
2212 pLine->SetEndPortion( pTEParaPortion->GetTextPortions().size() - 1 );
2213 }
2214
2215 if ( bFixedEnd )
2216 {
2217 pLine->SetEnd( nPortionStart );
2218 pLine->SetEndPortion( nTmpPortion-1 );
2219 }
2220 else if ( bBrokenLine )
2221 {
2222 pLine->SetEnd( nPortionStart+1 );
2223 pLine->SetEndPortion( nTmpPortion-1 );
2224 }
2225 else if ( !bEOL )
2226 {
2227 SAL_WARN_IF( (nPortionEnd-nPortionStart) != pPortion->GetLen(), "vcl", "CreateLines: There is a Portion after all?!" );
2228 const tools::Long nRemainingWidth = mnMaxTextWidth - nTmpWidth;
2229 ImpBreakLine( nPara, pLine, nPortionStart, nRemainingWidth );
2230 }
2231
2232 if ( ( ImpGetAlign() == TxtAlign::Center ) || ( ImpGetAlign() == TxtAlign::Right ) )
2233 {
2234 // adjust
2235 tools::Long nTextWidth = 0;
2236 for ( std::size_t nTP = pLine->GetStartPortion(); nTP <= pLine->GetEndPortion(); nTP++ )
2237 {
2238 TETextPortion& rTextPortion = pTEParaPortion->GetTextPortions()[ nTP ];
2239 nTextWidth += rTextPortion.GetWidth();
2240 }
2241 const tools::Long nSpace = mnMaxTextWidth - nTextWidth;
2242 if ( nSpace > 0 )
2243 {
2244 if ( ImpGetAlign() == TxtAlign::Center )
2245 pLine->SetStartX( static_cast<sal_uInt16>(nSpace / 2) );
2246 else // TxtAlign::Right
2247 pLine->SetStartX( static_cast<sal_uInt16>(nSpace) );
2248 }
2249 }
2250 else
2251 {
2252 pLine->SetStartX( mpDoc->GetLeftMargin() );
2253 }
2254
2255 // check if the line has to be printed again
2256 pLine->SetInvalid();
2257
2258 if ( pTEParaPortion->IsSimpleInvalid() )
2259 {
2260 // Change due to simple TextChange...
2261 // Do not abort formatting, as Portions might have to be split!
2262 // Once it is ok to abort, then validate the following lines!
2263 // But mark as valid, thus reduce printing...
2264 if ( pLine->GetEnd() < nInvalidStart )
2265 {
2266 if ( *pLine == aSaveLine )
2267 {
2268 pLine->SetValid();
2269 }
2270 }
2271 else
2272 {
2273 const sal_Int32 nStart = pLine->GetStart();
2274 const sal_Int32 nEnd = pLine->GetEnd();
2275
2276 if ( nStart > nInvalidEnd )
2277 {
2278 if ( ( ( nStart-nInvalidDiff ) == aSaveLine.GetStart() ) &&
2279 ( ( nEnd-nInvalidDiff ) == aSaveLine.GetEnd() ) )
2280 {
2281 pLine->SetValid();
2282 if ( bQuickFormat )
2283 {
2284 pTEParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
2285 break;
2286 }
2287 }
2288 }
2289 else if ( bQuickFormat && ( nEnd > nInvalidEnd) )
2290 {
2291 // If the invalid line ends such that the next line starts
2292 // at the 'same' position as before (no change in line breaks),
2293 // the text width does not have to be recalculated.
2294 if ( nEnd == ( aSaveLine.GetEnd() + nInvalidDiff ) )
2295 {
2296 pTEParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
2297 break;
2298 }
2299 }
2300 }
2301 }
2302
2303 nIndex = pLine->GetEnd(); // next line Start = previous line End
2304 // because nEnd is past the last char!
2305
2306 const std::size_t nEndPortion = pLine->GetEndPortion();
2307
2308 // next line or new line
2309 pLine = nullptr;
2310 if ( nLine < pTEParaPortion->GetLines().size()-1 )
2311 pLine = &( pTEParaPortion->GetLines()[ ++nLine ] );
2312 if ( pLine && ( nIndex >= pNode->GetText().getLength() ) )
2313 {
2314 nDelFromLine = nLine;
2315 break;
2316 }
2317 if ( !pLine )
2318 {
2319 if ( nIndex < pNode->GetText().getLength() )
2320 {
2321 ++nLine;
2322 pTEParaPortion->GetLines().insert( pTEParaPortion->GetLines().begin() + nLine, TextLine() );
2323 pLine = &pTEParaPortion->GetLines()[nLine];
2324 }
2325 else
2326 {
2327 break;
2328 }
2329 }
2330 aSaveLine = *pLine;
2331 pLine->SetStart( nIndex );
2332 pLine->SetEnd( nIndex );
2333 pLine->SetStartPortion( nEndPortion+1 );
2334 pLine->SetEndPortion( nEndPortion+1 );
2335
2336 } // while ( Index < Len )
2337
2338 if (nDelFromLine != TETextPortionList::npos)
2339 {
2340 pTEParaPortion->GetLines().erase( pTEParaPortion->GetLines().begin() + nDelFromLine,
2341 pTEParaPortion->GetLines().end() );
2342 }
2343
2344 SAL_WARN_IF( pTEParaPortion->GetLines().empty(), "vcl", "CreateLines: No Line!" );
2345
2346 pTEParaPortion->SetValid();
2347
2348 return nOldLineCount != pTEParaPortion->GetLines().size();
2349 }
2350
GetWord(const TextPaM & rCursorPos,TextPaM * pStartOfWord,TextPaM * pEndOfWord)2351 OUString TextEngine::GetWord( const TextPaM& rCursorPos, TextPaM* pStartOfWord, TextPaM* pEndOfWord )
2352 {
2353 OUString aWord;
2354 if ( rCursorPos.GetPara() < mpDoc->GetNodes().size() )
2355 {
2356 TextSelection aSel( rCursorPos );
2357 TextNode* pNode = mpDoc->GetNodes()[ rCursorPos.GetPara() ].get();
2358 uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
2359 i18n::Boundary aBoundary = xBI->getWordBoundary( pNode->GetText(), rCursorPos.GetIndex(), GetLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
2360 // tdf#57879 - expand selection to the left to include connector punctuations and search for additional word boundaries
2361 if (aBoundary.startPos > 0 && aBoundary.startPos < pNode->GetText().getLength() && u_charType(pNode->GetText()[aBoundary.startPos]) == U_CONNECTOR_PUNCTUATION)
2362 {
2363 aBoundary.startPos = xBI->getWordBoundary(pNode->GetText(), aBoundary.startPos - 1,
2364 GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true).startPos;
2365 }
2366 while (aBoundary.startPos > 0 && u_charType(pNode->GetText()[aBoundary.startPos - 1]) == U_CONNECTOR_PUNCTUATION)
2367 {
2368 aBoundary.startPos = std::min(aBoundary.startPos,
2369 xBI->getWordBoundary( pNode->GetText(), aBoundary.startPos - 2,
2370 GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true).startPos);
2371 }
2372 // tdf#57879 - expand selection to the right to include connector punctuations and search for additional word boundaries
2373 if (aBoundary.endPos > 0 && aBoundary.endPos < pNode->GetText().getLength() && u_charType(pNode->GetText()[aBoundary.endPos - 1]) == U_CONNECTOR_PUNCTUATION)
2374 {
2375 aBoundary.endPos = xBI->getWordBoundary(pNode->GetText(), aBoundary.endPos,
2376 GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true).endPos;
2377 }
2378 while (aBoundary.endPos < pNode->GetText().getLength() && u_charType(pNode->GetText()[aBoundary.endPos]) == U_CONNECTOR_PUNCTUATION)
2379 {
2380 aBoundary.endPos = xBI->getWordBoundary(pNode->GetText(), aBoundary.endPos + 1,
2381 GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true).endPos;
2382 }
2383 aSel.GetStart().GetIndex() = aBoundary.startPos;
2384 aSel.GetEnd().GetIndex() = aBoundary.endPos;
2385 aWord = pNode->GetText().copy( aSel.GetStart().GetIndex(), aSel.GetEnd().GetIndex() - aSel.GetStart().GetIndex() );
2386 if ( pStartOfWord )
2387 *pStartOfWord = aSel.GetStart();
2388 if (pEndOfWord)
2389 *pEndOfWord = aSel.GetEnd();
2390 }
2391 return aWord;
2392 }
2393
Read(SvStream & rInput,const TextSelection * pSel)2394 bool TextEngine::Read( SvStream& rInput, const TextSelection* pSel )
2395 {
2396 const bool bUpdate = GetUpdateMode();
2397 SetUpdateMode( false );
2398
2399 UndoActionStart();
2400 TextSelection aSel;
2401 if ( pSel )
2402 aSel = *pSel;
2403 else
2404 {
2405 const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
2406 TextNode* pNode = mpDoc->GetNodes()[ nParas - 1 ].get();
2407 aSel = TextPaM( nParas-1 , pNode->GetText().getLength() );
2408 }
2409
2410 if ( aSel.HasRange() )
2411 aSel = ImpDeleteText( aSel );
2412
2413 OStringBuffer aLine;
2414 bool bDone = rInput.ReadLine( aLine );
2415 OUString aTmpStr(OStringToOUString(aLine, rInput.GetStreamCharSet()));
2416 while ( bDone )
2417 {
2418 aSel = ImpInsertText( aSel, aTmpStr );
2419 bDone = rInput.ReadLine( aLine );
2420 aTmpStr = OStringToOUString(aLine, rInput.GetStreamCharSet());
2421 if ( bDone )
2422 aSel = ImpInsertParaBreak( aSel.GetEnd() );
2423 }
2424
2425 UndoActionEnd();
2426
2427 const TextSelection aNewSel( aSel.GetEnd(), aSel.GetEnd() );
2428
2429 // so that FormatAndUpdate does not access the invalid selection
2430 if ( GetActiveView() )
2431 GetActiveView()->ImpSetSelection( aNewSel );
2432
2433 SetUpdateMode( bUpdate );
2434 FormatAndUpdate( GetActiveView() );
2435
2436 return rInput.GetError() == ERRCODE_NONE;
2437 }
2438
Write(SvStream & rOutput)2439 void TextEngine::Write( SvStream& rOutput )
2440 {
2441 TextSelection aSel;
2442 const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
2443 TextNode* pSelNode = mpDoc->GetNodes()[ nParas - 1 ].get();
2444 aSel.GetStart() = TextPaM( 0, 0 );
2445 aSel.GetEnd() = TextPaM( nParas-1, pSelNode->GetText().getLength() );
2446
2447 for ( sal_uInt32 nPara = aSel.GetStart().GetPara(); nPara <= aSel.GetEnd().GetPara(); ++nPara )
2448 {
2449 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
2450
2451 const sal_Int32 nStartPos = nPara == aSel.GetStart().GetPara()
2452 ? aSel.GetStart().GetIndex() : 0;
2453 const sal_Int32 nEndPos = nPara == aSel.GetEnd().GetPara()
2454 ? aSel.GetEnd().GetIndex() : pNode->GetText().getLength();
2455
2456 const OUString aText = pNode->GetText().copy( nStartPos, nEndPos-nStartPos );
2457 rOutput.WriteLine(OUStringToOString(aText, rOutput.GetStreamCharSet()));
2458 }
2459 }
2460
RemoveAttribs(sal_uInt32 nPara)2461 void TextEngine::RemoveAttribs( sal_uInt32 nPara )
2462 {
2463 if ( nPara >= mpDoc->GetNodes().size() )
2464 return;
2465
2466 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
2467 if ( pNode->GetCharAttribs().Count() )
2468 {
2469 pNode->GetCharAttribs().Clear();
2470
2471 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
2472 pTEParaPortion->MarkSelectionInvalid( 0 );
2473
2474 mbFormatted = false;
2475
2476 IdleFormatAndUpdate( nullptr, 0xFFFF );
2477 }
2478 }
2479
SetAttrib(const TextAttrib & rAttr,sal_uInt32 nPara,sal_Int32 nStart,sal_Int32 nEnd)2480 void TextEngine::SetAttrib( const TextAttrib& rAttr, sal_uInt32 nPara, sal_Int32 nStart, sal_Int32 nEnd )
2481 {
2482
2483 // For now do not check if Attributes overlap!
2484 // This function is for TextEditors that want to _quickly_ generate the Syntax-Highlight
2485
2486 // As TextEngine is currently intended only for TextEditors, there is no Undo for Attributes!
2487
2488 if ( nPara >= mpDoc->GetNodes().size() )
2489 return;
2490
2491 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
2492 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
2493
2494 const sal_Int32 nMax = pNode->GetText().getLength();
2495 if ( nStart > nMax )
2496 nStart = nMax;
2497 if ( nEnd > nMax )
2498 nEnd = nMax;
2499
2500 pNode->GetCharAttribs().InsertAttrib( std::make_unique<TextCharAttrib>( rAttr, nStart, nEnd ) );
2501 pTEParaPortion->MarkSelectionInvalid( nStart );
2502
2503 mbFormatted = false;
2504 IdleFormatAndUpdate( nullptr, 0xFFFF );
2505 }
2506
SetTextAlign(TxtAlign eAlign)2507 void TextEngine::SetTextAlign( TxtAlign eAlign )
2508 {
2509 if ( eAlign != meAlign )
2510 {
2511 meAlign = eAlign;
2512 FormatFullDoc();
2513 UpdateViews();
2514 }
2515 }
2516
ValidateSelection(TextSelection & rSel) const2517 void TextEngine::ValidateSelection( TextSelection& rSel ) const
2518 {
2519 ValidatePaM( rSel.GetStart() );
2520 ValidatePaM( rSel.GetEnd() );
2521 }
2522
ValidatePaM(TextPaM & rPaM) const2523 void TextEngine::ValidatePaM( TextPaM& rPaM ) const
2524 {
2525 const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
2526 if ( rPaM.GetPara() >= nParas )
2527 {
2528 rPaM.GetPara() = nParas ? nParas-1 : 0;
2529 rPaM.GetIndex() = TEXT_INDEX_ALL;
2530 }
2531
2532 const sal_Int32 nMaxIndex = GetTextLen( rPaM.GetPara() );
2533 if ( rPaM.GetIndex() > nMaxIndex )
2534 rPaM.GetIndex() = nMaxIndex;
2535 }
2536
2537 // adjust State & Selection
2538
ImpParagraphInserted(sal_uInt32 nPara)2539 void TextEngine::ImpParagraphInserted( sal_uInt32 nPara )
2540 {
2541 // No adjustment needed for the active View;
2542 // but for all passive Views the Selection needs adjusting.
2543 if ( mpViews->size() > 1 )
2544 {
2545 for ( auto nView = mpViews->size(); nView; )
2546 {
2547 TextView* pView = (*mpViews)[ --nView ];
2548 if ( pView != GetActiveView() )
2549 {
2550 for ( int n = 0; n <= 1; n++ )
2551 {
2552 TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
2553 if ( rPaM.GetPara() >= nPara )
2554 rPaM.GetPara()++;
2555 }
2556 }
2557 }
2558 }
2559 Broadcast( TextHint( SfxHintId::TextParaInserted, nPara ) );
2560 }
2561
ImpParagraphRemoved(sal_uInt32 nPara)2562 void TextEngine::ImpParagraphRemoved( sal_uInt32 nPara )
2563 {
2564 if ( mpViews->size() > 1 )
2565 {
2566 for ( auto nView = mpViews->size(); nView; )
2567 {
2568 TextView* pView = (*mpViews)[ --nView ];
2569 if ( pView != GetActiveView() )
2570 {
2571 const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
2572 for ( int n = 0; n <= 1; n++ )
2573 {
2574 TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
2575 if ( rPaM.GetPara() > nPara )
2576 rPaM.GetPara()--;
2577 else if ( rPaM.GetPara() == nPara )
2578 {
2579 rPaM.GetIndex() = 0;
2580 if ( rPaM.GetPara() >= nParas )
2581 rPaM.GetPara()--;
2582 }
2583 }
2584 }
2585 }
2586 }
2587 Broadcast( TextHint( SfxHintId::TextParaRemoved, nPara ) );
2588 }
2589
ImpCharsRemoved(sal_uInt32 nPara,sal_Int32 nPos,sal_Int32 nChars)2590 void TextEngine::ImpCharsRemoved( sal_uInt32 nPara, sal_Int32 nPos, sal_Int32 nChars )
2591 {
2592 if ( mpViews->size() > 1 )
2593 {
2594 for ( auto nView = mpViews->size(); nView; )
2595 {
2596 TextView* pView = (*mpViews)[ --nView ];
2597 if ( pView != GetActiveView() )
2598 {
2599 const sal_Int32 nEnd = nPos + nChars;
2600 for ( int n = 0; n <= 1; n++ )
2601 {
2602 TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
2603 if ( rPaM.GetPara() == nPara )
2604 {
2605 if ( rPaM.GetIndex() > nEnd )
2606 rPaM.GetIndex() = rPaM.GetIndex() - nChars;
2607 else if ( rPaM.GetIndex() > nPos )
2608 rPaM.GetIndex() = nPos;
2609 }
2610 }
2611 }
2612 }
2613 }
2614 Broadcast( TextHint( SfxHintId::TextParaContentChanged, nPara ) );
2615 }
2616
ImpCharsInserted(sal_uInt32 nPara,sal_Int32 nPos,sal_Int32 nChars)2617 void TextEngine::ImpCharsInserted( sal_uInt32 nPara, sal_Int32 nPos, sal_Int32 nChars )
2618 {
2619 if ( mpViews->size() > 1 )
2620 {
2621 for ( auto nView = mpViews->size(); nView; )
2622 {
2623 TextView* pView = (*mpViews)[ --nView ];
2624 if ( pView != GetActiveView() )
2625 {
2626 for ( int n = 0; n <= 1; n++ )
2627 {
2628 TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
2629 if ( rPaM.GetPara() == nPara )
2630 {
2631 if ( rPaM.GetIndex() >= nPos )
2632 rPaM.GetIndex() += nChars;
2633 }
2634 }
2635 }
2636 }
2637 }
2638 Broadcast( TextHint( SfxHintId::TextParaContentChanged, nPara ) );
2639 }
2640
Draw(OutputDevice * pDev,const Point & rPos)2641 void TextEngine::Draw( OutputDevice* pDev, const Point& rPos )
2642 {
2643 ImpPaint( pDev, rPos, nullptr );
2644 }
2645
SetLeftMargin(sal_uInt16 n)2646 void TextEngine::SetLeftMargin( sal_uInt16 n )
2647 {
2648 mpDoc->SetLeftMargin( n );
2649 }
2650
GetBreakIterator()2651 uno::Reference< i18n::XBreakIterator > const & TextEngine::GetBreakIterator()
2652 {
2653 if ( !mxBreakIterator.is() )
2654 mxBreakIterator = vcl::unohelper::CreateBreakIterator();
2655 SAL_WARN_IF( !mxBreakIterator.is(), "vcl", "BreakIterator: Failed to create!" );
2656 return mxBreakIterator;
2657 }
2658
SetLocale(const css::lang::Locale & rLocale)2659 void TextEngine::SetLocale( const css::lang::Locale& rLocale )
2660 {
2661 maLocale = rLocale;
2662 mpLocaleDataWrapper.reset();
2663 }
2664
GetLocale()2665 css::lang::Locale const & TextEngine::GetLocale()
2666 {
2667 if ( maLocale.Language.isEmpty() )
2668 {
2669 maLocale = Application::GetSettings().GetUILanguageTag().getLocale(); // TODO: why UI locale?
2670 }
2671 return maLocale;
2672 }
2673
ImpGetLocaleDataWrapper()2674 LocaleDataWrapper* TextEngine::ImpGetLocaleDataWrapper()
2675 {
2676 if ( !mpLocaleDataWrapper )
2677 mpLocaleDataWrapper.reset( new LocaleDataWrapper( LanguageTag( GetLocale()) ) );
2678
2679 return mpLocaleDataWrapper.get();
2680 }
2681
SetRightToLeft(bool bR2L)2682 void TextEngine::SetRightToLeft( bool bR2L )
2683 {
2684 if ( mbRightToLeft != bR2L )
2685 {
2686 mbRightToLeft = bR2L;
2687 meAlign = bR2L ? TxtAlign::Right : TxtAlign::Left;
2688 FormatFullDoc();
2689 UpdateViews();
2690 }
2691 }
2692
ImpInitWritingDirections(sal_uInt32 nPara)2693 void TextEngine::ImpInitWritingDirections( sal_uInt32 nPara )
2694 {
2695 TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
2696 std::vector<TEWritingDirectionInfo>& rInfos = pParaPortion->GetWritingDirectionInfos();
2697 rInfos.clear();
2698
2699 if ( !pParaPortion->GetNode()->GetText().isEmpty() )
2700 {
2701 const UBiDiLevel nBidiLevel = IsRightToLeft() ? 1 /*RTL*/ : 0 /*LTR*/;
2702 OUString aText( pParaPortion->GetNode()->GetText() );
2703
2704 // Bidi functions from icu 2.0
2705
2706 UErrorCode nError = U_ZERO_ERROR;
2707 UBiDi* pBidi = ubidi_openSized( aText.getLength(), 0, &nError );
2708 nError = U_ZERO_ERROR;
2709
2710 ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aText.getStr()), aText.getLength(), nBidiLevel, nullptr, &nError );
2711 nError = U_ZERO_ERROR;
2712
2713 tools::Long nCount = ubidi_countRuns( pBidi, &nError );
2714
2715 int32_t nStart = 0;
2716 int32_t nEnd;
2717 UBiDiLevel nCurrDir;
2718
2719 for ( tools::Long nIdx = 0; nIdx < nCount; ++nIdx )
2720 {
2721 ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir );
2722 // bit 0 of nCurrDir indicates direction
2723 rInfos.emplace_back( /*bLeftToRight*/ nCurrDir % 2 == 0, nStart, nEnd );
2724 nStart = nEnd;
2725 }
2726
2727 ubidi_close( pBidi );
2728 }
2729
2730 // No infos mean no CTL and default dir is L2R...
2731 if ( rInfos.empty() )
2732 rInfos.emplace_back( 0, 0, pParaPortion->GetNode()->GetText().getLength() );
2733
2734 }
2735
ImpGetRightToLeft(sal_uInt32 nPara,sal_Int32 nPos)2736 bool TextEngine::ImpGetRightToLeft( sal_uInt32 nPara, sal_Int32 nPos )
2737 {
2738 bool bRightToLeft = false;
2739
2740 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
2741 if ( pNode && !pNode->GetText().isEmpty() )
2742 {
2743 TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
2744 if ( pParaPortion->GetWritingDirectionInfos().empty() )
2745 ImpInitWritingDirections( nPara );
2746
2747 std::vector<TEWritingDirectionInfo>& rDirInfos = pParaPortion->GetWritingDirectionInfos();
2748 for ( const auto& rWritingDirectionInfo : rDirInfos )
2749 {
2750 if ( rWritingDirectionInfo.nStartPos <= nPos && rWritingDirectionInfo.nEndPos >= nPos )
2751 {
2752 bRightToLeft = !rWritingDirectionInfo.bLeftToRight;
2753 break;
2754 }
2755 }
2756 }
2757 return bRightToLeft;
2758 }
2759
ImpGetPortionXOffset(sal_uInt32 nPara,TextLine const * pLine,std::size_t nTextPortion)2760 tools::Long TextEngine::ImpGetPortionXOffset( sal_uInt32 nPara, TextLine const * pLine, std::size_t nTextPortion )
2761 {
2762 tools::Long nX = pLine->GetStartX();
2763
2764 TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
2765
2766 for ( std::size_t i = pLine->GetStartPortion(); i < nTextPortion; i++ )
2767 {
2768 TETextPortion& rPortion = pParaPortion->GetTextPortions()[ i ];
2769 nX += rPortion.GetWidth();
2770 }
2771
2772 TETextPortion& rDestPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
2773 if ( rDestPortion.GetKind() != PORTIONKIND_TAB )
2774 {
2775 if ( !IsRightToLeft() && rDestPortion.IsRightToLeft() )
2776 {
2777 // Portions behind must be added, visual before this portion
2778 std::size_t nTmpPortion = nTextPortion+1;
2779 while ( nTmpPortion <= pLine->GetEndPortion() )
2780 {
2781 TETextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
2782 if ( rNextTextPortion.IsRightToLeft() && ( rNextTextPortion.GetKind() != PORTIONKIND_TAB ) )
2783 nX += rNextTextPortion.GetWidth();
2784 else
2785 break;
2786 nTmpPortion++;
2787 }
2788 // Portions before must be removed, visual behind this portion
2789 nTmpPortion = nTextPortion;
2790 while ( nTmpPortion > pLine->GetStartPortion() )
2791 {
2792 --nTmpPortion;
2793 TETextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
2794 if ( rPrevTextPortion.IsRightToLeft() && ( rPrevTextPortion.GetKind() != PORTIONKIND_TAB ) )
2795 nX -= rPrevTextPortion.GetWidth();
2796 else
2797 break;
2798 }
2799 }
2800 else if ( IsRightToLeft() && !rDestPortion.IsRightToLeft() )
2801 {
2802 // Portions behind must be removed, visual behind this portion
2803 std::size_t nTmpPortion = nTextPortion+1;
2804 while ( nTmpPortion <= pLine->GetEndPortion() )
2805 {
2806 TETextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
2807 if ( !rNextTextPortion.IsRightToLeft() && ( rNextTextPortion.GetKind() != PORTIONKIND_TAB ) )
2808 nX += rNextTextPortion.GetWidth();
2809 else
2810 break;
2811 nTmpPortion++;
2812 }
2813 // Portions before must be added, visual before this portion
2814 nTmpPortion = nTextPortion;
2815 while ( nTmpPortion > pLine->GetStartPortion() )
2816 {
2817 --nTmpPortion;
2818 TETextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
2819 if ( !rPrevTextPortion.IsRightToLeft() && ( rPrevTextPortion.GetKind() != PORTIONKIND_TAB ) )
2820 nX -= rPrevTextPortion.GetWidth();
2821 else
2822 break;
2823 }
2824 }
2825 }
2826
2827 return nX;
2828 }
2829
ImpInitLayoutMode(OutputDevice * pOutDev)2830 void TextEngine::ImpInitLayoutMode( OutputDevice* pOutDev )
2831 {
2832 vcl::text::ComplexTextLayoutFlags nLayoutMode = pOutDev->GetLayoutMode();
2833
2834 nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags(vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::BiDiStrong );
2835
2836 pOutDev->SetLayoutMode( nLayoutMode );
2837 }
2838
ImpGetAlign() const2839 TxtAlign TextEngine::ImpGetAlign() const
2840 {
2841 TxtAlign eAlign = meAlign;
2842 if ( IsRightToLeft() )
2843 {
2844 if ( eAlign == TxtAlign::Left )
2845 eAlign = TxtAlign::Right;
2846 else if ( eAlign == TxtAlign::Right )
2847 eAlign = TxtAlign::Left;
2848 }
2849 return eAlign;
2850 }
2851
ImpGetOutputOffset(sal_uInt32 nPara,TextLine * pLine,sal_Int32 nIndex,sal_Int32 nIndex2)2852 tools::Long TextEngine::ImpGetOutputOffset( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nIndex, sal_Int32 nIndex2 )
2853 {
2854 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
2855
2856 sal_Int32 nPortionStart {0};
2857 const std::size_t nPortion = pPortion->GetTextPortions().FindPortion( nIndex, nPortionStart, true );
2858
2859 TETextPortion& rTextPortion = pPortion->GetTextPortions()[ nPortion ];
2860
2861 tools::Long nX;
2862
2863 if ( ( nIndex == nPortionStart ) && ( nIndex == nIndex2 ) )
2864 {
2865 // Output of full portion, so we need portion x offset.
2866 // Use ImpGetPortionXOffset, because GetXPos may deliver left or right position from portion, depending on R2L, L2R
2867 nX = ImpGetPortionXOffset( nPara, pLine, nPortion );
2868 if ( IsRightToLeft() )
2869 {
2870 nX = -nX - rTextPortion.GetWidth();
2871 }
2872 }
2873 else
2874 {
2875 nX = ImpGetXPos( nPara, pLine, nIndex, nIndex == nPortionStart );
2876 if ( nIndex2 != nIndex )
2877 {
2878 const tools::Long nX2 = ImpGetXPos( nPara, pLine, nIndex2 );
2879 if ( ( !IsRightToLeft() && ( nX2 < nX ) ) ||
2880 ( IsRightToLeft() && ( nX2 > nX ) ) )
2881 {
2882 nX = nX2;
2883 }
2884 }
2885 if ( IsRightToLeft() )
2886 {
2887 nX = -nX;
2888 }
2889 }
2890
2891 return nX;
2892 }
2893
2894 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2895