Coverage Report

Created: 2026-05-16 09:25

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