xref: /core/editeng/source/editeng/impedit3.cxx (revision bb748ba4)
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 
21 #include <vcl/svapp.hxx>
22 #include <vcl/metaact.hxx>
23 #include <vcl/gdimtf.hxx>
24 #include <vcl/settings.hxx>
25 
26 #include <editeng/adjustitem.hxx>
27 #include <editeng/tstpitem.hxx>
28 #include <editeng/lspcitem.hxx>
29 #include <editeng/flditem.hxx>
30 #include <editeng/forbiddenruleitem.hxx>
31 #include "impedit.hxx"
32 #include <editeng/editeng.hxx>
33 #include <editeng/editview.hxx>
34 #include <editeng/txtrange.hxx>
35 #include <editeng/colritem.hxx>
36 #include <editeng/udlnitem.hxx>
37 #include <editeng/fhgtitem.hxx>
38 #include <editeng/kernitem.hxx>
39 #include <editeng/lrspitem.hxx>
40 #include <editeng/ulspitem.hxx>
41 #include <editeng/fontitem.hxx>
42 #include <editeng/wghtitem.hxx>
43 #include <editeng/postitem.hxx>
44 #include <editeng/langitem.hxx>
45 #include <editeng/scriptspaceitem.hxx>
46 #include <editeng/charscaleitem.hxx>
47 #include <editeng/numitem.hxx>
48 #include <editeng/justifyitem.hxx>
49 
50 #include <svtools/colorcfg.hxx>
51 #include <svl/ctloptions.hxx>
52 #include <svl/asiancfg.hxx>
53 
54 #include <editeng/hngpnctitem.hxx>
55 #include <editeng/forbiddencharacterstable.hxx>
56 
57 #include <unotools/configmgr.hxx>
58 #include <unotools/localedatawrapper.hxx>
59 
60 #include <editeng/unolingu.hxx>
61 
62 #include <set>
63 #include <math.h>
64 #include <vcl/metric.hxx>
65 #include <com/sun/star/i18n/BreakIterator.hpp>
66 #include <com/sun/star/i18n/ScriptType.hpp>
67 #include <com/sun/star/i18n/InputSequenceChecker.hpp>
68 #include <com/sun/star/text/CharacterCompressionType.hpp>
69 #include <vcl/pdfextoutdevdata.hxx>
70 #include <i18nlangtag/mslangid.hxx>
71 
72 #include <comphelper/processfactory.hxx>
73 #include <rtl/ustrbuf.hxx>
74 #include <sal/log.hxx>
75 #include <osl/diagnose.h>
76 #include <comphelper/string.hxx>
77 #include <comphelper/lok.hxx>
78 #include <memory>
79 
80 #include <vcl/outdev/ScopedStates.hxx>
81 
82 using namespace ::com::sun::star;
83 using namespace ::com::sun::star::uno;
84 using namespace ::com::sun::star::beans;
85 using namespace ::com::sun::star::linguistic2;
86 
87 #define CH_HYPH     '-'
88 
89 #define WRONG_SHOW_MIN       5
90 
91 struct TabInfo
92 {
93     bool        bValid;
94 
95     SvxTabStop  aTabStop;
96     sal_Int32   nTabPortion;
97     long        nStartPosX;
98     long        nTabPos;
99 
100     TabInfo()
101         : bValid(false)
102         , nTabPortion(0)
103         , nStartPosX(0)
104         , nTabPos(0)
105         { }
106 
107 };
108 
109 Point Rotate( const Point& rPoint, short nOrientation, const Point& rOrigin )
110 {
111     double nRealOrientation = nOrientation*F_PI1800;
112     double nCos = cos( nRealOrientation );
113     double nSin = sin( nRealOrientation );
114 
115     Point aRotatedPos;
116     Point aTranslatedPos( rPoint );
117 
118     // Translation
119     aTranslatedPos -= rOrigin;
120 
121     // Rotation...
122     aRotatedPos.setX( static_cast<long>( nCos*aTranslatedPos.X() + nSin*aTranslatedPos.Y() ) );
123     aRotatedPos.setY( static_cast<long>(- ( nSin*aTranslatedPos.X() - nCos*aTranslatedPos.Y() )) );
124     aTranslatedPos = aRotatedPos;
125 
126     // Translation...
127     aTranslatedPos += rOrigin;
128     return aTranslatedPos;
129 }
130 
131 AsianCompressionFlags GetCharTypeForCompression( sal_Unicode cChar )
132 {
133     switch ( cChar )
134     {
135         case 0x3008: case 0x300A: case 0x300C: case 0x300E:
136         case 0x3010: case 0x3014: case 0x3016: case 0x3018:
137         case 0x301A: case 0x301D:
138         {
139             return AsianCompressionFlags::PunctuationRight;
140         }
141         case 0x3001: case 0x3002: case 0x3009: case 0x300B:
142         case 0x300D: case 0x300F: case 0x3011: case 0x3015:
143         case 0x3017: case 0x3019: case 0x301B: case 0x301E:
144         case 0x301F:
145         {
146             return AsianCompressionFlags::PunctuationLeft;
147         }
148         default:
149         {
150             return ( ( 0x3040 <= cChar ) && ( 0x3100 > cChar ) ) ? AsianCompressionFlags::Kana : AsianCompressionFlags::Normal;
151         }
152     }
153 }
154 
155 static void lcl_DrawRedLines( OutputDevice* pOutDev,
156                               long nFontHeight,
157                               const Point& rPoint,
158                               size_t nIndex,
159                               size_t nMaxEnd,
160                               const long* pDXArray,
161                               WrongList const * pWrongs,
162                               short nOrientation,
163                               const Point& rOrigin,
164                               bool bVertical,
165                               bool bIsRightToLeft )
166 {
167     // But only if font is not too small...
168     long nHeight = pOutDev->LogicToPixel(Size(0, nFontHeight)).Height();
169     if (WRONG_SHOW_MIN >= nHeight)
170         return;
171 
172     size_t nEnd, nStart = nIndex;
173     bool bWrong = pWrongs->NextWrong(nStart, nEnd);
174 
175     while (bWrong)
176     {
177         if (nStart >= nMaxEnd)
178             break;
179 
180         if (nStart < nIndex)  // Corrected
181             nStart = nIndex;
182 
183         if (nEnd > nMaxEnd)
184             nEnd = nMaxEnd;
185 
186         Point aPoint1(rPoint);
187         if (bVertical)
188         {
189             // VCL doesn't know that the text is vertical, and is manipulating
190             // the positions a little bit in y direction...
191             long nOnePixel = pOutDev->PixelToLogic(Size(0, 1)).Height();
192             long nCorrect = 2 * nOnePixel;
193             aPoint1.AdjustY(-nCorrect);
194             aPoint1.AdjustX(-nCorrect);
195         }
196         if (nStart > nIndex)
197         {
198             if (!bVertical)
199             {
200                 // since for RTL portions rPoint is on the visual right end of the portion
201                 // (i.e. at the start of the first RTL char) we need to subtract the offset
202                 // for RTL portions...
203                 aPoint1.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nStart - nIndex - 1]);
204             }
205             else
206                 aPoint1.AdjustY(pDXArray[nStart - nIndex - 1]);
207         }
208         Point aPoint2(rPoint);
209         DBG_ASSERT(nEnd > nIndex, "RedLine: aPnt2?");
210         if (!bVertical)
211         {
212             // since for RTL portions rPoint is on the visual right end of the portion
213             // (i.e. at the start of the first RTL char) we need to subtract the offset
214             // for RTL portions...
215             aPoint2.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nEnd - nIndex - 1]);
216         }
217         else
218         {
219             aPoint2.AdjustY(pDXArray[nEnd - nIndex - 1]);
220         }
221 
222         if (nOrientation)
223         {
224             aPoint1 = Rotate(aPoint1, nOrientation, rOrigin);
225             aPoint2 = Rotate(aPoint2, nOrientation, rOrigin);
226         }
227 
228         {
229             vcl::ScopedAntialiasing a(*pOutDev, true);
230             pOutDev->DrawWaveLine(aPoint1, aPoint2);
231         }
232 
233         nStart = nEnd + 1;
234         if (nEnd < nMaxEnd)
235             bWrong = pWrongs->NextWrong(nStart, nEnd);
236         else
237             bWrong = false;
238     }
239 }
240 
241 static Point lcl_ImplCalcRotatedPos( Point rPos, Point rOrigin, double nSin, double nCos )
242 {
243     Point aRotatedPos;
244     // Translation...
245     Point aTranslatedPos( rPos);
246     aTranslatedPos -= rOrigin;
247 
248     aRotatedPos.setX( static_cast<long>( nCos*aTranslatedPos.X() + nSin*aTranslatedPos.Y() ) );
249     aRotatedPos.setY( static_cast<long>(- ( nSin*aTranslatedPos.X() - nCos*aTranslatedPos.Y() )) );
250     aTranslatedPos = aRotatedPos;
251     // Translation...
252     aTranslatedPos += rOrigin;
253 
254     return aTranslatedPos;
255 }
256 
257 static bool lcl_IsLigature( sal_Unicode cCh, sal_Unicode cNextCh ) // For Kashidas from sw/source/core/text/porlay.txt
258 {
259             // Lam + Alef
260     return ( 0x644 == cCh && 0x627 == cNextCh ) ||
261             // Beh + Reh
262            ( 0x628 == cCh && 0x631 == cNextCh );
263 }
264 
265 static bool lcl_ConnectToPrev( sal_Unicode cCh, sal_Unicode cPrevCh )  // For Kashidas from sw/source/core/text/porlay.txt
266 {
267     // Alef, Dal, Thal, Reh, Zain, and Waw do not connect to the left
268     bool bRet = 0x627 != cPrevCh && 0x62F != cPrevCh && 0x630 != cPrevCh &&
269                 0x631 != cPrevCh && 0x632 != cPrevCh && 0x648 != cPrevCh;
270 
271     // check for ligatures cPrevChar + cChar
272     if ( bRet )
273         bRet = ! lcl_IsLigature( cPrevCh, cCh );
274 
275     return bRet;
276 }
277 
278 
279 //  class ImpEditEngine
280 
281 void ImpEditEngine::UpdateViews( EditView* pCurView )
282 {
283     if ( !GetUpdateMode() || IsFormatting() || aInvalidRect.IsEmpty() )
284         return;
285 
286     DBG_ASSERT( IsFormatted(), "UpdateViews: Doc not formatted!" );
287 
288     for (EditView* pView : aEditViews)
289     {
290         pView->HideCursor();
291 
292         tools::Rectangle aClipRect( aInvalidRect );
293         tools::Rectangle aVisArea( pView->GetVisArea() );
294         aClipRect.Intersection( aVisArea );
295 
296         if ( !aClipRect.IsEmpty() )
297         {
298             // convert to window coordinates...
299             aClipRect = pView->pImpEditView->GetWindowPos( aClipRect );
300 
301             // moved to one executing method to allow finer control
302             pView->InvalidateWindow(aClipRect);
303 
304             pView->InvalidateOtherViewWindows( aClipRect );
305         }
306     }
307 
308     if ( pCurView )
309     {
310         bool bGotoCursor = pCurView->pImpEditView->DoAutoScroll();
311         pCurView->ShowCursor( bGotoCursor );
312     }
313 
314     aInvalidRect = tools::Rectangle();
315     CallStatusHdl();
316 }
317 
318 IMPL_LINK_NOARG(ImpEditEngine, OnlineSpellHdl, Timer *, void)
319 {
320     if ( !Application::AnyInput( VclInputFlags::KEYBOARD ) && GetUpdateMode() && IsFormatted() )
321         DoOnlineSpelling();
322     else
323         aOnlineSpellTimer.Start();
324 }
325 
326 IMPL_LINK_NOARG(ImpEditEngine, IdleFormatHdl, Timer *, void)
327 {
328     aIdleFormatter.ResetRestarts();
329 
330     // #i97146# check if that view is still available
331     // else probably the idle format timer fired while we're already
332     // downing
333     EditView* pView = aIdleFormatter.GetView();
334     for (EditView* aEditView : aEditViews)
335     {
336         if( aEditView == pView )
337         {
338             FormatAndUpdate( pView );
339             break;
340         }
341     }
342 }
343 
344 void ImpEditEngine::CheckIdleFormatter()
345 {
346     aIdleFormatter.ForceTimeout();
347     // If not idle, but still not formatted:
348     if ( !IsFormatted() )
349         FormatDoc();
350 }
351 
352 bool ImpEditEngine::IsPageOverflow( ) const
353 {
354     return mbNeedsChainingHandling;
355 }
356 
357 
358 void ImpEditEngine::FormatFullDoc()
359 {
360     for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ )
361         GetParaPortions()[nPortion]->MarkSelectionInvalid( 0 );
362     FormatDoc();
363 }
364 
365 void ImpEditEngine::FormatDoc()
366 {
367     if (!GetUpdateMode() || IsFormatting())
368         return;
369 
370     bIsFormatting = true;
371 
372     // Then I can also start the spell-timer...
373     if ( GetStatus().DoOnlineSpelling() )
374         StartOnlineSpellTimer();
375 
376     long nY = 0;
377     bool bGrow = false;
378 
379     vcl::Font aOldFont( GetRefDevice()->GetFont() );
380 
381     // Here already, so that not always in CreateLines...
382     bool bMapChanged = ImpCheckRefMapMode();
383 
384     aInvalidRect = tools::Rectangle();  // make empty
385     for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ )
386     {
387         ParaPortion* pParaPortion = GetParaPortions()[nPara];
388         if ( pParaPortion->MustRepaint() || ( pParaPortion->IsInvalid() && pParaPortion->IsVisible() ) )
389         {
390             // No formatting should be necessary for MustRepaint()!
391             if ( ( pParaPortion->MustRepaint() && !pParaPortion->IsInvalid() )
392                     || CreateLines( nPara, nY ) )
393             {
394                 if ( !bGrow && GetTextRanger() )
395                 {
396                     // For a change in height all below must be reformatted...
397                     for ( sal_Int32 n = nPara+1; n < GetParaPortions().Count(); n++ )
398                     {
399                         ParaPortion* pPP = GetParaPortions()[n];
400                         pPP->MarkSelectionInvalid( 0 );
401                         pPP->GetLines().Reset();
402                     }
403                 }
404                 bGrow = true;
405                 if ( IsCallParaInsertedOrDeleted() )
406                     GetEditEnginePtr()->ParagraphHeightChanged( nPara );
407                 pParaPortion->SetMustRepaint( false );
408             }
409 
410             // InvalidRect set only once...
411             if ( aInvalidRect.IsEmpty() )
412             {
413                 // For Paperwidth 0 (AutoPageSize) it would otherwise be Empty()...
414                 long nWidth = std::max( long(1), ( !IsVertical() ? aPaperSize.Width() : aPaperSize.Height() ) );
415                 Range aInvRange( GetInvalidYOffsets( pParaPortion ) );
416                 aInvalidRect = tools::Rectangle( Point( 0, nY+aInvRange.Min() ),
417                     Size( nWidth, aInvRange.Len() ) );
418             }
419             else
420             {
421                 aInvalidRect.SetBottom( nY + pParaPortion->GetHeight() );
422             }
423         }
424         else if ( bGrow )
425         {
426             aInvalidRect.SetBottom( nY + pParaPortion->GetHeight() );
427         }
428         nY += pParaPortion->GetHeight();
429     }
430 
431     // One can also get into the formatting through UpdateMode ON=>OFF=>ON...
432     // enable optimization first after Vobis delivery...
433     {
434         sal_uInt32 nNewHeightNTP;
435         sal_uInt32 nNewHeight = CalcTextHeight( &nNewHeightNTP );
436         long nDiff = nNewHeight - nCurTextHeight;
437         if ( nDiff )
438             aStatus.GetStatusWord() |= !IsVertical() ? EditStatusFlags::TextHeightChanged : EditStatusFlags::TEXTWIDTHCHANGED;
439         if ( nNewHeight < nCurTextHeight )
440         {
441             aInvalidRect.SetBottom( static_cast<long>(std::max( nNewHeight, nCurTextHeight )) );
442             if ( aInvalidRect.IsEmpty() )
443             {
444                 aInvalidRect.SetTop( 0 );
445                 // Left and Right are not evaluated, are however set due to IsEmpty.
446                 aInvalidRect.SetLeft( 0 );
447                 aInvalidRect.SetRight( !IsVertical() ? aPaperSize.Width() : aPaperSize.Height() );
448             }
449         }
450 
451         nCurTextHeight = nNewHeight;
452         nCurTextHeightNTP = nNewHeightNTP;
453 
454         if ( aStatus.AutoPageSize() )
455             CheckAutoPageSize();
456         else if ( nDiff )
457         {
458             for (EditView* pView : aEditViews)
459             {
460                 ImpEditView* pImpView = pView->pImpEditView.get();
461                 if ( pImpView->DoAutoHeight() )
462                 {
463                     Size aSz( pImpView->GetOutputArea().GetWidth(), nCurTextHeight );
464                     if ( aSz.Height() > aMaxAutoPaperSize.Height() )
465                         aSz.setHeight( aMaxAutoPaperSize.Height() );
466                     else if ( aSz.Height() < aMinAutoPaperSize.Height() )
467                         aSz.setHeight( aMinAutoPaperSize.Height() );
468                     pImpView->ResetOutputArea( tools::Rectangle(
469                         pImpView->GetOutputArea().TopLeft(), aSz ) );
470                 }
471             }
472         }
473     }
474 
475     bIsFormatting = false;
476     bFormatted = true;
477 
478     if ( bMapChanged )
479         GetRefDevice()->Pop();
480 
481     CallStatusHdl();    // If Modified...
482 }
483 
484 bool ImpEditEngine::ImpCheckRefMapMode()
485 {
486     bool bChange = false;
487 
488     if ( aStatus.DoFormat100() )
489     {
490         MapMode aMapMode( GetRefDevice()->GetMapMode() );
491         if ( aMapMode.GetScaleX().GetNumerator() != aMapMode.GetScaleX().GetDenominator() )
492             bChange = true;
493         else if ( aMapMode.GetScaleY().GetNumerator() != aMapMode.GetScaleY().GetDenominator() )
494             bChange = true;
495 
496         if ( bChange )
497         {
498             Fraction Scale1( 1, 1 );
499             aMapMode.SetScaleX( Scale1 );
500             aMapMode.SetScaleY( Scale1 );
501             GetRefDevice()->Push();
502             GetRefDevice()->SetMapMode( aMapMode );
503         }
504     }
505 
506     return bChange;
507 }
508 
509 void ImpEditEngine::CheckAutoPageSize()
510 {
511     Size aPrevPaperSize( GetPaperSize() );
512     if ( GetStatus().AutoPageWidth() )
513         aPaperSize.setWidth( !IsVertical() ? CalcTextWidth( true ) : GetTextHeight() );
514     if ( GetStatus().AutoPageHeight() )
515         aPaperSize.setHeight( !IsVertical() ? GetTextHeight() : CalcTextWidth( true ) );
516 
517     SetValidPaperSize( aPaperSize );    // consider Min, Max
518 
519     if ( aPaperSize != aPrevPaperSize )
520     {
521         if ( ( !IsVertical() && ( aPaperSize.Width() != aPrevPaperSize.Width() ) )
522              || ( IsVertical() && ( aPaperSize.Height() != aPrevPaperSize.Height() ) ) )
523         {
524             // If ahead is centered / right or tabs...
525             aStatus.GetStatusWord() |= !IsVertical() ? EditStatusFlags::TEXTWIDTHCHANGED : EditStatusFlags::TextHeightChanged;
526             for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ )
527             {
528                 // Only paragraphs which are not aligned to the left need to be
529                 // reformatted, the height can not be changed here anymore.
530                 ParaPortion* pParaPortion = GetParaPortions()[nPara];
531                 SvxAdjust eJustification = GetJustification( nPara );
532                 if ( eJustification != SvxAdjust::Left )
533                 {
534                     pParaPortion->MarkSelectionInvalid( 0 );
535                     CreateLines( nPara, 0 );  // 0: For AutoPageSize no TextRange!
536                 }
537             }
538         }
539 
540         Size aInvSize = aPaperSize;
541         if ( aPaperSize.Width() < aPrevPaperSize.Width() )
542             aInvSize.setWidth( aPrevPaperSize.Width() );
543         if ( aPaperSize.Height() < aPrevPaperSize.Height() )
544             aInvSize.setHeight( aPrevPaperSize.Height() );
545 
546         Size aSz( aInvSize );
547         if ( IsVertical() )
548         {
549             aSz.setWidth( aInvSize.Height() );
550             aSz.setHeight( aInvSize.Width() );
551         }
552         aInvalidRect = tools::Rectangle( Point(), aSz );
553 
554 
555         for (EditView* pView : aEditViews)
556         {
557             pView->pImpEditView->RecalcOutputArea();
558         }
559     }
560 }
561 
562 void ImpEditEngine::CheckPageOverflow()
563 {
564     SAL_INFO("editeng.chaining", "[CONTROL_STATUS] AutoPageSize is " << (( aStatus.GetControlWord() & EEControlBits::AUTOPAGESIZE ) ? "ON" : "OFF") );
565 
566     sal_uInt32 nBoxHeight = GetMaxAutoPaperSize().Height();
567     SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current MaxAutoPaperHeight is " << nBoxHeight);
568 
569     sal_uInt32 nTxtHeight = CalcTextHeight(nullptr);
570     SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current Text Height is " << nTxtHeight);
571 
572     sal_uInt32 nParaCount = GetParaPortions().Count();
573     sal_uInt32 nFirstLineCount = GetLineCount(0);
574     bool bOnlyOneEmptyPara = (nParaCount == 1) &&
575                             (nFirstLineCount == 1) &&
576                             (GetLineLen(0,0) == 0);
577 
578     if (nTxtHeight > nBoxHeight && !bOnlyOneEmptyPara)
579     {
580         // which paragraph is the first to cause higher size of the box?
581         ImplUpdateOverflowingParaNum( nBoxHeight); // XXX: currently only for horizontal text
582         //aStatus.SetPageOverflow(true);
583         mbNeedsChainingHandling = true;
584     } else
585     {
586         // No overflow if within box boundaries
587         //aStatus.SetPageOverflow(false);
588         mbNeedsChainingHandling = false;
589     }
590 
591 }
592 
593 static sal_Int32 ImplCalculateFontIndependentLineSpacing( const sal_Int32 nFontHeight )
594 {
595     return ( nFontHeight * 12 ) / 10;   // + 20%
596 }
597 
598 bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY )
599 {
600     ParaPortion* pParaPortion = GetParaPortions()[nPara];
601 
602     // sal_Bool: Changes in the height of paragraph Yes / No - sal_True/sal_False
603     DBG_ASSERT( pParaPortion->GetNode(), "Portion without Node in CreateLines" );
604     DBG_ASSERT( pParaPortion->IsVisible(), "Invisible paragraphs not formatted!" );
605     DBG_ASSERT( pParaPortion->IsInvalid(), "CreateLines: Portion not invalid!" );
606 
607     bool bProcessingEmptyLine = ( pParaPortion->GetNode()->Len() == 0 );
608     bool bEmptyNodeWithPolygon = ( pParaPortion->GetNode()->Len() == 0 ) && GetTextRanger();
609 
610 
611     // Fast special treatment for empty paragraphs...
612 
613     if ( ( pParaPortion->GetNode()->Len() == 0 ) && !GetTextRanger() )
614     {
615         // fast special treatment...
616         if ( pParaPortion->GetTextPortions().Count() )
617             pParaPortion->GetTextPortions().Reset();
618         if ( pParaPortion->GetLines().Count() )
619             pParaPortion->GetLines().Reset();
620         CreateAndInsertEmptyLine( pParaPortion );
621         return FinishCreateLines( pParaPortion );
622     }
623 
624 
625     // Initialization...
626 
627 
628     // Always format for 100%:
629     bool bMapChanged = ImpCheckRefMapMode();
630 
631     if ( pParaPortion->GetLines().Count() == 0 )
632     {
633         EditLine* pL = new EditLine;
634         pParaPortion->GetLines().Append(pL);
635     }
636 
637 
638     // Get Paragraph attributes...
639 
640     ContentNode* const pNode = pParaPortion->GetNode();
641 
642     bool bRightToLeftPara = IsRightToLeft( nPara );
643 
644     SvxAdjust eJustification = GetJustification( nPara );
645     bool bHyphenatePara = pNode->GetContentAttribs().GetItem( EE_PARA_HYPHENATE ).GetValue();
646     sal_Int32 nSpaceBefore      = 0;
647     sal_Int32 nMinLabelWidth    = 0;
648     sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pNode, &nSpaceBefore, &nMinLabelWidth );
649     const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pNode );
650     const SvxLineSpacingItem& rLSItem = pNode->GetContentAttribs().GetItem( EE_PARA_SBL );
651     const bool bScriptSpace = pNode->GetContentAttribs().GetItem( EE_PARA_ASIANCJKSPACING ).GetValue();
652 
653     const short nInvalidDiff = pParaPortion->GetInvalidDiff();
654     const sal_Int32 nInvalidStart = pParaPortion->GetInvalidPosStart();
655     const sal_Int32 nInvalidEnd =  nInvalidStart + std::abs( nInvalidDiff );
656 
657     bool bQuickFormat = false;
658     if ( !bEmptyNodeWithPolygon && !HasScriptType( nPara, i18n::ScriptType::COMPLEX ) )
659     {
660         if ( ( pParaPortion->IsSimpleInvalid() ) && ( nInvalidDiff > 0 ) &&
661              ( pNode->GetString().indexOf( CH_FEATURE, nInvalidStart ) > nInvalidEnd ) )
662         {
663             bQuickFormat = true;
664         }
665         else if ( ( pParaPortion->IsSimpleInvalid() ) && ( nInvalidDiff < 0 ) )
666         {
667             // check if delete over the portion boundaries was done...
668             sal_Int32 nStart = nInvalidStart;  // DOUBLE !!!!!!!!!!!!!!!
669             sal_Int32 nEnd = nStart - nInvalidDiff;  // negative
670             bQuickFormat = true;
671             sal_Int32 nPos = 0;
672             sal_Int32 nPortions = pParaPortion->GetTextPortions().Count();
673             for ( sal_Int32 nTP = 0; nTP < nPortions; nTP++ )
674             {
675                 // There must be no start / end in the deleted area.
676                 const TextPortion& rTP = pParaPortion->GetTextPortions()[ nTP ];
677                 nPos = nPos + rTP.GetLen();
678                 if ( ( nPos > nStart ) && ( nPos < nEnd ) )
679                 {
680                     bQuickFormat = false;
681                     break;
682                 }
683             }
684         }
685     }
686 
687     // Saving both layout mode and language (since I'm potentially changing both)
688     GetRefDevice()->Push( PushFlags::TEXTLAYOUTMODE|PushFlags::TEXTLANGUAGE );
689 
690     ImplInitLayoutMode( GetRefDevice(), nPara, -1 );
691 
692     sal_Int32 nRealInvalidStart = nInvalidStart;
693 
694     if ( bEmptyNodeWithPolygon )
695     {
696         TextPortion* pDummyPortion = new TextPortion( 0 );
697         pParaPortion->GetTextPortions().Reset();
698         pParaPortion->GetTextPortions().Append(pDummyPortion);
699     }
700     else if ( bQuickFormat )
701     {
702         // faster Method:
703         RecalcTextPortion( pParaPortion, nInvalidStart, nInvalidDiff );
704     }
705     else    // nRealInvalidStart can be before InvalidStart, since Portions were deleted...
706     {
707         CreateTextPortions( pParaPortion, nRealInvalidStart );
708     }
709 
710 
711     // Search for line with InvalidPos, start one line before
712     // Flag the line => do not remove it !
713 
714 
715     sal_Int32 nLine = pParaPortion->GetLines().Count()-1;
716     for ( sal_Int32 nL = 0; nL <= nLine; nL++ )
717     {
718         EditLine& rLine = pParaPortion->GetLines()[nL];
719         if ( rLine.GetEnd() > nRealInvalidStart )  // not nInvalidStart!
720         {
721             nLine = nL;
722             break;
723         }
724         rLine.SetValid();
725     }
726     // Begin one line before...
727     // If it is typed at the end, the line in front cannot change.
728     if ( nLine && ( !pParaPortion->IsSimpleInvalid() || ( nInvalidEnd < pNode->Len() ) || ( nInvalidDiff <= 0 ) ) )
729         nLine--;
730 
731     EditLine* pLine = &pParaPortion->GetLines()[nLine];
732 
733     static tools::Rectangle aZeroArea { Point(), Point() };
734     tools::Rectangle aBulletArea( aZeroArea );
735     if ( !nLine )
736     {
737         aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos( pParaPortion ) );
738         if ( !aBulletArea.IsWidthEmpty() && aBulletArea.Right() > 0 )
739             pParaPortion->SetBulletX( static_cast<sal_Int32>(GetXValue( aBulletArea.Right() )) );
740         else
741             pParaPortion->SetBulletX( 0 ); // if Bullet is set incorrectly
742     }
743 
744 
745     // Reformat all lines from here...
746 
747     sal_Int32 nDelFromLine = -1;
748     bool bLineBreak = false;
749 
750     sal_Int32 nIndex = pLine->GetStart();
751     EditLine aSaveLine( *pLine );
752     SvxFont aTmpFont( pNode->GetCharAttribs().GetDefFont() );
753 
754     ImplInitLayoutMode( GetRefDevice(), nPara, nIndex );
755 
756     std::unique_ptr<long[]> pBuf(new long[ pNode->Len() ]);
757 
758     bool bSameLineAgain = false;    // For TextRanger, if the height changes.
759     TabInfo aCurrentTab;
760 
761     bool bForceOneRun = bEmptyNodeWithPolygon;
762     bool bCompressedChars = false;
763 
764     while ( ( nIndex < pNode->Len() ) || bForceOneRun )
765     {
766         bForceOneRun = false;
767 
768         bool bEOL = false;
769         bool bEOC = false;
770         sal_Int32 nPortionStart = 0;
771         sal_Int32 nPortionEnd = 0;
772 
773         long nStartX = GetXValue( rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth );
774         if ( nIndex == 0 )
775         {
776             long nFI = GetXValue( rLRItem.GetTextFirstLineOfst() );
777             nStartX += nFI;
778 
779             if ( !nLine && ( pParaPortion->GetBulletX() > nStartX ) )
780             {
781                     nStartX = pParaPortion->GetBulletX();
782             }
783         }
784 
785         long nMaxLineWidth;
786         if ( !IsVertical() )
787             nMaxLineWidth = aStatus.AutoPageWidth() ? aMaxAutoPaperSize.Width() : aPaperSize.Width();
788         else
789             nMaxLineWidth = aStatus.AutoPageHeight() ? aMaxAutoPaperSize.Height() : aPaperSize.Height();
790 
791         nMaxLineWidth -= GetXValue( rLRItem.GetRight() );
792         nMaxLineWidth -= nStartX;
793 
794         // If PaperSize == long_max, one cannot take away any negative
795         // first line indent. (Overflow)
796         if ( ( nMaxLineWidth < 0 ) && ( nStartX < 0 ) )
797             nMaxLineWidth = ( !IsVertical() ? aPaperSize.Width() : aPaperSize.Height() ) - GetXValue( rLRItem.GetRight() );
798 
799         // If still less than 0, it may be just the right edge.
800         if ( nMaxLineWidth <= 0 )
801             nMaxLineWidth = 1;
802 
803         // Problem:
804         // Since formatting starts a line _before_ the invalid position,
805      // the positions unfortunately have to be redefined...
806         // Solution:
807         // The line before can only become longer, not smaller
808         // =>...
809         pLine->GetCharPosArray().clear();
810 
811         sal_Int32 nTmpPos = nIndex;
812         sal_Int32 nTmpPortion = pLine->GetStartPortion();
813         long nTmpWidth = 0;
814         long nXWidth = nMaxLineWidth;
815 
816         LongDqPtr pTextRanges = nullptr;
817         long nTextExtraYOffset = 0;
818         long nTextXOffset = 0;
819         long nTextLineHeight = 0;
820         if ( GetTextRanger() )
821         {
822             GetTextRanger()->SetVertical( IsVertical() );
823 
824             long nTextY = nStartPosY + GetEditCursor( pParaPortion, pLine->GetStart() ).Top();
825             if ( !bSameLineAgain )
826             {
827                 SeekCursor( pNode, nTmpPos+1, aTmpFont );
828                 aTmpFont.SetPhysFont( GetRefDevice() );
829                 ImplInitDigitMode(GetRefDevice(), aTmpFont.GetLanguage());
830 
831                 if ( IsFixedCellHeight() )
832                     nTextLineHeight = ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() );
833                 else
834                     nTextLineHeight = aTmpFont.GetPhysTxtSize( GetRefDevice() ).Height();
835                 // Metrics can be greater
836                 FormatterFontMetric aTempFormatterMetrics;
837                 RecalcFormatterFontMetrics( aTempFormatterMetrics, aTmpFont );
838                 sal_uInt16 nLineHeight = aTempFormatterMetrics.GetHeight();
839                 if ( nLineHeight > nTextLineHeight )
840                     nTextLineHeight = nLineHeight;
841             }
842             else
843                 nTextLineHeight = pLine->GetHeight();
844 
845             nXWidth = 0;
846             while ( !nXWidth )
847             {
848                 long nYOff = nTextY + nTextExtraYOffset;
849                 long nYDiff = nTextLineHeight;
850                 if ( IsVertical() )
851                 {
852                     long nMaxPolygonX = GetTextRanger()->GetBoundRect().Right();
853                     nYOff = nMaxPolygonX-nYOff;
854                     nYDiff = -nTextLineHeight;
855                 }
856                 pTextRanges = GetTextRanger()->GetTextRanges( Range( nYOff, nYOff + nYDiff ) );
857                 DBG_ASSERT( pTextRanges, "GetTextRanges?!" );
858                 long nMaxRangeWidth = 0;
859                 // Use the widest range...
860                 // The widest range could be a bit confusing, so normally it
861                 // is the first one. Best with gaps.
862                 assert(pTextRanges->size() % 2 == 0 && "textranges are always in pairs");
863                 if (!pTextRanges->empty())
864                 {
865                     long nA = pTextRanges->at(0);
866                     long nB = pTextRanges->at(1);
867                     DBG_ASSERT( nA <= nB, "TextRange distorted?" );
868                     long nW = nB - nA;
869                     if ( nW > nMaxRangeWidth )
870                     {
871                         nMaxRangeWidth = nW;
872                         nTextXOffset = nA;
873                     }
874                 }
875                 nXWidth = nMaxRangeWidth;
876                 if ( nXWidth )
877                     nMaxLineWidth = nXWidth - nStartX - GetXValue( rLRItem.GetRight() );
878                 else
879                 {
880                     // Try further down in the polygon.
881                     // Below the polygon use the Paper Width.
882                     nTextExtraYOffset += std::max( static_cast<long>(nTextLineHeight / 10), long(1) );
883                     if ( ( nTextY + nTextExtraYOffset  ) > GetTextRanger()->GetBoundRect().Bottom() )
884                     {
885                         nXWidth = !IsVertical() ? GetPaperSize().Width() : GetPaperSize().Height();
886                         if ( !nXWidth ) // AutoPaperSize
887                             nXWidth = 0x7FFFFFFF;
888                     }
889                 }
890             }
891         }
892 
893         // search for Portion that no longer fits in line...
894         TextPortion* pPortion = nullptr;
895         sal_Int32 nPortionLen = 0;
896         bool bContinueLastPortion = false;
897         bool bBrokenLine = false;
898         bLineBreak = false;
899         const EditCharAttrib* pNextFeature = pNode->GetCharAttribs().FindFeature( pLine->GetStart() );
900         while ( ( nTmpWidth < nXWidth ) && !bEOL )
901         {
902             const sal_Int32 nTextPortions = pParaPortion->GetTextPortions().Count();
903             assert(nTextPortions > 0);
904             bContinueLastPortion = (nTmpPortion >= nTextPortions);
905             if (bContinueLastPortion)
906             {
907                 if (nTmpPos >= pNode->Len())
908                     break;  // while
909 
910                 // Continue with remainder. This only to have *some* valid
911                 // X-values and not endlessly create new lines until DOOM..
912                 // Happened in the scenario of tdf#104152 where inserting a
913                 // paragraph lead to a11y attempting to format the doc to
914                 // obtain content when notified.
915                 nTmpPortion = nTextPortions - 1;
916                 SAL_WARN("editeng","ImpEditEngine::CreateLines - continuation of a broken portion");
917             }
918 
919             nPortionStart = nTmpPos;
920             pPortion = &pParaPortion->GetTextPortions()[nTmpPortion];
921             if ( !bContinueLastPortion && pPortion->GetKind() == PortionKind::HYPHENATOR )
922             {
923                 // Throw away a Portion, if necessary correct the one before,
924                 // if the Hyph portion has swallowed a character...
925                 sal_Int32 nTmpLen = pPortion->GetLen();
926                 pParaPortion->GetTextPortions().Remove( nTmpPortion );
927                 if (nTmpPortion && nTmpLen)
928                 {
929                     nTmpPortion--;
930                     TextPortion& rPrev = pParaPortion->GetTextPortions()[nTmpPortion];
931                     DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" );
932                     nTmpWidth -= rPrev.GetSize().Width();
933                     nTmpPos = nTmpPos - rPrev.GetLen();
934                     rPrev.SetLen(rPrev.GetLen() + nTmpLen);
935                     rPrev.GetSize().setWidth( -1 );
936                 }
937 
938                 DBG_ASSERT( nTmpPortion < pParaPortion->GetTextPortions().Count(), "No more Portions left!" );
939                 pPortion = &pParaPortion->GetTextPortions()[nTmpPortion];
940             }
941 
942             if (bContinueLastPortion)
943             {
944                 // Note that this may point behind the portion and is only to
945                 // be used with the node's string offsets to generate X-values.
946                 nPortionLen = pNode->Len() - nPortionStart;
947             }
948             else
949             {
950                 nPortionLen = pPortion->GetLen();
951             }
952 
953             DBG_ASSERT( pPortion->GetKind() != PortionKind::HYPHENATOR, "CreateLines: Hyphenator-Portion!" );
954             DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion in CreateLines ?!" );
955             if ( pNextFeature && ( pNextFeature->GetStart() == nTmpPos ) )
956             {
957                 SAL_WARN_IF( bContinueLastPortion,
958                         "editeng","ImpEditEngine::CreateLines - feature in continued portion will be wrong");
959                 sal_uInt16 nWhich = pNextFeature->GetItem()->Which();
960                 switch ( nWhich )
961                 {
962                     case EE_FEATURE_TAB:
963                     {
964                         long nOldTmpWidth = nTmpWidth;
965 
966                         // Search for Tab-Pos...
967                         long nCurPos = nTmpWidth+nStartX;
968                         // consider scaling
969                         if ( aStatus.DoStretch() && ( nStretchX != 100 ) )
970                             nCurPos = nCurPos*100/std::max(static_cast<sal_Int32>(nStretchX), static_cast<sal_Int32>(1));
971 
972                         short nAllSpaceBeforeText = static_cast< short >(rLRItem.GetTextLeft()/* + rLRItem.GetTextLeft()*/ + nSpaceBeforeAndMinLabelWidth);
973                         aCurrentTab.aTabStop = pNode->GetContentAttribs().FindTabStop( nCurPos - nAllSpaceBeforeText /*rLRItem.GetTextLeft()*/, aEditDoc.GetDefTab() );
974                         aCurrentTab.nTabPos = GetXValue( static_cast<long>( aCurrentTab.aTabStop.GetTabPos() + nAllSpaceBeforeText /*rLRItem.GetTextLeft()*/ ) );
975                         aCurrentTab.bValid = false;
976 
977                         // Switch direction in R2L para...
978                         if ( bRightToLeftPara )
979                         {
980                             if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right )
981                                 aCurrentTab.aTabStop.GetAdjustment() = SvxTabAdjust::Left;
982                             else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Left )
983                                 aCurrentTab.aTabStop.GetAdjustment() = SvxTabAdjust::Right;
984                         }
985 
986                         if ( ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right ) ||
987                              ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center ) ||
988                              ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal ) )
989                         {
990                             // For LEFT / DEFAULT this tab is not considered.
991                             aCurrentTab.bValid = true;
992                             aCurrentTab.nStartPosX = nTmpWidth;
993                             aCurrentTab.nTabPortion = nTmpPortion;
994                         }
995 
996                         pPortion->SetKind(PortionKind::TAB);
997                         pPortion->SetExtraValue( aCurrentTab.aTabStop.GetFill() );
998                         pPortion->GetSize().setWidth( aCurrentTab.nTabPos - (nTmpWidth+nStartX) );
999 
1000                         // Height needed...
1001                         SeekCursor( pNode, nTmpPos+1, aTmpFont );
1002                         pPortion->GetSize().setHeight( aTmpFont.QuickGetTextSize( GetRefDevice(), OUString(), 0, 0 ).Height() );
1003 
1004                         DBG_ASSERT( pPortion->GetSize().Width() >= 0, "Tab incorrectly calculated!" );
1005 
1006                         nTmpWidth = aCurrentTab.nTabPos-nStartX;
1007 
1008                         // If this is the first token on the line,
1009                         // and nTmpWidth > aPaperSize.Width, => infinite loop!
1010                         if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
1011                         {
1012                             // What now?
1013                             // make the tab fitting
1014                             pPortion->GetSize().setWidth( nXWidth-nOldTmpWidth );
1015                             nTmpWidth = nXWidth-1;
1016                             bEOL = true;
1017                             bBrokenLine = true;
1018                         }
1019                         EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray();
1020                         size_t nPos = nTmpPos - pLine->GetStart();
1021                         rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
1022                         bCompressedChars = false;
1023                     }
1024                     break;
1025                     case EE_FEATURE_LINEBR:
1026                     {
1027                         DBG_ASSERT( pPortion, "?!" );
1028                         pPortion->GetSize().setWidth( 0 );
1029                         bEOL = true;
1030                         bLineBreak = true;
1031                         pPortion->SetKind( PortionKind::LINEBREAK );
1032                         bCompressedChars = false;
1033                         EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray();
1034                         size_t nPos = nTmpPos - pLine->GetStart();
1035                         rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
1036                     }
1037                     break;
1038                     case EE_FEATURE_FIELD:
1039                     {
1040                         SeekCursor( pNode, nTmpPos+1, aTmpFont );
1041                         aTmpFont.SetPhysFont( GetRefDevice() );
1042                         ImplInitDigitMode(GetRefDevice(), aTmpFont.GetLanguage());
1043 
1044                         OUString aFieldValue = static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue();
1045                         // get size, but also DXArray to allow length information in line breaking below
1046                         const sal_Int32 nLength(aFieldValue.getLength());
1047                         std::unique_ptr<long[]> pTmpDXArray(new long[nLength]);
1048                         pPortion->GetSize() = aTmpFont.QuickGetTextSize(GetRefDevice(), aFieldValue, 0, aFieldValue.getLength(), pTmpDXArray.get());
1049 
1050                         // So no scrolling for oversized fields
1051                         if ( pPortion->GetSize().Width() > nXWidth )
1052                         {
1053                             // create ExtraPortionInfo on-demand, flush lineBreaksList
1054                             ExtraPortionInfo *pExtraInfo = pPortion->GetExtraInfos();
1055 
1056                             if(nullptr == pExtraInfo)
1057                             {
1058                                 pExtraInfo = new ExtraPortionInfo();
1059                                 pExtraInfo->nOrgWidth = nXWidth;
1060                                 pPortion->SetExtraInfos(pExtraInfo);
1061                             }
1062                             else
1063                             {
1064                                 pExtraInfo->lineBreaksList.clear();
1065                             }
1066 
1067                             // iterate over CellBreaks using XBreakIterator to be on the
1068                             // safe side with international texts/charSets
1069                             Reference < i18n::XBreakIterator > xBreakIterator(ImplGetBreakIterator());
1070                             const sal_Int32 nTextLength(aFieldValue.getLength());
1071                             const lang::Locale aLocale(GetLocale(EditPaM(pNode, nPortionStart)));
1072                             sal_Int32 nDone(0);
1073                             sal_Int32 nNextCellBreak(
1074                                 xBreakIterator->nextCharacters(
1075                                         aFieldValue,
1076                                         0,
1077                                         aLocale,
1078                                         css::i18n::CharacterIteratorMode::SKIPCELL,
1079                                         0,
1080                                         nDone));
1081                             sal_Int32 nLastCellBreak(0);
1082                             sal_Int32 nLineStartX(0);
1083 
1084                             // always add 1st line break (safe, we already know we are larger than nXWidth)
1085                             pExtraInfo->lineBreaksList.push_back(0);
1086 
1087                             for(sal_Int32 a(0); a < nTextLength; a++)
1088                             {
1089                                 if(a == nNextCellBreak)
1090                                 {
1091                                     // check width
1092                                     if(pTmpDXArray[a] - nLineStartX > nXWidth)
1093                                     {
1094                                         // new CellBreak does not fit in current line, need to
1095                                         // create a break at LastCellBreak - but do not add 1st
1096                                         // line break twice for very tall frames
1097                                         if(0 != a)
1098                                         {
1099                                             pExtraInfo->lineBreaksList.push_back(a);
1100                                         }
1101 
1102                                         // moveLineStart forward in X
1103                                         nLineStartX = pTmpDXArray[nLastCellBreak];
1104                                     }
1105 
1106                                     // update CellBreak iteration values
1107                                     nLastCellBreak = a;
1108                                     nNextCellBreak = xBreakIterator->nextCharacters(
1109                                         aFieldValue,
1110                                         a,
1111                                         aLocale,
1112                                         css::i18n::CharacterIteratorMode::SKIPCELL,
1113                                         1,
1114                                         nDone);
1115                                 }
1116                             }
1117                         }
1118                         nTmpWidth += pPortion->GetSize().Width();
1119                         EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray();
1120                         size_t nPos = nTmpPos - pLine->GetStart();
1121                         rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
1122                         pPortion->SetKind(PortionKind::FIELD);
1123                         // If this is the first token on the line,
1124                         // and nTmpWidth > aPaperSize.Width, => infinite loop!
1125                         if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
1126                         {
1127                             nTmpWidth = nXWidth-1;
1128                             bEOL = true;
1129                             bBrokenLine = true;
1130                         }
1131                         // Compression in Fields????
1132                         // I think this could be a little bit difficult and is not very useful
1133                         bCompressedChars = false;
1134                     }
1135                     break;
1136                     default:    OSL_FAIL( "What feature?" );
1137                 }
1138                 pNextFeature = pNode->GetCharAttribs().FindFeature( pNextFeature->GetStart() + 1  );
1139             }
1140             else
1141             {
1142                 DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion - Extra Space?!" );
1143                 SeekCursor( pNode, nTmpPos+1, aTmpFont );
1144                 aTmpFont.SetPhysFont( GetRefDevice() );
1145                 ImplInitDigitMode(GetRefDevice(), aTmpFont.GetLanguage());
1146 
1147                 if (!bContinueLastPortion)
1148                     pPortion->SetRightToLeftLevel( GetRightToLeft( nPara, nTmpPos+1 ) );
1149 
1150                 if (bContinueLastPortion)
1151                 {
1152                      Size aSize( aTmpFont.QuickGetTextSize( GetRefDevice(),
1153                             pParaPortion->GetNode()->GetString(), nTmpPos, nPortionLen, pBuf.get() ));
1154                      pPortion->GetSize().AdjustWidth(aSize.Width() );
1155                      if (pPortion->GetSize().Height() < aSize.Height())
1156                          pPortion->GetSize().setHeight( aSize.Height() );
1157                 }
1158                 else
1159                 {
1160                     pPortion->GetSize() = aTmpFont.QuickGetTextSize( GetRefDevice(),
1161                             pParaPortion->GetNode()->GetString(), nTmpPos, nPortionLen, pBuf.get() );
1162                 }
1163 
1164                 // #i9050# Do Kerning also behind portions...
1165                 if ( ( aTmpFont.GetFixKerning() > 0 ) && ( ( nTmpPos + nPortionLen ) < pNode->Len() ) )
1166                     pPortion->GetSize().AdjustWidth(aTmpFont.GetFixKerning() );
1167                 if ( IsFixedCellHeight() )
1168                     pPortion->GetSize().setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) );
1169                 // The array is  generally flattened at the beginning
1170                 // => Always simply quick inserts.
1171                 size_t nPos = nTmpPos - pLine->GetStart();
1172                 EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray();
1173                 rArray.insert( rArray.begin() + nPos, pBuf.get(), pBuf.get() + nPortionLen);
1174 
1175                 // And now check for Compression:
1176                 if ( !bContinueLastPortion && nPortionLen && GetAsianCompressionMode() != CharCompressType::NONE )
1177                 {
1178                     long* pDXArray = rArray.data() + nTmpPos - pLine->GetStart();
1179                     bCompressedChars |= ImplCalcAsianCompression(
1180                         pNode, pPortion, nTmpPos, pDXArray, 10000, false);
1181                 }
1182 
1183                 nTmpWidth += pPortion->GetSize().Width();
1184 
1185                 sal_Int32 _nPortionEnd = nTmpPos + nPortionLen;
1186                 if( bScriptSpace && ( _nPortionEnd < pNode->Len() ) && ( nTmpWidth < nXWidth ) && IsScriptChange( EditPaM( pNode, _nPortionEnd ) ) )
1187                 {
1188                     bool bAllow = false;
1189                     sal_uInt16 nScriptTypeLeft = GetI18NScriptType( EditPaM( pNode, _nPortionEnd ) );
1190                     sal_uInt16 nScriptTypeRight = GetI18NScriptType( EditPaM( pNode, _nPortionEnd+1 ) );
1191                     if ( ( nScriptTypeLeft == i18n::ScriptType::ASIAN ) || ( nScriptTypeRight == i18n::ScriptType::ASIAN ) )
1192                         bAllow = true;
1193 
1194                     // No spacing within L2R/R2L nesting
1195                     if ( bAllow )
1196                     {
1197                         long nExtraSpace = pPortion->GetSize().Height()/5;
1198                         nExtraSpace = GetXValue( nExtraSpace );
1199                         pPortion->GetSize().AdjustWidth(nExtraSpace );
1200                         nTmpWidth += nExtraSpace;
1201                     }
1202                 }
1203             }
1204 
1205             if ( aCurrentTab.bValid && ( nTmpPortion != aCurrentTab.nTabPortion ) )
1206             {
1207                 long nWidthAfterTab = 0;
1208                 for ( sal_Int32 n = aCurrentTab.nTabPortion+1; n <= nTmpPortion; n++  )
1209                 {
1210                     const TextPortion& rTP = pParaPortion->GetTextPortions()[n];
1211                     nWidthAfterTab += rTP.GetSize().Width();
1212                 }
1213                 long nW = nWidthAfterTab;   // Length before tab position
1214                 if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right )
1215                 {
1216                 }
1217                 else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center )
1218                 {
1219                     nW = nWidthAfterTab/2;
1220                 }
1221                 else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal )
1222                 {
1223                     OUString aText = GetSelected( EditSelection(  EditPaM( pParaPortion->GetNode(), nTmpPos ),
1224                                                                 EditPaM( pParaPortion->GetNode(), nTmpPos + nPortionLen ) ) );
1225                     sal_Int32 nDecPos = aText.indexOf( aCurrentTab.aTabStop.GetDecimal() );
1226                     if ( nDecPos != -1 )
1227                     {
1228                         nW -= pParaPortion->GetTextPortions()[nTmpPortion].GetSize().Width();
1229                         nW += aTmpFont.QuickGetTextSize( GetRefDevice(), pParaPortion->GetNode()->GetString(), nTmpPos, nDecPos ).Width();
1230                         aCurrentTab.bValid = false;
1231                     }
1232                 }
1233                 else
1234                 {
1235                     OSL_FAIL( "CreateLines: Tab not handled!" );
1236                 }
1237                 long nMaxW = aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nStartX;
1238                 if ( nW >= nMaxW )
1239                 {
1240                     nW = nMaxW;
1241                     aCurrentTab.bValid = false;
1242                 }
1243                 TextPortion& rTabPortion = pParaPortion->GetTextPortions()[aCurrentTab.nTabPortion];
1244                 rTabPortion.GetSize().setWidth( aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nW - nStartX );
1245                 nTmpWidth = aCurrentTab.nStartPosX + rTabPortion.GetSize().Width() + nWidthAfterTab;
1246             }
1247 
1248             nTmpPos = nTmpPos + nPortionLen;
1249             nPortionEnd = nTmpPos;
1250             nTmpPortion++;
1251             if ( aStatus.OneCharPerLine() )
1252                 bEOL = true;
1253         }
1254 
1255         DBG_ASSERT( pPortion, "no portion!?" );
1256 
1257         aCurrentTab.bValid = false;
1258 
1259         assert(pLine);
1260 
1261         // this was possibly a portion too far:
1262         bool bFixedEnd = false;
1263         if ( aStatus.OneCharPerLine() )
1264         {
1265             // State before Portion (apart from nTmpWidth):
1266             nTmpPos -= pPortion ? nPortionLen : 0;
1267             nPortionStart = nTmpPos;
1268             nTmpPortion--;
1269 
1270             bEOL = true;
1271             bEOC = false;
1272 
1273             // And now just one character:
1274             nTmpPos++;
1275             nTmpPortion++;
1276             nPortionEnd = nTmpPortion;
1277             // one Non-Feature-Portion has to be wrapped
1278             if ( pPortion && nPortionLen > 1 )
1279             {
1280                 DBG_ASSERT( pPortion->GetKind() == PortionKind::TEXT, "Len>1, but no TextPortion?" );
1281                 nTmpWidth -= pPortion->GetSize().Width();
1282                 sal_Int32 nP = SplitTextPortion( pParaPortion, nTmpPos, pLine );
1283                 nTmpWidth += pParaPortion->GetTextPortions()[nP].GetSize().Width();
1284             }
1285         }
1286         else if ( nTmpWidth >= nXWidth )
1287         {
1288             nPortionEnd = nTmpPos;
1289             nTmpPos -= pPortion ? nPortionLen : 0;
1290             nPortionStart = nTmpPos;
1291             nTmpPortion--;
1292             bEOL = false;
1293             bEOC = false;
1294             if( pPortion ) switch ( pPortion->GetKind() )
1295             {
1296                 case PortionKind::TEXT:
1297                 {
1298                     nTmpWidth -= pPortion->GetSize().Width();
1299                 }
1300                 break;
1301                 case PortionKind::FIELD:
1302                 case PortionKind::TAB:
1303                 {
1304                     nTmpWidth -= pPortion->GetSize().Width();
1305                     bEOL = true;
1306                     bFixedEnd = true;
1307                 }
1308                 break;
1309                 default:
1310                 {
1311                     //  A feature is not wrapped:
1312                     DBG_ASSERT( ( pPortion->GetKind() == PortionKind::LINEBREAK ), "What Feature ?" );
1313                     bEOL = true;
1314                     bFixedEnd = true;
1315                 }
1316             }
1317         }
1318         else
1319         {
1320             bEOL = true;
1321             bEOC = true;
1322             pLine->SetEnd( nPortionEnd );
1323             DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No TextPortions?" );
1324             pLine->SetEndPortion( pParaPortion->GetTextPortions().Count() - 1 );
1325         }
1326 
1327         if ( aStatus.OneCharPerLine() )
1328         {
1329             pLine->SetEnd( nPortionEnd );
1330             pLine->SetEndPortion( nTmpPortion-1 );
1331         }
1332         else if ( bFixedEnd )
1333         {
1334             pLine->SetEnd( nPortionStart );
1335             pLine->SetEndPortion( nTmpPortion-1 );
1336         }
1337         else if ( bLineBreak || bBrokenLine )
1338         {
1339             pLine->SetEnd( nPortionStart+1 );
1340             pLine->SetEndPortion( nTmpPortion-1 );
1341             bEOC = false; // was set above, maybe change the sequence of the if's?
1342         }
1343         else if ( !bEOL && !bContinueLastPortion )
1344         {
1345             DBG_ASSERT( pPortion && ((nPortionEnd-nPortionStart) == pPortion->GetLen()), "However, another portion?!" );
1346             long nRemainingWidth = nMaxLineWidth - nTmpWidth;
1347             bool bCanHyphenate = ( aTmpFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL );
1348             if ( bCompressedChars && pPortion && ( pPortion->GetLen() > 1 ) && pPortion->GetExtraInfos() && pPortion->GetExtraInfos()->bCompressed )
1349             {
1350                 // I need the manipulated DXArray for determining the break position...
1351                 long* pDXArray = pLine->GetCharPosArray().data() + (nPortionStart - pLine->GetStart());
1352                 ImplCalcAsianCompression(
1353                     pNode, pPortion, nPortionStart, pDXArray, 10000, true);
1354             }
1355             if( pPortion )
1356                 ImpBreakLine( pParaPortion, pLine, pPortion, nPortionStart,
1357                                                 nRemainingWidth, bCanHyphenate && bHyphenatePara );
1358         }
1359 
1360 
1361         // Line finished => adjust
1362 
1363 
1364         // CalcTextSize should be replaced by a continuous registering!
1365         Size aTextSize = pLine->CalcTextSize( *pParaPortion );
1366 
1367         if ( aTextSize.Height() == 0 )
1368         {
1369             SeekCursor( pNode, pLine->GetStart()+1, aTmpFont );
1370             aTmpFont.SetPhysFont( pRefDev );
1371             ImplInitDigitMode(pRefDev, aTmpFont.GetLanguage());
1372 
1373             if ( IsFixedCellHeight() )
1374                 aTextSize.setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) );
1375             else
1376                 aTextSize.setHeight( aTmpFont.GetPhysTxtSize( pRefDev ).Height() );
1377             pLine->SetHeight( static_cast<sal_uInt16>(aTextSize.Height()) );
1378         }
1379 
1380         // The font metrics can not be calculated continuously, if the font is
1381         // set anyway, because a large font only after wrapping suddenly ends
1382         // up in the next line => Font metrics too big.
1383         FormatterFontMetric aFormatterMetrics;
1384         sal_Int32 nTPos = pLine->GetStart();
1385         for ( sal_Int32 nP = pLine->GetStartPortion(); nP <= pLine->GetEndPortion(); nP++ )
1386         {
1387             const TextPortion& rTP = pParaPortion->GetTextPortions()[nP];
1388             // problem with hard font height attribute, when everything but the line break has this attribute
1389             if ( rTP.GetKind() != PortionKind::LINEBREAK )
1390             {
1391                 SeekCursor( pNode, nTPos+1, aTmpFont );
1392                 aTmpFont.SetPhysFont( GetRefDevice() );
1393                 ImplInitDigitMode(GetRefDevice(), aTmpFont.GetLanguage());
1394                 RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont );
1395             }
1396             nTPos = nTPos + rTP.GetLen();
1397         }
1398         sal_uInt16 nLineHeight = aFormatterMetrics.GetHeight();
1399         if ( nLineHeight > pLine->GetHeight() )
1400             pLine->SetHeight( nLineHeight );
1401         pLine->SetMaxAscent( aFormatterMetrics.nMaxAscent );
1402 
1403         bSameLineAgain = false;
1404         if ( GetTextRanger() && ( pLine->GetHeight() > nTextLineHeight ) )
1405         {
1406             // put down with the other size!
1407             bSameLineAgain = true;
1408         }
1409 
1410 
1411         if ( !bSameLineAgain && !aStatus.IsOutliner() )
1412         {
1413             if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min )
1414             {
1415                 sal_uInt16 nMinHeight = GetYValue( rLSItem.GetLineHeight() );
1416                 sal_uInt16 nTxtHeight = pLine->GetHeight();
1417                 if ( nTxtHeight < nMinHeight )
1418                 {
1419                     // The Ascent has to be adjusted for the difference:
1420                     long nDiff = nMinHeight - nTxtHeight;
1421                     pLine->SetMaxAscent( static_cast<sal_uInt16>(pLine->GetMaxAscent() + nDiff) );
1422                     pLine->SetHeight( nMinHeight, nTxtHeight );
1423                 }
1424             }
1425             else if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Fix )
1426             {
1427                 sal_uInt16 nFixHeight = GetYValue( rLSItem.GetLineHeight() );
1428                 sal_uInt16 nTxtHeight = pLine->GetHeight();
1429                 pLine->SetMaxAscent( static_cast<sal_uInt16>(pLine->GetMaxAscent() + ( nFixHeight - nTxtHeight ) ) );
1430                 pLine->SetHeight( nFixHeight, nTxtHeight );
1431             }
1432             else if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop )
1433             {
1434                 // There are documents with PropLineSpace 0, why?
1435                 // (cmc: re above question :-) such documents can be seen by importing a .ppt
1436                 if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() < 100 ) )
1437                 {
1438                     // Adapted code from sw/source/core/text/itrform2.cxx
1439                     sal_uInt16 nPropLineSpace = rLSItem.GetPropLineSpace();
1440                     sal_uInt16 nAscent = pLine->GetMaxAscent();
1441                     sal_uInt16 nNewAscent = pLine->GetTxtHeight() * nPropLineSpace / 100 * 4 / 5; // 80%
1442                     if ( !nAscent || nAscent > nNewAscent )
1443                     {
1444                         pLine->SetMaxAscent( nNewAscent );
1445                     }
1446                     sal_uInt16 nHeight = pLine->GetHeight() * nPropLineSpace / 100;
1447                     pLine->SetHeight( nHeight, pLine->GetTxtHeight() );
1448                 }
1449                 else if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() != 100 ) )
1450                 {
1451                     sal_uInt16 nTxtHeight = pLine->GetHeight();
1452                     sal_Int32 nPropTextHeight = nTxtHeight * rLSItem.GetPropLineSpace() / 100;
1453                     // The Ascent has to be adjusted for the difference:
1454                     long nDiff = pLine->GetHeight() - nPropTextHeight;
1455                     pLine->SetMaxAscent( static_cast<sal_uInt16>( pLine->GetMaxAscent() - nDiff ) );
1456                     pLine->SetHeight( static_cast<sal_uInt16>( nPropTextHeight ), nTxtHeight );
1457                 }
1458             }
1459         }
1460 
1461         if ( ( !IsVertical() && aStatus.AutoPageWidth() ) ||
1462              ( IsVertical() && aStatus.AutoPageHeight() ) )
1463         {
1464             // If the row fits within the current paper width, then this width
1465             // has to be used for the Alignment. If it does not fit or if it
1466             // will change the paper width, it will be formatted again for
1467             // Justification! = LEFT anyway.
1468             long nMaxLineWidthFix = ( !IsVertical() ? aPaperSize.Width() : aPaperSize.Height() )
1469                                         - GetXValue( rLRItem.GetRight() ) - nStartX;
1470             if ( aTextSize.Width() < nMaxLineWidthFix )
1471                 nMaxLineWidth = nMaxLineWidthFix;
1472         }
1473 
1474         if ( bCompressedChars )
1475         {
1476             long nRemainingWidth = nMaxLineWidth - aTextSize.Width();
1477             if ( nRemainingWidth > 0 )
1478             {
1479                 ImplExpandCompressedPortions( pLine, pParaPortion, nRemainingWidth );
1480                 aTextSize = pLine->CalcTextSize( *pParaPortion );
1481             }
1482         }
1483 
1484         if ( pLine->IsHangingPunctuation() )
1485         {
1486             // Width from HangingPunctuation was set to 0 in ImpBreakLine,
1487             // check for rel width now, maybe create compression...
1488             long n = nMaxLineWidth - aTextSize.Width();
1489             TextPortion& rTP = pParaPortion->GetTextPortions()[pLine->GetEndPortion()];
1490             sal_Int32 nPosInArray = pLine->GetEnd()-1-pLine->GetStart();
1491             long nNewValue = ( nPosInArray ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 ) + n;
1492             pLine->GetCharPosArray()[ nPosInArray ] = nNewValue;
1493             rTP.GetSize().AdjustWidth(n );
1494         }
1495 
1496         pLine->SetTextWidth( aTextSize.Width() );
1497         switch ( eJustification )
1498         {
1499             case SvxAdjust::Center:
1500             {
1501                 long n = ( nMaxLineWidth - aTextSize.Width() ) / 2;
1502                 n += nStartX;  // Indentation is kept.
1503                 pLine->SetStartPosX( n );
1504             }
1505             break;
1506             case SvxAdjust::Right:
1507             {
1508                 // For automatically wrapped lines, which has a blank at the end
1509                 // the blank must not be displayed!
1510                 long n = nMaxLineWidth - aTextSize.Width();
1511                 n += nStartX;  // Indentation is kept.
1512                 pLine->SetStartPosX( n );
1513             }
1514             break;
1515             case SvxAdjust::Block:
1516             {
1517                 bool bDistLastLine = (GetJustifyMethod(nPara) == SvxCellJustifyMethod::Distribute);
1518                 long nRemainingSpace = nMaxLineWidth - aTextSize.Width();
1519                 pLine->SetStartPosX( nStartX );
1520                 if ( nRemainingSpace > 0 && (!bEOC || bDistLastLine) )
1521                     ImpAdjustBlocks( pParaPortion, pLine, nRemainingSpace );
1522             }
1523             break;
1524             default:
1525             {
1526                 pLine->SetStartPosX( nStartX ); // FI, LI
1527             }
1528             break;
1529         }
1530 
1531 
1532         // Check whether the line must be re-issued...
1533 
1534         pLine->SetInvalid();
1535 
1536         // If a portion was wrapped there may be far too many positions in
1537         // CharPosArray:
1538         EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray();
1539         size_t nLen = pLine->GetLen();
1540         if (rArray.size() > nLen)
1541             rArray.erase(rArray.begin()+nLen, rArray.end());
1542 
1543         if ( GetTextRanger() )
1544         {
1545             if ( nTextXOffset )
1546                 pLine->SetStartPosX( pLine->GetStartPosX() + nTextXOffset );
1547             if ( nTextExtraYOffset )
1548             {
1549                 pLine->SetHeight( static_cast<sal_uInt16>( pLine->GetHeight() + nTextExtraYOffset ), 0 );
1550                 pLine->SetMaxAscent( static_cast<sal_uInt16>( pLine->GetMaxAscent() + nTextExtraYOffset ) );
1551             }
1552         }
1553 
1554         // for <0 think over !
1555         if ( pParaPortion->IsSimpleInvalid() )
1556         {
1557             // Change through simple Text changes...
1558             // Do not cancel formatting since Portions possibly have to be split
1559             // again! If at some point cancelable, then validate the following
1560             // line! But if applicable, mark as valid, so there is less output...
1561             if ( pLine->GetEnd() < nInvalidStart )
1562             {
1563                 if ( *pLine == aSaveLine )
1564                 {
1565                     pLine->SetValid();
1566                 }
1567             }
1568             else
1569             {
1570                 sal_Int32 nStart = pLine->GetStart();
1571                 sal_Int32 nEnd = pLine->GetEnd();
1572 
1573                 if ( nStart > nInvalidEnd )
1574                 {
1575                     if ( ( ( nStart-nInvalidDiff ) == aSaveLine.GetStart() ) &&
1576                             ( ( nEnd-nInvalidDiff ) == aSaveLine.GetEnd() ) )
1577                     {
1578                         pLine->SetValid();
1579                         if (bQuickFormat)
1580                         {
1581                             bLineBreak = false;
1582                             pParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
1583                             break;
1584                         }
1585                     }
1586                 }
1587                 else if (bQuickFormat && (nEnd > nInvalidEnd))
1588                 {
1589                     // If the invalid line ends so that the next begins on the
1590                     // 'same' passage as before, i.e. not wrapped differently,
1591                     //  then the text width does not have to be determined anew:
1592                     if ( nEnd == ( aSaveLine.GetEnd() + nInvalidDiff ) )
1593                     {
1594                         bLineBreak = false;
1595                         pParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
1596                         break;
1597                     }
1598                 }
1599             }
1600         }
1601 
1602         if ( !bSameLineAgain )
1603         {
1604             nIndex = pLine->GetEnd();   // next line start = last line end
1605                                         // as nEnd points to the last character!
1606 
1607             sal_Int32 nEndPortion = pLine->GetEndPortion();
1608 
1609             // Next line or maybe a new line...
1610             pLine = nullptr;
1611             if ( nLine < pParaPortion->GetLines().Count()-1 )
1612                 pLine = &pParaPortion->GetLines()[++nLine];
1613             if ( pLine && ( nIndex >= pNode->Len() ) )
1614             {
1615                 nDelFromLine = nLine;
1616                 break;
1617             }
1618             if ( !pLine )
1619             {
1620                 if ( nIndex < pNode->Len() )
1621                 {
1622                     pLine = new EditLine;
1623                     pParaPortion->GetLines().Insert(++nLine, pLine);
1624                 }
1625                 else if ( nIndex && bLineBreak && GetTextRanger() )
1626                 {
1627                     // normally CreateAndInsertEmptyLine would be called, but I want to use
1628                     // CreateLines, so I need Polygon code only here...
1629                     TextPortion* pDummyPortion = new TextPortion( 0 );
1630                     pParaPortion->GetTextPortions().Append(pDummyPortion);
1631                     pLine = new EditLine;
1632                     pParaPortion->GetLines().Insert(++nLine, pLine);
1633                     bForceOneRun = true;
1634                     bProcessingEmptyLine = true;
1635                 }
1636             }
1637             if ( pLine )
1638             {
1639                 aSaveLine = *pLine;
1640                 pLine->SetStart( nIndex );
1641                 pLine->SetEnd( nIndex );
1642                 pLine->SetStartPortion( nEndPortion+1 );
1643                 pLine->SetEndPortion( nEndPortion+1 );
1644             }
1645         }
1646     }   // while ( Index < Len )
1647 
1648     if ( nDelFromLine >= 0 )
1649         pParaPortion->GetLines().DeleteFromLine( nDelFromLine );
1650 
1651     DBG_ASSERT( pParaPortion->GetLines().Count(), "No line after CreateLines!" );
1652 
1653     if ( bLineBreak )
1654         CreateAndInsertEmptyLine( pParaPortion );
1655 
1656     pBuf.reset();
1657 
1658     bool bHeightChanged = FinishCreateLines( pParaPortion );
1659 
1660     if ( bMapChanged )
1661         GetRefDevice()->Pop();
1662 
1663     GetRefDevice()->Pop();
1664 
1665     return bHeightChanged;
1666 }
1667 
1668 void ImpEditEngine::CreateAndInsertEmptyLine( ParaPortion* pParaPortion )
1669 {
1670     DBG_ASSERT( !GetTextRanger(), "Don't use CreateAndInsertEmptyLine with a polygon!" );
1671 
1672     EditLine* pTmpLine = new EditLine;
1673     pTmpLine->SetStart( pParaPortion->GetNode()->Len() );
1674     pTmpLine->SetEnd( pParaPortion->GetNode()->Len() );
1675     pParaPortion->GetLines().Append(pTmpLine);
1676 
1677     bool bLineBreak = pParaPortion->GetNode()->Len() > 0;
1678     sal_Int32 nSpaceBefore = 0;
1679     sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pParaPortion->GetNode(), &nSpaceBefore );
1680     const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pParaPortion->GetNode() );
1681     const SvxLineSpacingItem& rLSItem = pParaPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
1682     long nStartX = GetXValue( rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOfst() + nSpaceBefore );
1683 
1684     tools::Rectangle aBulletArea { Point(), Point() };
1685     if ( bLineBreak )
1686     {
1687         nStartX = GetXValue( rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOfst() + nSpaceBeforeAndMinLabelWidth );
1688     }
1689     else
1690     {
1691         aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos( pParaPortion ) );
1692         if ( !aBulletArea.IsEmpty() && aBulletArea.Right() > 0 )
1693             pParaPortion->SetBulletX( static_cast<sal_Int32>(GetXValue( aBulletArea.Right() )) );
1694         else
1695             pParaPortion->SetBulletX( 0 ); // If Bullet set incorrectly.
1696         if ( pParaPortion->GetBulletX() > nStartX )
1697         {
1698             nStartX = GetXValue( rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOfst() + nSpaceBeforeAndMinLabelWidth );
1699             if ( pParaPortion->GetBulletX() > nStartX )
1700                 nStartX = pParaPortion->GetBulletX();
1701         }
1702     }
1703 
1704     SvxFont aTmpFont;
1705     SeekCursor( pParaPortion->GetNode(), bLineBreak ? pParaPortion->GetNode()->Len() : 0, aTmpFont );
1706     aTmpFont.SetPhysFont( pRefDev );
1707 
1708     TextPortion* pDummyPortion = new TextPortion( 0 );
1709     pDummyPortion->GetSize() = aTmpFont.GetPhysTxtSize( pRefDev );
1710     if ( IsFixedCellHeight() )
1711         pDummyPortion->GetSize().setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) );
1712     pParaPortion->GetTextPortions().Append(pDummyPortion);
1713     FormatterFontMetric aFormatterMetrics;
1714     RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont );
1715     pTmpLine->SetMaxAscent( aFormatterMetrics.nMaxAscent );
1716     pTmpLine->SetHeight( static_cast<sal_uInt16>(pDummyPortion->GetSize().Height()) );
1717     sal_uInt16 nLineHeight = aFormatterMetrics.GetHeight();
1718     if ( nLineHeight > pTmpLine->GetHeight() )
1719         pTmpLine->SetHeight( nLineHeight );
1720 
1721     if ( !aStatus.IsOutliner() )
1722     {
1723         sal_Int32 nPara = GetParaPortions().GetPos( pParaPortion );
1724         SvxAdjust eJustification = GetJustification( nPara );
1725         long nMaxLineWidth = !IsVertical() ? aPaperSize.Width() : aPaperSize.Height();
1726         nMaxLineWidth -= GetXValue( rLRItem.GetRight() );
1727         if ( nMaxLineWidth < 0 )
1728             nMaxLineWidth = 1;
1729         if ( eJustification ==  SvxAdjust::Center )
1730             nStartX = nMaxLineWidth / 2;
1731         else if ( eJustification ==  SvxAdjust::Right )
1732             nStartX = nMaxLineWidth;
1733     }
1734 
1735     pTmpLine->SetStartPosX( nStartX );
1736 
1737     if ( !aStatus.IsOutliner() )
1738     {
1739         if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min )
1740         {
1741             sal_uInt16 nMinHeight = rLSItem.GetLineHeight();
1742             sal_uInt16 nTxtHeight = pTmpLine->GetHeight();
1743             if ( nTxtHeight < nMinHeight )
1744             {
1745                 // The Ascent has to be adjusted for the difference:
1746                 long nDiff = nMinHeight - nTxtHeight;
1747                 pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + nDiff) );
1748                 pTmpLine->SetHeight( nMinHeight, nTxtHeight );
1749             }
1750         }
1751         else if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Fix )
1752         {
1753             sal_uInt16 nFixHeight = rLSItem.GetLineHeight();
1754             sal_uInt16 nTxtHeight = pTmpLine->GetHeight();
1755 
1756             pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + ( nFixHeight - nTxtHeight ) ) );
1757             pTmpLine->SetHeight( nFixHeight, nTxtHeight );
1758         }
1759         else if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop )
1760         {
1761             sal_Int32 nPara = GetParaPortions().GetPos( pParaPortion );
1762             if ( nPara || pTmpLine->GetStartPortion() ) // Not the very first line
1763             {
1764                 // There are documents with PropLineSpace 0, why?
1765                 // (cmc: re above question :-) such documents can be seen by importing a .ppt
1766                 if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() != 100 ) )
1767                 {
1768                     sal_uInt16 nTxtHeight = pTmpLine->GetHeight();
1769                     sal_Int32 nH = nTxtHeight;
1770                     nH *= rLSItem.GetPropLineSpace();
1771                     nH /= 100;
1772                     // The Ascent has to be adjusted for the difference:
1773                     long nDiff = pTmpLine->GetHeight() - nH;
1774                     if ( nDiff > pTmpLine->GetMaxAscent() )
1775                         nDiff = pTmpLine->GetMaxAscent();
1776                     pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() - nDiff) );
1777                     pTmpLine->SetHeight( static_cast<sal_uInt16>(nH), nTxtHeight );
1778                 }
1779             }
1780         }
1781     }
1782 
1783     if ( !bLineBreak )
1784     {
1785         long nMinHeight = aBulletArea.GetHeight();
1786         if ( nMinHeight > static_cast<long>(pTmpLine->GetHeight()) )
1787         {
1788             long nDiff = nMinHeight - static_cast<long>(pTmpLine->GetHeight());
1789             // distribute nDiff upwards and downwards
1790             pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + nDiff/2) );
1791             pTmpLine->SetHeight( static_cast<sal_uInt16>(nMinHeight) );
1792         }
1793     }
1794     else
1795     {
1796         // -2: The new one is already inserted.
1797 #ifdef DBG_UTIL
1798         EditLine& rLastLine = pParaPortion->GetLines()[pParaPortion->GetLines().Count()-2];
1799         DBG_ASSERT( rLastLine.GetEnd() == pParaPortion->GetNode()->Len(), "different anyway?" );
1800 #endif
1801         sal_Int32 nPos = pParaPortion->GetTextPortions().Count() - 1 ;
1802         pTmpLine->SetStartPortion( nPos );
1803         pTmpLine->SetEndPortion( nPos );
1804     }
1805 }
1806 
1807 bool ImpEditEngine::FinishCreateLines( ParaPortion* pParaPortion )
1808 {
1809 //  CalcCharPositions( pParaPortion );
1810     pParaPortion->SetValid();
1811     long nOldHeight = pParaPortion->GetHeight();
1812     CalcHeight( pParaPortion );
1813 
1814     DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "FinishCreateLines: No Text-Portion?" );
1815     bool bRet = ( pParaPortion->GetHeight() != nOldHeight );
1816     return bRet;
1817 }
1818 
1819 void ImpEditEngine::ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, TextPortion const * pPortion, sal_Int32 nPortionStart, long nRemainingWidth, bool bCanHyphenate )
1820 {
1821     ContentNode* const pNode = pParaPortion->GetNode();
1822 
1823     sal_Int32 nBreakInLine = nPortionStart - pLine->GetStart();
1824     sal_Int32 nMax = nBreakInLine + pPortion->GetLen();
1825     while ( ( nBreakInLine < nMax ) && ( pLine->GetCharPosArray()[nBreakInLine] < nRemainingWidth ) )
1826         nBreakInLine++;
1827 
1828     sal_Int32 nMaxBreakPos = nBreakInLine + pLine->GetStart();
1829     sal_Int32 nBreakPos = SAL_MAX_INT32;
1830 
1831     bool bCompressBlank = false;
1832     bool bHyphenated = false;
1833     bool bHangingPunctuation = false;
1834     sal_Unicode cAlternateReplChar = 0;
1835     sal_Unicode cAlternateExtraChar = 0;
1836     bool bAltFullLeft = false;
1837     bool bAltFullRight = false;
1838     sal_uInt32 nAltDelChar = 0;
1839 
1840     if ( ( nMaxBreakPos < ( nMax + pLine->GetStart() ) ) && ( pNode->GetChar( nMaxBreakPos ) == ' ' ) )
1841     {
1842         // Break behind the blank, blank will be compressed...
1843         nBreakPos = nMaxBreakPos + 1;
1844         bCompressBlank = true;
1845     }
1846     else
1847     {
1848         sal_Int32 nMinBreakPos = pLine->GetStart();
1849         const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs();
1850         for (size_t nAttr = rAttrs.size(); nAttr; )
1851         {
1852             const EditCharAttrib& rAttr = *rAttrs[--nAttr].get();
1853             if (rAttr.IsFeature() && rAttr.GetEnd() > nMinBreakPos && rAttr.GetEnd() <= nMaxBreakPos)
1854             {
1855                 nMinBreakPos = rAttr.GetEnd();
1856                 break;
1857             }
1858         }
1859         assert(nMinBreakPos <= nMaxBreakPos);
1860 
1861         lang::Locale aLocale = GetLocale( EditPaM( pNode, nMaxBreakPos ) );
1862 
1863         Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
1864         const bool bAllowPunctuationOutsideMargin = static_cast<const SfxBoolItem&>(
1865                 pNode->GetContentAttribs().GetItem( EE_PARA_HANGINGPUNCTUATION )).GetValue();
1866 
1867         if (nMinBreakPos == nMaxBreakPos)
1868         {
1869             nBreakPos = nMinBreakPos;
1870         }
1871         else
1872         {
1873             Reference< XHyphenator > xHyph;
1874             if ( bCanHyphenate )
1875                 xHyph = GetHyphenator();
1876             i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, Sequence< PropertyValue >(), 1 );
1877             i18n::LineBreakUserOptions aUserOptions;
1878 
1879             const i18n::ForbiddenCharacters* pForbidden = GetForbiddenCharsTable()->GetForbiddenCharacters( LanguageTag::convertToLanguageType( aLocale ), true );
1880             aUserOptions.forbiddenBeginCharacters = pForbidden->beginLine;
1881             aUserOptions.forbiddenEndCharacters = pForbidden->endLine;
1882             aUserOptions.applyForbiddenRules = static_cast<const SfxBoolItem&>(pNode->GetContentAttribs().GetItem( EE_PARA_FORBIDDENRULES )).GetValue();
1883             aUserOptions.allowPunctuationOutsideMargin = bAllowPunctuationOutsideMargin;
1884             aUserOptions.allowHyphenateEnglish = false;
1885 
1886             i18n::LineBreakResults aLBR = _xBI->getLineBreak(
1887                 pNode->GetString(), nMaxBreakPos, aLocale, nMinBreakPos, aHyphOptions, aUserOptions );
1888             nBreakPos = aLBR.breakIndex;
1889 
1890             // BUG in I18N - under special condition (break behind field, #87327#) breakIndex is < nMinBreakPos
1891             if ( nBreakPos < nMinBreakPos )
1892             {
1893                 nBreakPos = nMinBreakPos;
1894             }
1895             else if ( ( nBreakPos > nMaxBreakPos ) && !aUserOptions.allowPunctuationOutsideMargin )
1896             {
1897                 OSL_FAIL( "I18N: XBreakIterator::getLineBreak returns position > Max" );
1898                 nBreakPos = nMaxBreakPos;
1899             }
1900 
1901             // nBreakPos can never be outside the portion, even not with hanging punctuation
1902             if ( nBreakPos > nMaxBreakPos )
1903                 nBreakPos = nMaxBreakPos;
1904         }
1905 
1906         // BUG in I18N - the japanese dot is in the next line!
1907         // !!!  Test!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1908         if ( (nBreakPos + ( bAllowPunctuationOutsideMargin ? 0 : 1 ) ) <= nMaxBreakPos )
1909         {
1910             sal_Unicode cFirstInNextLine = ( (nBreakPos+1) < pNode->Len() ) ? pNode->GetChar( nBreakPos ) : 0;
1911             if ( cFirstInNextLine == 12290 )
1912                 nBreakPos++;
1913         }
1914 
1915         bHangingPunctuation = nBreakPos > nMaxBreakPos;
1916         pLine->SetHangingPunctuation( bHangingPunctuation );
1917 
1918         // Whether a separator or not, push the word after the separator through
1919         // hyphenation... NMaxBreakPos is the last character that fits into
1920         // the line, nBreakPos is the beginning of the word.
1921         // There is a problem if the Doc is so narrow that a word is broken
1922         // into more than two lines...
1923         if ( !bHangingPunctuation && bCanHyphenate && GetHyphenator().is() )
1924         {
1925             i18n::Boundary aBoundary = _xBI->getWordBoundary(
1926                 pNode->GetString(), nBreakPos, GetLocale( EditPaM( pNode, nBreakPos ) ), css::i18n::WordType::DICTIONARY_WORD, true);
1927             sal_Int32 nWordStart = nBreakPos;
1928             sal_Int32 nWordEnd = aBoundary.endPos;
1929             DBG_ASSERT( nWordEnd >= nWordStart, "Start >= End?" );
1930 
1931             sal_Int32 nWordLen = nWordEnd - nWordStart;
1932             if ( ( nWordEnd >= nMaxBreakPos ) && ( nWordLen > 3 ) )
1933             {
1934                 // May happen, because getLineBreak may differ from getWordBoudary with DICTIONARY_WORD
1935                 const OUString aWord = pNode->GetString().copy(nWordStart, nWordLen);
1936                 sal_Int32 nMinTrail = nWordEnd-nMaxBreakPos+1; //+1: Before the dickey letter
1937                 Reference< XHyphenatedWord > xHyphWord;
1938                 if (xHyphenator.is())
1939                     xHyphWord = xHyphenator->hyphenate( aWord, aLocale, aWord.getLength() - nMinTrail, Sequence< PropertyValue >() );
1940                 if (xHyphWord.is())
1941                 {
1942                     bool bAlternate = xHyphWord->isAlternativeSpelling();
1943                     sal_Int32 _nWordLen = 1 + xHyphWord->getHyphenPos();
1944 
1945                     if ( ( _nWordLen >= 2 ) && ( (nWordStart+_nWordLen) >= (pLine->GetStart() + 2 ) ) )
1946                     {
1947                         if ( !bAlternate )
1948                         {
1949                             bHyphenated = true;
1950                             nBreakPos = nWordStart + _nWordLen;
1951                         }
1952                         else
1953                         {
1954                             // TODO: handle all alternative hyphenations (see hyphen-1.2.8/tests/unicode.*)
1955                             OUString aAlt( xHyphWord->getHyphenatedWord() );
1956                             OUString aAltLeft(aAlt.copy(0, _nWordLen));
1957                             OUString aAltRight(aAlt.copy(_nWordLen));
1958                             bAltFullLeft = aWord.startsWith(aAltLeft);
1959                             bAltFullRight = aWord.endsWith(aAltRight);
1960                             nAltDelChar = aWord.getLength() - aAlt.getLength() + static_cast<int>(!bAltFullLeft) + static_cast<int>(!bAltFullRight);
1961 
1962                             // NOTE: improved for other cases, see fdo#63711
1963 
1964                             // We expect[ed] the two cases:
1965                             // 1) packen becomes pak-ken
1966                             // 2) Schiffahrt becomes Schiff-fahrt
1967                             // In case 1, a character has to be replaced
1968                             // in case 2 a character is added.
1969                             // The identification is complicated by long
1970                             // compound words because the Hyphenator separates
1971                             // all position of the word. [This is not true for libhyphen.]
1972                             // "Schiffahrtsbrennesseln" -> "Schifffahrtsbrennnesseln"
1973                             // We can thus actually not directly connect the index of the
1974                             // AlternativeWord to aWord. The whole issue will be simplified
1975                             // by a function in the  Hyphenator as soon as AMA builds this in...
1976                             sal_Int32 nAltStart = _nWordLen - 1;
1977                             sal_Int32 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength());
1978                             sal_Int32 nTxtEnd = nTxtStart;
1979                             sal_Int32 nAltEnd = nAltStart;
1980 
1981                             // The regions between the nStart and nEnd is the
1982                             // difference between alternative and original string.
1983                             while( nTxtEnd < aWord.getLength() && nAltEnd < aAlt.getLength() &&
1984                                    aWord[nTxtEnd] != aAlt[nAltEnd] )
1985                             {
1986                                 ++nTxtEnd;
1987                                 ++nAltEnd;
1988                             }
1989 
1990                             // If a character is added, then we notice it now:
1991                             if( nAltEnd > nTxtEnd && nAltStart == nAltEnd &&
1992                                 aWord[ nTxtEnd ] == aAlt[nAltEnd] )
1993                             {
1994                                 ++nAltEnd;
1995                                 ++nTxtStart;
1996                                 ++nTxtEnd;
1997                             }
1998 
1999                             DBG_ASSERT( ( nAltEnd - nAltStart ) == 1, "Alternate: Wrong assumption!" );
2000 
2001                             if ( nTxtEnd > nTxtStart )
2002                                 cAlternateReplChar = aAlt[nAltStart];
2003                             else
2004                                 cAlternateExtraChar = aAlt[nAltStart];
2005 
2006                             bHyphenated = true;
2007                             nBreakPos = nWordStart + nTxtStart;
2008                             if ( cAlternateReplChar || aAlt.getLength() < aWord.getLength() || !bAltFullRight) // also for "oma-tje", "re-eel"
2009                                 nBreakPos++;
2010                         }
2011                     }
2012                 }
2013             }
2014         }
2015 
2016         if ( nBreakPos <= pLine->GetStart() )
2017         {
2018             // No separator in line => Chop!
2019             nBreakPos = nMaxBreakPos;
2020             // I18N nextCharacters !
2021             if ( nBreakPos <= pLine->GetStart() )
2022                 nBreakPos = pLine->GetStart() + 1;  // Otherwise infinite loop!
2023         }
2024     }
2025 
2026     // the dickey portion is the end portion
2027     pLine->SetEnd( nBreakPos );
2028 
2029     sal_Int32 nEndPortion = SplitTextPortion( pParaPortion, nBreakPos, pLine );
2030 
2031     if ( !bCompressBlank && !bHangingPunctuation )
2032     {
2033         // When justification is not SvxAdjust::Left, it's important to compress
2034         // the trailing space even if there is enough room for the space...
2035         // Don't check for SvxAdjust::Left, doesn't matter to compress in this case too...
2036         DBG_ASSERT( nBreakPos > pLine->GetStart(), "ImpBreakLines - BreakPos not expected!" );
2037         if ( pNode->GetChar( nBreakPos-1 ) == ' ' )
2038             bCompressBlank = true;
2039     }
2040 
2041     if ( bCompressBlank || bHangingPunctuation )
2042     {
2043         TextPortion& rTP = pParaPortion->GetTextPortions()[nEndPortion];
2044         DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "BlankRubber: No TextPortion!" );
2045         DBG_ASSERT( nBreakPos > pLine->GetStart(), "SplitTextPortion at the beginning of the line?" );
2046         sal_Int32 nPosInArray = nBreakPos - 1 - pLine->GetStart();
2047         rTP.GetSize().setWidth( ( nPosInArray && ( rTP.GetLen() > 1 ) ) ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 );
2048         pLine->GetCharPosArray()[ nPosInArray ] = rTP.GetSize().Width();
2049     }
2050     else if ( bHyphenated )
2051     {
2052         // A portion for inserting the separator...
2053         TextPortion* pHyphPortion = new TextPortion( 0 );
2054         pHyphPortion->SetKind( PortionKind::HYPHENATOR );
2055         OUString aHyphText(CH_HYPH);
2056         if ( (cAlternateReplChar || cAlternateExtraChar) && bAltFullRight ) // alternation after the break doesn't supported
2057         {
2058             TextPortion& rPrev = pParaPortion->GetTextPortions()[nEndPortion];
2059             DBG_ASSERT( rPrev.GetLen(), "Hyphenate: Prev portion?!" );
2060             rPrev.SetLen( rPrev.GetLen() - nAltDelChar );
2061             pHyphPortion->SetLen( nAltDelChar );
2062             if (cAlternateReplChar && !bAltFullLeft) pHyphPortion->SetExtraValue( cAlternateReplChar );
2063             // Correct width of the portion above:
2064             rPrev.GetSize().setWidth(
2065                 pLine->GetCharPosArray()[ nBreakPos-1 - pLine->GetStart() - nAltDelChar ] );
2066         }
2067 
2068         // Determine the width of the Hyph-Portion:
2069         SvxFont aFont;
2070         SeekCursor( pParaPortion->GetNode(), nBreakPos, aFont );
2071         aFont.SetPhysFont( GetRefDevice() );
2072         pHyphPortion->GetSize().setHeight( GetRefDevice()->GetTextHeight() );
2073         pHyphPortion->GetSize().setWidth( GetRefDevice()->GetTextWidth( aHyphText ) );
2074 
2075         pParaPortion->GetTextPortions().Insert(++nEndPortion, pHyphPortion);
2076     }
2077     pLine->SetEndPortion( nEndPortion );
2078 }
2079 
2080 void ImpEditEngine::ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine, long nRemainingSpace )
2081 {
2082     DBG_ASSERT( nRemainingSpace > 0, "AdjustBlocks: Somewhat too little..." );
2083     DBG_ASSERT( pLine, "AdjustBlocks: Line ?!" );
2084     if ( ( nRemainingSpace < 0 ) || pLine->IsEmpty() )
2085         return ;
2086 
2087     const sal_Int32 nFirstChar = pLine->GetStart();
2088     const sal_Int32 nLastChar = pLine->GetEnd() -1;    // Last points behind
2089     ContentNode* pNode = pParaPortion->GetNode();
2090 
2091     DBG_ASSERT( nLastChar < pNode->Len(), "AdjustBlocks: Out of range!" );
2092 
2093     // Search blanks or Kashidas...
2094     std::vector<sal_Int32> aPositions;
2095     sal_uInt16 nLastScript = i18n::ScriptType::LATIN;
2096     for ( sal_Int32 nChar = nFirstChar; nChar <= nLastChar; nChar++ )
2097     {
2098         EditPaM aPaM( pNode, nChar+1 );
2099         LanguageType eLang = GetLanguage(aPaM);
2100         sal_uInt16 nScript = GetI18NScriptType(aPaM);
2101         if ( MsLangId::getPrimaryLanguage( eLang) == LANGUAGE_ARABIC_PRIMARY_ONLY )
2102             // Arabic script is handled later.
2103             continue;
2104 
2105         if ( pNode->GetChar(nChar) == ' ' )
2106         {
2107             // Normal latin script.
2108             aPositions.push_back( nChar );
2109         }
2110         else if (nChar > nFirstChar)
2111         {
2112             if (nLastScript == i18n::ScriptType::ASIAN)
2113             {
2114                 // Set break position between this and the last character if
2115                 // the last character is asian script.
2116                 aPositions.push_back( nChar-1 );
2117             }
2118             else if (nScript == i18n::ScriptType::ASIAN)
2119             {
2120                 // Set break position between a latin script and asian script.
2121                 aPositions.push_back( nChar-1 );
2122             }
2123         }
2124 
2125         nLastScript = nScript;
2126     }
2127 
2128     // Kashidas ?
2129     ImpFindKashidas( pNode, nFirstChar, nLastChar, aPositions );
2130 
2131     if ( aPositions.empty() )
2132         return;
2133 
2134     // If the last character is a blank, it is rejected!
2135     // The width must be distributed to the blockers in front...
2136     // But not if it is the only one.
2137     if ( ( pNode->GetChar( nLastChar ) == ' ' ) && ( aPositions.size() > 1 ) &&
2138          ( MsLangId::getPrimaryLanguage( GetLanguage( EditPaM( pNode, nLastChar ) ) ) != LANGUAGE_ARABIC_PRIMARY_ONLY ) )
2139     {
2140         aPositions.pop_back();
2141         sal_Int32 nPortionStart, nPortion;
2142         nPortion = pParaPortion->GetTextPortions().FindPortion( nLastChar+1, nPortionStart );
2143         TextPortion& rLastPortion = pParaPortion->GetTextPortions()[ nPortion ];
2144         long nRealWidth = pLine->GetCharPosArray()[nLastChar-nFirstChar];
2145         long nBlankWidth = nRealWidth;
2146         if ( nLastChar > nPortionStart )
2147             nBlankWidth -= pLine->GetCharPosArray()[nLastChar-nFirstChar-1];
2148         // Possibly the blank has already been deducted in ImpBreakLine:
2149         if ( nRealWidth == rLastPortion.GetSize().Width() )
2150         {
2151             // For the last character the portion must stop behind the blank
2152             // => Simplify correction:
2153             DBG_ASSERT( ( nPortionStart + rLastPortion.GetLen() ) == ( nLastChar+1 ), "Blank actually not at the end of the portion!?");
2154             rLastPortion.GetSize().AdjustWidth( -nBlankWidth );
2155             nRemainingSpace += nBlankWidth;
2156         }
2157         pLine->GetCharPosArray()[nLastChar-nFirstChar] -= nBlankWidth;
2158     }
2159 
2160     size_t nGaps = aPositions.size();
2161     const long nMore4Everyone = nRemainingSpace / nGaps;
2162     long nSomeExtraSpace = nRemainingSpace - nMore4Everyone*nGaps;
2163 
2164     DBG_ASSERT( nSomeExtraSpace < static_cast<long>(nGaps), "AdjustBlocks: ExtraSpace too large" );
2165     DBG_ASSERT( nSomeExtraSpace >= 0, "AdjustBlocks: ExtraSpace < 0 " );
2166 
2167     // Correct the positions in the Array and the portion widths:
2168     // Last character won't be considered...
2169     for (auto const& nChar : aPositions)
2170     {
2171         if ( nChar < nLastChar )
2172         {
2173             sal_Int32 nPortionStart, nPortion;
2174             nPortion = pParaPortion->GetTextPortions().FindPortion( nChar, nPortionStart, true );
2175             TextPortion& rLastPortion = pParaPortion->GetTextPortions()[ nPortion ];
2176 
2177             // The width of the portion:
2178             rLastPortion.GetSize().AdjustWidth(nMore4Everyone );
2179             if ( nSomeExtraSpace )
2180                 rLastPortion.GetSize().AdjustWidth( 1 );
2181 
2182             // Correct positions in array
2183             // Even for kashidas just change positions, VCL will then draw the kashida automatically
2184             sal_Int32 nPortionEnd = nPortionStart + rLastPortion.GetLen();
2185             for ( sal_Int32 _n = nChar; _n < nPortionEnd; _n++ )
2186             {
2187                 pLine->GetCharPosArray()[_n-nFirstChar] += nMore4Everyone;
2188                 if ( nSomeExtraSpace )
2189                     pLine->GetCharPosArray()[_n-nFirstChar]++;
2190             }
2191 
2192             if ( nSomeExtraSpace )
2193                 nSomeExtraSpace--;
2194         }
2195     }
2196 
2197     // Now the text width contains the extra width...
2198     pLine->SetTextWidth( pLine->GetTextWidth() + nRemainingSpace );
2199 }
2200 
2201 void ImpEditEngine::ImpFindKashidas( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, std::vector<sal_Int32>& rArray )
2202 {
2203     // the search has to be performed on a per word base
2204 
2205     EditSelection aWordSel( EditPaM( pNode, nStart ) );
2206     aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD );
2207     if ( aWordSel.Min().GetIndex() < nStart )
2208        aWordSel.Min().SetIndex( nStart );
2209 
2210     while ( ( aWordSel.Min().GetNode() == pNode ) && ( aWordSel.Min().GetIndex() < nEnd ) )
2211     {
2212         const sal_Int32 nSavPos = aWordSel.Max().GetIndex();
2213         if ( aWordSel.Max().GetIndex() > nEnd )
2214            aWordSel.Max().SetIndex( nEnd );
2215 
2216         OUString aWord = GetSelected( aWordSel );
2217 
2218         // restore selection for proper iteration at the end of the function
2219         aWordSel.Max().SetIndex( nSavPos );
2220 
2221         sal_Int32 nIdx = 0;
2222         sal_Int32 nKashidaPos = -1;
2223         sal_Unicode cCh;
2224         sal_Unicode cPrevCh = 0;
2225 
2226         while ( nIdx < aWord.getLength() )
2227         {
2228             cCh = aWord[ nIdx ];
2229 
2230             // 1. Priority:
2231             // after user inserted kashida
2232             if ( 0x640 == cCh )
2233             {
2234                 nKashidaPos = aWordSel.Min().GetIndex() + nIdx;
2235                 break;
2236             }
2237 
2238             // 2. Priority:
2239             // after a Seen or Sad
2240             if ( nIdx + 1 < aWord.getLength() &&
2241                  ( 0x633 == cCh || 0x635 == cCh ) )
2242             {
2243                 nKashidaPos = aWordSel.Min().GetIndex() + nIdx;
2244                 break;
2245             }
2246 
2247             // 3. Priority:
2248             // before final form of the Marbuta, Hah, Dal
2249             // 4. Priority:
2250             // before final form of Alef, Lam or Kaf
2251             if ( nIdx && nIdx + 1 == aWord.getLength() &&
2252                  ( 0x629 == cCh || 0x62D == cCh || 0x62F == cCh ||
2253                    0x627 == cCh || 0x644 == cCh || 0x643 == cCh ) )
2254             {
2255                 DBG_ASSERT( 0 != cPrevCh, "No previous character" );
2256 
2257                 // check if character is connectable to previous character,
2258                 if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
2259                 {
2260                     nKashidaPos = aWordSel.Min().GetIndex() + nIdx - 1;
2261                     break;
2262                 }
2263             }
2264 
2265             // 5. Priority:
2266             // before media Bah
2267             if ( nIdx && nIdx + 1 < aWord.getLength() && 0x628 == cCh )
2268             {
2269                 DBG_ASSERT( 0 != cPrevCh, "No previous character" );
2270 
2271                 // check if next character is Reh, Yeh or Alef Maksura
2272                 sal_Unicode cNextCh = aWord[ nIdx + 1 ];
2273 
2274                 if ( 0x631 == cNextCh || 0x64A == cNextCh ||
2275                      0x649 == cNextCh )
2276                 {
2277                     // check if character is connectable to previous character,
2278                     if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
2279                         nKashidaPos = aWordSel.Min().GetIndex() + nIdx - 1;
2280                 }
2281             }
2282 
2283             // 6. Priority:
2284             // other connecting possibilities
2285             if ( nIdx && nIdx + 1 == aWord.getLength() &&
2286                  0x60C <= cCh && 0x6FE >= cCh )
2287             {
2288                 DBG_ASSERT( 0 != cPrevCh, "No previous character" );
2289 
2290                 // check if character is connectable to previous character,
2291                 if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
2292                 {
2293                     // only choose this position if we did not find
2294                     // a better one:
2295                     if ( nKashidaPos<0 )
2296                         nKashidaPos = aWordSel.Min().GetIndex() + nIdx - 1;
2297                     break;
2298                 }
2299             }
2300 
2301             // Do not consider Fathatan, Dammatan, Kasratan, Fatha,
2302             // Damma, Kasra, Shadda and Sukun when checking if
2303             // a character can be connected to previous character.
2304             if ( cCh < 0x64B || cCh > 0x652 )
2305                 cPrevCh = cCh;
2306 
2307             ++nIdx;
2308         } // end of current word
2309 
2310         if ( nKashidaPos>=0 )
2311             rArray.push_back( nKashidaPos );
2312 
2313         aWordSel = WordRight( aWordSel.Max(), css::i18n::WordType::DICTIONARY_WORD );
2314         aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD );
2315     }
2316 }
2317 
2318 sal_Int32 ImpEditEngine::SplitTextPortion( ParaPortion* pPortion, sal_Int32 nPos, EditLine* pCurLine )
2319 {
2320     DBG_ASSERT( pPortion, "SplitTextPortion: Which ?" );
2321 
2322     // The portion at nPos is split, if there is not a transition at nPos anyway
2323     if ( nPos == 0 )
2324         return 0;
2325 
2326     sal_Int32 nSplitPortion;
2327     sal_Int32 nTmpPos = 0;
2328     TextPortion* pTextPortion = nullptr;
2329     sal_Int32 nPortions = pPortion->GetTextPortions().Count();
2330     for ( nSplitPortion = 0; nSplitPortion < nPortions; nSplitPortion++ )
2331     {
2332         TextPortion& rTP = pPortion->GetTextPortions()[nSplitPortion];
2333         nTmpPos = nTmpPos + rTP.GetLen();
2334         if ( nTmpPos >= nPos )
2335         {
2336             if ( nTmpPos == nPos )  // then nothing needs to be split
2337             {
2338                 return nSplitPortion;
2339             }
2340             pTextPortion = &rTP;
2341             break;
2342         }
2343     }
2344 
2345     DBG_ASSERT( pTextPortion, "Position outside the area!" );
2346 
2347     if (!pTextPortion)
2348         return 0;
2349 
2350     DBG_ASSERT( pTextPortion->GetKind() == PortionKind::TEXT, "SplitTextPortion: No TextPortion!" );
2351 
2352     sal_Int32 nOverlapp = nTmpPos - nPos;
2353     pTextPortion->SetLen( pTextPortion->GetLen() - nOverlapp );
2354     TextPortion* pNewPortion = new TextPortion( nOverlapp );
2355     pPortion->GetTextPortions().Insert(nSplitPortion+1, pNewPortion);
2356     // Set sizes
2357     if ( pCurLine )
2358     {
2359         // No new GetTextSize, instead use values from the Array:
2360         DBG_ASSERT( nPos > pCurLine->GetStart(), "SplitTextPortion at the beginning of the line?" );
2361         pTextPortion->GetSize().setWidth( pCurLine->GetCharPosArray()[ nPos-pCurLine->GetStart()-1 ] );
2362 
2363         if ( pTextPortion->GetExtraInfos() && pTextPortion->GetExtraInfos()->bCompressed )
2364         {
2365             // We need the original size from the portion
2366             sal_Int32 nTxtPortionStart = pPortion->GetTextPortions().GetStartPos( nSplitPortion );
2367             SvxFont aTmpFont( pPortion->GetNode()->GetCharAttribs().GetDefFont() );
2368             SeekCursor( pPortion->GetNode(), nTxtPortionStart+1, aTmpFont );
2369             aTmpFont.SetPhysFont( GetRefDevice() );
2370             GetRefDevice()->Push( PushFlags::TEXTLANGUAGE );
2371             ImplInitDigitMode(GetRefDevice(), aTmpFont.GetLanguage());
2372             Size aSz = aTmpFont.QuickGetTextSize( GetRefDevice(), pPortion->GetNode()->GetString(), nTxtPortionStart, pTextPortion->GetLen() );
2373             GetRefDevice()->Pop();
2374             pTextPortion->GetExtraInfos()->nOrgWidth = aSz.Width();
2375         }
2376     }
2377     else
2378         pTextPortion->GetSize().setWidth( -1 );
2379 
2380     return nSplitPortion;
2381 }
2382 
2383 void ImpEditEngine::CreateTextPortions( ParaPortion* pParaPortion, sal_Int32& rStart )
2384 {
2385     sal_Int32 nStartPos = rStart;
2386     ContentNode* pNode = pParaPortion->GetNode();
2387     DBG_ASSERT( pNode->Len(), "CreateTextPortions should not be used for empty paragraphs!" );
2388 
2389     std::set< sal_Int32 > aPositions;
2390     aPositions.insert( 0 );
2391 
2392     sal_uInt16 nAttr = 0;
2393     EditCharAttrib* pAttrib = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
2394     while ( pAttrib )
2395     {
2396         // Insert Start and End into the Array...
2397         // The Insert method does not allow for duplicate values...
2398         aPositions.insert( pAttrib->GetStart() );
2399         aPositions.insert( pAttrib->GetEnd() );
2400         nAttr++;
2401         pAttrib = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
2402     }
2403     aPositions.insert( pNode->Len() );
2404 
2405     if ( pParaPortion->aScriptInfos.empty() )
2406         InitScriptTypes( GetParaPortions().GetPos( pParaPortion ) );
2407 
2408     const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos;
2409     for (const ScriptTypePosInfo& rType : rTypes)
2410         aPositions.insert( rType.nStartPos );
2411 
2412     const WritingDirectionInfos& rWritingDirections = pParaPortion->aWritingDirectionInfos;
2413     for (const WritingDirectionInfo & rWritingDirection : rWritingDirections)
2414         aPositions.insert( rWritingDirection.nStartPos );
2415 
2416     if ( mpIMEInfos && mpIMEInfos->nLen && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetNode() == pNode ) )
2417     {
2418         ExtTextInputAttr nLastAttr = ExtTextInputAttr(0xFFFF);
2419         for( sal_Int32 n = 0; n < mpIMEInfos->nLen; n++ )
2420         {
2421             if ( mpIMEInfos->pAttribs[n] != nLastAttr )
2422             {
2423                 aPositions.insert( mpIMEInfos->aPos.GetIndex() + n );
2424                 nLastAttr = mpIMEInfos->pAttribs[n];
2425             }
2426         }
2427         aPositions.insert( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen );
2428     }
2429 
2430     // From ... Delete:
2431     // Unfortunately, the number of text portions does not have to match
2432     // aPositions.Count(), since there might be line breaks...
2433     sal_Int32 nPortionStart = 0;
2434     sal_Int32 nInvPortion = 0;
2435     sal_Int32 nP;
2436     for ( nP = 0; nP < pParaPortion->GetTextPortions().Count(); nP++ )
2437     {
2438         const TextPortion& rTmpPortion = pParaPortion->GetTextPortions()[nP];
2439         nPortionStart = nPortionStart + rTmpPortion.GetLen();
2440         if ( nPortionStart >= nStartPos )
2441         {
2442             nPortionStart = nPortionStart - rTmpPortion.GetLen();
2443             rStart = nPortionStart;
2444             nInvPortion = nP;
2445             break;
2446         }
2447     }
2448     DBG_ASSERT( nP < pParaPortion->GetTextPortions().Count() || !pParaPortion->GetTextPortions().Count(), "Nothing to delete: CreateTextPortions" );
2449     if ( nInvPortion && ( nPortionStart+pParaPortion->GetTextPortions()[nInvPortion].GetLen() > nStartPos ) )
2450     {
2451         // prefer one in front...
2452         // But only if it was in the middle of the portion of, otherwise it
2453         // might be the only one in the row in front!
2454         nInvPortion--;
2455         nPortionStart = nPortionStart - pParaPortion->GetTextPortions()[nInvPortion].GetLen();
2456     }
2457     pParaPortion->GetTextPortions().DeleteFromPortion( nInvPortion );
2458 
2459     // A portion may also have been formed by a line break:
2460     aPositions.insert( nPortionStart );
2461 
2462     std::set< sal_Int32 >::iterator nInvPos = aPositions.find(  nPortionStart );
2463     DBG_ASSERT( (nInvPos != aPositions.end()), "InvPos ?!" );
2464 
2465     std::set< sal_Int32 >::iterator i = nInvPos;
2466     ++i;
2467     while ( i != aPositions.end() )
2468     {
2469         TextPortion* pNew = new TextPortion( (*i++) - *nInvPos++ );
2470         pParaPortion->GetTextPortions().Append(pNew);
2471     }
2472 
2473     DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No Portions?!" );
2474 #if OSL_DEBUG_LEVEL > 0
2475     OSL_ENSURE( ParaPortion::DbgCheckTextPortions(*pParaPortion), "Portion is broken?" );
2476 #endif
2477 }
2478 
2479 void ImpEditEngine::RecalcTextPortion( ParaPortion* pParaPortion, sal_Int32 nStartPos, sal_Int32 nNewChars )
2480 {
2481     DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No Portions!" );
2482     DBG_ASSERT( nNewChars, "RecalcTextPortion with Diff == 0" );
2483 
2484     ContentNode* const pNode = pParaPortion->GetNode();
2485     if ( nNewChars > 0 )
2486     {
2487         // If an Attribute begins/ends at nStartPos, then a new portion starts
2488         // otherwise the portion is extended at nStartPos.
2489         if ( pNode->GetCharAttribs().HasBoundingAttrib( nStartPos ) || IsScriptChange( EditPaM( pNode, nStartPos ) ) )
2490         {
2491             sal_Int32 nNewPortionPos = 0;
2492             if ( nStartPos )
2493                 nNewPortionPos = SplitTextPortion( pParaPortion, nStartPos ) + 1;
2494 
2495             // A blank portion may be here, if the paragraph was empty,
2496             // or if a line was created by a hard line break.
2497             if ( ( nNewPortionPos < pParaPortion->GetTextPortions().Count() ) &&
2498                     !pParaPortion->GetTextPortions()[nNewPortionPos].GetLen() )
2499             {
2500                 TextPortion& rTP = pParaPortion->GetTextPortions()[nNewPortionPos];
2501                 DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "the empty portion was no TextPortion!" );
2502                 rTP.SetLen( rTP.GetLen() + nNewChars );
2503             }
2504             else
2505             {
2506                 TextPortion* pNewPortion = new TextPortion( nNewChars );
2507                 pParaPortion->GetTextPortions().Insert(nNewPortionPos, pNewPortion);
2508             }
2509         }
2510         else
2511         {
2512             sal_Int32 nPortionStart;
2513             const sal_Int32 nTP = pParaPortion->GetTextPortions().
2514                 FindPortion( nStartPos, nPortionStart );
2515             TextPortion& rTP = pParaPortion->GetTextPortions()[ nTP ];
2516             rTP.SetLen( rTP.GetLen() + nNewChars );
2517             rTP.GetSize().setWidth( -1 );
2518         }
2519     }
2520     else
2521     {
2522         // Shrink or remove portion if necessary.
2523         // Before calling this method it must be ensured that no portions were
2524         // in the deleted area!
2525 
2526         // There must be no portions extending into the area or portions starting in
2527         // the area, so it must be:
2528         //    nStartPos <= nPos <= nStartPos - nNewChars(neg.)
2529         sal_Int32 nPortion = 0;
2530         sal_Int32 nPos = 0;
2531         sal_Int32 nEnd = nStartPos-nNewChars;
2532         sal_Int32 nPortions = pParaPortion->GetTextPortions().Count();
2533         TextPortion* pTP = nullptr;
2534         for ( nPortion = 0; nPortion < nPortions; nPortion++ )
2535         {
2536             pTP = &pParaPortion->GetTextPortions()[ nPortion ];
2537             if ( ( nPos+pTP->GetLen() ) > nStartPos )
2538             {
2539                 DBG_ASSERT( nPos <= nStartPos, "Wrong Start!" );
2540                 DBG_ASSERT( nPos+pTP->GetLen() >= nEnd, "Wrong End!" );
2541                 break;
2542             }
2543             nPos = nPos + pTP->GetLen();
2544         }
2545         DBG_ASSERT( pTP, "RecalcTextPortion: Portion not found" );
2546         if ( ( nPos == nStartPos ) && ( (nPos+pTP->GetLen()) == nEnd ) )
2547         {
2548             // Remove portion;
2549             PortionKind nType = pTP->GetKind();
2550             pParaPortion->GetTextPortions().Remove( nPortion );
2551             if ( nType == PortionKind::LINEBREAK )
2552             {
2553                 TextPortion& rNext = pParaPortion->GetTextPortions()[ nPortion ];
2554                 if ( !rNext.GetLen() )
2555                 {
2556                     // Remove dummy portion
2557                     pParaPortion->GetTextPortions().Remove( nPortion );
2558                 }
2559             }
2560         }
2561         else
2562         {
2563             DBG_ASSERT( pTP->GetLen() > (-nNewChars), "Portion too small to shrink! ");
2564             pTP->SetLen( pTP->GetLen() + nNewChars );
2565         }
2566 
2567         sal_Int32 nPortionCount = pParaPortion->GetTextPortions().Count();
2568         assert( nPortionCount );
2569         if (nPortionCount)
2570         {
2571             // No HYPHENATOR portion is allowed to get stuck right at the end...
2572             sal_Int32 nLastPortion = nPortionCount - 1;
2573             pTP = &pParaPortion->GetTextPortions()[nLastPortion];
2574             if ( pTP->GetKind() == PortionKind::HYPHENATOR )
2575             {
2576                 // Discard portion; if possible, correct the ones before,
2577                 // if the Hyphenator portion has swallowed one character...
2578                 if ( nLastPortion && pTP->GetLen() )
2579                 {
2580                     TextPortion& rPrev = pParaPortion->GetTextPortions()[nLastPortion - 1];
2581                     DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" );
2582                     rPrev.SetLen( rPrev.GetLen() + pTP->GetLen() );
2583                     rPrev.GetSize().setWidth( -1 );
2584                 }
2585                 pParaPortion->GetTextPortions().Remove( nLastPortion );
2586             }
2587         }
2588     }
2589 #if OSL_DEBUG_LEVEL > 0
2590     OSL_ENSURE( ParaPortion::DbgCheckTextPortions(*pParaPortion), "Portions are broken?" );
2591 #endif
2592 }
2593 
2594 void ImpEditEngine::SetTextRanger( std::unique_ptr<TextRanger> pRanger )
2595 {
2596     pTextRanger = std::move(pRanger);
2597 
2598     for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ )
2599     {
2600         ParaPortion* pParaPortion = GetParaPortions()[nPara];
2601         pParaPortion->MarkSelectionInvalid( 0 );
2602         pParaPortion->GetLines().Reset();
2603     }
2604 
2605     FormatFullDoc();
2606     UpdateViews( GetActiveView() );
2607     if ( GetUpdateMode() && GetActiveView() )
2608         pActiveView->ShowCursor(false, false);
2609 }
2610 
2611 void ImpEditEngine::SetVertical( bool bVertical, bool bTopToBottom)
2612 {
2613     if ( IsVertical() != bVertical || IsTopToBottom() != (bVertical && bTopToBottom))
2614     {
2615         GetEditDoc().SetVertical( bVertical, bTopToBottom);
2616         bool bUseCharAttribs = bool(aStatus.GetControlWord() & EEControlBits::USECHARATTRIBS);
2617         GetEditDoc().CreateDefFont( bUseCharAttribs );
2618         if ( IsFormatted() )
2619         {
2620             FormatFullDoc();
2621             UpdateViews( GetActiveView() );
2622         }
2623     }
2624 }
2625 
2626 void ImpEditEngine::SetFixedCellHeight( bool bUseFixedCellHeight )
2627 {
2628     if ( IsFixedCellHeight() != bUseFixedCellHeight )
2629     {
2630         GetEditDoc().SetFixedCellHeight( bUseFixedCellHeight );
2631         if ( IsFormatted() )
2632         {
2633             FormatFullDoc();
2634             UpdateViews( GetActiveView() );
2635         }
2636     }
2637 }
2638 
2639 void ImpEditEngine::SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFont, OutputDevice* pOut )
2640 {
2641     // It was planned, SeekCursor( nStartPos, nEndPos,... ), so that it would
2642     // only be searched anew at the StartPosition.
2643     // Problem: There would be two lists to consider/handle:
2644     // OrderedByStart,OrderedByEnd.
2645 
2646     if ( nPos > pNode->Len() )
2647         nPos = pNode->Len();
2648 
2649     rFont = pNode->GetCharAttribs().GetDefFont();
2650 
2651     /*
2652      * Set attributes for script types Asian and Complex
2653     */
2654     short nScriptTypeI18N = GetI18NScriptType( EditPaM( pNode, nPos ) );
2655     SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N);
2656     if ( ( nScriptTypeI18N == i18n::ScriptType::ASIAN ) || ( nScriptTypeI18N == i18n::ScriptType::COMPLEX ) )
2657     {
2658         const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_FONTINFO, nScriptType ) ));
2659         rFont.SetFamilyName( rFontItem.GetFamilyName() );
2660         rFont.SetFamily( rFontItem.GetFamily() );
2661         rFont.SetPitch( rFontItem.GetPitch() );
2662         rFont.SetCharSet( rFontItem.GetCharSet() );
2663         Size aSz( rFont.GetFontSize() );
2664         aSz.setHeight( static_cast<const SvxFontHeightItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType ) ) ).GetHeight() );
2665         rFont.SetFontSize( aSz );
2666         rFont.SetWeight( static_cast<const SvxWeightItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_WEIGHT, nScriptType ))).GetWeight() );
2667         rFont.SetItalic( static_cast<const SvxPostureItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_ITALIC, nScriptType ))).GetPosture() );
2668         rFont.SetLanguage( static_cast<const SvxLanguageItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ))).GetLanguage() );
2669     }
2670 
2671     sal_uInt16 nRelWidth = pNode->GetContentAttribs().GetItem( EE_CHAR_FONTWIDTH).GetValue();
2672 
2673     /*
2674      * Set output device's line and overline colors
2675     */
2676     if ( pOut )
2677     {
2678         const SvxUnderlineItem& rTextLineColor = pNode->GetContentAttribs().GetItem( EE_CHAR_UNDERLINE );
2679         if ( rTextLineColor.GetColor() != COL_TRANSPARENT )
2680             pOut->SetTextLineColor( rTextLineColor.GetColor() );
2681         else
2682             pOut->SetTextLineColor();
2683 
2684         const SvxOverlineItem& rOverlineColor = pNode->GetContentAttribs().GetItem( EE_CHAR_OVERLINE );
2685         if ( rOverlineColor.GetColor() != COL_TRANSPARENT )
2686             pOut->SetOverlineColor( rOverlineColor.GetColor() );
2687         else
2688             pOut->SetOverlineColor();
2689     }
2690 
2691     const SvxLanguageItem* pCJKLanguageItem = nullptr;
2692 
2693     /*
2694      * Scan through char attributes of pNode
2695     */
2696     if ( aStatus.UseCharAttribs() )
2697     {
2698         CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs();
2699         size_t nAttr = 0;
2700         EditCharAttrib* pAttrib = GetAttrib(rAttribs, nAttr);
2701         while ( pAttrib && ( pAttrib->GetStart() <= nPos ) )
2702         {
2703             // when seeking, ignore attributes which start there! Empty attributes
2704             // are considered (used) as these are just set. But do not use empty
2705             // attributes: When just set and empty => no effect on font
2706             // In a blank paragraph, set characters take effect immediately.
2707             if ( ( pAttrib->Which() != 0 ) &&
2708                  ( ( ( pAttrib->GetStart() < nPos ) && ( pAttrib->GetEnd() >= nPos ) )
2709                      || ( !pNode->Len() ) ) )
2710             {
2711                 DBG_ASSERT( ( pAttrib->Which() >= EE_CHAR_START ) && ( pAttrib->Which() <= EE_FEATURE_END ), "Invalid Attribute in Seek() " );
2712                 if ( IsScriptItemValid( pAttrib->Which(), nScriptTypeI18N ) )
2713                 {
2714                     pAttrib->SetFont( rFont, pOut );
2715                     // #i1550# hard color attrib should win over text color from field
2716                     if ( pAttrib->Which() == EE_FEATURE_FIELD )
2717                     {
2718                         EditCharAttrib* pColorAttr = pNode->GetCharAttribs().FindAttrib( EE_CHAR_COLOR, nPos );
2719                         if ( pColorAttr )
2720                             pColorAttr->SetFont( rFont, pOut );
2721                     }
2722                 }
2723                 if ( pAttrib->Which() == EE_CHAR_FONTWIDTH )
2724                     nRelWidth = static_cast<const SvxCharScaleWidthItem*>(pAttrib->GetItem())->GetValue();
2725                 if ( pAttrib->Which() == EE_CHAR_LANGUAGE_CJK )
2726                     pCJKLanguageItem = static_cast<const SvxLanguageItem*>( pAttrib->GetItem() );
2727             }
2728             pAttrib = GetAttrib( rAttribs, ++nAttr );
2729         }
2730     }
2731 
2732     if ( !pCJKLanguageItem )
2733         pCJKLanguageItem = &pNode->GetContentAttribs().GetItem( EE_CHAR_LANGUAGE_CJK );
2734 
2735     rFont.SetCJKContextLanguage( pCJKLanguageItem->GetLanguage() );
2736 
2737     if ( (rFont.GetKerning() != FontKerning::NONE) && IsKernAsianPunctuation() && ( nScriptTypeI18N == i18n::ScriptType::ASIAN ) )
2738         rFont.SetKerning( rFont.GetKerning() | FontKerning::Asian );
2739 
2740     if ( aStatus.DoNotUseColors() )
2741     {
2742         rFont.SetColor( /* rColorItem.GetValue() */ COL_BLACK );
2743     }
2744 
2745     if ( aStatus.DoStretch() || ( nRelWidth != 100 ) )
2746     {
2747         // For the current Output device, because otherwise if RefDev=Printer its looks
2748         // ugly on the screen!
2749         OutputDevice* pDev = pOut ? pOut : GetRefDevice();
2750         rFont.SetPhysFont( pDev );
2751         FontMetric aMetric( pDev->GetFontMetric() );
2752 
2753         // Set the font as we want it to look like & reset the Propr attribute
2754         // so that it is not counted twice.
2755         Size aRealSz( aMetric.GetFontSize() );
2756         rFont.SetPropr( 100 );
2757 
2758         if ( aStatus.DoStretch() )
2759         {
2760             if ( nStretchY != 100 )
2761             {
2762                 aRealSz.setHeight( aRealSz.Height() * nStretchY );
2763                 aRealSz.setHeight( aRealSz.Height() / 100 );
2764             }
2765             if ( nStretchX != 100 )
2766             {
2767                 if ( nStretchX == nStretchY &&
2768                      nRelWidth == 100 )
2769                 {
2770                     aRealSz.setWidth( 0 );
2771                 }
2772                 else
2773                 {
2774                     aRealSz.setWidth( aRealSz.Width() * nStretchX );
2775                     aRealSz.setWidth( aRealSz.Width() / 100 );
2776 
2777                     // Also the Kerning: (long due to handle Interim results)
2778                     long nKerning = rFont.GetFixKerning();
2779 /*
2780   The consideration was: If negative kerning, but StretchX = 200
2781   => Do not double the kerning, thus pull the letters closer together
2782   ---------------------------
2783   Kern  StretchX    =>Kern
2784   ---------------------------
2785   >0        <100        < (Proportional)
2786   <0        <100        < (Proportional)
2787   >0        >100        > (Proportional)
2788   <0        >100        < (The amount, thus disproportional)
2789 */
2790                     if ( ( nKerning < 0  ) && ( nStretchX > 100 ) )
2791                     {
2792                         // disproportional
2793                         nKerning *= 100;
2794                         nKerning /= nStretchX;
2795                     }
2796                     else if ( nKerning )
2797                     {
2798                         // Proportional
2799                         nKerning *= nStretchX;
2800                         nKerning /= 100;
2801                     }
2802                     rFont.SetFixKerning( static_cast<short>(nKerning) );
2803                 }
2804             }
2805         }
2806         if ( nRelWidth != 100 )
2807         {
2808             aRealSz.setWidth( aRealSz.Width() * nRelWidth );
2809             aRealSz.setWidth( aRealSz.Width() / 100 );
2810         }
2811         rFont.SetFontSize( aRealSz );
2812         // Font is not restored...
2813     }
2814 
2815     if ( ( ( rFont.GetColor() == COL_AUTO ) || ( IsForceAutoColor() ) ) && pOut )
2816     {
2817         // #i75566# Do not use AutoColor when printing OR Pdf export
2818         const bool bPrinting(OUTDEV_PRINTER == pOut->GetOutDevType());
2819         const bool bPDFExporting(OUTDEV_PDF == pOut->GetOutDevType());
2820 
2821         if ( IsAutoColorEnabled() && !bPrinting && !bPDFExporting)
2822         {
2823             // Never use WindowTextColor on the printer
2824             rFont.SetColor( GetAutoColor() );
2825         }
2826         else
2827         {
2828             if ( ( GetBackgroundColor() != COL_AUTO ) && GetBackgroundColor().IsDark() )
2829                 rFont.SetColor( COL_WHITE );
2830             else
2831                 rFont.SetColor( COL_BLACK );
2832         }
2833     }
2834 
2835     if ( mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetNode() == pNode ) &&
2836         ( nPos > mpIMEInfos->aPos.GetIndex() ) && ( nPos <= ( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen ) ) )
2837     {
2838         ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[ nPos - mpIMEInfos->aPos.GetIndex() - 1 ];
2839         if ( nAttr & ExtTextInputAttr::Underline )
2840             rFont.SetUnderline( LINESTYLE_SINGLE );
2841         else if ( nAttr & ExtTextInputAttr::BoldUnderline )
2842             rFont.SetUnderline( LINESTYLE_BOLD );
2843         else if ( nAttr & ExtTextInputAttr::DottedUnderline )
2844             rFont.SetUnderline( LINESTYLE_DOTTED );
2845         else if ( nAttr & ExtTextInputAttr::DashDotUnderline )
2846             rFont.SetUnderline( LINESTYLE_DOTTED );
2847         else if ( nAttr & ExtTextInputAttr::RedText )
2848             rFont.SetColor( COL_RED );
2849         else if ( nAttr & ExtTextInputAttr::HalfToneText )
2850             rFont.SetColor( COL_LIGHTGRAY );
2851         if ( nAttr & ExtTextInputAttr::Highlight )
2852         {
2853             const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
2854             rFont.SetColor( rStyleSettings.GetHighlightTextColor() );
2855             rFont.SetFillColor( rStyleSettings.GetHighlightColor() );
2856             rFont.SetTransparent( false );
2857         }
2858         else if ( nAttr & ExtTextInputAttr::GrayWaveline )
2859         {
2860             rFont.SetUnderline( LINESTYLE_WAVE );
2861             if( pOut )
2862                 pOut->SetTextLineColor( COL_LIGHTGRAY );
2863         }
2864     }
2865 }
2866 
2867 void ImpEditEngine::RecalcFormatterFontMetrics( FormatterFontMetric& rCurMetrics, SvxFont& rFont )
2868 {
2869     // for line height at high / low first without Propr!
2870     sal_uInt16 nPropr = rFont.GetPropr();
2871     DBG_ASSERT( ( nPropr == 100 ) || rFont.GetEscapement(), "Propr without Escape?!" );
2872     if ( nPropr != 100 )
2873     {
2874         rFont.SetPropr( 100 );
2875         rFont.SetPhysFont( pRefDev );
2876     }
2877     sal_uInt16 nAscent, nDescent;
2878 
2879     FontMetric aMetric( pRefDev->GetFontMetric() );
2880     nAscent = static_cast<sal_uInt16>(aMetric.GetAscent());
2881     if ( IsAddExtLeading() )
2882         nAscent = sal::static_int_cast< sal_uInt16 >(
2883             nAscent + aMetric.GetExternalLeading() );
2884     nDescent = static_cast<sal_uInt16>(aMetric.GetDescent());
2885 
2886     if ( IsFixedCellHeight() )
2887     {
2888         nAscent = sal::static_int_cast< sal_uInt16 >( rFont.GetFontHeight() );
2889         nDescent= sal::static_int_cast< sal_uInt16 >( ImplCalculateFontIndependentLineSpacing( rFont.GetFontHeight() ) - nAscent );
2890     }
2891     else
2892     {
2893         sal_uInt16 nIntLeading = ( aMetric.GetInternalLeading() > 0 ) ? static_cast<sal_uInt16>(aMetric.GetInternalLeading()) : 0;
2894         // Fonts without leading cause problems
2895         if ( ( nIntLeading == 0 ) && ( pRefDev->GetOutDevType() == OUTDEV_PRINTER ) )
2896         {
2897             // Lets see what Leading one gets on the screen
2898             VclPtr<VirtualDevice> pVDev = GetVirtualDevice( pRefDev->GetMapMode(), pRefDev->GetDrawMode() );
2899             rFont.SetPhysFont( pVDev );
2900             aMetric = pVDev->GetFontMetric();
2901 
2902             // This is so that the Leading does not count itself out again,
2903             // if the whole line has the font, nTmpLeading.
2904             nAscent = static_cast<sal_uInt16>(aMetric.GetAscent());
2905             nDescent = static_cast<sal_uInt16>(aMetric.GetDescent());
2906         }
2907     }
2908     if ( nAscent > rCurMetrics.nMaxAscent )
2909         rCurMetrics.nMaxAscent = nAscent;
2910     if ( nDescent > rCurMetrics.nMaxDescent )
2911         rCurMetrics.nMaxDescent= nDescent;
2912     // Special treatment of high/low:
2913     if ( rFont.GetEscapement() )
2914     {
2915         // Now in consideration of Escape/Propr
2916         // possibly enlarge Ascent or Descent
2917         short nDiff = static_cast<short>(rFont.GetFontSize().Height()*rFont.GetEscapement()/100L);
2918         if ( rFont.GetEscapement() > 0 )
2919         {
2920             nAscent = static_cast<sal_uInt16>(static_cast<long>(nAscent)*nPropr/100 + nDiff);
2921             if ( nAscent > rCurMetrics.nMaxAscent )
2922                 rCurMetrics.nMaxAscent = nAscent;
2923         }
2924         else    // has to be < 0
2925         {
2926             nDescent = static_cast<sal_uInt16>(static_cast<long>(nDescent)*nPropr/100 - nDiff);
2927             if ( nDescent > rCurMetrics.nMaxDescent )
2928                 rCurMetrics.nMaxDescent= nDescent;
2929         }
2930     }
2931 }
2932 
2933 void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Point aStartPos, bool bStripOnly, short nOrientation )
2934 {
2935     if ( !GetUpdateMode() && !bStripOnly )
2936         return;
2937 
2938     if ( !IsFormatted() )
2939         FormatDoc();
2940 
2941     long nFirstVisXPos = - pOutDev->GetMapMode().GetOrigin().X();
2942     long nFirstVisYPos = - pOutDev->GetMapMode().GetOrigin().Y();
2943 
2944     const EditLine* pLine = nullptr;
2945     Point aTmpPos;
2946     Point aRedLineTmpPos;
2947     DBG_ASSERT( GetParaPortions().Count(), "No ParaPortion?!" );
2948     SvxFont aTmpFont( GetParaPortions()[0]->GetNode()->GetCharAttribs().GetDefFont() );
2949     vcl::Font aOldFont( pOutDev->GetFont() );
2950     vcl::PDFExtOutDevData* pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData* >( pOutDev->GetExtOutDevData() );
2951 
2952     // In the case of rotated text is aStartPos considered TopLeft because
2953     // other information is missing, and since the whole object is shown anyway
2954     // un-scrolled.
2955     // The rectangle is infinite.
2956     Point aOrigin( aStartPos );
2957     double nCos = 0.0, nSin = 0.0;
2958     if ( nOrientation )
2959     {
2960         double nRealOrientation = nOrientation*F_PI1800;
2961         nCos = cos( nRealOrientation );
2962         nSin = sin( nRealOrientation );
2963     }
2964 
2965     // #110496# Added some more optional metafile comments. This
2966     // change: factored out some duplicated code.
2967     GDIMetaFile* pMtf = pOutDev->GetConnectMetaFile();
2968     const bool bMetafileValid( pMtf != nullptr );
2969 
2970     long nVertLineSpacing = CalcVertLineSpacing(aStartPos);
2971 
2972 
2973     // Over all the paragraphs...
2974 
2975     for ( sal_Int32 n = 0; n < GetParaPortions().Count(); n++ )
2976     {
2977         const ParaPortion* pPortion = GetParaPortions()[n];
2978         DBG_ASSERT( pPortion, "NULL-Pointer in TokenList in Paint" );
2979         // if when typing idle formatting,  asynchronous Paint.
2980         // Invisible Portions may be invalid.
2981         if ( pPortion->IsVisible() && pPortion->IsInvalid() )
2982             return;
2983 
2984         if ( pPDFExtOutDevData )
2985             pPDFExtOutDevData->BeginStructureElement( vcl::PDFWriter::Paragraph );
2986 
2987         long nParaHeight = pPortion->GetHeight();
2988         sal_Int32 nIndex = 0;
2989         if ( pPortion->IsVisible() && (
2990                 ( !IsVertical() && ( ( aStartPos.Y() + nParaHeight ) > aClipRect.Top() ) ) ||
2991                 ( IsVertical() && IsTopToBottom() && ( ( aStartPos.X() - nParaHeight ) < aClipRect.Right() ) ) ||
2992                 ( IsVertical() && !IsTopToBottom() && ( ( aStartPos.X() + nParaHeight ) > aClipRect.Left() ) ) ) )
2993 
2994         {
2995 
2996             // Over the lines of the paragraph...
2997 
2998             sal_Int32 nLines = pPortion->GetLines().Count();
2999             sal_Int32 nLastLine = nLines-1;
3000 
3001             bool bEndOfParagraphWritten(false);
3002 
3003             if ( !IsVertical() )
3004                 aStartPos.AdjustY(pPortion->GetFirstLineOffset() );
3005             else
3006             {
3007                 if( IsTopToBottom() )
3008                     aStartPos.AdjustX( -(pPortion->GetFirstLineOffset()) );
3009                 else
3010                     aStartPos.AdjustX(pPortion->GetFirstLineOffset() );
3011             }
3012 
3013             Point aParaStart( aStartPos );
3014 
3015             const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
3016             sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix )
3017                                 ? GetYValue( rLSItem.GetInterLineSpace() ) : 0;
3018             bool bPaintBullet (false);
3019 
3020             for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ )
3021             {
3022                 pLine = &pPortion->GetLines()[nLine];
3023                 nIndex = pLine->GetStart();
3024                 DBG_ASSERT( pLine, "NULL-Pointer in the line iterator in UpdateViews" );
3025                 aTmpPos = aStartPos;
3026                 if ( !IsVertical() )
3027                 {
3028                     aTmpPos.AdjustX(pLine->GetStartPosX() );
3029                     aTmpPos.AdjustY(pLine->GetMaxAscent() );
3030                     aStartPos.AdjustY(pLine->GetHeight() );
3031                     if (nLine != nLastLine)
3032                         aStartPos.AdjustY(nVertLineSpacing );
3033                 }
3034                 else
3035                 {
3036                     if ( IsTopToBottom() )
3037                     {
3038                         aTmpPos.AdjustY(pLine->GetStartPosX() );
3039                         aTmpPos.AdjustX( -(pLine->GetMaxAscent()) );
3040                         aStartPos.AdjustX( -(pLine->GetHeight()) );
3041                         if (nLine != nLastLine)
3042                             aStartPos.AdjustX( -nVertLineSpacing );
3043                     }
3044                     else
3045                     {
3046                         aTmpPos.AdjustY( -(pLine->GetStartPosX()) );
3047                         aTmpPos.AdjustX(pLine->GetMaxAscent() );
3048                         aStartPos.AdjustX(pLine->GetHeight() );
3049                         if (nLine != nLastLine)
3050                             aStartPos.AdjustX(nVertLineSpacing );
3051                     }
3052                 }
3053 
3054                 if ( ( !IsVertical() && ( aStartPos.Y() > aClipRect.Top() ) )
3055                     || ( IsVertical() && IsTopToBottom() && aStartPos.X() < aClipRect.Right() )
3056                     || ( IsVertical() && !IsTopToBottom() && aStartPos.X() > aClipRect.Left() ) )
3057                 {
3058                     bPaintBullet = false;
3059 
3060                     // Why not just also call when stripping portions? This will give the correct values
3061                     // and needs no position corrections in OutlinerEditEng::DrawingText which tries to call
3062                     // PaintBullet correctly; exactly what GetEditEnginePtr()->PaintingFirstLine
3063                     // does, too. No change for not-layouting (painting).
3064                     if(0 == nLine) // && !bStripOnly)
3065                     {
3066                         GetEditEnginePtr()->PaintingFirstLine( n, aParaStart, aTmpPos.Y(), aOrigin, nOrientation, pOutDev );
3067 
3068                         // Remember whether a bullet was painted.
3069                         const SfxBoolItem& rBulletState = pEditEngine->GetParaAttrib(n, EE_PARA_BULLETSTATE);
3070                         bPaintBullet = rBulletState.GetValue();
3071                     }
3072 
3073 
3074                     // Over the Portions of the line...
3075 
3076                     bool bParsingFields = false;
3077                     std::vector< sal_Int32 >::iterator itSubLines;
3078 
3079                     for ( sal_Int32 nPortion = pLine->GetStartPortion(); nPortion <= pLine->GetEndPortion(); nPortion++ )
3080                     {
3081                         DBG_ASSERT( pPortion->GetTextPortions().Count(), "Line without Textportion in Paint!" );
3082                         const TextPortion& rTextPortion = pPortion->GetTextPortions()[nPortion];
3083 
3084                         long nPortionXOffset = GetPortionXOffset( pPortion, pLine, nPortion );
3085                         if ( !IsVertical() )
3086                         {
3087                             aTmpPos.setX( aStartPos.X() + nPortionXOffset );
3088                             if ( aTmpPos.X() > aClipRect.Right() )
3089                                 break;  // No further output in line necessary
3090                         }
3091                         else
3092                         {
3093                             if( IsTopToBottom() )
3094                             {
3095                                 aTmpPos.setY( aStartPos.Y() + nPortionXOffset );
3096                                 if ( aTmpPos.Y() > aClipRect.Bottom() )
3097                                     break;  // No further output in line necessary
3098                             }
3099                             else
3100                             {
3101                                 aTmpPos.setY( aStartPos.Y() - nPortionXOffset );
3102                                 if (aTmpPos.Y() < aClipRect.Top())
3103                                     break;  // No further output in line necessary
3104                             }
3105                         }
3106 
3107                         switch ( rTextPortion.GetKind() )
3108                         {
3109                             case PortionKind::TEXT:
3110                             case PortionKind::FIELD:
3111                             case PortionKind::HYPHENATOR:
3112                             {
3113                                 SeekCursor( pPortion->GetNode(), nIndex+1, aTmpFont, pOutDev );
3114 
3115                                 bool bDrawFrame = false;
3116 
3117                                 if ( ( rTextPortion.GetKind() == PortionKind::FIELD ) && !aTmpFont.IsTransparent() &&
3118                                      ( GetBackgroundColor() != COL_AUTO ) && GetBackgroundColor().IsDark() &&
3119                                      ( IsAutoColorEnabled() && ( pOutDev->GetOutDevType() != OUTDEV_PRINTER ) ) )
3120                                 {
3121                                     aTmpFont.SetTransparent( true );
3122                                     pOutDev->SetFillColor();
3123                                     pOutDev->SetLineColor( GetAutoColor() );
3124                                     bDrawFrame = true;
3125                                 }
3126 
3127 #if OSL_DEBUG_LEVEL > 2
3128                                 // Do we really need this if statement?
3129                                 if ( rTextPortion.GetKind() == PortionKind::HYPHENATOR )
3130                                 {
3131                                     aTmpFont.SetFillColor( COL_LIGHTGRAY );
3132                                     aTmpFont.SetTransparent( sal_False );
3133                                 }
3134                                 if ( rTextPortion.IsRightToLeft()  )
3135                                 {
3136                                     aTmpFont.SetFillColor( COL_LIGHTGRAY );
3137                                     aTmpFont.SetTransparent( sal_False );
3138                                 }
3139                                 else if ( GetI18NScriptType( EditPaM( pPortion->GetNode(), nIndex+1 ) ) == i18n::ScriptType::COMPLEX )
3140                                 {
3141                                     aTmpFont.SetFillColor( COL_LIGHTCYAN );
3142                                     aTmpFont.SetTransparent( sal_False );
3143                                 }
3144 #endif
3145                                 aTmpFont.SetPhysFont( pOutDev );
3146 
3147                                 // #114278# Saving both layout mode and language (since I'm
3148                                 // potentially changing both)
3149                                 pOutDev->Push( PushFlags::TEXTLAYOUTMODE|PushFlags::TEXTLANGUAGE );
3150                                 ImplInitLayoutMode( pOutDev, n, nIndex );
3151                                 ImplInitDigitMode(pOutDev, aTmpFont.GetLanguage());
3152 
3153                                 OUString aText;
3154                                 sal_Int32 nTextStart = 0;
3155                                 sal_Int32 nTextLen = 0;
3156                                 const long* pDXArray = nullptr;
3157                                 std::unique_ptr<long[]> pTmpDXArray;
3158 
3159                                 if ( rTextPortion.GetKind() == PortionKind::TEXT )
3160                                 {
3161                                     aText = pPortion->GetNode()->GetString();
3162                                     nTextStart = nIndex;
3163                                     nTextLen = rTextPortion.GetLen();
3164                                     pDXArray = pLine->GetCharPosArray().data() + (nIndex - pLine->GetStart());
3165 
3166                                     // Paint control characters (#i55716#)
3167                                     /* XXX: Given that there's special handling
3168                                      * only for some specific characters
3169                                      * (U+200B ZERO WIDTH SPACE and U+2060 WORD
3170                                      * JOINER) it is assumed to be not relevant
3171                                      * for MarkUrlFields(). */
3172                                     if ( aStatus.MarkNonUrlFields() )
3173                                     {
3174                                         sal_Int32 nTmpIdx;
3175                                         const sal_Int32 nTmpEnd = nTextStart + rTextPortion.GetLen();
3176 
3177                                         for ( nTmpIdx = nTextStart; nTmpIdx <= nTmpEnd ; ++nTmpIdx )
3178                                         {
3179                                             const sal_Unicode cChar = ( nTmpIdx != aText.getLength() && ( nTmpIdx != nTextStart || 0 == nTextStart ) ) ?
3180                                                                         aText[nTmpIdx] :
3181                                                                         0;
3182 
3183                                             if ( 0x200B == cChar || 0x2060 == cChar )
3184                                             {
3185                                                 const OUString aBlank( ' ' );
3186                                                 long nHalfBlankWidth = aTmpFont.QuickGetTextSize( pOutDev, aBlank, 0, 1 ).Width() / 2;
3187 
3188                                                 const long nAdvanceX = ( nTmpIdx == nTmpEnd ?
3189                                                                          rTextPortion.GetSize().Width() :
3190                                                                          pDXArray[ nTmpIdx - nTextStart ] ) - nHalfBlankWidth;
3191                                                 const long nAdvanceY = -pLine->GetMaxAscent();
3192 
3193                                                 Point aTopLeftRectPos( aTmpPos );
3194                                                 if ( !IsVertical() )
3195                                                 {
3196                                                     aTopLeftRectPos.AdjustX(nAdvanceX );
3197                                                     aTopLeftRectPos.AdjustY(nAdvanceY );
3198                                                 }
3199                                                 else
3200                                                 {
3201                                                     if( IsTopToBottom() )
3202                                                     {
3203                                                         aTopLeftRectPos.AdjustY( -nAdvanceX );
3204                                                         aTopLeftRectPos.AdjustX(nAdvanceY );
3205                                                     }
3206                                                     else
3207                                                     {
3208                                                         aTopLeftRectPos.AdjustY(nAdvanceX );
3209                                                         aTopLeftRectPos.AdjustX( -nAdvanceY );
3210                                                     }
3211                                                 }
3212 
3213                                                 Point aBottomRightRectPos( aTopLeftRectPos );
3214                                                 if ( !IsVertical() )
3215                                                 {
3216                                                     aBottomRightRectPos.AdjustX(2 * nHalfBlankWidth );
3217                                                     aBottomRightRectPos.AdjustY(pLine->GetHeight() );
3218                                                 }
3219                                                 else
3220                                                 {
3221                                                     if (IsTopToBottom())
3222                                                     {
3223                                                         aBottomRightRectPos.AdjustX(pLine->GetHeight() );
3224                                                         aBottomRightRectPos.AdjustY( -(2 * nHalfBlankWidth) );
3225                                                     }
3226                                                     else
3227                                                     {
3228                                                         aBottomRightRectPos.AdjustX( -(pLine->GetHeight()) );
3229                                                         aBottomRightRectPos.AdjustY(2 * nHalfBlankWidth );
3230                                                     }
3231                                                 }
3232 
3233                                                 pOutDev->Push( PushFlags::FILLCOLOR );
3234                                                 pOutDev->Push( PushFlags::LINECOLOR );
3235                                                 pOutDev->SetFillColor( COL_LIGHTGRAY );
3236                                                 pOutDev->SetLineColor( COL_LIGHTGRAY );
3237 
3238                                                 const tools::Rectangle aBackRect( aTopLeftRectPos, aBottomRightRectPos );
3239                                                 pOutDev->DrawRect( aBackRect );
3240 
3241                                                 pOutDev->Pop();
3242                                                 pOutDev->Pop();
3243 
3244                                                 if ( 0x200B == cChar )
3245                                                 {
3246                                                     const OUString aSlash( '/' );
3247                                                     const short nOldEscapement = aTmpFont.GetEscapement();
3248                                                     const sal_uInt8 nOldPropr = aTmpFont.GetPropr();
3249 
3250                                                     aTmpFont.SetEscapement( -20 );
3251                                                     aTmpFont.SetPropr( 25 );
3252                                                     aTmpFont.SetPhysFont( pOutDev );
3253 
3254                                                     const Size aSlashSize = aTmpFont.QuickGetTextSize( pOutDev, aSlash, 0, 1 );
3255                                                     Point aSlashPos( aTmpPos );
3256                                                     const long nAddX = nHalfBlankWidth - aSlashSize.Width() / 2;
3257                                                     if ( !IsVertical() )
3258                                                     {
3259                                                         aSlashPos.setX( aTopLeftRectPos.X() + nAddX );
3260                                                     }
3261                                                     else
3262                                                     {
3263                                                         if (IsTopToBottom())
3264                                                             aSlashPos.setY( aTopLeftRectPos.Y() + nAddX );
3265                                                         else
3266                                                             aSlashPos.setY( aTopLeftRectPos.Y() - nAddX );
3267                                                     }
3268 
3269                                                     aTmpFont.QuickDrawText( pOutDev, aSlashPos, aSlash, 0, 1 );
3270 
3271                                                     aTmpFont.SetEscapement( nOldEscapement );
3272                                                     aTmpFont.SetPropr( nOldPropr );
3273                                                     aTmpFont.SetPhysFont( pOutDev );
3274                                                 }
3275                                             }
3276                                         }
3277                                     }
3278                                 }
3279                                 else if ( rTextPortion.GetKind() == PortionKind::FIELD )
3280                                 {
3281                                     const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex);
3282                                     DBG_ASSERT( pAttr, "Field not found");
3283                                     DBG_ASSERT( pAttr && dynamic_cast< const SvxFieldItem* >( pAttr->GetItem() ) !=  nullptr, "Field of the wrong type! ");
3284                                     aText = static_cast<const EditCharAttribField*>(pAttr)->GetFieldValue();
3285                                     nTextStart = 0;
3286                                     nTextLen = aText.getLength();
3287                                     ExtraPortionInfo *pExtraInfo = rTextPortion.GetExtraInfos();
3288                                     // Do not split the Fields into different lines while editing
3289                                     // With EditView on Overlay bStripOnly is now set for stripping to
3290                                     // primitives. To stay compatible in EditMode use pActiveView to detect
3291                                     // when we are in EditMode. For whatever reason URLs are drawn as single
3292                                     // line in edit mode, originally clipped against edit area (which is no
3293                                     // longer done in Overlay mode and allows to *read* the URL).
3294                                     // It would be difficult to change this due to needed adaptations in
3295                                     // EditEngine (look for lineBreaksList creation)
3296                                     if( nullptr == pActiveView && bStripOnly && !bParsingFields && pExtraInfo && !pExtraInfo->lineBreaksList.empty() )
3297                                     {
3298                                         bParsingFields = true;
3299                                         itSubLines = pExtraInfo->lineBreaksList.begin();
3300                                     }
3301 
3302                                     if( bParsingFields )
3303                                     {
3304                                         if( itSubLines != pExtraInfo->lineBreaksList.begin() )
3305                                         {
3306                                             // only use GetMaxAscent(), pLine->GetHeight() will not
3307                                             // proceed as needed (see PortionKind::TEXT above and nAdvanceY)
3308                                             // what will lead to a compressed look with multiple lines
3309                                             const sal_uInt16 nMaxAscent(pLine->GetMaxAscent());
3310 
3311                                             if ( !IsVertical() )
3312                                             {
3313                                                 aStartPos.AdjustY(nMaxAscent );
3314                                                 aTmpPos.AdjustY(nMaxAscent );
3315                                             }
3316                                             else
3317                                             {
3318                                                 if (IsTopToBottom())
3319                                                 {
3320                                                     aTmpPos.AdjustX( -nMaxAscent );
3321                                                     aStartPos.AdjustX( -nMaxAscent );
3322                                                 }
3323                                                 else
3324                                                 {
3325                                                     aTmpPos.AdjustX(nMaxAscent );
3326                                                     aStartPos.AdjustX(nMaxAscent );
3327                                                 }
3328                                             }
3329                                         }
3330                                         std::vector< sal_Int32 >::iterator curIt = itSubLines;
3331                                         ++itSubLines;
3332                                         if( itSubLines != pExtraInfo->lineBreaksList.end() )
3333                                         {
3334                                             nTextStart = *curIt;
3335                                             nTextLen = *itSubLines - nTextStart;
3336                                         }
3337                                         else
3338                                         {
3339                                             nTextStart = *curIt;
3340                                             nTextLen = nTextLen - nTextStart;
3341                                             bParsingFields = false;
3342                                         }
3343                                     }
3344 
3345                                     pTmpDXArray.reset(new long[ aText.getLength() ]);
3346                                     pDXArray = pTmpDXArray.get();
3347                                     vcl::Font _aOldFont( GetRefDevice()->GetFont() );
3348                                     aTmpFont.SetPhysFont( GetRefDevice() );
3349                                     aTmpFont.QuickGetTextSize( GetRefDevice(), aText, nTextStart, nTextLen, pTmpDXArray.get() );
3350 
3351                                     // add a meta file comment if we record to a metafile
3352                                     if( bMetafileValid )
3353                                     {
3354                                         const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
3355                                         if( pFieldItem )
3356                                         {
3357                                             const SvxFieldData* pFieldData = pFieldItem->GetField();
3358                                             if( pFieldData )
3359                                                 pMtf->AddAction( pFieldData->createBeginComment() );
3360                                         }
3361                                     }
3362 
3363                                 }
3364                                 else if ( rTextPortion.GetKind() == PortionKind::HYPHENATOR )
3365                                 {
3366                                     if ( rTextPortion.GetExtraValue() )
3367                                         aText = OUString(rTextPortion.GetExtraValue());
3368                                     aText += OUStringLiteral1(CH_HYPH);
3369                                     nTextStart = 0;
3370                                     nTextLen = aText.getLength();
3371 
3372                                     // crash when accessing 0 pointer in pDXArray
3373                                     pTmpDXArray.reset(new long[ aText.getLength() ]);
3374                                     pDXArray = pTmpDXArray.get();
3375                                     vcl::Font _aOldFont( GetRefDevice()->GetFont() );
3376                                     aTmpFont.SetPhysFont( GetRefDevice() );
3377                                     aTmpFont.QuickGetTextSize( GetRefDevice(), aText, 0, aText.getLength(), pTmpDXArray.get() );
3378                                 }
3379 
3380                                 long nTxtWidth = rTextPortion.GetSize().Width();
3381 
3382                                 Point aOutPos( aTmpPos );
3383                                 aRedLineTmpPos = aTmpPos;
3384                                 // In RTL portions spell markup pos should be at the start of the
3385                                 // first chara as well. That is on the right end of the portion
3386                                 if (rTextPortion.IsRightToLeft())
3387                                     aRedLineTmpPos.AdjustX(rTextPortion.GetSize().Width() );
3388 
3389                                 if ( bStripOnly )
3390                                 {
3391                                     EEngineData::WrongSpellVector aWrongSpellVector;
3392 
3393                                     if(GetStatus().DoOnlineSpelling() && rTextPortion.GetLen())
3394                                     {
3395                                         WrongList* pWrongs = pPortion->GetNode()->GetWrongList();
3396 
3397                                         if(pWrongs && !pWrongs->empty())
3398                                         {
3399                                             size_t nStart = nIndex, nEnd = 0;
3400                                             bool bWrong = pWrongs->NextWrong(nStart, nEnd);
3401                                             const size_t nMaxEnd(nIndex + rTextPortion.GetLen());
3402 
3403                                             while(bWrong)
3404                                             {
3405                                                 if(nStart >= nMaxEnd)
3406                                                 {
3407                                                     break;
3408                                                 }
3409 
3410                                                 if(nStart < static_cast<size_t>(nIndex))
3411                                                 {
3412                                                     nStart = nIndex;
3413                                                 }
3414 
3415                                                 if(nEnd > nMaxEnd)
3416                                                 {
3417                                                     nEnd = nMaxEnd;
3418                                                 }
3419 
3420                                                 // add to vector
3421                                                 aWrongSpellVector.emplace_back(nStart, nEnd);
3422 
3423                                                 // goto next index
3424                                                 nStart = nEnd + 1;
3425 
3426                                                 if(nEnd < nMaxEnd)
3427                                                 {
3428                                                     bWrong = pWrongs->NextWrong(nStart, nEnd);
3429                                                 }
3430                                                 else
3431                                                 {
3432                                                     bWrong = false;
3433                                                 }
3434                                             }
3435                                         }
3436                                     }
3437 
3438                                     const SvxFieldData* pFieldData = nullptr;
3439 
3440                                     if(PortionKind::FIELD == rTextPortion.GetKind())
3441                                     {
3442                                         const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex);
3443                                         const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
3444 
3445                                         if(pFieldItem)
3446                                         {
3447                                             pFieldData = pFieldItem->GetField();
3448                                         }
3449                                     }
3450 
3451                                     // support for EOC, EOW, EOS TEXT comments. To support that,
3452                                     // the locale is needed. With the locale and a XBreakIterator it is
3453                                     // possible to re-create the text marking info on primitive level
3454                                     const lang::Locale aLocale(GetLocale(EditPaM(pPortion->GetNode(), nIndex + 1)));
3455 
3456                                     // create EOL and EOP bools
3457                                     const bool bEndOfLine(nPortion == pLine->GetEndPortion());
3458                                     const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines);
3459 
3460                                     // get Overline color (from ((const SvxOverlineItem*)GetItem())->GetColor() in
3461                                     // consequence, but also already set at pOutDev)
3462                                     const Color aOverlineColor(pOutDev->GetOverlineColor());
3463 
3464                                     // get TextLine color (from ((const SvxUnderlineItem*)GetItem())->GetColor() in
3465                                     // consequence, but also already set at pOutDev)
3466                                     const Color aTextLineColor(pOutDev->GetTextLineColor());
3467 
3468                                     // Unicode code points conversion according to ctl text numeral setting
3469                                     aText = convertDigits(aText, nTextStart, nTextLen,
3470                                         ImplCalcDigitLang(aTmpFont.GetLanguage()));
3471 
3472                                     // StripPortions() data callback
3473                                     GetEditEnginePtr()->DrawingText( aOutPos, aText, nTextStart, nTextLen, pDXArray,
3474                                         aTmpFont, n, rTextPortion.GetRightToLeftLevel(),
3475                                         !aWrongSpellVector.empty() ? &aWrongSpellVector : nullptr,
3476                                         pFieldData,
3477                                         bEndOfLine, bEndOfParagraph, // support for EOL/EOP TEXT comments
3478                                         &aLocale,
3479                                         aOverlineColor,
3480                                         aTextLineColor);
3481 
3482                                     // #108052# remember that EOP is written already for this ParaPortion
3483                                     if(bEndOfParagraph)
3484                                     {
3485                                         bEndOfParagraphWritten = true;
3486                                     }
3487                                 }
3488                                 else
3489                                 {
3490                                     short nEsc = aTmpFont.GetEscapement();
3491                                     if ( nOrientation )
3492                                     {
3493                                         // In case of high/low do it yourself:
3494                                         if ( aTmpFont.GetEscapement() )
3495                                         {
3496                                             long nDiff = aTmpFont.GetFontSize().Height() * aTmpFont.GetEscapement() / 100L;
3497                                             if ( !IsVertical() )
3498                                                 aOutPos.AdjustY( -nDiff );
3499                                             else
3500                                             {
3501                                                 if (IsTopToBottom())
3502                                                     aOutPos.AdjustX(nDiff );
3503                                                 else
3504                                                     aOutPos.AdjustX( -nDiff );
3505                                             }
3506                                             aRedLineTmpPos = aOutPos;
3507                                             aTmpFont.SetEscapement( 0 );
3508                                         }
3509 
3510                                         aOutPos = lcl_ImplCalcRotatedPos( aOutPos, aOrigin, nSin, nCos );
3511                                         aTmpFont.SetOrientation( aTmpFont.GetOrientation()+nOrientation );
3512                                         aTmpFont.SetPhysFont( pOutDev );
3513 
3514                                     }
3515 
3516                                     // Take only what begins in the visible range:
3517                                     // Important, because of a bug in some graphic cards
3518                                     // when transparent font, output when negative
3519                                     if ( nOrientation || ( !IsVertical() && ( ( aTmpPos.X() + nTxtWidth ) >= nFirstVisXPos ) )
3520                                             || ( IsVertical() && ( ( aTmpPos.Y() + nTxtWidth ) >= nFirstVisYPos ) ) )
3521                                     {
3522                                         if ( nEsc && ( aTmpFont.GetUnderline() != LINESTYLE_NONE ) )
3523                                         {
3524                                             // Paint the high/low without underline,
3525                                             // Display the Underline on the
3526                                             // base line of the original font height...
3527                                             // But only if there was something underlined before!
3528                                             bool bSpecialUnderline = false;
3529                                             EditCharAttrib* pPrev = pPortion->GetNode()->GetCharAttribs().FindAttrib( EE_CHAR_ESCAPEMENT, nIndex );
3530                                             if ( pPrev )
3531                                             {
3532                                                 SvxFont aDummy;
3533                                                 // Underscore in front?
3534                                                 if ( pPrev->GetStart() )
3535                                                 {
3536                                                     SeekCursor( pPortion->GetNode(), pPrev->GetStart(), aDummy );
3537                                                     if ( aDummy.GetUnderline() != LINESTYLE_NONE )
3538                                                         bSpecialUnderline = true;
3539                                                 }
3540                                                 if ( !bSpecialUnderline && ( pPrev->GetEnd() < pPortion->GetNode()->Len() ) )
3541                                                 {
3542                                                     SeekCursor( pPortion->GetNode(), pPrev->GetEnd()+1, aDummy );
3543                                                     if ( aDummy.GetUnderline() != LINESTYLE_NONE )
3544                                                         bSpecialUnderline = true;
3545                                                 }
3546                                             }
3547                                             if ( bSpecialUnderline )
3548                                             {
3549                                                 Size aSz = aTmpFont.GetPhysTxtSize( pOutDev, aText, nTextStart, nTextLen );
3550                                                 sal_uInt8 nProp = aTmpFont.GetPropr();
3551                                                 aTmpFont.SetEscapement( 0 );
3552                                                 aTmpFont.SetPropr( 100 );
3553                                                 aTmpFont.SetPhysFont( pOutDev );
3554                                                 OUStringBuffer aBlanks;
3555                                                 comphelper::string::padToLength( aBlanks, nTextLen, ' ' );
3556                                                 Point aUnderlinePos( aOutPos );
3557                                                 if ( nOrientation )
3558                                                     aUnderlinePos = lcl_ImplCalcRotatedPos( aTmpPos, aOrigin, nSin, nCos );
3559                                                 pOutDev->DrawStretchText( aUnderlinePos, aSz.Width(), aBlanks.makeStringAndClear(), 0, nTextLen );
3560 
3561                                                 aTmpFont.SetUnderline( LINESTYLE_NONE );
3562                                                 if ( !nOrientation )
3563                                                     aTmpFont.SetEscapement( nEsc );
3564                                                 aTmpFont.SetPropr( nProp );
3565                                                 aTmpFont.SetPhysFont( pOutDev );
3566                                             }
3567                                         }
3568                                         Point aRealOutPos( aOutPos );
3569                                         if ( ( rTextPortion.GetKind() == PortionKind::TEXT )
3570                                                && rTextPortion.GetExtraInfos() && rTextPortion.GetExtraInfos()->bCompressed
3571                                                && rTextPortion.GetExtraInfos()->bFirstCharIsRightPunktuation )
3572                                         {
3573                                             aRealOutPos.AdjustX(rTextPortion.GetExtraInfos()->nPortionOffsetX );
3574                                         }
3575 
3576                                         // RTL portions with (#i37132#)
3577                                         // compressed blank should not paint this blank:
3578                                         if ( rTextPortion.IsRightToLeft() && nTextLen >= 2 &&
3579                                              pDXArray[ nTextLen - 1 ] ==
3580                                              pDXArray[ nTextLen - 2 ] &&
3581                                              ' ' == aText[nTextStart + nTextLen - 1] )
3582                                             --nTextLen;
3583 
3584                                         // output directly
3585                                         aTmpFont.QuickDrawText( pOutDev, aRealOutPos, aText, nTextStart, nTextLen, pDXArray );
3586 
3587                                         if ( bDrawFrame )
3588                                         {
3589                                             Point aTopLeft( aTmpPos );
3590                                             aTopLeft.AdjustY( -(pLine->GetMaxAscent()) );
3591                                             if ( nOrientation )
3592                                                 aTopLeft = lcl_ImplCalcRotatedPos( aTopLeft, aOrigin, nSin, nCos );
3593                                             tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() );
3594                                             pOutDev->DrawRect( aRect );
3595                                         }
3596 
3597                                         // PDF export:
3598                                         if ( pPDFExtOutDevData )
3599                                         {
3600                                             if ( rTextPortion.GetKind() == PortionKind::FIELD )
3601                                             {
3602                                                 const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex);
3603                                                 const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
3604                                                 if( pFieldItem )
3605                                                 {
3606                                                     const SvxFieldData* pFieldData = pFieldItem->GetField();
3607                                                     if ( auto pUrlField = dynamic_cast< const SvxURLField* >( pFieldData ) )
3608                                                     {
3609                                                         Point aTopLeft( aTmpPos );
3610                                                         aTopLeft.AdjustY( -(pLine->GetMaxAscent()) );
3611 
3612                                                         tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() );
3613                                                         vcl::PDFExtOutDevBookmarkEntry aBookmark;
3614                                                         aBookmark.nLinkId = pPDFExtOutDevData->CreateLink( aRect );
3615                                                         aBookmark.aBookmark = pUrlField->GetURL();
3616                                                         std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks();
3617                                                         rBookmarks.push_back( aBookmark );
3618                                                     }
3619                                                 }
3620                                             }
3621                                         }
3622                                     }
3623 
3624                                     const WrongList* const pWrongList = pPortion->GetNode()->GetWrongList();
3625                                     if ( GetStatus().DoOnlineSpelling() && pWrongList && !pWrongList->empty() && rTextPortion.GetLen() )
3626                                     {
3627                                         {//#105750# adjust LinePos for superscript or subscript text
3628                                             short _nEsc = aTmpFont.GetEscapement();
3629                                             if( _nEsc )
3630                                             {
3631                                                 long nShift = (_nEsc * aTmpFont.GetFontSize().Height()) / 100L;
3632                                                 if( !IsVertical() )
3633                                                     aRedLineTmpPos.AdjustY( -nShift );
3634                                                 else
3635                                                     if (IsTopToBottom())
3636                                                         aRedLineTmpPos.AdjustX(nShift );
3637                                                     else
3638                                                         aRedLineTmpPos.AdjustX( -nShift );
3639                                             }
3640                                         }
3641                                         Color aOldColor( pOutDev->GetLineColor() );
3642                                         pOutDev->SetLineColor( GetColorConfig().GetColorValue( svtools::SPELL ).nColor );
3643                                         lcl_DrawRedLines( pOutDev, aTmpFont.GetFontSize().Height(), aRedLineTmpPos, static_cast<size_t>(nIndex), static_cast<size_t>(nIndex) + rTextPortion.GetLen(), pDXArray, pPortion->GetNode()->GetWrongList(), nOrientation, aOrigin, IsVertical(), rTextPortion.IsRightToLeft() );
3644                                         pOutDev->SetLineColor( aOldColor );
3645                                     }
3646                                 }
3647 
3648                                 pOutDev->Pop();
3649 
3650                                 pTmpDXArray.reset();
3651 
3652                                 if ( rTextPortion.GetKind() == PortionKind::FIELD )
3653                                 {
3654                                     const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex);
3655                                     DBG_ASSERT( pAttr, "Field not found" );
3656                                     DBG_ASSERT( pAttr && dynamic_cast< const SvxFieldItem* >( pAttr->GetItem() ) !=  nullptr, "Wrong type of field!" );
3657 
3658                                     // add a meta file comment if we record to a metafile
3659                                     if( bMetafileValid )
3660                                     {
3661                                         const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
3662 
3663                                         if( pFieldItem )
3664                                         {
3665                                             const SvxFieldData* pFieldData = pFieldItem->GetField();
3666                                             if( pFieldData )
3667                                                 pMtf->AddAction( SvxFieldData::createEndComment() );
3668                                         }
3669                                     }
3670 
3671                                 }
3672 
3673                             }
3674                             break;
3675                             case PortionKind::TAB:
3676                             {
3677                                 if ( rTextPortion.GetExtraValue() && ( rTextPortion.GetExtraValue() != ' ' ) )
3678                                 {
3679                                     SeekCursor( pPortion->GetNode(), nIndex+1, aTmpFont, pOutDev );
3680                                     aTmpFont.SetTransparent( false );
3681                                     aTmpFont.SetEscapement( 0 );
3682                                     aTmpFont.SetPhysFont( pOutDev );
3683                                     long nCharWidth = aTmpFont.QuickGetTextSize( pOutDev,
3684                                         OUString(rTextPortion.GetExtraValue()), 0, 1 ).Width();
3685                                     sal_Int32 nChars = 2;
3686                                     if( nCharWidth )
3687                                         nChars = rTextPortion.GetSize().Width() / nCharWidth;
3688                                     if ( nChars < 2 )
3689                                         nChars = 2; // is compressed by DrawStretchText.
3690                                     else if ( nChars == 2 )
3691                                         nChars = 3; // looks better
3692 
3693                                     OUStringBuffer aBuf;
3694                                     comphelper::string::padToLength(aBuf, nChars, rTextPortion.GetExtraValue());
3695                                     OUString aText(aBuf.makeStringAndClear());
3696                                     aTmpFont.QuickDrawText( pOutDev, aTmpPos, aText, 0, aText.getLength() );
3697                                     pOutDev->DrawStretchText( aTmpPos, rTextPortion.GetSize().Width(), aText );
3698 
3699                                     if ( bStripOnly )
3700                                     {
3701                                         // create EOL and EOP bools
3702                                         const bool bEndOfLine(nPortion == pLine->GetEndPortion());
3703                                         const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines);
3704 
3705                                         const Color aOverlineColor(pOutDev->GetOverlineColor());
3706                                         const Color aTextLineColor(pOutDev->GetTextLineColor());
3707 
3708                                         // StripPortions() data callback
3709                                         GetEditEnginePtr()->DrawingTab( aTmpPos,
3710                                             rTextPortion.GetSize().Width(),
3711                                             OUString(rTextPortion.GetExtraValue()),
3712                                             aTmpFont, n, rTextPortion.GetRightToLeftLevel(),
3713                                             bEndOfLine, bEndOfParagraph,
3714                                             aOverlineColor, aTextLineColor);
3715                                     }
3716                                 }
3717                                 else if ( bStripOnly )
3718                                 {
3719                                     // #i108052# When stripping, a callback for _empty_ paragraphs is also needed.
3720                                     // This was optimized away (by not rendering the space-only tab portion), so do
3721                                     // it manually here.
3722                                     const bool bEndOfLine(nPortion == pLine->GetEndPortion());
3723                                     const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines);
3724 
3725                                     const Color aOverlineColor(pOutDev->GetOverlineColor());
3726                                     const Color aTextLineColor(pOutDev->GetTextLineColor());
3727 
3728                                     GetEditEnginePtr()->DrawingText(
3729                                         aTmpPos, OUString(), 0, 0, nullptr,
3730                                         aTmpFont, n, 0,
3731                                         nullptr,
3732                                         nullptr,
3733                                         bEndOfLine, bEndOfParagraph,
3734                                         nullptr,
3735                                         aOverlineColor,
3736                                         aTextLineColor);
3737                                 }
3738                             }
3739                             break;
3740                             case PortionKind::LINEBREAK: break;
3741                         }
3742                         if( bParsingFields )
3743                             nPortion--;
3744                         else
3745                             nIndex = nIndex + rTextPortion.GetLen();
3746 
3747                     }
3748                 }
3749 
3750                 if ( ( nLine != nLastLine ) && !aStatus.IsOutliner() )
3751                 {
3752                     if ( !IsVertical() )
3753                         aStartPos.AdjustY(nSBL );
3754                     else
3755                     {
3756                         if( IsTopToBottom() )
3757                             aStartPos.AdjustX( -nSBL );
3758                         else
3759                             aStartPos.AdjustX(nSBL );
3760                     }
3761                 }
3762 
3763                 // no more visible actions?
3764                 if ( !IsVertical() && ( aStartPos.Y() >= aClipRect.Bottom() ) )
3765                     break;
3766                 else if ( IsVertical() && IsTopToBottom() && ( aStartPos.X() <= aClipRect.Left() ) )
3767                     break;
3768                 else if (IsVertical() && !IsTopToBottom() && (aStartPos.X() >= aClipRect.Right()))
3769                     break;
3770             }
3771 
3772             if ( !aStatus.IsOutliner() )
3773             {
3774                 const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE );
3775                 long nUL = GetYValue( rULItem.GetLower() );
3776                 if ( !IsVertical() )
3777                     aStartPos.AdjustY(nUL );
3778                 else
3779                 {
3780                     if (IsTopToBottom())
3781                         aStartPos.AdjustX( -nUL );
3782                     else
3783                         aStartPos.AdjustX(nUL );
3784                 }
3785             }
3786 
3787             // #108052# Safer way for #i108052# and #i118881#: If for the current ParaPortion
3788             // EOP is not written, do it now. This will be safer than before. It has shown
3789             // that the reason for #i108052# was fixed/removed again, so this is a try to fix
3790             // the number of paragraphs (and counting empty ones) now independent from the
3791             // changes in EditEngine behaviour.
3792             if(!bEndOfParagraphWritten && !bPaintBullet && bStripOnly)
3793             {
3794                 const Color aOverlineColor(pOutDev->GetOverlineColor());
3795                 const Color aTextLineColor(pOutDev->GetTextLineColor());
3796 
3797                 GetEditEnginePtr()->DrawingText(
3798                     aTmpPos, OUString(), 0, 0, nullptr,
3799                     aTmpFont, n, 0,
3800                     nullptr,
3801                     nullptr,
3802                     false, true, // support for EOL/EOP TEXT comments
3803                     nullptr,
3804                     aOverlineColor,
3805                     aTextLineColor);
3806             }
3807         }
3808         else
3809         {
3810             if ( !IsVertical() )
3811                 aStartPos.AdjustY(nParaHeight );
3812             else
3813             {
3814                 if (IsTopToBottom())
3815                     aStartPos.AdjustX( -nParaHeight );
3816                 else
3817                     aStartPos.AdjustX(nParaHeight );
3818             }
3819         }
3820 
3821         if ( pPDFExtOutDevData )
3822             pPDFExtOutDevData->EndStructureElement();
3823 
3824         // no more visible actions?
3825         if ( !IsVertical() && ( aStartPos.Y() > aClipRect.Bottom() ) )
3826             break;
3827         if ( IsVertical() && IsTopToBottom() && ( aStartPos.X() < aClipRect.Left() ) )
3828             break;
3829         if (IsVertical() && !IsTopToBottom() && ( aStartPos.X() > aClipRect.Right() ) )
3830             break;
3831     }
3832 }
3833 
3834 void ImpEditEngine::Paint( ImpEditView* pView, const tools::Rectangle& rRect, OutputDevice* pTargetDevice )
3835 {
3836     DBG_ASSERT( pView, "No View - No Paint!" );
3837 
3838     if ( !GetUpdateMode() || IsInUndo() )
3839         return;
3840 
3841     // Intersection of paint area and output area.
3842     tools::Rectangle aClipRect( pView->GetOutputArea() );
3843     aClipRect.Intersection( rRect );
3844 
3845     OutputDevice* pTarget = pTargetDevice ? pTargetDevice : pView->GetWindow();
3846 
3847     Point aStartPos;
3848     if ( !IsVertical() )
3849     {
3850         aStartPos = pView->GetOutputArea().TopLeft();
3851         aStartPos.AdjustX( -(pView->GetVisDocLeft()) );
3852         aStartPos.AdjustY( -(pView->GetVisDocTop()) );
3853     }
3854     else
3855     {
3856         if( IsTopToBottom() )
3857         {
3858             aStartPos = pView->GetOutputArea().TopRight();
3859             aStartPos.AdjustX(pView->GetVisDocTop() );
3860             aStartPos.AdjustY( -(pView->GetVisDocLeft()) );
3861         }
3862         else
3863         {
3864             aStartPos = pView->GetOutputArea().BottomLeft();
3865             aStartPos.AdjustX( -(pView->GetVisDocTop()) );
3866             aStartPos.AdjustY(pView->GetVisDocLeft() );
3867         }
3868     }
3869 
3870     // If Doc-width < Output Area,Width and not wrapped fields,
3871     // the fields usually protrude if > line.
3872     // (Not at the top, since there the Doc-width from formatting is already
3873     // there)
3874     if ( !IsVertical() && ( pView->GetOutputArea().GetWidth() > GetPaperSize().Width() ) )
3875     {
3876         long nMaxX = pView->GetOutputArea().Left() + GetPaperSize().Width();
3877         if ( aClipRect.Left() > nMaxX )
3878             return;
3879         if ( aClipRect.Right() > nMaxX )
3880             aClipRect.SetRight( nMaxX );
3881     }
3882 
3883     bool bClipRegion = pTarget->IsClipRegion();
3884     vcl::Region aOldRegion = pTarget->GetClipRegion();
3885     pTarget->IntersectClipRegion( aClipRect );
3886 
3887     Paint( pTarget, aClipRect, aStartPos );
3888 
3889     if ( bClipRegion )
3890         pTarget->SetClipRegion( aOldRegion );
3891     else
3892         pTarget->SetClipRegion();
3893 
3894     // In case of tiled rendering pass a region to DrawSelectionXOR(), so that
3895     // selection callbacks are not emitted during every repaint.
3896     vcl::Region aRegion;
3897     pView->DrawSelectionXOR(pView->GetEditSelection(), comphelper::LibreOfficeKit::isActive() ? &aRegion : nullptr, pTarget);
3898 }
3899 
3900 void ImpEditEngine::InsertContent( ContentNode* pNode, sal_Int32 nPos )
3901 {
3902     DBG_ASSERT( pNode, "NULL-Pointer in InsertContent! " );
3903     DBG_ASSERT( IsInUndo(), "InsertContent only for Undo()!" );
3904     GetParaPortions().Insert(nPos, std::make_unique<ParaPortion>( pNode ));
3905     aEditDoc.Insert(nPos, pNode);
3906     if ( IsCallParaInsertedOrDeleted() )
3907         GetEditEnginePtr()->ParagraphInserted( nPos );
3908 }
3909 
3910 EditPaM ImpEditEngine::SplitContent( sal_Int32 nNode, sal_Int32 nSepPos )
3911 {
3912     ContentNode* pNode = aEditDoc.GetObject( nNode );
3913     DBG_ASSERT( pNode, "Invalid Node in SplitContent" );
3914     DBG_ASSERT( IsInUndo(), "SplitContent only for Undo()!" );
3915     DBG_ASSERT( nSepPos <= pNode->Len(), "Index out of range: SplitContent" );
3916     EditPaM aPaM( pNode, nSepPos );
3917     return ImpInsertParaBreak( aPaM );
3918 }
3919 
3920 EditPaM ImpEditEngine::ConnectContents( sal_Int32 nLeftNode, bool bBackward )
3921 {
3922     ContentNode* pLeftNode = aEditDoc.GetObject( nLeftNode );
3923     ContentNode* pRightNode = aEditDoc.GetObject( nLeftNode+1 );
3924     DBG_ASSERT( pLeftNode, "Invalid left node in ConnectContents ");
3925     DBG_ASSERT( pRightNode, "Invalid right node in ConnectContents ");
3926     return ImpConnectParagraphs( pLeftNode, pRightNode, bBackward );
3927 }
3928 
3929 void ImpEditEngine::SetUpdateMode( bool bUp, EditView* pCurView, bool bForceUpdate )
3930 {
3931     bool bChanged = ( GetUpdateMode() != bUp );
3932 
3933     // When switching from sal_True to sal_False, all selections were visible,
3934     // => paint over
3935     // the other hand, were all invisible => paint
3936     // If !bFormatted, e.g. after SetText, then if UpdateMode=sal_True
3937     // formatting is not needed immediately, probably because more text is coming.
3938     // At latest it is formatted at a Paint/CalcTextWidth.
3939     bUpdate = bUp;
3940     if ( bUpdate && ( bChanged || bForceUpdate ) )
3941         FormatAndUpdate( pCurView );
3942 }
3943 
3944 void ImpEditEngine::ShowParagraph( sal_Int32 nParagraph, bool bShow )
3945 {
3946     ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
3947     DBG_ASSERT( pPPortion, "ShowParagraph: Paragraph does not exist! ");
3948     if ( pPPortion && ( pPPortion->IsVisible() != bShow ) )
3949     {
3950         pPPortion->SetVisible( bShow );
3951 
3952         if ( !bShow )
3953         {
3954             // Mark as deleted, so that no selection will end or begin at
3955             // this paragraph...
3956             aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pPPortion->GetNode(), nParagraph ));
3957             UpdateSelections();
3958             // The region below will not be invalidated if UpdateMode = sal_False!
3959             // If anyway, then save as sal_False before SetVisible !
3960         }
3961 
3962         if ( bShow && ( pPPortion->IsInvalid() || !pPPortion->nHeight ) )
3963         {
3964             if ( !GetTextRanger() )
3965             {
3966                 if ( pPPortion->IsInvalid() )
3967                 {
3968                     vcl::Font aOldFont( GetRefDevice()->GetFont() );
3969                     CreateLines( nParagraph, 0 );   // 0: No TextRanger
3970                 }
3971                 else
3972                 {
3973                     CalcHeight( pPPortion );
3974                 }
3975                 nCurTextHeight += pPPortion->GetHeight();
3976             }
3977             else
3978             {
3979                 nCurTextHeight = 0x7fffffff;
3980             }
3981         }
3982 
3983         pPPortion->SetMustRepaint( true );
3984         if ( GetUpdateMode() && !IsInUndo() && !GetTextRanger() )
3985         {
3986             aInvalidRect = tools::Rectangle(    Point( 0, GetParaPortions().GetYOffset( pPPortion ) ),
3987                                         Point( GetPaperSize().Width(), nCurTextHeight ) );
3988             UpdateViews( GetActiveView() );
3989         }
3990     }
3991 }
3992 
3993 EditSelection ImpEditEngine::MoveParagraphs( Range aOldPositions, sal_Int32 nNewPos, EditView* pCurView )
3994 {
3995     DBG_ASSERT( GetParaPortions().Count() != 0, "No paragraphs found: MoveParagraphs" );
3996     if ( GetParaPortions().Count() == 0 )
3997         return EditSelection();
3998     aOldPositions.Justify();
3999 
4000     EditSelection aSel( ImpMoveParagraphs( aOldPositions, nNewPos ) );
4001 
4002     if ( nNewPos >= GetParaPortions().Count() )
4003         nNewPos = GetParaPortions().Count() - 1;
4004 
4005     // Where the paragraph was inserted it has to be properly redrawn:
4006     // Where the paragraph was removed it has to be properly redrawn:
4007     // ( and correspondingly in between as well...)
4008     if ( pCurView && GetUpdateMode() )
4009     {
4010         // in this case one can redraw directly without invalidating the
4011         // Portions
4012         sal_Int32 nFirstPortion = std::min( static_cast<sal_Int32>(aOldPositions.Min()), nNewPos );
4013         sal_Int32 nLastPortion = std::max( static_cast<sal_Int32>(aOldPositions.Max()), nNewPos );
4014 
4015         ParaPortion* pUpperPortion = GetParaPortions().SafeGetObject( nFirstPortion );
4016         ParaPortion* pLowerPortion = GetParaPortions().SafeGetObject( nLastPortion );
4017         if (pUpperPortion && pLowerPortion)
4018         {
4019             aInvalidRect = tools::Rectangle();  // make empty
4020             aInvalidRect.SetLeft( 0 );
4021             aInvalidRect.SetRight( aPaperSize.Width() );
4022             aInvalidRect.SetTop( GetParaPortions().GetYOffset( pUpperPortion ) );
4023             aInvalidRect.SetBottom( GetParaPortions().GetYOffset( pLowerPortion ) + pLowerPortion->GetHeight() );
4024 
4025             UpdateViews( pCurView );
4026         }
4027     }
4028     else
4029     {
4030         // redraw from the upper invalid position
4031         sal_Int32 nFirstInvPara = std::min( static_cast<sal_Int32>(aOldPositions.Min()), nNewPos );
4032         InvalidateFromParagraph( nFirstInvPara );
4033     }
4034     return aSel;
4035 }
4036 
4037 void ImpEditEngine::InvalidateFromParagraph( sal_Int32 nFirstInvPara )
4038 {
4039     // The following paragraphs are not invalidated, since ResetHeight()
4040     // => size change => all the following are re-issued anyway.
4041     ParaPortion* pTmpPortion;
4042     if ( nFirstInvPara != 0 )
4043     {
4044         pTmpPortion = GetParaPortions()[nFirstInvPara-1];
4045         pTmpPortion->MarkInvalid( pTmpPortion->GetNode()->Len(), 0 );
4046     }
4047     else
4048     {
4049         pTmpPortion = GetParaPortions()[0];
4050         pTmpPortion->MarkSelectionInvalid( 0 );
4051     }
4052     pTmpPortion->ResetHeight();
4053 }
4054 
4055 IMPL_LINK_NOARG(ImpEditEngine, StatusTimerHdl, Timer *, void)
4056 {
4057     CallStatusHdl();
4058 }
4059 
4060 void ImpEditEngine::CallStatusHdl()
4061 {
4062     if ( aStatusHdlLink.IsSet() && bool(aStatus.GetStatusWord()) )
4063     {
4064         // The Status has to be reset before the Call,
4065         // since other Flags might be set in the handler...
4066         EditStatus aTmpStatus( aStatus );
4067         aStatus.Clear();
4068         aStatusHdlLink.Call( aTmpStatus );
4069         aStatusTimer.Stop();    // If called by hand...
4070     }
4071 }
4072 
4073 ContentNode* ImpEditEngine::GetPrevVisNode( ContentNode const * pCurNode )
4074 {
4075     const ParaPortion* pPortion = FindParaPortion( pCurNode );
4076     DBG_ASSERT( pPortion, "GetPrevVisibleNode: No matching portion!" );
4077     pPortion = GetPrevVisPortion( pPortion );
4078     if ( pPortion )
4079         return pPortion->GetNode();
4080     return nullptr;
4081 }
4082 
4083 ContentNode* ImpEditEngine::GetNextVisNode( ContentNode const * pCurNode )
4084 {
4085     const ParaPortion* pPortion = FindParaPortion( pCurNode );
4086     DBG_ASSERT( pPortion, "GetNextVisibleNode: No matching portion!" );
4087     pPortion = GetNextVisPortion( pPortion );
4088     if ( pPortion )
4089         return pPortion->GetNode();
4090     return nullptr;
4091 }
4092 
4093 const ParaPortion* ImpEditEngine::GetPrevVisPortion( const ParaPortion* pCurPortion ) const
4094 {
4095     sal_Int32 nPara = GetParaPortions().GetPos( pCurPortion );
4096     DBG_ASSERT( nPara < GetParaPortions().Count() , "Portion not found: GetPrevVisPortion" );
4097     const ParaPortion* pPortion = nPara ? GetParaPortions()[--nPara] : nullptr;
4098     while ( pPortion && !pPortion->IsVisible() )
4099         pPortion = nPara ? GetParaPortions()[--nPara] : nullptr;
4100 
4101     return pPortion;
4102 }
4103 
4104 const ParaPortion* ImpEditEngine::GetNextVisPortion( const ParaPortion* pCurPortion ) const
4105 {
4106     sal_Int32 nPara = GetParaPortions().GetPos( pCurPortion );
4107     DBG_ASSERT( nPara < GetParaPortions().Count() , "Portion not found: GetPrevVisNode" );
4108     const ParaPortion* pPortion = GetParaPortions().SafeGetObject( ++nPara );
4109     while ( pPortion && !pPortion->IsVisible() )
4110         pPortion = GetParaPortions().SafeGetObject( ++nPara );
4111 
4112     return pPortion;
4113 }
4114 
4115 long ImpEditEngine::CalcVertLineSpacing(Point& rStartPos) const
4116 {
4117     long nTotalOccupiedHeight = 0;
4118     sal_Int32 nTotalLineCount = 0;
4119     const ParaPortionList& rParaPortions = GetParaPortions();
4120     sal_Int32 nParaCount = rParaPortions.Count();
4121 
4122     for (sal_Int32 i = 0; i < nParaCount; ++i)
4123     {
4124         if (GetVerJustification(i) != SvxCellVerJustify::Block)
4125             // All paragraphs must have the block justification set.
4126             return 0;
4127 
4128         const ParaPortion* pPortion = rParaPortions[i];
4129         nTotalOccupiedHeight += pPortion->GetFirstLineOffset();
4130 
4131         const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL);
4132         sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix )
4133                             ? GetYValue( rLSItem.GetInterLineSpace() ) : 0;
4134 
4135         const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE);
4136         long nUL = GetYValue( rULItem.GetLower() );
4137 
4138         const EditLineList& rLines = pPortion->GetLines();
4139         sal_Int32 nLineCount = rLines.Count();
4140         nTotalLineCount += nLineCount;
4141         for (sal_Int32 j = 0; j < nLineCount; ++j)
4142         {
4143             const EditLine& rLine = rLines[j];
4144             nTotalOccupiedHeight += rLine.GetHeight();
4145             if (j < nLineCount-1)
4146                 nTotalOccupiedHeight += nSBL;
4147             nTotalOccupiedHeight += nUL;
4148         }
4149     }
4150 
4151     long nTotalSpace = IsVertical() ? aPaperSize.Width() : aPaperSize.Height();
4152     nTotalSpace -= nTotalOccupiedHeight;
4153     if (nTotalSpace <= 0 || nTotalLineCount <= 1)
4154         return 0;
4155 
4156     if (IsVertical())
4157     {
4158         if( IsTopToBottom() )
4159             // Shift the text to the right for the asian layout mode.
4160             rStartPos.AdjustX(nTotalSpace );
4161         else
4162             rStartPos.AdjustX( -nTotalSpace );
4163     }
4164 
4165     return nTotalSpace / (nTotalLineCount-1);
4166 }
4167 
4168 EditPaM ImpEditEngine::InsertParagraph( sal_Int32 nPara )
4169 {
4170     EditPaM aPaM;
4171     if ( nPara != 0 )
4172     {
4173         ContentNode* pNode = GetEditDoc().GetObject( nPara-1 );
4174         if ( !pNode )
4175             pNode = GetEditDoc().GetObject( GetEditDoc().Count() - 1 );
4176         assert(pNode && "Not a single paragraph in InsertParagraph ?");
4177         aPaM = EditPaM( pNode, pNode->Len() );
4178     }
4179     else
4180     {
4181         ContentNode* pNode = GetEditDoc().GetObject( 0 );
4182         aPaM = EditPaM( pNode, 0 );
4183     }
4184 
4185     return ImpInsertParaBreak( aPaM );
4186 }
4187 
4188 std::unique_ptr<EditSelection> ImpEditEngine::SelectParagraph( sal_Int32 nPara )
4189 {
4190     std::unique_ptr<EditSelection> pSel;
4191     ContentNode* pNode = GetEditDoc().GetObject( nPara );
4192     SAL_WARN_IF( !pNode, "editeng", "Paragraph does not exist: SelectParagraph" );
4193     if ( pNode )
4194         pSel.reset(new EditSelection( EditPaM( pNode, 0 ), EditPaM( pNode, pNode->Len() ) ));
4195 
4196     return pSel;
4197 }
4198 
4199 void ImpEditEngine::FormatAndUpdate( EditView* pCurView, bool bCalledFromUndo )
4200 {
4201     if ( bDowning )
4202         return ;
4203 
4204     if ( IsInUndo() )
4205         IdleFormatAndUpdate( pCurView );
4206     else
4207     {
4208         if (bCalledFromUndo)
4209             // in order to make bullet points that have had their styles changed, redraw themselves
4210             for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ )
4211                 GetParaPortions()[nPortion]->MarkInvalid( 0, 0 );
4212         FormatDoc();
4213         UpdateViews( pCurView );
4214     }
4215 
4216     EENotify aNotify(EE_NOTIFY_PROCESSNOTIFICATIONS);
4217     GetNotifyHdl().Call(aNotify);
4218 }
4219 
4220 void ImpEditEngine::SetFlatMode( bool bFlat )
4221 {
4222     if ( bFlat != aStatus.UseCharAttribs() )
4223         return;
4224 
4225     if ( !bFlat )
4226         aStatus.TurnOnFlags( EEControlBits::USECHARATTRIBS );
4227     else
4228         aStatus.TurnOffFlags( EEControlBits::USECHARATTRIBS );
4229 
4230     aEditDoc.CreateDefFont( !bFlat );
4231 
4232     FormatFullDoc();
4233     UpdateViews();
4234     if ( pActiveView )
4235         pActiveView->ShowCursor();
4236 }
4237 
4238 void ImpEditEngine::SetCharStretching( sal_uInt16 nX, sal_uInt16 nY )
4239 {
4240     bool bChanged(false);
4241     if ( !IsVertical() )
4242     {
4243         bChanged = nStretchX!=nX || nStretchY!=nY;
4244         nStretchX = nX;
4245         nStretchY = nY;
4246     }
4247     else
4248     {
4249         bChanged = nStretchX!=nY || nStretchY!=nX;
4250         nStretchX = nY;
4251         nStretchY = nX;
4252     }
4253 
4254     if (bChanged && aStatus.DoStretch())
4255     {
4256         FormatFullDoc();
4257         // (potentially) need everything redrawn
4258         aInvalidRect=tools::Rectangle(0,0,1000000,1000000);
4259         UpdateViews( GetActiveView() );
4260     }
4261 }
4262 
4263 const SvxNumberFormat* ImpEditEngine::GetNumberFormat( const ContentNode *pNode ) const
4264 {
4265     const SvxNumberFormat *pRes = nullptr;
4266 
4267     if (pNode)
4268     {
4269         // get index of paragraph
4270         sal_Int32 nPara = GetEditDoc().GetPos( pNode );
4271         DBG_ASSERT( nPara < EE_PARA_NOT_FOUND, "node not found in array" );
4272         if (nPara < EE_PARA_NOT_FOUND)
4273         {
4274             // the called function may be overridden by an OutlinerEditEng
4275             // object to provide
4276             // access to the SvxNumberFormat of the Outliner.
4277             // The EditEngine implementation will just return 0.
4278             pRes = pEditEngine->GetNumberFormat( nPara );
4279         }
4280     }
4281 
4282     return pRes;
4283 }
4284 
4285 sal_Int32 ImpEditEngine::GetSpaceBeforeAndMinLabelWidth(
4286     const ContentNode *pNode,
4287     sal_Int32 *pnSpaceBefore, sal_Int32 *pnMinLabelWidth ) const
4288 {
4289     // nSpaceBefore     matches the ODF attribute text:space-before
4290     // nMinLabelWidth   matches the ODF attribute text:min-label-width
4291 
4292     const SvxNumberFormat *pNumFmt = GetNumberFormat( pNode );
4293 
4294     // if no number format was found we have no Outliner or the numbering level
4295     // within the Outliner is -1 which means no number format should be applied.
4296     // Thus the default values to be returned are 0.
4297     sal_Int32 nSpaceBefore   = 0;
4298     sal_Int32 nMinLabelWidth = 0;
4299 
4300     if (pNumFmt)
4301     {
4302         nMinLabelWidth = -pNumFmt->GetFirstLineOffset();
4303         nSpaceBefore   = pNumFmt->GetAbsLSpace() - nMinLabelWidth;
4304         DBG_ASSERT( nMinLabelWidth >= 0, "ImpEditEngine::GetSpaceBeforeAndMinLabelWidth: min-label-width < 0 encountered" );
4305     }
4306     if (pnSpaceBefore)
4307         *pnSpaceBefore      = nSpaceBefore;
4308     if (pnMinLabelWidth)
4309         *pnMinLabelWidth    = nMinLabelWidth;
4310 
4311     return nSpaceBefore + nMinLabelWidth;
4312 }
4313 
4314 const SvxLRSpaceItem& ImpEditEngine::GetLRSpaceItem( ContentNode* pNode )
4315 {
4316     return pNode->GetContentAttribs().GetItem( aStatus.IsOutliner() ? EE_PARA_OUTLLRSPACE : EE_PARA_LRSPACE );
4317 }
4318 
4319 // select a representative text language for the digit type according to the
4320 // text numeral setting:
4321 LanguageType ImpEditEngine::ImplCalcDigitLang(LanguageType eCurLang) const
4322 {
4323     if (utl::ConfigManager::IsFuzzing())
4324         return LANGUAGE_ENGLISH_US;
4325 
4326     // #114278# Also setting up digit language from Svt options
4327     // (cannot reliably inherit the outdev's setting)
4328     if( !pCTLOptions )
4329         pCTLOptions.reset( new SvtCTLOptions );
4330 
4331     LanguageType eLang = eCurLang;
4332     const SvtCTLOptions::TextNumerals nCTLTextNumerals = pCTLOptions->GetCTLTextNumerals();
4333 
4334     if ( SvtCTLOptions::NUMERALS_HINDI == nCTLTextNumerals )
4335         eLang = LANGUAGE_ARABIC_SAUDI_ARABIA;
4336     else if ( SvtCTLOptions::NUMERALS_ARABIC == nCTLTextNumerals )
4337         eLang = LANGUAGE_ENGLISH;
4338     else if ( SvtCTLOptions::NUMERALS_SYSTEM == nCTLTextNumerals )
4339         eLang = Application::GetSettings().GetLanguageTag().getLanguageType();
4340 
4341     return eLang;
4342 }
4343 
4344 OUString ImpEditEngine::convertDigits(const OUString &rString, sal_Int32 nStt, sal_Int32 nLen, LanguageType eDigitLang)
4345 {
4346     OUStringBuffer aBuf(rString);
4347     for (sal_Int32 nIdx = nStt, nEnd = nStt + nLen; nIdx < nEnd; ++nIdx)
4348     {
4349         sal_Unicode cChar = aBuf[nIdx];
4350         if (cChar >= '0' && cChar <= '9')
4351             aBuf[nIdx] = GetLocalizedChar(cChar, eDigitLang);
4352     }
4353     return aBuf.makeStringAndClear();
4354 }
4355 
4356 // Either sets the digit mode at the output device
4357 void ImpEditEngine::ImplInitDigitMode(OutputDevice* pOutDev, LanguageType eCurLang)
4358 {
4359     assert(pOutDev); //presumably there isn't any case where pOutDev should be NULL?
4360     if (pOutDev)
4361         pOutDev->SetDigitLanguage(ImplCalcDigitLang(eCurLang));
4362 }
4363 
4364 void ImpEditEngine::ImplInitLayoutMode( OutputDevice* pOutDev, sal_Int32 nPara, sal_Int32 nIndex )
4365 {
4366     bool bCTL = false;
4367     bool bR2L = false;
4368     if ( nIndex == -1 )
4369     {
4370         bCTL = HasScriptType( nPara, i18n::ScriptType::COMPLEX );
4371         bR2L = IsRightToLeft( nPara );
4372     }
4373     else
4374     {
4375         ContentNode* pNode = GetEditDoc().GetObject( nPara );
4376         short nScriptType = GetI18NScriptType( EditPaM( pNode, nIndex+1 ) );
4377         bCTL = nScriptType == i18n::ScriptType::COMPLEX;
4378         // this change was discussed in issue 37190
4379         bR2L = (GetRightToLeft( nPara, nIndex + 1) % 2) != 0;
4380         // it also works for issue 55927
4381     }
4382 
4383     ComplexTextLayoutFlags nLayoutMode = pOutDev->GetLayoutMode();
4384 
4385     // We always use the left position for DrawText()
4386     nLayoutMode &= ~ComplexTextLayoutFlags::BiDiRtl;
4387 
4388     if ( !bCTL && !bR2L)
4389     {
4390         // No Bidi checking necessary
4391         nLayoutMode |= ComplexTextLayoutFlags::BiDiStrong;
4392     }
4393     else
4394     {
4395         // Bidi checking necessary
4396         // Don't use BIDI_STRONG, VCL must do some checks.
4397         nLayoutMode &= ~ComplexTextLayoutFlags::BiDiStrong;
4398 
4399         if ( bR2L )
4400             nLayoutMode |= ComplexTextLayoutFlags::BiDiRtl|ComplexTextLayoutFlags::TextOriginLeft;
4401     }
4402 
4403     pOutDev->SetLayoutMode( nLayoutMode );
4404 
4405     // #114278# Also setting up digit language from Svt options
4406     // (cannot reliably inherit the outdev's setting)
4407     LanguageType eLang = Application::GetSettings().GetLanguageTag().getLanguageType();
4408     ImplInitDigitMode( pOutDev, eLang );
4409 }
4410 
4411 Reference < i18n::XBreakIterator > const & ImpEditEngine::ImplGetBreakIterator() const
4412 {
4413     if ( !xBI.is() )
4414     {
4415         Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
4416         xBI = i18n::BreakIterator::create( xContext );
4417     }
4418     return xBI;
4419 }
4420 
4421 Reference < i18n::XExtendedInputSequenceChecker > const & ImpEditEngine::ImplGetInputSequenceChecker() const
4422 {
4423     if ( !xISC.is() )
4424     {
4425         Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
4426         xISC = i18n::InputSequenceChecker::create( xContext );
4427     }
4428     return xISC;
4429 }
4430 
4431 Color ImpEditEngine::GetAutoColor() const
4432 {
4433     Color aColor = GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor;
4434 
4435     if ( GetBackgroundColor() != COL_AUTO )
4436     {
4437         if ( GetBackgroundColor().IsDark() && aColor.IsDark() )
4438             aColor = COL_WHITE;
4439         else if ( GetBackgroundColor().IsBright() && aColor.IsBright() )
4440             aColor = COL_BLACK;
4441     }
4442 
4443     return aColor;
4444 }
4445 
4446 bool ImpEditEngine::ImplCalcAsianCompression(ContentNode* pNode,
4447                                              TextPortion* pTextPortion, sal_Int32 nStartPos,
4448                                              long* pDXArray, sal_uInt16 n100thPercentFromMax,
4449                                              bool bManipulateDXArray)
4450 {
4451     DBG_ASSERT( GetAsianCompressionMode() != CharCompressType::NONE, "ImplCalcAsianCompression - Why?" );
4452     DBG_ASSERT( pTextPortion->GetLen(), "ImplCalcAsianCompression - Empty Portion?" );
4453 
4454     // Percent is 1/100 Percent...
4455     if ( n100thPercentFromMax == 10000 )
4456         pTextPortion->SetExtraInfos( nullptr );
4457 
4458     bool bCompressed = false;
4459 
4460     if ( GetI18NScriptType( EditPaM( pNode, nStartPos+1 ) ) == i18n::ScriptType::ASIAN )
4461     {
4462         long nNewPortionWidth = pTextPortion->GetSize().Width();
4463         sal_Int32 nPortionLen = pTextPortion->GetLen();
4464         for ( sal_Int32 n = 0; n < nPortionLen; n++ )
4465         {
4466             AsianCompressionFlags nType = GetCharTypeForCompression( pNode->GetChar( n+nStartPos ) );
4467 
4468             bool bCompressPunctuation = ( nType == AsianCompressionFlags::PunctuationLeft ) || ( nType == AsianCompressionFlags::PunctuationRight );
4469             bool bCompressKana = ( nType == AsianCompressionFlags::Kana ) && ( GetAsianCompressionMode() == CharCompressType::PunctuationAndKana );
4470 
4471             // create Extra infos only if needed...
4472             if ( bCompressPunctuation || bCompressKana )
4473             {
4474                 if ( !pTextPortion->GetExtraInfos() )
4475                 {
4476                     ExtraPortionInfo* pExtraInfos = new ExtraPortionInfo;
4477                     pTextPortion->SetExtraInfos( pExtraInfos );
4478                     pExtraInfos->nOrgWidth = pTextPortion->GetSize().Width();
4479                     pExtraInfos->nAsianCompressionTypes = AsianCompressionFlags::Normal;
4480                 }
4481                 pTextPortion->GetExtraInfos()->nMaxCompression100thPercent = n100thPercentFromMax;
4482                 pTextPortion->GetExtraInfos()->nAsianCompressionTypes |= nType;
4483 
4484                 long nOldCharWidth;
4485                 if ( (n+1) < nPortionLen )
4486                 {
4487                     nOldCharWidth = pDXArray[n];
4488                 }
4489                 else
4490                 {
4491                     if ( bManipulateDXArray )
4492                         nOldCharWidth = nNewPortionWidth - pTextPortion->GetExtraInfos()->nPortionOffsetX;
4493                     else
4494                         nOldCharWidth = pTextPortion->GetExtraInfos()->nOrgWidth;
4495                 }
4496                 nOldCharWidth -= ( n ? pDXArray[n-1] : 0 );
4497 
4498                 long nCompress = 0;
4499 
4500                 if ( bCompressPunctuation )
4501                 {
4502                     nCompress = nOldCharWidth / 2;
4503                 }
4504                 else // Kana
4505                 {
4506                     nCompress = nOldCharWidth / 10;
4507                 }
4508 
4509                 if ( n100thPercentFromMax != 10000 )
4510                 {
4511                     nCompress *= n100thPercentFromMax;
4512                     nCompress /= 10000;
4513                 }
4514 
4515                 if ( nCompress )
4516                 {
4517                     bCompressed = true;
4518                     nNewPortionWidth -= nCompress;
4519                     pTextPortion->GetExtraInfos()->bCompressed = true;
4520 
4521 
4522                     // Special handling for rightpunctuation: For the 'compression' we must
4523                     // start the output before the normal char position...
4524                     if ( bManipulateDXArray && ( pTextPortion->GetLen() > 1 ) )
4525                     {
4526                         if ( !pTextPortion->GetExtraInfos()->pOrgDXArray )
4527                             pTextPortion->GetExtraInfos()->SaveOrgDXArray( pDXArray, pTextPortion->GetLen()-1 );
4528 
4529                         if ( nType == AsianCompressionFlags::PunctuationRight )
4530                         {
4531                             // If it's the first char, I must handle it in Paint()...
4532                             if ( n )
4533                             {
4534                                 // -1: No entry for the last character
4535                                 for ( sal_Int32 i = n-1; i < (nPortionLen-1); i++ )
4536                                     pDXArray[i] -= nCompress;
4537                             }
4538                             else
4539                             {
4540                                 pTextPortion->GetExtraInfos()->bFirstCharIsRightPunktuation = true;
4541                                 pTextPortion->GetExtraInfos()->nPortionOffsetX = -nCompress;
4542                             }
4543                         }
4544                         else
4545                         {
4546                             // -1: No entry for the last character
4547                             for ( sal_Int32 i = n; i < (nPortionLen-1); i++ )
4548                                 pDXArray[i] -= nCompress;
4549                         }
4550                     }
4551                 }
4552             }
4553         }
4554 
4555         if ( bCompressed && ( n100thPercentFromMax == 10000 ) )
4556             pTextPortion->GetExtraInfos()->nWidthFullCompression = nNewPortionWidth;
4557 
4558         pTextPortion->GetSize().setWidth( nNewPortionWidth );
4559 
4560         if ( pTextPortion->GetExtraInfos() && ( n100thPercentFromMax != 10000 ) )
4561         {
4562             // Maybe rounding errors in nNewPortionWidth, assure that width not bigger than expected
4563             long nShrink = pTextPortion->GetExtraInfos()->nOrgWidth - pTextPortion->GetExtraInfos()->nWidthFullCompression;
4564             nShrink *= n100thPercentFromMax;
4565             nShrink /= 10000;
4566             long nNewWidth = pTextPortion->GetExtraInfos()->nOrgWidth - nShrink;
4567             if ( nNewWidth < pTextPortion->GetSize().Width() )
4568             pTextPortion->GetSize().setWidth( nNewWidth );
4569         }
4570     }
4571     return bCompressed;
4572 }
4573 
4574 
4575 void ImpEditEngine::ImplExpandCompressedPortions( EditLine* pLine, ParaPortion* pParaPortion, long nRemainingWidth )
4576 {
4577     bool bFoundCompressedPortion = false;
4578     long nCompressed = 0;
4579     std::vector<TextPortion*> aCompressedPortions;
4580 
4581     sal_Int32 nPortion = pLine->GetEndPortion();
4582     TextPortion* pTP = &pParaPortion->GetTextPortions()[ nPortion ];
4583     while ( pTP && ( pTP->GetKind() == PortionKind::TEXT ) )
4584     {
4585         if ( pTP->GetExtraInfos() && pTP->GetExtraInfos()->bCompressed )
4586         {
4587             bFoundCompressedPortion = true;
4588             nCompressed += pTP->GetExtraInfos()->nOrgWidth - pTP->GetSize().Width();
4589             aCompressedPortions.push_back(pTP);
4590         }
4591         pTP = ( nPortion > pLine->GetStartPortion() ) ? &pParaPortion->GetTextPortions()[ --nPortion ] : nullptr;
4592     }
4593 
4594     if ( bFoundCompressedPortion )
4595     {
4596         long nCompressPercent = 0;
4597         if ( nCompressed > nRemainingWidth )
4598         {
4599             nCompressPercent = nCompressed - nRemainingWidth;
4600             DBG_ASSERT( nCompressPercent < 200000, "ImplExpandCompressedPortions - Overflow!" );
4601             nCompressPercent *= 10000;
4602             nCompressPercent /= nCompressed;
4603         }
4604 
4605         for (TextPortion* pTP2 : aCompressedPortions)
4606         {
4607             pTP = pTP2;
4608             pTP->GetExtraInfos()->bCompressed = false;
4609             pTP->GetSize().setWidth( pTP->GetExtraInfos()->nOrgWidth );
4610             if ( nCompressPercent )
4611             {
4612                 sal_Int32 nTxtPortion = pParaPortion->GetTextPortions().GetPos( pTP );
4613                 sal_Int32 nTxtPortionStart = pParaPortion->GetTextPortions().GetStartPos( nTxtPortion );
4614                 DBG_ASSERT( nTxtPortionStart >= pLine->GetStart(), "Portion doesn't belong to the line!!!" );
4615                 long* pDXArray = pLine->GetCharPosArray().data() + (nTxtPortionStart - pLine->GetStart());
4616                 if ( pTP->GetExtraInfos()->pOrgDXArray )
4617                     memcpy( pDXArray, pTP->GetExtraInfos()->pOrgDXArray.get(), (pTP->GetLen()-1)*sizeof(sal_Int32) );
4618                 ImplCalcAsianCompression( pParaPortion->GetNode(), pTP, nTxtPortionStart, pDXArray, static_cast<sal_uInt16>(nCompressPercent), true );
4619             }
4620         }
4621     }
4622 }
4623 
4624 void ImpEditEngine::ImplUpdateOverflowingParaNum(sal_uInt32 nPaperHeight)
4625 {
4626     sal_uInt32 nY = 0;
4627     sal_uInt32 nPH;
4628 
4629     for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) {
4630         ParaPortion* pPara = GetParaPortions()[nPara];
4631         nPH = pPara->GetHeight();
4632         nY += nPH;
4633         if ( nY > nPaperHeight /*nCurTextHeight*/ ) // found first paragraph overflowing
4634         {
4635             mnOverflowingPara = nPara;
4636             SAL_INFO("editeng.chaining", "[CHAINING] Setting first overflowing #Para#: " << nPara);
4637             ImplUpdateOverflowingLineNum( nPaperHeight, nPara, nY-nPH);
4638             return;
4639         }
4640     }
4641 }
4642 
4643 void ImpEditEngine::ImplUpdateOverflowingLineNum(sal_uInt32 nPaperHeight,
4644                                              sal_uInt32 nOverflowingPara,
4645                                              sal_uInt32 nHeightBeforeOverflowingPara)
4646 {
4647     sal_uInt32 nY = nHeightBeforeOverflowingPara;
4648     sal_uInt32 nLH;
4649 
4650     ParaPortion *pPara = GetParaPortions()[nOverflowingPara];
4651 
4652     // Like UpdateOverflowingParaNum but for each line in the first
4653     //  overflowing paragraph.
4654     for ( sal_Int32 nLine = 0; nLine < pPara->GetLines().Count(); nLine++ ) {
4655         // XXX: We must use a reference here because the copy constructor resets the height
4656         EditLine &aLine = pPara->GetLines()[nLine];
4657         nLH = aLine.GetHeight();
4658         nY += nLH;
4659 
4660         // Debugging output
4661         if (nLine == 0) {
4662             SAL_INFO("editeng.chaining", "[CHAINING] First line has height " << nLH);
4663         }
4664 
4665         if ( nY > nPaperHeight ) // found first line overflowing
4666         {
4667             mnOverflowingLine = nLine;
4668             SAL_INFO("editeng.chaining", "[CHAINING] Setting first overflowing -Line- to: " << nLine);
4669             return;
4670         }
4671     }
4672 
4673     assert(false && "You should never get here");
4674 }
4675 
4676 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
4677