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