xref: /core/sw/source/core/text/itrform2.cxx (revision 56e95f8e)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <hintids.hxx>
21 
22 #include <memory>
23 #include <com/sun/star/i18n/ScriptType.hpp>
24 #include <editeng/lspcitem.hxx>
25 #include <dcontact.hxx>
26 #include <txtflcnt.hxx>
27 #include <txtftn.hxx>
28 #include <flyfrms.hxx>
29 #include <fmtflcnt.hxx>
30 #include <fmtftn.hxx>
31 #include <ftninfo.hxx>
32 #include <charfmt.hxx>
33 #include <editeng/charrotateitem.hxx>
34 #include <layfrm.hxx>
35 #include <viewsh.hxx>
36 #include <viewopt.hxx>
37 #include <paratr.hxx>
38 #include "itrform2.hxx"
39 #include "porrst.hxx"
40 #include "portab.hxx"
41 #include "porfly.hxx"
42 #include "portox.hxx"
43 #include "porref.hxx"
44 #include "porfld.hxx"
45 #include "porftn.hxx"
46 #include "porhyph.hxx"
47 #include "pordrop.hxx"
48 #include "guess.hxx"
49 #include <blink.hxx>
50 #include <ftnfrm.hxx>
51 #include "redlnitr.hxx"
52 #include <pagefrm.hxx>
53 #include <pagedesc.hxx>
54 #include <tgrditem.hxx>
55 #include <doc.hxx>
56 #include "pormulti.hxx"
57 #include <unotools/charclass.hxx>
58 #include <xmloff/odffields.hxx>
59 #include <IDocumentSettingAccess.hxx>
60 #include <IMark.hxx>
61 #include <IDocumentMarkAccess.hxx>
62 #include <svl/zforlist.hxx>
63 
64 #include <vector>
65 
66 using namespace ::com::sun::star;
67 
68 namespace {
69     //! Calculates and sets optimal repaint offset for the current line
70     long lcl_CalcOptRepaint( SwTextFormatter &rThis,
71                                     SwLineLayout const &rCurr,
72                                     TextFrameIndex nOldLineEnd,
73                                     const std::vector<long> &rFlyStarts );
74     //! Determine if we need to build hidden portions
75     bool lcl_BuildHiddenPortion(const SwTextSizeInfo& rInf, TextFrameIndex &rPos);
76 
77     // Check whether the two font has the same border
78     bool lcl_HasSameBorder(const SwFont& rFirst, const SwFont& rSecond);
79 }
80 
81 static void ClearFly( SwTextFormatInfo &rInf )
82 {
83     delete rInf.GetFly();
84     rInf.SetFly(nullptr);
85 }
86 
87 void SwTextFormatter::CtorInitTextFormatter( SwTextFrame *pNewFrame, SwTextFormatInfo *pNewInf )
88 {
89     CtorInitTextPainter( pNewFrame, pNewInf );
90     m_pInf = pNewInf;
91     pDropFormat = GetInfo().GetDropFormat();
92     pMulti = nullptr;
93 
94     bOnceMore = false;
95     bFlyInCntBase = false;
96     bTruncLines = false;
97     nCntEndHyph = 0;
98     nCntMidHyph = 0;
99     nLeftScanIdx = TextFrameIndex(COMPLETE_STRING);
100     nRightScanIdx = TextFrameIndex(0);
101     m_pByEndIter.reset();
102     m_pFirstOfBorderMerge = nullptr;
103 
104     if (m_nStart > TextFrameIndex(GetInfo().GetText().getLength()))
105     {
106         OSL_ENSURE( false, "+SwTextFormatter::CTOR: bad offset" );
107         m_nStart = TextFrameIndex(GetInfo().GetText().getLength());
108     }
109 
110 }
111 
112 SwTextFormatter::~SwTextFormatter()
113 {
114     // Extremely unlikely, but still possible
115     // e.g.: field splits up, widows start to matter
116     if( GetInfo().GetRest() )
117     {
118         delete GetInfo().GetRest();
119         GetInfo().SetRest(nullptr);
120     }
121 }
122 
123 void SwTextFormatter::Insert( SwLineLayout *pLay )
124 {
125     // Insert BEHIND the current element
126     if ( m_pCurr )
127     {
128         pLay->SetNext( m_pCurr->GetNext() );
129         m_pCurr->SetNext( pLay );
130     }
131     else
132         m_pCurr = pLay;
133 }
134 
135 sal_uInt16 SwTextFormatter::GetFrameRstHeight() const
136 {
137     // We want the rest height relative to the page.
138     // If we're in a table, then pFrame->GetUpper() is not the page.
139 
140     // GetFrameRstHeight() is being called with Footnote.
141     // Wrong: const SwFrame *pUpper = pFrame->GetUpper();
142     const SwFrame *pPage = static_cast<const SwFrame*>(m_pFrame->FindPageFrame());
143     const SwTwips nHeight = pPage->getFrameArea().Top()
144                           + pPage->getFramePrintArea().Top()
145                           + pPage->getFramePrintArea().Height() - Y();
146     if( 0 > nHeight )
147         return m_pCurr->Height();
148     else
149         return sal_uInt16( nHeight );
150 }
151 
152 SwLinePortion *SwTextFormatter::Underflow( SwTextFormatInfo &rInf )
153 {
154     // Save values and initialize rInf
155     SwLinePortion *pUnderflow = rInf.GetUnderflow();
156     if( !pUnderflow )
157         return nullptr;
158 
159     // We format backwards, i.e. attribute changes can happen the next
160     // line again.
161     // Can be seen in 8081.sdw, if you enter text in the first line
162 
163     TextFrameIndex const nSoftHyphPos = rInf.GetSoftHyphPos();
164     TextFrameIndex const nUnderScorePos = rInf.GetUnderScorePos();
165 
166     // Save flys and set to 0, or else segmentation fault
167     // Not ClearFly(rInf) !
168     SwFlyPortion *pFly = rInf.GetFly();
169     rInf.SetFly( nullptr );
170 
171     FeedInf( rInf );
172     rInf.SetLast( m_pCurr );
173     // pUnderflow does not need to be deleted, because it will drown in the following
174     // Truncate()
175     rInf.SetUnderflow(nullptr);
176     rInf.SetSoftHyphPos( nSoftHyphPos );
177     rInf.SetUnderScorePos( nUnderScorePos );
178     rInf.SetPaintOfst( GetLeftMargin() );
179 
180     // We look for the portion with the under-flow position
181     SwLinePortion *pPor = m_pCurr->GetFirstPortion();
182     if( pPor != pUnderflow )
183     {
184         // pPrev will be the last portion before pUnderflow,
185         // which still has a real width.
186         // Exception: SoftHyphPortion must not be forgotten, of course!
187         // Although they don't have a width.
188         SwLinePortion *pTmpPrev = pPor;
189         while( pPor && pPor != pUnderflow )
190         {
191             if( !pPor->IsKernPortion() &&
192                 ( pPor->Width() || pPor->IsSoftHyphPortion() ) )
193             {
194                 while( pTmpPrev != pPor )
195                 {
196                     pTmpPrev->Move( rInf );
197                     rInf.SetLast( pTmpPrev );
198                     pTmpPrev = pTmpPrev->GetNextPortion();
199                     OSL_ENSURE( pTmpPrev, "Underflow: losing control!" );
200                 };
201             }
202             pPor = pPor->GetNextPortion();
203         }
204         pPor = pTmpPrev;
205         if( pPor && // Skip flys and initials when underflow.
206             ( pPor->IsFlyPortion() || pPor->IsDropPortion() ||
207               pPor->IsFlyCntPortion() ) )
208         {
209             pPor->Move( rInf );
210             rInf.SetLast( pPor );
211             rInf.SetStopUnderflow( true );
212             pPor = pUnderflow;
213         }
214     }
215 
216     // What? The under-flow portion is not in the portion chain?
217     OSL_ENSURE( pPor, "SwTextFormatter::Underflow: overflow but underflow" );
218 
219     // Snapshot
220     if ( pPor==rInf.GetLast() )
221     {
222         // We end up here, if the portion triggering the under-flow
223         // spans over the whole line. E.g. if a word spans across
224         // multiple lines and flows into a fly in the second line.
225         rInf.SetFly( pFly );
226         pPor->Truncate();
227         return pPor; // Is that enough?
228     }
229     // End the snapshot
230 
231     // X + Width == 0 with SoftHyph > Line?!
232     if( !pPor || !(rInf.X() + pPor->Width()) )
233     {
234         delete pFly;
235         return nullptr;
236     }
237 
238     // Preparing for Format()
239     // We need to chip off the chain behind pLast, because we Insert after the Format()
240     SeekAndChg( rInf );
241 
242     // line width is adjusted, so that pPor does not fit to current
243     // line anymore
244     rInf.Width( static_cast<sal_uInt16>(rInf.X() + (pPor->Width() ? pPor->Width() - 1 : 0)) );
245     rInf.SetLen( pPor->GetLen() );
246     rInf.SetFull( false );
247     if( pFly )
248     {
249         // We need to recalculate the FlyPortion due to the following reason:
250         // If the base line is lowered by a big font in the middle of the line,
251         // causing overlapping with a fly, the FlyPortion has a wrong size/fixed
252         // size.
253         rInf.SetFly( pFly );
254         CalcFlyWidth( rInf );
255     }
256     rInf.GetLast()->SetNextPortion(nullptr);
257 
258     // The SwLineLayout is an exception to this, which splits at the first
259     // portion change.
260     // Here only the other way around:
261     if( rInf.GetLast() == m_pCurr )
262     {
263         if( pPor->InTextGrp() && !pPor->InExpGrp() )
264         {
265             const PortionType nOldWhich = m_pCurr->GetWhichPor();
266             *static_cast<SwLinePortion*>(m_pCurr) = *pPor;
267             m_pCurr->SetNextPortion( pPor->GetNextPortion() );
268             m_pCurr->SetWhichPor( nOldWhich );
269             pPor->SetNextPortion( nullptr );
270             delete pPor;
271             pPor = m_pCurr;
272         }
273     }
274 
275     // Make sure that m_pFirstOfBorderMerge does not point to a portion which
276     // will be deleted by Truncate() below.
277     SwLinePortion* pNext = pPor->GetNextPortion();
278     while (pNext)
279     {
280         if (pNext == m_pFirstOfBorderMerge)
281         {
282             m_pFirstOfBorderMerge = nullptr;
283             break;
284         }
285         pNext = pNext->GetNextPortion();
286     }
287     pPor->Truncate();
288     SwLinePortion *const pRest( rInf.GetRest() );
289     if (pRest && pRest->InFieldGrp() &&
290         static_cast<SwFieldPortion*>(pRest)->IsNoLength())
291     {
292         // HACK: decrement again, so we pick up the suffix in next line!
293         m_pByEndIter->PrevAttr();
294     }
295     delete pRest;
296     rInf.SetRest(nullptr);
297     return pPor;
298 }
299 
300 void SwTextFormatter::InsertPortion( SwTextFormatInfo &rInf,
301                                     SwLinePortion *pPor )
302 {
303     SwLinePortion *pLast = nullptr;
304     // The new portion is inserted, but everything's different for
305     // LineLayout...
306     if( pPor == m_pCurr )
307     {
308         if ( m_pCurr->GetNextPortion() )
309         {
310             pLast = pPor;
311             pPor = m_pCurr->GetNextPortion();
312         }
313 
314         // i#112181 - Prevent footnote anchor being wrapped to next line
315         // without preceding word
316         rInf.SetOtherThanFootnoteInside( rInf.IsOtherThanFootnoteInside() || !pPor->IsFootnotePortion() );
317     }
318     else
319     {
320         pLast = rInf.GetLast();
321         if( pLast->GetNextPortion() )
322         {
323             while( pLast->GetNextPortion() )
324                 pLast = pLast->GetNextPortion();
325             rInf.SetLast( pLast );
326         }
327         pLast->Insert( pPor );
328 
329         rInf.SetOtherThanFootnoteInside( rInf.IsOtherThanFootnoteInside() || !pPor->IsFootnotePortion() );
330 
331         // Adjust maxima
332         if( m_pCurr->Height() < pPor->Height() )
333             m_pCurr->Height( pPor->Height() );
334         if( m_pCurr->GetAscent() < pPor->GetAscent() )
335             m_pCurr->SetAscent( pPor->GetAscent() );
336     }
337 
338     // Sometimes chains are constructed (e.g. by hyphenate)
339     rInf.SetLast( pPor );
340     while( pPor )
341     {
342         if (!pPor->IsDropPortion())
343             MergeCharacterBorder(*pPor, pLast, rInf);
344 
345         pPor->Move( rInf );
346         rInf.SetLast( pPor );
347         pLast = pPor;
348         pPor = pPor->GetNextPortion();
349     }
350 }
351 
352 void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf )
353 {
354     OSL_ENSURE( rInf.GetText().getLength() < COMPLETE_STRING,
355             "SwTextFormatter::BuildPortions: bad text length in info" );
356 
357     rInf.ChkNoHyph( CntEndHyph(), CntMidHyph() );
358 
359     // First NewTextPortion() decides whether pCurr ends up in pPor.
360     // We need to make sure that the font is being set in any case.
361     // This is done automatically in CalcAscent.
362     rInf.SetLast( m_pCurr );
363     rInf.ForcedLeftMargin( 0 );
364 
365     OSL_ENSURE( m_pCurr->FindLastPortion() == m_pCurr, "pLast supposed to equal pCurr" );
366 
367     if( !m_pCurr->GetAscent() && !m_pCurr->Height() )
368         CalcAscent( rInf, m_pCurr );
369 
370     SeekAndChg( rInf );
371 
372     // Width() is shortened in CalcFlyWidth if we have a FlyPortion
373     OSL_ENSURE( !rInf.X() || pMulti, "SwTextFormatter::BuildPortion X=0?" );
374     CalcFlyWidth( rInf );
375     SwFlyPortion *pFly = rInf.GetFly();
376     if( pFly )
377     {
378         if ( 0 < pFly->GetFix() )
379             ClearFly( rInf );
380         else
381             rInf.SetFull(true);
382     }
383 
384     SwLinePortion *pPor = NewPortion( rInf );
385 
386     // Asian grid stuff
387     SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
388     const bool bHasGrid = pGrid && rInf.SnapToGrid() &&
389                               GRID_LINES_CHARS == pGrid->GetGridType();
390 
391 
392     const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc();
393     const sal_uInt16 nGridWidth = bHasGrid ? GetGridWidth(*pGrid, rDoc) : 0;
394 
395     // used for grid mode only:
396     // the pointer is stored, because after formatting of non-asian text,
397     // the width of the kerning portion has to be adjusted
398     // Inserting a SwKernPortion before a SwTabPortion isn't necessary
399     // and will break the SwTabPortion.
400     SwKernPortion* pGridKernPortion = nullptr;
401 
402     bool bFull = false;
403     SwTwips nUnderLineStart = 0;
404     rInf.Y( Y() );
405 
406     while( pPor && !rInf.IsStop() )
407     {
408         OSL_ENSURE(rInf.GetLen() < TextFrameIndex(COMPLETE_STRING) &&
409                 rInf.GetIdx() <= TextFrameIndex(rInf.GetText().getLength()),
410                 "SwTextFormatter::BuildPortions: bad length in info" );
411 
412         // We have to check the script for fields in order to set the
413         // correct nActual value for the font.
414         if( pPor->InFieldGrp() )
415             static_cast<SwFieldPortion*>(pPor)->CheckScript( rInf );
416 
417         if( ! bHasGrid && rInf.HasScriptSpace() &&
418             rInf.GetLast() && rInf.GetLast()->InTextGrp() &&
419             rInf.GetLast()->Width() && !rInf.GetLast()->InNumberGrp() )
420         {
421             SwFontScript nNxtActual = rInf.GetFont()->GetActual();
422             SwFontScript nLstActual = nNxtActual;
423             sal_uInt16 nLstHeight = static_cast<sal_uInt16>(rInf.GetFont()->GetHeight());
424             bool bAllowBehind = false;
425             const CharClass& rCC = GetAppCharClass();
426 
427             // are there any punctuation characters on both sides
428             // of the kerning portion?
429             if ( pPor->InFieldGrp() )
430             {
431                 OUString aAltText;
432                 if ( static_cast<SwFieldPortion*>(pPor)->GetExpText( rInf, aAltText ) &&
433                         !aAltText.isEmpty() )
434                 {
435                     bAllowBehind = rCC.isLetterNumeric( aAltText, 0 );
436 
437                     const SwFont* pTmpFnt = static_cast<SwFieldPortion*>(pPor)->GetFont();
438                     if ( pTmpFnt )
439                         nNxtActual = pTmpFnt->GetActual();
440                 }
441             }
442             else
443             {
444                 const OUString& rText = rInf.GetText();
445                 sal_Int32 nIdx = sal_Int32(rInf.GetIdx());
446                 bAllowBehind = nIdx < rText.getLength() && rCC.isLetterNumeric(rText, nIdx);
447             }
448 
449             const SwLinePortion* pLast = rInf.GetLast();
450             if ( bAllowBehind && pLast )
451             {
452                 bool bAllowBefore = false;
453 
454                 if ( pLast->InFieldGrp() )
455                 {
456                     OUString aAltText;
457                     if ( static_cast<const SwFieldPortion*>(pLast)->GetExpText( rInf, aAltText ) &&
458                          !aAltText.isEmpty() )
459                     {
460                         bAllowBefore = rCC.isLetterNumeric( aAltText, aAltText.getLength() - 1 );
461 
462                         const SwFont* pTmpFnt = static_cast<const SwFieldPortion*>(pLast)->GetFont();
463                         if ( pTmpFnt )
464                         {
465                             nLstActual = pTmpFnt->GetActual();
466                             nLstHeight = static_cast<sal_uInt16>(pTmpFnt->GetHeight());
467                         }
468                     }
469                 }
470                 else if ( rInf.GetIdx() )
471                 {
472                     bAllowBefore = rCC.isLetterNumeric(rInf.GetText(), sal_Int32(rInf.GetIdx()) - 1);
473                     // Note: ScriptType returns values in [1,4]
474                     if ( bAllowBefore )
475                         nLstActual = SwFontScript(m_pScriptInfo->ScriptType(rInf.GetIdx() - TextFrameIndex(1)) - 1);
476                 }
477 
478                 nLstHeight /= 5;
479                 // does the kerning portion still fit into the line?
480                 if( bAllowBefore && ( nLstActual != nNxtActual ) &&
481                     nLstHeight && rInf.X() + nLstHeight <= rInf.Width() &&
482                     ! pPor->InTabGrp() )
483                 {
484                     SwKernPortion* pKrn =
485                         new SwKernPortion( *rInf.GetLast(), nLstHeight,
486                                            pLast->InFieldGrp() && pPor->InFieldGrp() );
487                     rInf.GetLast()->SetNextPortion( nullptr );
488                     InsertPortion( rInf, pKrn );
489                 }
490             }
491         }
492         else if ( bHasGrid && pGrid->IsSnapToChars() && ! pGridKernPortion && ! pMulti && ! pPor->InTabGrp() )
493         {
494             // insert a grid kerning portion
495             pGridKernPortion = pPor->IsKernPortion() ?
496                                static_cast<SwKernPortion*>(pPor) :
497                                new SwKernPortion( *m_pCurr );
498 
499             // if we have a new GridKernPortion, we initially calculate
500             // its size so that its ends on the grid
501             const SwPageFrame* pPageFrame = m_pFrame->FindPageFrame();
502             const SwLayoutFrame* pBody = pPageFrame->FindBodyCont();
503             SwRectFnSet aRectFnSet(pPageFrame);
504 
505             const long nGridOrigin = pBody ?
506                                     aRectFnSet.GetPrtLeft(*pBody) :
507                                     aRectFnSet.GetPrtLeft(*pPageFrame);
508 
509             SwTwips nStartX = rInf.X() + GetLeftMargin();
510             if ( aRectFnSet.IsVert() )
511             {
512                 Point aPoint( nStartX, 0 );
513                 m_pFrame->SwitchHorizontalToVertical( aPoint );
514                 nStartX = aPoint.Y();
515             }
516 
517             const SwTwips nOfst = nStartX - nGridOrigin;
518             if ( nOfst )
519             {
520                 const sal_uLong i = ( nOfst > 0 ) ?
521                                 ( ( nOfst - 1 ) / nGridWidth + 1 ) :
522                                 0;
523                 const SwTwips nKernWidth = i * nGridWidth - nOfst;
524                 const SwTwips nRestWidth = rInf.Width() - rInf.X();
525 
526                 if ( nKernWidth <= nRestWidth )
527                     pGridKernPortion->Width( static_cast<sal_uInt16>(nKernWidth) );
528             }
529 
530             if ( pGridKernPortion != pPor )
531                 InsertPortion( rInf, pGridKernPortion );
532         }
533 
534         if( pPor->IsDropPortion() )
535             MergeCharacterBorder(*static_cast<SwDropPortion*>(pPor));
536 
537         // the multi-portion has its own format function
538         if( pPor->IsMultiPortion() && ( !pMulti || pMulti->IsBidi() ) )
539             bFull = BuildMultiPortion( rInf, *static_cast<SwMultiPortion*>(pPor) );
540         else
541             bFull = pPor->Format( rInf );
542 
543         if( rInf.IsRuby() && !rInf.GetRest() )
544             bFull = true;
545 
546         // if we are underlined, we store the beginning of this underlined
547         // segment for repaint optimization
548         if ( LINESTYLE_NONE != m_pFont->GetUnderline() && ! nUnderLineStart )
549             nUnderLineStart = GetLeftMargin() + rInf.X();
550 
551         if ( pPor->IsFlyPortion() )
552             m_pCurr->SetFly( true );
553         // some special cases, where we have to take care for the repaint
554         // offset:
555         // 1. Underlined portions due to special underline feature
556         // 2. Right Tab
557         // 3. BidiPortions
558         // 4. other Multiportions
559         // 5. DropCaps
560         // 6. Grid Mode
561         else if ( ( ! rInf.GetPaintOfst() || nUnderLineStart < rInf.GetPaintOfst() ) &&
562                   // 1. Underlined portions
563                   nUnderLineStart &&
564                      // reformat is at end of an underlined portion and next portion
565                      // is not underlined
566                   ( ( rInf.GetReformatStart() == rInf.GetIdx() &&
567                       LINESTYLE_NONE == m_pFont->GetUnderline()
568                     ) ||
569                      // reformat is inside portion and portion is underlined
570                     ( rInf.GetReformatStart() >= rInf.GetIdx() &&
571                       rInf.GetReformatStart() <= rInf.GetIdx() + pPor->GetLen() &&
572                       LINESTYLE_NONE != m_pFont->GetUnderline() ) ) )
573             rInf.SetPaintOfst( nUnderLineStart );
574         else if (  ! rInf.GetPaintOfst() &&
575                    // 2. Right Tab
576                    ( ( pPor->InTabGrp() && !pPor->IsTabLeftPortion() ) ||
577                    // 3. BidiPortions
578                      ( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsBidi() ) ||
579                    // 4. Multi Portion and 5. Drop Caps
580                      ( ( pPor->IsDropPortion() || pPor->IsMultiPortion() ) &&
581                        rInf.GetReformatStart() >= rInf.GetIdx() &&
582                        rInf.GetReformatStart() <= rInf.GetIdx() + pPor->GetLen() )
583                    // 6. Grid Mode
584                      || ( bHasGrid && SwFontScript::CJK != m_pFont->GetActual() )
585                    )
586                 )
587             // we store the beginning of the critical portion as our
588             // paint offset
589             rInf.SetPaintOfst( GetLeftMargin() + rInf.X() );
590 
591         // under one of these conditions we are allowed to delete the
592         // start of the underline portion
593         if ( IsUnderlineBreak( *pPor, *m_pFont ) )
594             nUnderLineStart = 0;
595 
596         if( pPor->IsFlyCntPortion() || ( pPor->IsMultiPortion() &&
597             static_cast<SwMultiPortion*>(pPor)->HasFlyInContent() ) )
598             SetFlyInCntBase();
599         // bUnderflow needs to be reset or we wrap again at the next softhyphen
600         if ( !bFull )
601         {
602             rInf.ClrUnderflow();
603             if( ! bHasGrid && rInf.HasScriptSpace() && pPor->InTextGrp() &&
604                 pPor->GetLen() && !pPor->InFieldGrp() )
605             {
606                 // The distance between two different scripts is set
607                 // to 20% of the fontheight.
608                 TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen();
609                 if (nTmp == m_pScriptInfo->NextScriptChg(nTmp - TextFrameIndex(1)) &&
610                     nTmp != TextFrameIndex(rInf.GetText().getLength()) &&
611                     (m_pScriptInfo->ScriptType(nTmp - TextFrameIndex(1)) == css::i18n::ScriptType::ASIAN ||
612                      m_pScriptInfo->ScriptType(nTmp) == css::i18n::ScriptType::ASIAN) )
613                 {
614                     const sal_uInt16 nDist = static_cast<sal_uInt16>(rInf.GetFont()->GetHeight()/5);
615 
616                     if( nDist )
617                     {
618                         // we do not want a kerning portion if any end
619                         // would be a punctuation character
620                         const CharClass& rCC = GetAppCharClass();
621                         if (rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nTmp) - 1)
622                             && rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nTmp)))
623                         {
624                             // does the kerning portion still fit into the line?
625                             if ( rInf.X() + pPor->Width() + nDist <= rInf.Width() )
626                                 new SwKernPortion( *pPor, nDist );
627                             else
628                                 bFull = true;
629                         }
630                     }
631                 }
632             }
633         }
634 
635         if ( bHasGrid && pGrid->IsSnapToChars() && pPor != pGridKernPortion && ! pMulti && ! pPor->InTabGrp() )
636         {
637             TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen();
638             const SwTwips nRestWidth = rInf.Width() - rInf.X() - pPor->Width();
639 
640             const SwFontScript nCurrScript = m_pFont->GetActual(); // pScriptInfo->ScriptType( rInf.GetIdx() );
641             const SwFontScript nNextScript =
642                 nTmp >= TextFrameIndex(rInf.GetText().getLength())
643                     ? SwFontScript::CJK
644                     : m_pScriptInfo->WhichFont(nTmp);
645 
646             // snap non-asian text to grid if next portion is ASIAN or
647             // there are no more portions in this line
648             // be careful when handling an underflow event: the gridkernportion
649             // could have been deleted
650             if ( nRestWidth > 0 && SwFontScript::CJK != nCurrScript &&
651                 ! rInf.IsUnderflow() && ( bFull || SwFontScript::CJK == nNextScript ) )
652             {
653                 OSL_ENSURE( pGridKernPortion, "No GridKernPortion available" );
654 
655                 // calculate size
656                 SwLinePortion* pTmpPor = pGridKernPortion->GetNextPortion();
657                 sal_uInt16 nSumWidth = pPor->Width();
658                 while ( pTmpPor )
659                 {
660                     nSumWidth = nSumWidth + pTmpPor->Width();
661                     pTmpPor = pTmpPor->GetNextPortion();
662                 }
663 
664                 const SwTwips i = nSumWidth ?
665                                  ( nSumWidth - 1 ) / nGridWidth + 1 :
666                                  0;
667                 const SwTwips nTmpWidth = i * nGridWidth;
668                 const SwTwips nKernWidth = std::min(nTmpWidth - nSumWidth, nRestWidth);
669                 const sal_uInt16 nKernWidth_1 = static_cast<sal_uInt16>(nKernWidth / 2);
670 
671                 OSL_ENSURE( nKernWidth <= nRestWidth,
672                         "Not enough space left for adjusting non-asian text in grid mode" );
673 
674                 pGridKernPortion->Width( pGridKernPortion->Width() + nKernWidth_1 );
675                 rInf.X( rInf.X() + nKernWidth_1 );
676 
677                 if ( ! bFull )
678                     new SwKernPortion( *pPor, static_cast<short>(nKernWidth - nKernWidth_1),
679                                        false, true );
680 
681                 pGridKernPortion = nullptr;
682             }
683             else if ( pPor->IsMultiPortion() || pPor->InFixMargGrp() ||
684                       pPor->IsFlyCntPortion() || pPor->InNumberGrp() ||
685                       pPor->InFieldGrp() || nCurrScript != nNextScript )
686                 // next portion should snap to grid
687                 pGridKernPortion = nullptr;
688         }
689 
690         rInf.SetFull( bFull );
691 
692         // Restportions from fields with multiple lines don't yet have the right ascent
693         if ( !pPor->GetLen() && !pPor->IsFlyPortion()
694             && !pPor->IsGrfNumPortion() && ! pPor->InNumberGrp()
695             && !pPor->IsMultiPortion() )
696             CalcAscent( rInf, pPor );
697 
698         InsertPortion( rInf, pPor );
699         pPor = NewPortion( rInf );
700     }
701 
702     if( !rInf.IsStop() )
703     {
704         // The last right centered, decimal tab
705         SwTabPortion *pLastTab = rInf.GetLastTab();
706         if( pLastTab )
707             pLastTab->FormatEOL( rInf );
708         else if( rInf.GetLast() && rInf.LastKernPortion() )
709             rInf.GetLast()->FormatEOL( rInf );
710     }
711     if( m_pCurr->GetNextPortion() && m_pCurr->GetNextPortion()->InNumberGrp()
712         && static_cast<SwNumberPortion*>(m_pCurr->GetNextPortion())->IsHide() )
713         rInf.SetNumDone( false );
714 
715     // Delete fly in any case
716     ClearFly( rInf );
717 
718     // Reinit the tab overflow flag after the line
719     rInf.SetTabOverflow( false );
720 }
721 
722 void SwTextFormatter::CalcAdjustLine( SwLineLayout *pCurrent )
723 {
724     if( SvxAdjust::Left != GetAdjust() && !pMulti)
725     {
726         pCurrent->SetFormatAdj(true);
727         if( IsFlyInCntBase() )
728         {
729             CalcAdjLine( pCurrent );
730             // For e.g. centered fly we need to switch the RefPoint
731             // That's why bAlways = true
732             UpdatePos( pCurrent, GetTopLeft(), GetStart(), true );
733         }
734     }
735 }
736 
737 void SwTextFormatter::CalcAscent( SwTextFormatInfo &rInf, SwLinePortion *pPor )
738 {
739     bool bCalc = false;
740     if ( pPor->InFieldGrp() && static_cast<SwFieldPortion*>(pPor)->GetFont() )
741     {
742         // Numbering + InterNetFields can keep an own font, then their size is
743         // independent from hard attribute values
744         SwFont* pFieldFnt = static_cast<SwFieldPortion*>(pPor)->m_pFont.get();
745         SwFontSave aSave( rInf, pFieldFnt );
746         pPor->Height( rInf.GetTextHeight() );
747         pPor->SetAscent( rInf.GetAscent() );
748         bCalc = true;
749     }
750     // i#89179
751     // tab portion representing the list tab of a list label gets the
752     // same height and ascent as the corresponding number portion
753     else if ( pPor->InTabGrp() && pPor->GetLen() == TextFrameIndex(0) &&
754               rInf.GetLast() && rInf.GetLast()->InNumberGrp() &&
755               static_cast<const SwNumberPortion*>(rInf.GetLast())->HasFont() )
756     {
757         const SwLinePortion* pLast = rInf.GetLast();
758         pPor->Height( pLast->Height() );
759         pPor->SetAscent( pLast->GetAscent() );
760     }
761     else
762     {
763         const SwLinePortion *pLast = rInf.GetLast();
764         bool bChg = false;
765 
766         // In empty lines the attributes are switched on via SeekStart
767         const bool bFirstPor = rInf.GetLineStart() == rInf.GetIdx();
768         if ( pPor->IsQuoVadisPortion() )
769             bChg = SeekStartAndChg( rInf, true );
770         else
771         {
772             if( bFirstPor )
773             {
774                 if( !rInf.GetText().isEmpty() )
775                 {
776                     if ( pPor->GetLen() || !rInf.GetIdx()
777                          || ( m_pCurr != pLast && !pLast->IsFlyPortion() )
778                          || !m_pCurr->IsRest() ) // instead of !rInf.GetRest()
779                         bChg = SeekAndChg( rInf );
780                     else
781                         bChg = SeekAndChgBefore( rInf );
782                 }
783                 else if ( pMulti )
784                     // do not open attributes starting at 0 in empty multi
785                     // portions (rotated numbering followed by a footnote
786                     // can cause trouble, because the footnote attribute
787                     // starts at 0, but if we open it, the attribute handler
788                     // cannot handle it.
789                     bChg = false;
790                 else
791                     bChg = SeekStartAndChg( rInf );
792             }
793             else
794                 bChg = SeekAndChg( rInf );
795         }
796         if( bChg || bFirstPor || !pPor->GetAscent()
797             || !rInf.GetLast()->InTextGrp() )
798         {
799             pPor->SetAscent( rInf.GetAscent()  );
800             pPor->Height( rInf.GetTextHeight() );
801             bCalc = true;
802         }
803         else
804         {
805             pPor->Height( pLast->Height() );
806             pPor->SetAscent( pLast->GetAscent() );
807         }
808     }
809 
810     if( pPor->InTextGrp() && bCalc )
811     {
812         pPor->SetAscent(pPor->GetAscent() +
813             rInf.GetFont()->GetTopBorderSpace());
814         pPor->Height(pPor->Height() +
815             rInf.GetFont()->GetTopBorderSpace() +
816             rInf.GetFont()->GetBottomBorderSpace() );
817     }
818 }
819 
820 class SwMetaPortion : public SwTextPortion
821 {
822 public:
823     SwMetaPortion() { SetWhichPor( PortionType::Meta ); }
824     virtual void Paint( const SwTextPaintInfo &rInf ) const override;
825 };
826 
827 void SwMetaPortion::Paint( const SwTextPaintInfo &rInf ) const
828 {
829     if ( Width() )
830     {
831         rInf.DrawViewOpt( *this, PortionType::Meta );
832         SwTextPortion::Paint( rInf );
833     }
834 }
835 
836 namespace sw { namespace mark {
837     OUString ExpandFieldmark(IFieldmark* pBM)
838     {
839         const IFieldmark::parameter_map_t* const pParameters = pBM->GetParameters();
840         sal_Int32 nCurrentIdx = 0;
841         const IFieldmark::parameter_map_t::const_iterator pResult = pParameters->find(OUString(ODF_FORMDROPDOWN_RESULT));
842         if(pResult != pParameters->end())
843             pResult->second >>= nCurrentIdx;
844 
845         const IFieldmark::parameter_map_t::const_iterator pListEntries = pParameters->find(OUString(ODF_FORMDROPDOWN_LISTENTRY));
846         if (pListEntries != pParameters->end())
847         {
848             uno::Sequence< OUString > vListEntries;
849             pListEntries->second >>= vListEntries;
850             if (nCurrentIdx < vListEntries.getLength())
851                 return vListEntries[nCurrentIdx];
852         }
853 
854         static const sal_Unicode vEnSpaces[ODF_FORMFIELD_DEFAULT_LENGTH] = {8194, 8194, 8194, 8194, 8194};
855         return OUString(vEnSpaces, ODF_FORMFIELD_DEFAULT_LENGTH);
856     }
857 } }
858 
859 SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const
860 {
861     SwTextPortion *pPor = nullptr;
862     if( GetFnt()->IsTox() )
863     {
864         pPor = new SwToxPortion;
865     }
866     else if ( GetFnt()->IsInputField() )
867     {
868         pPor = new SwTextInputFieldPortion();
869     }
870     else
871     {
872         if( GetFnt()->IsRef() )
873             pPor = new SwRefPortion;
874         else if (GetFnt()->IsMeta())
875         {
876             pPor = new SwMetaPortion;
877         }
878         else
879         {
880             // Only at the End!
881             // If pCurr does not have a width, it can however already have content.
882             // E.g. for non-displayable characters
883 
884             auto const ch(rInf.GetText()[sal_Int32(rInf.GetIdx())]);
885             SwTextFrame const*const pFrame(rInf.GetTextFrame());
886             SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx()));
887             sw::mark::IFieldmark *pBM = pFrame->GetDoc().getIDocumentMarkAccess()->getFieldmarkFor(aPosition);
888             if(pBM != nullptr && pBM->GetFieldname( ) == ODF_FORMDATE)
889             {
890                 if (ch == CH_TXT_ATR_FIELDSTART)
891                     pPor = new SwFieldFormDatePortion(pBM, true);
892                 else if (ch == CH_TXT_ATR_FIELDEND)
893                     pPor = new SwFieldFormDatePortion(pBM, false);
894             }
895             else if (ch == CH_TXT_ATR_FIELDSTART)
896                 pPor = new SwFieldMarkPortion();
897             else if (ch == CH_TXT_ATR_FIELDEND)
898                 pPor = new SwFieldMarkPortion();
899             else if (ch == CH_TXT_ATR_FORMELEMENT)
900             {
901                 OSL_ENSURE(pBM != nullptr, "Where is my form field bookmark???");
902                 if (pBM != nullptr)
903                 {
904                     if (pBM->GetFieldname( ) == ODF_FORMCHECKBOX)
905                     {
906                         pPor = new SwFieldFormCheckboxPortion();
907                     }
908                     else if (pBM->GetFieldname( ) == ODF_FORMDROPDOWN)
909                     {
910                         pPor = new SwFieldFormDropDownPortion(pBM, sw::mark::ExpandFieldmark(pBM));
911                     }
912                     /* we need to check for ODF_FORMTEXT for scenario having FormFields inside FORMTEXT.
913                      * Otherwise file will crash on open.
914                      */
915                     else if (pBM->GetFieldname( ) == ODF_FORMTEXT)
916                     {
917                         pPor = new SwFieldMarkPortion();
918                     }
919                 }
920             }
921             if( !pPor )
922             {
923                 if( !rInf.X() && !m_pCurr->GetNextPortion() && !m_pCurr->GetLen() )
924                     pPor = m_pCurr;
925                 else
926                     pPor = new SwTextPortion;
927             }
928         }
929     }
930     return pPor;
931 }
932 
933 // We calculate the length, the following portion limits are defined:
934 // 1) Tabs
935 // 2) Linebreaks
936 // 3) CH_TXTATR_BREAKWORD / CH_TXTATR_INWORD
937 // 4) next attribute change
938 
939 SwTextPortion *SwTextFormatter::NewTextPortion( SwTextFormatInfo &rInf )
940 {
941     // If we're at the line's beginning, we take pCurr
942     // If pCurr is not derived from SwTextPortion, we need to duplicate
943     Seek( rInf.GetIdx() );
944     SwTextPortion *pPor = WhichTextPor( rInf );
945 
946     // until next attribute change:
947     const TextFrameIndex nNextAttr = GetNextAttr();
948     TextFrameIndex nNextChg = std::min(nNextAttr, TextFrameIndex(rInf.GetText().getLength()));
949 
950     // end of script type:
951     const TextFrameIndex nNextScript = m_pScriptInfo->NextScriptChg(rInf.GetIdx());
952     nNextChg = std::min( nNextChg, nNextScript );
953 
954     // end of direction:
955     const TextFrameIndex nNextDir = m_pScriptInfo->NextDirChg(rInf.GetIdx());
956     nNextChg = std::min( nNextChg, nNextDir );
957 
958     // hidden change (potentially via bookmark):
959     const TextFrameIndex nNextHidden = m_pScriptInfo->NextHiddenChg(rInf.GetIdx());
960     nNextChg = std::min( nNextChg, nNextHidden );
961 
962     // Turbo boost:
963     // We assume that font characters are not larger than twice
964     // as wide as height.
965     // Very crazy: we need to take the ascent into account.
966 
967     // Mind the trap! GetSize() contains the wished-for height, the real height
968     // is only known in CalcAscent!
969 
970     // The ratio is even crazier: a blank in Times New Roman has an ascent of
971     // 182, a height of 200 and a width of 53!
972     // It follows that a line with a lot of blanks is processed incorrectly.
973     // Therefore we increase from factor 2 to 8 (due to negative kerning).
974 
975     pPor->SetLen(TextFrameIndex(1));
976     CalcAscent( rInf, pPor );
977 
978     const SwFont* pTmpFnt = rInf.GetFont();
979     sal_Int32 nExpect = std::min( sal_Int32( pTmpFnt->GetHeight() ),
980                              sal_Int32( pPor->GetAscent() ) ) / 8;
981     if ( !nExpect )
982         nExpect = 1;
983     nExpect = sal_Int32(rInf.GetIdx()) + (rInf.GetLineWidth() / nExpect);
984     if (TextFrameIndex(nExpect) > rInf.GetIdx() && nNextChg > TextFrameIndex(nExpect))
985         nNextChg = TextFrameIndex(std::min(nExpect, rInf.GetText().getLength()));
986 
987     // we keep an invariant during method calls:
988     // there are no portion ending characters like hard spaces
989     // or tabs in [ nLeftScanIdx, nRightScanIdx ]
990     if ( nLeftScanIdx <= rInf.GetIdx() && rInf.GetIdx() <= nRightScanIdx )
991     {
992         if ( nNextChg > nRightScanIdx )
993             nNextChg = nRightScanIdx =
994                 rInf.ScanPortionEnd( nRightScanIdx, nNextChg );
995     }
996     else
997     {
998         nLeftScanIdx = rInf.GetIdx();
999         nNextChg = nRightScanIdx =
1000                 rInf.ScanPortionEnd( rInf.GetIdx(), nNextChg );
1001     }
1002 
1003     pPor->SetLen( nNextChg - rInf.GetIdx() );
1004     rInf.SetLen( pPor->GetLen() );
1005     return pPor;
1006 }
1007 
1008 SwLinePortion *SwTextFormatter::WhichFirstPortion(SwTextFormatInfo &rInf)
1009 {
1010     SwLinePortion *pPor = nullptr;
1011 
1012     if( rInf.GetRest() )
1013     {
1014         // Tabs and fields
1015         if( '\0' != rInf.GetHookChar() )
1016             return nullptr;
1017 
1018         pPor = rInf.GetRest();
1019         if( pPor->IsErgoSumPortion() )
1020             rInf.SetErgoDone(true);
1021         else
1022             if( pPor->IsFootnoteNumPortion() )
1023                 rInf.SetFootnoteDone(true);
1024             else
1025                 if( pPor->InNumberGrp() )
1026                     rInf.SetNumDone(true);
1027 
1028         rInf.SetRest(nullptr);
1029         m_pCurr->SetRest( true );
1030         return pPor;
1031     }
1032 
1033     // We can stand in the follow, it's crucial that
1034     // pFrame->GetOfst() == 0!
1035     if( rInf.GetIdx() )
1036     {
1037         // We now too can elongate FootnotePortions and ErgoSumPortions
1038 
1039         // 1. The ErgoSumTexts
1040         if( !rInf.IsErgoDone() )
1041         {
1042             if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() )
1043                 pPor = static_cast<SwLinePortion*>(NewErgoSumPortion( rInf ));
1044             rInf.SetErgoDone( true );
1045         }
1046 
1047         // 2. Arrow portions
1048         if( !pPor && !rInf.IsArrowDone() )
1049         {
1050             if( m_pFrame->GetOfst() && !m_pFrame->IsFollow() &&
1051                 rInf.GetIdx() == m_pFrame->GetOfst() )
1052                 pPor = new SwArrowPortion( *m_pCurr );
1053             rInf.SetArrowDone( true );
1054         }
1055 
1056         // 3. Kerning portions at beginning of line in grid mode
1057         if ( ! pPor && ! m_pCurr->GetNextPortion() )
1058         {
1059             SwTextGridItem const*const pGrid(
1060                     GetGridItem(GetTextFrame()->FindPageFrame()));
1061             if ( pGrid )
1062                 pPor = new SwKernPortion( *m_pCurr );
1063         }
1064 
1065         // 4. The line rests (multiline fields)
1066         if( !pPor )
1067         {
1068             pPor = rInf.GetRest();
1069             // Only for pPor of course
1070             if( pPor )
1071             {
1072                 m_pCurr->SetRest( true );
1073                 rInf.SetRest(nullptr);
1074             }
1075         }
1076     }
1077     else
1078     {
1079         // 5. The foot note count
1080         if( !rInf.IsFootnoteDone() )
1081         {
1082             OSL_ENSURE( ( ! rInf.IsMulti() && ! pMulti ) || pMulti->HasRotation(),
1083                      "Rotated number portion trouble" );
1084 
1085             const bool bFootnoteNum = m_pFrame->IsFootnoteNumFrame();
1086             rInf.GetParaPortion()->SetFootnoteNum( bFootnoteNum );
1087             if( bFootnoteNum )
1088                 pPor = static_cast<SwLinePortion*>(NewFootnoteNumPortion( rInf ));
1089             rInf.SetFootnoteDone( true );
1090         }
1091 
1092         // 6. The ErgoSumTexts of course also exist in the TextMaster,
1093         // it's crucial whether the SwFootnoteFrame is aFollow
1094         if( !rInf.IsErgoDone() && !pPor && ! rInf.IsMulti() )
1095         {
1096             if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() )
1097                 pPor = static_cast<SwLinePortion*>(NewErgoSumPortion( rInf ));
1098             rInf.SetErgoDone( true );
1099         }
1100 
1101         // 7. The numbering
1102         if( !rInf.IsNumDone() && !pPor )
1103         {
1104             OSL_ENSURE( ( ! rInf.IsMulti() && ! pMulti ) || pMulti->HasRotation(),
1105                      "Rotated number portion trouble" );
1106 
1107             // If we're in the follow, then of course not
1108             if (GetTextFrame()->GetTextNodeForParaProps()->GetNumRule())
1109                 pPor = static_cast<SwLinePortion*>(NewNumberPortion( rInf ));
1110             rInf.SetNumDone( true );
1111         }
1112         // 8. The DropCaps
1113         if( !pPor && GetDropFormat() && ! rInf.IsMulti() )
1114             pPor = static_cast<SwLinePortion*>(NewDropPortion( rInf ));
1115 
1116         // 9. Kerning portions at beginning of line in grid mode
1117         if ( !pPor && !m_pCurr->GetNextPortion() )
1118         {
1119             SwTextGridItem const*const pGrid(
1120                     GetGridItem(GetTextFrame()->FindPageFrame()));
1121             if ( pGrid )
1122                 pPor = new SwKernPortion( *m_pCurr );
1123         }
1124     }
1125 
1126     // 10. Decimal tab portion at the beginning of each line in table cells
1127     if ( !pPor && !m_pCurr->GetNextPortion() &&
1128              GetTextFrame()->IsInTab() &&
1129              GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT))
1130     {
1131         pPor = NewTabPortion( rInf, true );
1132     }
1133 
1134     // 11. suffix of meta-field
1135     if (!pPor)
1136     {
1137         pPor = TryNewNoLengthPortion(rInf);
1138     }
1139 
1140     return pPor;
1141 }
1142 
1143 static bool lcl_OldFieldRest( const SwLineLayout* pCurr )
1144 {
1145     if( !pCurr->GetNext() )
1146         return false;
1147     const SwLinePortion *pPor = pCurr->GetNext()->GetNextPortion();
1148     bool bRet = false;
1149     while( pPor && !bRet )
1150     {
1151         bRet = (pPor->InFieldGrp() && static_cast<const SwFieldPortion*>(pPor)->IsFollow()) ||
1152             (pPor->IsMultiPortion() && static_cast<const SwMultiPortion*>(pPor)->IsFollowField());
1153         if( !pPor->GetLen() )
1154             break;
1155         pPor = pPor->GetNextPortion();
1156     }
1157     return bRet;
1158 }
1159 
1160 /* NewPortion sets rInf.nLen
1161  * A SwTextPortion is limited by a tab, break, txtatr or attr change
1162  * We can have three cases:
1163  * 1) The line is full and the wrap was not emulated
1164  *    -> return 0;
1165  * 2) The line is full and a wrap was emulated
1166  *    -> Reset width and return new FlyPortion
1167  * 3) We need to construct a new portion
1168  *    -> CalcFlyWidth emulates the width and return portion, if needed
1169  */
1170 
1171 SwLinePortion *SwTextFormatter::NewPortion( SwTextFormatInfo &rInf )
1172 {
1173     // Underflow takes precedence
1174     rInf.SetStopUnderflow( false );
1175     if( rInf.GetUnderflow() )
1176     {
1177         OSL_ENSURE( rInf.IsFull(), "SwTextFormatter::NewPortion: underflow but not full" );
1178         return Underflow( rInf );
1179     }
1180 
1181     // If the line is full, flys and Underflow portions could be waiting ...
1182     if( rInf.IsFull() )
1183     {
1184         // LineBreaks and Flys (bug05.sdw)
1185         // IsDummy()
1186         if( rInf.IsNewLine() && (!rInf.GetFly() || !m_pCurr->IsDummy()) )
1187             return nullptr;
1188 
1189         // When the text bumps into the Fly, or when the Fly comes first because
1190         // it juts out over the left edge, GetFly() is returned.
1191         // When IsFull() and no GetFly() is available, naturally zero is returned.
1192         if( rInf.GetFly() )
1193         {
1194             if( rInf.GetLast()->IsBreakPortion() )
1195             {
1196                 delete rInf.GetFly();
1197                 rInf.SetFly( nullptr );
1198             }
1199 
1200             return rInf.GetFly();
1201         }
1202 
1203         // A nasty special case: A frame without wrap overlaps the Footnote area.
1204         // We must declare the Footnote portion as rest of line, so that
1205         // SwTextFrame::Format doesn't abort (the text mass already was formatted).
1206         if( rInf.GetRest() )
1207             rInf.SetNewLine( true );
1208         else
1209         {
1210             // When the next line begins with a rest of a field, but now no
1211             // rest remains, the line must definitely be formatted anew!
1212             if( lcl_OldFieldRest( GetCurr() ) )
1213                 rInf.SetNewLine( true );
1214             else
1215             {
1216                 SwLinePortion *pFirst = WhichFirstPortion( rInf );
1217                 if( pFirst )
1218                 {
1219                     rInf.SetNewLine( true );
1220                     if( pFirst->InNumberGrp() )
1221                         rInf.SetNumDone( false) ;
1222                     delete pFirst;
1223                 }
1224             }
1225         }
1226 
1227         return nullptr;
1228     }
1229 
1230     SwLinePortion *pPor = WhichFirstPortion( rInf );
1231 
1232     // Check for Hidden Portion:
1233     if ( !pPor )
1234     {
1235         TextFrameIndex nEnd = rInf.GetIdx();
1236         if ( ::lcl_BuildHiddenPortion( rInf, nEnd ) )
1237             pPor = new SwHiddenTextPortion( nEnd - rInf.GetIdx() );
1238     }
1239 
1240     if( !pPor )
1241     {
1242         if( ( !pMulti || pMulti->IsBidi() ) &&
1243             // i#42734
1244             // No multi portion if there is a hook character waiting:
1245             ( !rInf.GetRest() || '\0' == rInf.GetHookChar() ) )
1246         {
1247             // We open a multiportion part, if we enter a multi-line part
1248             // of the paragraph.
1249             TextFrameIndex nEnd = rInf.GetIdx();
1250             std::unique_ptr<SwMultiCreator> pCreate = rInf.GetMultiCreator( nEnd, pMulti );
1251             if( pCreate )
1252             {
1253                 SwMultiPortion* pTmp = nullptr;
1254 
1255                 if ( SwMultiCreatorId::Bidi == pCreate->nId )
1256                     pTmp = new SwBidiPortion( nEnd, pCreate->nLevel );
1257                 else if ( SwMultiCreatorId::Ruby == pCreate->nId )
1258                 {
1259                     pTmp = new SwRubyPortion( *pCreate, *rInf.GetFont(),
1260                           GetTextFrame()->GetDoc().getIDocumentSettingAccess(),
1261                           nEnd, TextFrameIndex(0), rInf );
1262                 }
1263                 else if( SwMultiCreatorId::Rotate == pCreate->nId )
1264                     pTmp = new SwRotatedPortion( *pCreate, nEnd,
1265                                                  GetTextFrame()->IsRightToLeft() );
1266                 else
1267                     pTmp = new SwDoubleLinePortion( *pCreate, nEnd );
1268 
1269                 pCreate.reset();
1270                 CalcFlyWidth( rInf );
1271 
1272                 return pTmp;
1273             }
1274         }
1275         // Tabs and Fields
1276         sal_Unicode cChar = rInf.GetHookChar();
1277 
1278         if( cChar )
1279         {
1280             /* We fetch cChar again to be sure that the tab is pending now and
1281              * didn't move to the next line (as happens behind frames).
1282              * However, when a FieldPortion is in the rest, we must naturally fetch
1283              * the cChar from the field content, e.g. DecimalTabs and fields (22615)
1284              */
1285             if( !rInf.GetRest() || !rInf.GetRest()->InFieldGrp() )
1286                 cChar = rInf.GetChar( rInf.GetIdx() );
1287             rInf.ClearHookChar();
1288         }
1289         else
1290         {
1291             if (rInf.GetIdx() >= TextFrameIndex(rInf.GetText().getLength()))
1292             {
1293                 rInf.SetFull(true);
1294                 CalcFlyWidth( rInf );
1295                 return pPor;
1296             }
1297             cChar = rInf.GetChar( rInf.GetIdx() );
1298         }
1299 
1300         switch( cChar )
1301         {
1302             case CH_TAB:
1303                 pPor = NewTabPortion( rInf, false ); break;
1304 
1305             case CH_BREAK:
1306                 pPor = new SwBreakPortion( *rInf.GetLast() ); break;
1307 
1308             case CHAR_SOFTHYPHEN:                   // soft hyphen
1309                 pPor = new SwSoftHyphPortion; break;
1310 
1311             case CHAR_HARDBLANK:                    // no-break space
1312                 // Please check tdf#115067 if you want to edit the char
1313                 pPor = new SwBlankPortion( cChar ); break;
1314 
1315             case CHAR_HARDHYPHEN:               // non-breaking hyphen
1316                 pPor = new SwBlankPortion( '-' ); break;
1317 
1318             case CHAR_ZWSP:                     // zero width space
1319             case CHAR_ZWNBSP :                  // word joiner
1320                 pPor = new SwControlCharPortion( cChar ); break;
1321 
1322             case CH_TXTATR_BREAKWORD:
1323             case CH_TXTATR_INWORD:
1324                 if( rInf.HasHint( rInf.GetIdx() ) )
1325                 {
1326                     pPor = NewExtraPortion( rInf );
1327                     break;
1328                 }
1329                 [[fallthrough]];
1330             default        :
1331                 {
1332                     SwTabPortion* pLastTabPortion = rInf.GetLastTab();
1333                     if ( pLastTabPortion && cChar == rInf.GetTabDecimal() )
1334                     {
1335                         // Abandon dec. tab position if line is full
1336                         // We have a decimal tab portion in the line and the next character has to be
1337                         // aligned at the tab stop position. We store the width from the beginning of
1338                         // the tab stop portion up to the portion containing the decimal separator:
1339                         if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT) /*rInf.GetVsh()->IsTabCompat();*/ &&
1340                              PortionType::TabDecimal == pLastTabPortion->GetWhichPor() )
1341                         {
1342                             OSL_ENSURE( rInf.X() >= pLastTabPortion->GetFix(), "Decimal tab stop position cannot be calculated" );
1343                             const sal_uInt16 nWidthOfPortionsUpToDecimalPosition = static_cast<sal_uInt16>(rInf.X() - pLastTabPortion->GetFix() );
1344                             static_cast<SwTabDecimalPortion*>(pLastTabPortion)->SetWidthOfPortionsUpToDecimalPosition( nWidthOfPortionsUpToDecimalPosition );
1345                             rInf.SetTabDecimal( 0 );
1346                         }
1347                         else
1348                             rInf.SetFull( rInf.GetLastTab()->Format( rInf ) );
1349                     }
1350 
1351                     if( rInf.GetRest() )
1352                     {
1353                         if( rInf.IsFull() )
1354                         {
1355                             rInf.SetNewLine(true);
1356                             return nullptr;
1357                         }
1358                         pPor = rInf.GetRest();
1359                         rInf.SetRest(nullptr);
1360                     }
1361                     else
1362                     {
1363                         if( rInf.IsFull() )
1364                             return nullptr;
1365                         pPor = NewTextPortion( rInf );
1366                     }
1367                     break;
1368                 }
1369         }
1370 
1371         // if a portion is created despite there being a pending RestPortion,
1372         // then it is a field which has been split (e.g. because it contains a Tab)
1373         if( pPor && rInf.GetRest() )
1374             pPor->SetLen(TextFrameIndex(0));
1375 
1376         // robust:
1377         if( !pPor || rInf.IsStop() )
1378         {
1379             delete pPor;
1380             return nullptr;
1381         }
1382     }
1383 
1384     assert(pPor && "can only reach here with pPor existing");
1385 
1386     // Special portions containing numbers (footnote anchor, footnote number,
1387     // numbering) can be contained in a rotated portion, if the user
1388     // choose a rotated character attribute.
1389     if (!pMulti)
1390     {
1391         if ( pPor->IsFootnotePortion() )
1392         {
1393             const SwTextFootnote* pTextFootnote = static_cast<SwFootnotePortion*>(pPor)->GetTextFootnote();
1394 
1395             if ( pTextFootnote )
1396             {
1397                 SwFormatFootnote& rFootnote = const_cast<SwFormatFootnote&>(pTextFootnote->GetFootnote());
1398                 const SwDoc *const pDoc = &rInf.GetTextFrame()->GetDoc();
1399                 const SwEndNoteInfo* pInfo;
1400                 if( rFootnote.IsEndNote() )
1401                     pInfo = &pDoc->GetEndNoteInfo();
1402                 else
1403                     pInfo = &pDoc->GetFootnoteInfo();
1404                 const SwAttrSet& rSet = pInfo->GetAnchorCharFormat(const_cast<SwDoc&>(*pDoc))->GetAttrSet();
1405 
1406                 const SfxPoolItem* pItem;
1407                 sal_uInt16 nDir = 0;
1408                 if( SfxItemState::SET == rSet.GetItemState( RES_CHRATR_ROTATE,
1409                     true, &pItem ))
1410                     nDir = static_cast<const SvxCharRotateItem*>(pItem)->GetValue();
1411 
1412                 if ( 0 != nDir )
1413                 {
1414                     delete pPor;
1415                     pPor = new SwRotatedPortion(rInf.GetIdx() + TextFrameIndex(1),
1416                                                 900 == nDir
1417                                                     ? DIR_BOTTOM2TOP
1418                                                     : DIR_TOP2BOTTOM );
1419                 }
1420             }
1421         }
1422         else if ( pPor->InNumberGrp() )
1423         {
1424             const SwFont* pNumFnt = static_cast<SwFieldPortion*>(pPor)->GetFont();
1425 
1426             if ( pNumFnt )
1427             {
1428                 sal_uInt16 nDir = pNumFnt->GetOrientation( rInf.GetTextFrame()->IsVertical() );
1429                 if ( 0 != nDir )
1430                 {
1431                     delete pPor;
1432                     pPor = new SwRotatedPortion(TextFrameIndex(0), 900 == nDir
1433                                                     ? DIR_BOTTOM2TOP
1434                                                     : DIR_TOP2BOTTOM );
1435 
1436                     rInf.SetNumDone( false );
1437                     rInf.SetFootnoteDone( false );
1438                 }
1439             }
1440         }
1441     }
1442 
1443     // The font is set in output device,
1444     // the ascent and the height will be calculated.
1445     if( !pPor->GetAscent() && !pPor->Height() )
1446         CalcAscent( rInf, pPor );
1447     rInf.SetLen( pPor->GetLen() );
1448 
1449     // In CalcFlyWidth Width() will be shortened if a FlyPortion is present.
1450     CalcFlyWidth( rInf );
1451 
1452     // One must not forget that pCurr as GetLast() must provide reasonable values:
1453     if( !m_pCurr->Height() )
1454     {
1455         OSL_ENSURE( m_pCurr->Height(), "SwTextFormatter::NewPortion: limbo dance" );
1456         m_pCurr->Height( pPor->Height() );
1457         m_pCurr->SetAscent( pPor->GetAscent() );
1458     }
1459 
1460     OSL_ENSURE(pPor->Height(), "SwTextFormatter::NewPortion: something went wrong");
1461     if( pPor->IsPostItsPortion() && rInf.X() >= rInf.Width() && rInf.GetFly() )
1462     {
1463         delete pPor;
1464         pPor = rInf.GetFly();
1465     }
1466     return pPor;
1467 }
1468 
1469 TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos)
1470 {
1471     OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
1472             "SwTextFormatter::FormatLine( nStartPos ) with unswapped frame" );
1473 
1474     // For the formatting routines, we set pOut to the reference device.
1475     SwHookOut aHook( GetInfo() );
1476     if (GetInfo().GetLen() < TextFrameIndex(GetInfo().GetText().getLength()))
1477         GetInfo().SetLen(TextFrameIndex(GetInfo().GetText().getLength()));
1478 
1479     bool bBuild = true;
1480     SetFlyInCntBase( false );
1481     GetInfo().SetLineHeight( 0 );
1482     GetInfo().SetLineNetHeight( 0 );
1483 
1484     // Recycling must be suppressed by changed line height and also
1485     // by changed ascent (lowering of baseline).
1486     const sal_uInt16 nOldHeight = m_pCurr->Height();
1487     const sal_uInt16 nOldAscent = m_pCurr->GetAscent();
1488 
1489     m_pCurr->SetEndHyph( false );
1490     m_pCurr->SetMidHyph( false );
1491 
1492     // fly positioning can make it necessary format a line several times
1493     // for this, we have to keep a copy of our rest portion
1494     SwLinePortion* pField = GetInfo().GetRest();
1495     std::unique_ptr<SwFieldPortion> xSaveField;
1496 
1497     if ( pField && pField->InFieldGrp() && !pField->IsFootnotePortion() )
1498         xSaveField.reset(new SwFieldPortion( *static_cast<SwFieldPortion*>(pField) ));
1499 
1500     // for an optimal repaint rectangle, we want to compare fly portions
1501     // before and after the BuildPortions call
1502     const bool bOptimizeRepaint = AllowRepaintOpt();
1503     TextFrameIndex const nOldLineEnd = nStartPos + m_pCurr->GetLen();
1504     std::vector<long> flyStarts;
1505 
1506     // these are the conditions for a fly position comparison
1507     if ( bOptimizeRepaint && m_pCurr->IsFly() )
1508     {
1509         SwLinePortion* pPor = m_pCurr->GetFirstPortion();
1510         long nPOfst = 0;
1511         while ( pPor )
1512         {
1513             if ( pPor->IsFlyPortion() )
1514                 // insert start value of fly portion
1515                 flyStarts.push_back( nPOfst );
1516 
1517             nPOfst += pPor->Width();
1518             pPor = pPor->GetNextPortion();
1519         }
1520     }
1521 
1522     // Here soon the underflow check follows.
1523     while( bBuild )
1524     {
1525         GetInfo().SetFootnoteInside( false );
1526         GetInfo().SetOtherThanFootnoteInside( false );
1527 
1528         // These values must not be reset by FormatReset();
1529         const bool bOldNumDone = GetInfo().IsNumDone();
1530         const bool bOldArrowDone = GetInfo().IsArrowDone();
1531         const bool bOldErgoDone = GetInfo().IsErgoDone();
1532 
1533         // besides other things, this sets the repaint offset to 0
1534         FormatReset( GetInfo() );
1535 
1536         GetInfo().SetNumDone( bOldNumDone );
1537         GetInfo().SetArrowDone( bOldArrowDone );
1538         GetInfo().SetErgoDone( bOldErgoDone );
1539 
1540         // build new portions for this line
1541         BuildPortions( GetInfo() );
1542 
1543         if( GetInfo().IsStop() )
1544         {
1545             m_pCurr->SetLen(TextFrameIndex(0));
1546             m_pCurr->Height( GetFrameRstHeight() + 1 );
1547             m_pCurr->SetRealHeight( GetFrameRstHeight() + 1 );
1548             m_pCurr->Width(0);
1549             m_pCurr->Truncate();
1550             return nStartPos;
1551         }
1552         else if( GetInfo().IsDropInit() )
1553         {
1554             DropInit();
1555             GetInfo().SetDropInit( false );
1556         }
1557 
1558         m_pCurr->CalcLine( *this, GetInfo() );
1559         CalcRealHeight( GetInfo().IsNewLine() );
1560 
1561         //i#120864 For Special case that at the first calculation couldn't get
1562         //correct height. And need to recalculate for the right height.
1563         SwLinePortion* pPorTmp = m_pCurr->GetNextPortion();
1564         if ( IsFlyInCntBase() && (!IsQuick() || (pPorTmp && pPorTmp->IsFlyCntPortion() && !pPorTmp->GetNextPortion() &&
1565             m_pCurr->Height() > pPorTmp->Height())))
1566         {
1567             sal_uInt16 nTmpAscent, nTmpHeight;
1568             CalcAscentAndHeight( nTmpAscent, nTmpHeight );
1569             AlignFlyInCntBase( Y() + long( nTmpAscent ) );
1570             m_pCurr->CalcLine( *this, GetInfo() );
1571             CalcRealHeight();
1572         }
1573 
1574         // bBuild decides if another lap of honor is done
1575         if ( m_pCurr->GetRealHeight() <= GetInfo().GetLineHeight() )
1576         {
1577             m_pCurr->SetRealHeight( GetInfo().GetLineHeight() );
1578             bBuild = false;
1579         }
1580         else
1581         {
1582             bBuild = ( GetInfo().GetTextFly().IsOn() && ChkFlyUnderflow(GetInfo()) )
1583                      || GetInfo().CheckFootnotePortion(m_pCurr);
1584             if( bBuild )
1585             {
1586                 GetInfo().SetNumDone( bOldNumDone );
1587                 GetInfo().ResetMaxWidthDiff();
1588 
1589                 // delete old rest
1590                 if ( GetInfo().GetRest() )
1591                 {
1592                     delete GetInfo().GetRest();
1593                     GetInfo().SetRest( nullptr );
1594                 }
1595 
1596                 // set original rest portion
1597                 if ( xSaveField )
1598                     GetInfo().SetRest( new SwFieldPortion( *xSaveField ) );
1599 
1600                 m_pCurr->SetLen(TextFrameIndex(0));
1601                 m_pCurr->Width(0);
1602                 m_pCurr->Truncate();
1603             }
1604         }
1605     }
1606 
1607     // In case of compat mode, it's possible that a tab portion is wider after
1608     // formatting than before. If this is the case, we also have to make sure
1609     // the SwLineLayout is wider as well.
1610     if (GetInfo().GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_OVER_MARGIN))
1611     {
1612         sal_uInt16 nSum = 0;
1613         SwLinePortion* pPor = m_pCurr->GetFirstPortion();
1614 
1615         while (pPor)
1616         {
1617             nSum += pPor->Width();
1618             pPor = pPor->GetNextPortion();
1619         }
1620 
1621         if (nSum > m_pCurr->Width())
1622             m_pCurr->Width(nSum);
1623     }
1624 
1625     // calculate optimal repaint rectangle
1626     if ( bOptimizeRepaint )
1627     {
1628         GetInfo().SetPaintOfst( ::lcl_CalcOptRepaint( *this, *m_pCurr, nOldLineEnd, flyStarts ) );
1629         flyStarts.clear();
1630     }
1631     else
1632         // Special case: we do not allow an optimization of the repaint
1633         // area, but during formatting the repaint offset is set to indicate
1634         // a maximum value for the offset. This value has to be reset:
1635         GetInfo().SetPaintOfst( 0 );
1636 
1637     // This corrects the start of the reformat range if something has
1638     // moved to the next line. Otherwise IsFirstReformat in AllowRepaintOpt
1639     // will give us a wrong result if we have to reformat another line
1640     GetInfo().GetParaPortion()->GetReformat().LeftMove( GetInfo().GetIdx() );
1641 
1642     // delete master copy of rest portion
1643     xSaveField.reset();
1644 
1645     TextFrameIndex const nNewStart = nStartPos + m_pCurr->GetLen();
1646 
1647     // adjust text if kana compression is enabled
1648     if ( GetInfo().CompressLine() )
1649     {
1650         SwTwips nRepaintOfst = CalcKanaAdj( m_pCurr );
1651 
1652         // adjust repaint offset
1653         if ( nRepaintOfst < GetInfo().GetPaintOfst() )
1654             GetInfo().SetPaintOfst( nRepaintOfst );
1655     }
1656 
1657     CalcAdjustLine( m_pCurr );
1658 
1659     if( nOldHeight != m_pCurr->Height() || nOldAscent != m_pCurr->GetAscent() )
1660     {
1661         SetFlyInCntBase();
1662         GetInfo().SetPaintOfst( 0 ); // changed line height => no recycling
1663         // all following line must be painted and when Flys are around,
1664         // also formatted
1665         GetInfo().SetShift( true );
1666     }
1667 
1668     if ( IsFlyInCntBase() && !IsQuick() )
1669         UpdatePos( m_pCurr, GetTopLeft(), GetStart() );
1670 
1671     return nNewStart;
1672 }
1673 
1674 void SwTextFormatter::RecalcRealHeight()
1675 {
1676     do
1677     {
1678         CalcRealHeight();
1679     } while (Next());
1680 }
1681 
1682 void SwTextFormatter::CalcRealHeight( bool bNewLine )
1683 {
1684     sal_uInt16 nLineHeight = m_pCurr->Height();
1685     m_pCurr->SetClipping( false );
1686 
1687     SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
1688     if ( pGrid && GetInfo().SnapToGrid() )
1689     {
1690         const sal_uInt16 nGridWidth = pGrid->GetBaseHeight();
1691         const sal_uInt16 nRubyHeight = pGrid->GetRubyHeight();
1692         const bool bRubyTop = ! pGrid->GetRubyTextBelow();
1693 
1694         nLineHeight = nGridWidth + nRubyHeight;
1695         const sal_uInt16 nAmpRatio = (m_pCurr->Height() + nLineHeight - 1)/nLineHeight;
1696         nLineHeight *= nAmpRatio;
1697 
1698         const sal_uInt16 nAsc = m_pCurr->GetAscent() +
1699                       ( bRubyTop ?
1700                        ( nLineHeight - m_pCurr->Height() + nRubyHeight ) / 2 :
1701                        ( nLineHeight - m_pCurr->Height() - nRubyHeight ) / 2 );
1702 
1703         m_pCurr->Height( nLineHeight );
1704         m_pCurr->SetAscent( nAsc );
1705         m_pInf->GetParaPortion()->SetFixLineHeight();
1706 
1707         // we ignore any line spacing options except from ...
1708         const SvxLineSpacingItem* pSpace = m_aLineInf.GetLineSpacing();
1709         if ( ! IsParaLine() && pSpace &&
1710              SvxInterLineSpaceRule::Prop == pSpace->GetInterLineSpaceRule() )
1711         {
1712             sal_uLong nTmp = pSpace->GetPropLineSpace();
1713 
1714             if( nTmp < 100 )
1715                 nTmp = 100;
1716 
1717             nTmp *= nLineHeight;
1718             nLineHeight = static_cast<sal_uInt16>(nTmp / 100);
1719         }
1720 
1721         m_pCurr->SetRealHeight( nLineHeight );
1722         return;
1723     }
1724 
1725     // The dummy flag is set on lines that only contain flyportions, these shouldn't
1726     // consider register-true and so on. Unfortunately an empty line can be at
1727     // the end of a paragraph (empty paragraphs or behind a Shift-Return),
1728     // which should consider the register.
1729     if (!m_pCurr->IsDummy() || (!m_pCurr->GetNext()
1730             && GetStart() >= TextFrameIndex(GetTextFrame()->GetText().getLength())
1731             && !bNewLine))
1732     {
1733         const SvxLineSpacingItem *pSpace = m_aLineInf.GetLineSpacing();
1734         if( pSpace )
1735         {
1736             switch( pSpace->GetLineSpaceRule() )
1737             {
1738                 case SvxLineSpaceRule::Auto:
1739                     // shrink first line of paragraph too on spacing < 100%
1740                     if (IsParaLine() &&
1741                         pSpace->GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop
1742                         && GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::PROP_LINE_SPACING_SHRINKS_FIRST_LINE))
1743                     {
1744                         long nTmp = pSpace->GetPropLineSpace();
1745                         // Word will render < 50% too but it's just not readable
1746                         if( nTmp < 50 )
1747                             nTmp = nTmp ? 50 : 100;
1748                         if (nTmp<100) { // code adapted from fixed line height
1749                             nTmp *= nLineHeight;
1750                             nTmp /= 100;
1751                             if( !nTmp )
1752                                 ++nTmp;
1753                             nLineHeight = static_cast<sal_uInt16>(nTmp);
1754                             sal_uInt16 nAsc = ( 4 * nLineHeight ) / 5;  // 80%
1755 #if 0
1756                             // could do clipping here (like Word does)
1757                             // but at 0.5 its unreadable either way...
1758                             if( nAsc < pCurr->GetAscent() ||
1759                                 nLineHeight - nAsc < pCurr->Height() -
1760                                 pCurr->GetAscent() )
1761                                 pCurr->SetClipping( true );
1762 #endif
1763                             m_pCurr->SetAscent( nAsc );
1764                             m_pCurr->Height( nLineHeight );
1765                             m_pInf->GetParaPortion()->SetFixLineHeight();
1766                         }
1767                     }
1768                 break;
1769                 case SvxLineSpaceRule::Min:
1770                 {
1771                     if( nLineHeight < pSpace->GetLineHeight() )
1772                         nLineHeight = pSpace->GetLineHeight();
1773                     break;
1774                 }
1775                 case SvxLineSpaceRule::Fix:
1776                 {
1777                     nLineHeight = pSpace->GetLineHeight();
1778                     const sal_uInt16 nAsc = ( 4 * nLineHeight ) / 5;  // 80%
1779                     if( nAsc < m_pCurr->GetAscent() ||
1780                         nLineHeight - nAsc < m_pCurr->Height() - m_pCurr->GetAscent() )
1781                         m_pCurr->SetClipping( true );
1782                     m_pCurr->Height( nLineHeight );
1783                     m_pCurr->SetAscent( nAsc );
1784                     m_pInf->GetParaPortion()->SetFixLineHeight();
1785                 }
1786                 break;
1787                 default: OSL_FAIL( ": unknown LineSpaceRule" );
1788             }
1789             // Note: for the _first_ line the line spacing of the previous
1790             // paragraph is applied in SwFlowFrame::CalcUpperSpace()
1791             if( !IsParaLine() )
1792                 switch( pSpace->GetInterLineSpaceRule() )
1793                 {
1794                     case SvxInterLineSpaceRule::Off:
1795                     break;
1796                     case SvxInterLineSpaceRule::Prop:
1797                     {
1798                         long nTmp = pSpace->GetPropLineSpace();
1799                         // 50% is the minimum, if 0% we switch to the
1800                         // default value 100% ...
1801                         if( nTmp < 50 )
1802                             nTmp = nTmp ? 50 : 100;
1803 
1804                         nTmp *= nLineHeight;
1805                         nTmp /= 100;
1806                         if( !nTmp )
1807                             ++nTmp;
1808                         nLineHeight = static_cast<sal_uInt16>(nTmp);
1809                         break;
1810                     }
1811                     case SvxInterLineSpaceRule::Fix:
1812                     {
1813                         nLineHeight = nLineHeight + pSpace->GetInterLineSpace();
1814                         break;
1815                     }
1816                     default: OSL_FAIL( ": unknown InterLineSpaceRule" );
1817                 }
1818         }
1819 
1820         if( IsRegisterOn() )
1821         {
1822             SwTwips nTmpY = Y() + m_pCurr->GetAscent() + nLineHeight - m_pCurr->Height();
1823             SwRectFnSet aRectFnSet(m_pFrame);
1824             if ( aRectFnSet.IsVert() )
1825                 nTmpY = m_pFrame->SwitchHorizontalToVertical( nTmpY );
1826             nTmpY = aRectFnSet.YDiff( nTmpY, RegStart() );
1827             const sal_uInt16 nDiff = sal_uInt16( nTmpY % RegDiff() );
1828             if( nDiff )
1829                 nLineHeight += RegDiff() - nDiff;
1830         }
1831     }
1832     m_pCurr->SetRealHeight( nLineHeight );
1833 }
1834 
1835 void SwTextFormatter::FeedInf( SwTextFormatInfo &rInf ) const
1836 {
1837     // delete Fly in any case!
1838     ClearFly( rInf );
1839     rInf.Init();
1840 
1841     rInf.ChkNoHyph( CntEndHyph(), CntMidHyph() );
1842     rInf.SetRoot( m_pCurr );
1843     rInf.SetLineStart( m_nStart );
1844     rInf.SetIdx( m_nStart );
1845     rInf.Left( Left() );
1846     rInf.Right( Right() );
1847     rInf.First( FirstLeft() );
1848     rInf.LeftMargin(GetLeftMargin());
1849 
1850     rInf.RealWidth( sal_uInt16(rInf.Right() - GetLeftMargin()) );
1851     rInf.Width( rInf.RealWidth() );
1852     if( const_cast<SwTextFormatter*>(this)->GetRedln() )
1853     {
1854         const_cast<SwTextFormatter*>(this)->GetRedln()->Clear( const_cast<SwTextFormatter*>(this)->GetFnt() );
1855         const_cast<SwTextFormatter*>(this)->GetRedln()->Reset();
1856     }
1857 }
1858 
1859 void SwTextFormatter::FormatReset( SwTextFormatInfo &rInf )
1860 {
1861     m_pFirstOfBorderMerge = nullptr;
1862     m_pCurr->Truncate();
1863     m_pCurr->Init();
1864     if( pBlink && m_pCurr->IsBlinking() )
1865         pBlink->Delete( m_pCurr );
1866 
1867     // delete pSpaceAdd and pKanaComp
1868     m_pCurr->FinishSpaceAdd();
1869     m_pCurr->FinishKanaComp();
1870     m_pCurr->ResetFlags();
1871     FeedInf( rInf );
1872 }
1873 
1874 bool SwTextFormatter::CalcOnceMore()
1875 {
1876     if( pDropFormat )
1877     {
1878         const sal_uInt16 nOldDrop = GetDropHeight();
1879         CalcDropHeight( pDropFormat->GetLines() );
1880         bOnceMore = nOldDrop != GetDropHeight();
1881     }
1882     else
1883         bOnceMore = false;
1884     return bOnceMore;
1885 }
1886 
1887 SwTwips SwTextFormatter::CalcBottomLine() const
1888 {
1889     SwTwips nRet = Y() + GetLineHeight();
1890     SwTwips nMin = GetInfo().GetTextFly().GetMinBottom();
1891     if( nMin && ++nMin > nRet )
1892     {
1893         SwTwips nDist = m_pFrame->getFrameArea().Height() - m_pFrame->getFramePrintArea().Height()
1894                         - m_pFrame->getFramePrintArea().Top();
1895         if( nRet + nDist < nMin )
1896         {
1897             const bool bRepaint = HasTruncLines() &&
1898                 GetInfo().GetParaPortion()->GetRepaint().Bottom() == nRet-1;
1899             nRet = nMin - nDist;
1900             if( bRepaint )
1901             {
1902                 const_cast<SwRepaint&>(GetInfo().GetParaPortion()
1903                     ->GetRepaint()).Bottom( nRet-1 );
1904                 const_cast<SwTextFormatInfo&>(GetInfo()).SetPaintOfst( 0 );
1905             }
1906         }
1907     }
1908     return nRet;
1909 }
1910 
1911 // FME/OD: This routine does a limited text formatting.
1912 SwTwips SwTextFormatter::CalcFitToContent_()
1913 {
1914     FormatReset( GetInfo() );
1915     BuildPortions( GetInfo() );
1916     m_pCurr->CalcLine( *this, GetInfo() );
1917     return m_pCurr->Width();
1918 }
1919 
1920 // determines if the calculation of a repaint offset is allowed
1921 // otherwise each line is painted from 0 (this is a copy of the beginning
1922 // of the former SwTextFormatter::Recycle() function
1923 bool SwTextFormatter::AllowRepaintOpt() const
1924 {
1925     // reformat position in front of current line? Only in this case
1926     // we want to set the repaint offset
1927     bool bOptimizeRepaint = m_nStart < GetInfo().GetReformatStart() &&
1928                                 m_pCurr->GetLen();
1929 
1930     // a special case is the last line of a block adjusted paragraph:
1931     if ( bOptimizeRepaint )
1932     {
1933         switch( GetAdjust() )
1934         {
1935         case SvxAdjust::Block:
1936         {
1937             if( IsLastBlock() || IsLastCenter() )
1938                 bOptimizeRepaint = false;
1939             else
1940             {
1941                 // ????: blank in the last master line (blocksat.sdw)
1942                 bOptimizeRepaint = nullptr == m_pCurr->GetNext() && !m_pFrame->GetFollow();
1943                 if ( bOptimizeRepaint )
1944                 {
1945                     SwLinePortion *pPos = m_pCurr->GetFirstPortion();
1946                     while ( pPos && !pPos->IsFlyPortion() )
1947                         pPos = pPos->GetNextPortion();
1948                     bOptimizeRepaint = !pPos;
1949                 }
1950             }
1951             break;
1952         }
1953         case SvxAdjust::Center:
1954         case SvxAdjust::Right:
1955             bOptimizeRepaint = false;
1956             break;
1957         default: ;
1958         }
1959     }
1960 
1961     // Again another special case: invisible SoftHyphs
1962     const TextFrameIndex nReformat = GetInfo().GetReformatStart();
1963     if (bOptimizeRepaint && TextFrameIndex(COMPLETE_STRING) != nReformat)
1964     {
1965         const sal_Unicode cCh = nReformat >= TextFrameIndex(GetInfo().GetText().getLength())
1966             ? 0
1967             : GetInfo().GetText()[ sal_Int32(nReformat) ];
1968         bOptimizeRepaint = ( CH_TXTATR_BREAKWORD != cCh && CH_TXTATR_INWORD != cCh )
1969                             || ! GetInfo().HasHint( nReformat );
1970     }
1971 
1972     return bOptimizeRepaint;
1973 }
1974 
1975 void SwTextFormatter::CalcUnclipped( SwTwips& rTop, SwTwips& rBottom )
1976 {
1977     OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
1978             "SwTextFormatter::CalcUnclipped with unswapped frame" );
1979 
1980     long nFlyAsc, nFlyDesc;
1981     m_pCurr->MaxAscentDescent( rTop, rBottom, nFlyAsc, nFlyDesc );
1982     rTop = Y() + GetCurr()->GetAscent();
1983     rBottom = rTop + nFlyDesc;
1984     rTop -= nFlyAsc;
1985 }
1986 
1987 void SwTextFormatter::UpdatePos( SwLineLayout *pCurrent, Point aStart,
1988     TextFrameIndex const nStartIdx, bool bAlways) const
1989 {
1990     OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
1991             "SwTextFormatter::UpdatePos with unswapped frame" );
1992 
1993     if( GetInfo().IsTest() )
1994         return;
1995     SwLinePortion *pFirst = pCurrent->GetFirstPortion();
1996     SwLinePortion *pPos = pFirst;
1997     SwTextPaintInfo aTmpInf( GetInfo() );
1998     aTmpInf.SetpSpaceAdd( pCurrent->GetpLLSpaceAdd() );
1999     aTmpInf.ResetSpaceIdx();
2000     aTmpInf.SetKanaComp( pCurrent->GetpKanaComp() );
2001     aTmpInf.ResetKanaIdx();
2002 
2003     // The frame's size
2004     aTmpInf.SetIdx( nStartIdx );
2005     aTmpInf.SetPos( aStart );
2006 
2007     long nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc;
2008     pCurrent->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc );
2009 
2010     const sal_uInt16 nTmpHeight = pCurrent->GetRealHeight();
2011     sal_uInt16 nAscent = pCurrent->GetAscent() + nTmpHeight - pCurrent->Height();
2012     AsCharFlags nFlags = AsCharFlags::UlSpace;
2013     if( GetMulti() )
2014     {
2015         aTmpInf.SetDirection( GetMulti()->GetDirection() );
2016         if( GetMulti()->HasRotation() )
2017         {
2018             nFlags |= AsCharFlags::Rotate;
2019             if( GetMulti()->IsRevers() )
2020             {
2021                 nFlags |= AsCharFlags::Reverse;
2022                 aTmpInf.X( aTmpInf.X() - nAscent );
2023             }
2024             else
2025                 aTmpInf.X( aTmpInf.X() + nAscent );
2026         }
2027         else
2028         {
2029             if ( GetMulti()->IsBidi() )
2030                 nFlags |= AsCharFlags::Bidi;
2031             aTmpInf.Y( aTmpInf.Y() + nAscent );
2032         }
2033     }
2034     else
2035         aTmpInf.Y( aTmpInf.Y() + nAscent );
2036 
2037     while( pPos )
2038     {
2039         // We only know one case where changing the position (caused by the
2040         // adjustment) could be relevant for a portion: We need to SetRefPoint
2041         // for FlyCntPortions.
2042         if( ( pPos->IsFlyCntPortion() || pPos->IsGrfNumPortion() )
2043             && ( bAlways || !IsQuick() ) )
2044         {
2045             pCurrent->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, pPos );
2046 
2047             if( pPos->IsGrfNumPortion() )
2048             {
2049                 if( !nFlyAsc && !nFlyDesc )
2050                 {
2051                     nTmpAscent = nAscent;
2052                     nFlyAsc = nAscent;
2053                     nTmpDescent = nTmpHeight - nAscent;
2054                     nFlyDesc = nTmpDescent;
2055                 }
2056                 static_cast<SwGrfNumPortion*>(pPos)->SetBase( nTmpAscent, nTmpDescent,
2057                                                    nFlyAsc, nFlyDesc );
2058             }
2059             else
2060             {
2061                 Point aBase( aTmpInf.GetPos() );
2062                 if ( GetInfo().GetTextFrame()->IsVertical() )
2063                     GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aBase );
2064 
2065                 static_cast<SwFlyCntPortion*>(pPos)->SetBase( *aTmpInf.GetTextFrame(),
2066                     aBase, nTmpAscent, nTmpDescent, nFlyAsc,
2067                     nFlyDesc, nFlags );
2068             }
2069         }
2070         if( pPos->IsMultiPortion() && static_cast<SwMultiPortion*>(pPos)->HasFlyInContent() )
2071         {
2072             OSL_ENSURE( !GetMulti(), "Too much multi" );
2073             const_cast<SwTextFormatter*>(this)->pMulti = static_cast<SwMultiPortion*>(pPos);
2074             SwLineLayout *pLay = &GetMulti()->GetRoot();
2075             Point aSt( aTmpInf.X(), aStart.Y() );
2076 
2077             if ( GetMulti()->HasBrackets() )
2078             {
2079                 OSL_ENSURE( GetMulti()->IsDouble(), "Brackets only for doubles");
2080                 aSt.AdjustX(static_cast<SwDoubleLinePortion*>(GetMulti())->PreWidth() );
2081             }
2082             else if( GetMulti()->HasRotation() )
2083             {
2084                 aSt.AdjustY(pCurrent->GetAscent() - GetMulti()->GetAscent() );
2085                 if( GetMulti()->IsRevers() )
2086                     aSt.AdjustX(GetMulti()->Width() );
2087                 else
2088                     aSt.AdjustY(GetMulti()->Height() );
2089                }
2090             else if ( GetMulti()->IsBidi() )
2091                 // jump to end of the bidi portion
2092                 aSt.AdjustX(pLay->Width() );
2093 
2094             TextFrameIndex nStIdx = aTmpInf.GetIdx();
2095             do
2096             {
2097                 UpdatePos( pLay, aSt, nStIdx, bAlways );
2098                 nStIdx = nStIdx + pLay->GetLen();
2099                 aSt.AdjustY(pLay->Height() );
2100                 pLay = pLay->GetNext();
2101             } while ( pLay );
2102             const_cast<SwTextFormatter*>(this)->pMulti = nullptr;
2103         }
2104         pPos->Move( aTmpInf );
2105         pPos = pPos->GetNextPortion();
2106     }
2107 }
2108 
2109 void SwTextFormatter::AlignFlyInCntBase( long nBaseLine ) const
2110 {
2111     OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
2112             "SwTextFormatter::AlignFlyInCntBase with unswapped frame" );
2113 
2114     if( GetInfo().IsTest() )
2115         return;
2116     SwLinePortion *pFirst = m_pCurr->GetFirstPortion();
2117     SwLinePortion *pPos = pFirst;
2118     AsCharFlags nFlags = AsCharFlags::None;
2119     if( GetMulti() && GetMulti()->HasRotation() )
2120     {
2121         nFlags |= AsCharFlags::Rotate;
2122         if( GetMulti()->IsRevers() )
2123             nFlags |= AsCharFlags::Reverse;
2124     }
2125 
2126     long nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc;
2127 
2128     while( pPos )
2129     {
2130         if( pPos->IsFlyCntPortion() || pPos->IsGrfNumPortion() )
2131         {
2132             m_pCurr->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, pPos );
2133 
2134             if( pPos->IsGrfNumPortion() )
2135                 static_cast<SwGrfNumPortion*>(pPos)->SetBase( nTmpAscent, nTmpDescent,
2136                                                    nFlyAsc, nFlyDesc );
2137             else
2138             {
2139                 Point aBase;
2140                 if ( GetInfo().GetTextFrame()->IsVertical() )
2141                 {
2142                     nBaseLine = GetInfo().GetTextFrame()->SwitchHorizontalToVertical( nBaseLine );
2143                     aBase = Point( nBaseLine, static_cast<SwFlyCntPortion*>(pPos)->GetRefPoint().Y() );
2144                 }
2145                 else
2146                     aBase = Point( static_cast<SwFlyCntPortion*>(pPos)->GetRefPoint().X(), nBaseLine );
2147 
2148                 static_cast<SwFlyCntPortion*>(pPos)->SetBase( *GetInfo().GetTextFrame(), aBase, nTmpAscent, nTmpDescent,
2149                     nFlyAsc, nFlyDesc, nFlags );
2150             }
2151         }
2152         pPos = pPos->GetNextPortion();
2153     }
2154 }
2155 
2156 bool SwTextFormatter::ChkFlyUnderflow( SwTextFormatInfo &rInf ) const
2157 {
2158     OSL_ENSURE( rInf.GetTextFly().IsOn(), "SwTextFormatter::ChkFlyUnderflow: why?" );
2159     if( GetCurr() )
2160     {
2161         // First we check, whether a fly overlaps with the line.
2162         // = GetLineHeight()
2163         const sal_uInt16 nHeight = GetCurr()->GetRealHeight();
2164         SwRect aLine( GetLeftMargin(), Y(), rInf.RealWidth(), nHeight );
2165 
2166         SwRect aLineVert( aLine );
2167         if ( m_pFrame->IsVertical() )
2168             m_pFrame->SwitchHorizontalToVertical( aLineVert );
2169         SwRect aInter( rInf.GetTextFly().GetFrame( aLineVert ) );
2170         if ( m_pFrame->IsVertical() )
2171             m_pFrame->SwitchVerticalToHorizontal( aInter );
2172 
2173         if( !aInter.HasArea() )
2174             return false;
2175 
2176         // We now check every portion that could have lowered for overlapping
2177         // with the fly.
2178         const SwLinePortion *pPos = GetCurr()->GetFirstPortion();
2179         aLine.Pos().setY( Y() + GetCurr()->GetRealHeight() - GetCurr()->Height() );
2180         aLine.Height( GetCurr()->Height() );
2181 
2182         while( pPos )
2183         {
2184             aLine.Width( pPos->Width() );
2185 
2186             aLineVert = aLine;
2187             if ( m_pFrame->IsVertical() )
2188                 m_pFrame->SwitchHorizontalToVertical( aLineVert );
2189             aInter = rInf.GetTextFly().GetFrame( aLineVert );
2190             if ( m_pFrame->IsVertical() )
2191                 m_pFrame->SwitchVerticalToHorizontal( aInter );
2192 
2193             // New flys from below?
2194             if( !pPos->IsFlyPortion() )
2195             {
2196                 if( aInter.IsOver( aLine ) )
2197                 {
2198                     aInter.Intersection_( aLine );
2199                     if( aInter.HasArea() )
2200                     {
2201                         // To be evaluated during reformat of this line:
2202                         // RealHeight including spacing
2203                         rInf.SetLineHeight( nHeight );
2204                         // Height without extra spacing
2205                         rInf.SetLineNetHeight( m_pCurr->Height() );
2206                         return true;
2207                     }
2208                 }
2209             }
2210             else
2211             {
2212                 // The fly portion is not intersected by a fly anymore
2213                 if ( ! aInter.IsOver( aLine ) )
2214                 {
2215                     rInf.SetLineHeight( nHeight );
2216                     rInf.SetLineNetHeight( m_pCurr->Height() );
2217                     return true;
2218                 }
2219                 else
2220                 {
2221                     aInter.Intersection_( aLine );
2222 
2223                     // No area means a fly has become invalid because of
2224                     // lowering the line => reformat the line
2225                     // we also have to reformat the line, if the fly size
2226                     // differs from the intersection interval's size.
2227                     if( ! aInter.HasArea() ||
2228                         static_cast<const SwFlyPortion*>(pPos)->GetFixWidth() != aInter.Width() )
2229                     {
2230                         rInf.SetLineHeight( nHeight );
2231                         rInf.SetLineNetHeight( m_pCurr->Height() );
2232                         return true;
2233                     }
2234                 }
2235             }
2236 
2237             aLine.Left( aLine.Left() + pPos->Width() );
2238             pPos = pPos->GetNextPortion();
2239         }
2240     }
2241     return false;
2242 }
2243 
2244 void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo &rInf )
2245 {
2246     if( GetMulti() || rInf.GetFly() )
2247         return;
2248 
2249     SwTextFly& rTextFly = rInf.GetTextFly();
2250     if( !rTextFly.IsOn() || rInf.IsIgnoreFly() )
2251         return;
2252 
2253     const SwLinePortion *pLast = rInf.GetLast();
2254 
2255     long nAscent;
2256     long nTop = Y();
2257     long nHeight;
2258 
2259     if( rInf.GetLineHeight() )
2260     {
2261         // Real line height has already been calculated, we only have to
2262         // search for intersections in the lower part of the strip
2263         nAscent = m_pCurr->GetAscent();
2264         nHeight = rInf.GetLineNetHeight();
2265         nTop += rInf.GetLineHeight() - nHeight;
2266     }
2267     else
2268     {
2269         // We make a first guess for the lines real height
2270         if ( ! m_pCurr->GetRealHeight() )
2271             CalcRealHeight();
2272 
2273         nAscent = pLast->GetAscent();
2274         nHeight = pLast->Height();
2275 
2276         if ( m_pCurr->GetRealHeight() > nHeight )
2277             nTop += m_pCurr->GetRealHeight() - nHeight;
2278         else
2279             // Important for fixed space between lines
2280             nHeight = m_pCurr->GetRealHeight();
2281     }
2282 
2283     const long nLeftMar = GetLeftMargin();
2284     const long nLeftMin = (rInf.X() || GetDropLeft()) ? nLeftMar : GetLeftMin();
2285 
2286     SwRect aLine( rInf.X() + nLeftMin, nTop, rInf.RealWidth() - rInf.X()
2287                   + nLeftMar - nLeftMin , nHeight );
2288 
2289     // tdf#116486: consider also the upper margin from getFramePrintArea because intersections
2290     //             with this additional space should lead to repositioning of paragraphs
2291     //             For compatibility we grab a related compat flag:
2292     if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS))
2293     {
2294         const long nUpper = m_pFrame->getFramePrintArea().Top();
2295         // Increase the rectangle
2296         if( nUpper > 0 && nTop >= nUpper  )
2297             aLine.SubTop( nUpper );
2298     }
2299     SwRect aLineVert( aLine );
2300     if ( m_pFrame->IsRightToLeft() )
2301         m_pFrame->SwitchLTRtoRTL( aLineVert );
2302 
2303     if ( m_pFrame->IsVertical() )
2304         m_pFrame->SwitchHorizontalToVertical( aLineVert );
2305 
2306     // GetFrame(...) determines and returns the intersection rectangle
2307     SwRect aInter( rTextFly.GetFrame( aLineVert ) );
2308 
2309     if ( m_pFrame->IsRightToLeft() )
2310         m_pFrame->SwitchRTLtoLTR( aInter );
2311 
2312     if ( m_pFrame->IsVertical() )
2313         m_pFrame->SwitchVerticalToHorizontal( aInter );
2314 
2315     if( !aInter.IsOver( aLine ) )
2316         return;
2317 
2318     aLine.Left( rInf.X() + nLeftMar );
2319     bool bForced = false;
2320     if( aInter.Left() <= nLeftMin )
2321     {
2322         SwTwips nFrameLeft = GetTextFrame()->getFrameArea().Left();
2323         if( GetTextFrame()->getFramePrintArea().Left() < 0 )
2324             nFrameLeft += GetTextFrame()->getFramePrintArea().Left();
2325         if( aInter.Left() < nFrameLeft )
2326             aInter.Left( nFrameLeft );
2327 
2328         long nAddMar = 0;
2329         if ( m_pFrame->IsRightToLeft() )
2330         {
2331             nAddMar = m_pFrame->getFrameArea().Right() - Right();
2332             if ( nAddMar < 0 )
2333                 nAddMar = 0;
2334         }
2335         else
2336             nAddMar = nLeftMar - nFrameLeft;
2337 
2338         aInter.Width( aInter.Width() + nAddMar );
2339         // For a negative first line indent, we set this flag to show
2340         // that the indentation/margin has been moved.
2341         // This needs to be respected by the DefaultTab at the zero position.
2342         if( IsFirstTextLine() && HasNegFirst() )
2343             bForced = true;
2344     }
2345     aInter.Intersection( aLine );
2346     if( !aInter.HasArea() )
2347         return;
2348 
2349     const bool bFullLine =  aLine.Left()  == aInter.Left() &&
2350                             aLine.Right() == aInter.Right();
2351 
2352     // Although no text is left, we need to format another line,
2353     // because also empty lines need to avoid a Fly with no wrapping.
2354     if (bFullLine && rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength()))
2355     {
2356         rInf.SetNewLine( true );
2357         // We know that for dummies, it holds ascent == height
2358         m_pCurr->SetDummy(true);
2359     }
2360 
2361     // aInter becomes frame-local
2362     aInter.Pos().AdjustX( -nLeftMar );
2363     SwFlyPortion *pFly = new SwFlyPortion( aInter );
2364     if( bForced )
2365     {
2366         m_pCurr->SetForcedLeftMargin();
2367         rInf.ForcedLeftMargin( static_cast<sal_uInt16>(aInter.Width()) );
2368     }
2369 
2370     if( bFullLine )
2371     {
2372         // In order to properly flow around Flys with different
2373         // wrapping attributes, we need to increase by units of line height.
2374         // The last avoiding line should be adjusted in height, so that
2375         // we don't get a frame spacing effect.
2376         // It is important that ascent == height, because the FlyPortion
2377         // values are transferred to pCurr in CalcLine and IsDummy() relies
2378         // on this behaviour.
2379         // To my knowledge we only have two places where DummyLines can be
2380         // created: here and in MakeFlyDummies.
2381         // IsDummy() is evaluated in IsFirstTextLine(), when moving lines
2382         // and in relation with DropCaps.
2383         pFly->Height( sal_uInt16(aInter.Height()) );
2384 
2385         // nNextTop now contains the margin's bottom edge, which we avoid
2386         // or the next margin's top edge, which we need to respect.
2387         // That means we can comfortably grow up to this value; that's how
2388         // we save a few empty lines.
2389         long nNextTop = rTextFly.GetNextTop();
2390         if ( m_pFrame->IsVertical() )
2391             nNextTop = m_pFrame->SwitchVerticalToHorizontal( nNextTop );
2392         if( nNextTop > aInter.Bottom() )
2393         {
2394             SwTwips nH = nNextTop - aInter.Top();
2395             if( nH < SAL_MAX_UINT16 )
2396                 pFly->Height( sal_uInt16( nH ) );
2397         }
2398         if( nAscent < pFly->Height() )
2399             pFly->SetAscent( sal_uInt16(nAscent) );
2400         else
2401             pFly->SetAscent( pFly->Height() );
2402     }
2403     else
2404     {
2405         if (rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength()))
2406         {
2407             // Don't use nHeight, or we have a huge descent
2408             pFly->Height( pLast->Height() );
2409             pFly->SetAscent( pLast->GetAscent() );
2410         }
2411         else
2412         {
2413             pFly->Height( sal_uInt16(aInter.Height()) );
2414             if( nAscent < pFly->Height() )
2415                 pFly->SetAscent( sal_uInt16(nAscent) );
2416             else
2417                 pFly->SetAscent( pFly->Height() );
2418         }
2419     }
2420 
2421     rInf.SetFly( pFly );
2422 
2423     if( pFly->GetFix() < rInf.Width() )
2424         rInf.Width( pFly->GetFix() );
2425 
2426     SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
2427     if ( !pGrid )
2428         return;
2429 
2430     const SwPageFrame* pPageFrame = m_pFrame->FindPageFrame();
2431     const SwLayoutFrame* pBody = pPageFrame->FindBodyCont();
2432 
2433     SwRectFnSet aRectFnSet(pPageFrame);
2434 
2435     const long nGridOrigin = pBody ?
2436                             aRectFnSet.GetPrtLeft(*pBody) :
2437                             aRectFnSet.GetPrtLeft(*pPageFrame);
2438 
2439     const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc();
2440     const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, rDoc);
2441 
2442     SwTwips nStartX = GetLeftMargin();
2443     if ( aRectFnSet.IsVert() )
2444     {
2445         Point aPoint( nStartX, 0 );
2446         m_pFrame->SwitchHorizontalToVertical( aPoint );
2447         nStartX = aPoint.Y();
2448     }
2449 
2450     const SwTwips nOfst = nStartX - nGridOrigin;
2451     const SwTwips nTmpWidth = rInf.Width() + nOfst;
2452 
2453     const sal_uLong i = nTmpWidth / nGridWidth + 1;
2454 
2455     const long nNewWidth = ( i - 1 ) * nGridWidth - nOfst;
2456     if ( nNewWidth > 0 )
2457         rInf.Width( static_cast<sal_uInt16>(nNewWidth) );
2458     else
2459         rInf.Width( 0 );
2460 
2461 
2462 }
2463 
2464 SwFlyCntPortion *SwTextFormatter::NewFlyCntPortion( SwTextFormatInfo &rInf,
2465                                                    SwTextAttr *pHint ) const
2466 {
2467     const SwFrame *pFrame = static_cast<SwFrame*>(m_pFrame);
2468 
2469     SwFlyInContentFrame *pFly;
2470     SwFrameFormat* pFrameFormat = static_cast<SwTextFlyCnt*>(pHint)->GetFlyCnt().GetFrameFormat();
2471     if( RES_FLYFRMFMT == pFrameFormat->Which() )
2472         pFly = static_cast<SwTextFlyCnt*>(pHint)->GetFlyFrame(pFrame);
2473     else
2474         pFly = nullptr;
2475     // aBase is the document-global position, from which the new extra portion is placed
2476     // aBase.X() = Offset in the line after the current position
2477     // aBase.Y() = LineIter.Y() + Ascent of the current position
2478 
2479     long nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc;
2480     // i#11859 - use new method <SwLineLayout::MaxAscentDescent(..)>
2481     // to change line spacing behaviour at paragraph - Compatibility to MS Word
2482     //SwLinePortion *pPos = pCurr->GetFirstPortion();
2483     //lcl_MaxAscDescent( pPos, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc );
2484     m_pCurr->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc );
2485 
2486     // If the ascent of the frame is larger than the ascent of the current position,
2487     // we use this one when calculating the base, or the frame would be positioned
2488     // too much to the top, sliding down after all causing a repaint in an area
2489     // he actually never was in.
2490     sal_uInt16 nAscent = 0;
2491 
2492     const bool bTextFrameVertical = GetInfo().GetTextFrame()->IsVertical();
2493 
2494     const bool bUseFlyAscent = pFly && pFly->isFrameAreaPositionValid() &&
2495                                0 != ( bTextFrameVertical ?
2496                                       pFly->GetRefPoint().X() :
2497                                       pFly->GetRefPoint().Y() );
2498 
2499     if ( bUseFlyAscent )
2500          nAscent = static_cast<sal_uInt16>( std::abs( int( bTextFrameVertical ?
2501                                                   pFly->GetRelPos().X() :
2502                                                   pFly->GetRelPos().Y() ) ) );
2503 
2504     // Check if be prefer to use the ascent of the last portion:
2505     if ( IsQuick() ||
2506          !bUseFlyAscent ||
2507          nAscent < rInf.GetLast()->GetAscent() )
2508     {
2509         nAscent = rInf.GetLast()->GetAscent();
2510     }
2511     else if( nAscent > nFlyAsc )
2512         nFlyAsc = nAscent;
2513 
2514     Point aBase( GetLeftMargin() + rInf.X(), Y() + nAscent );
2515     AsCharFlags nMode = IsQuick() ? AsCharFlags::Quick : AsCharFlags::None;
2516     if( GetMulti() && GetMulti()->HasRotation() )
2517     {
2518         nMode |= AsCharFlags::Rotate;
2519         if( GetMulti()->IsRevers() )
2520             nMode |= AsCharFlags::Reverse;
2521     }
2522 
2523     Point aTmpBase( aBase );
2524     if ( GetInfo().GetTextFrame()->IsVertical() )
2525         GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aTmpBase );
2526 
2527     SwFlyCntPortion* pRet(nullptr);
2528     if( pFly )
2529     {
2530         pRet = sw::FlyContentPortion::Create(*GetInfo().GetTextFrame(), pFly, aTmpBase, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, nMode);
2531         // We need to make sure that our font is set again in the OutputDevice
2532         // It could be that the FlyInCnt was added anew and GetFlyFrame() would
2533         // in turn cause, that it'd be created anew again.
2534         // This one's frames get formatted right away, which change the font.
2535         rInf.SelectFont();
2536         if( pRet->GetAscent() > nAscent )
2537         {
2538             aBase.setY( Y() + pRet->GetAscent() );
2539             nMode |= AsCharFlags::UlSpace;
2540             if( !rInf.IsTest() )
2541             {
2542                 aTmpBase = aBase;
2543                 if ( GetInfo().GetTextFrame()->IsVertical() )
2544                     GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aTmpBase );
2545 
2546                 pRet->SetBase( *rInf.GetTextFrame(), aTmpBase, nTmpAscent,
2547                                nTmpDescent, nFlyAsc, nFlyDesc, nMode );
2548             }
2549         }
2550     }
2551     else
2552     {
2553         pRet = sw::DrawFlyCntPortion::Create(*rInf.GetTextFrame(), *pFrameFormat, aTmpBase, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, nMode);
2554     }
2555     return pRet;
2556 }
2557 
2558 /* Drop portion is a special case, because it has parts which aren't portions
2559    but we have handle them just like portions */
2560 void SwTextFormatter::MergeCharacterBorder( SwDropPortion const & rPortion )
2561 {
2562     if( rPortion.GetLines() > 1 )
2563     {
2564         SwDropPortionPart* pCurrPart = rPortion.GetPart();
2565         while( pCurrPart )
2566         {
2567             if( pCurrPart->GetFollow() &&
2568                 ::lcl_HasSameBorder(pCurrPart->GetFont(), pCurrPart->GetFollow()->GetFont()) )
2569             {
2570                 pCurrPart->SetJoinBorderWithNext(true);
2571                 pCurrPart->GetFollow()->SetJoinBorderWithPrev(true);
2572             }
2573             pCurrPart = pCurrPart->GetFollow();
2574         }
2575     }
2576 }
2577 
2578 void SwTextFormatter::MergeCharacterBorder( SwLinePortion& rPortion, SwLinePortion const *pPrev, SwTextFormatInfo& rInf )
2579 {
2580     const SwFont aCurFont = *rInf.GetFont();
2581     if( aCurFont.HasBorder() )
2582     {
2583         if (pPrev && pPrev->GetJoinBorderWithNext() )
2584         {
2585             // In some case border merge is called twice to the portion
2586             if( !rPortion.GetJoinBorderWithPrev() )
2587             {
2588                 rPortion.SetJoinBorderWithPrev(true);
2589                 if( rPortion.InTextGrp() && rPortion.Width() > aCurFont.GetLeftBorderSpace() )
2590                     rPortion.Width(rPortion.Width() - aCurFont.GetLeftBorderSpace());
2591             }
2592         }
2593         else
2594         {
2595             rPortion.SetJoinBorderWithPrev(false);
2596             m_pFirstOfBorderMerge = &rPortion;
2597         }
2598 
2599         // Get next portion's font
2600         bool bSeek = false;
2601         if (!rInf.IsFull() && // Not the last portion of the line (in case of line break)
2602             rInf.GetIdx() + rPortion.GetLen() != TextFrameIndex(rInf.GetText().getLength())) // Not the last portion of the paragraph
2603         {
2604             bSeek = Seek(rInf.GetIdx() + rPortion.GetLen());
2605         }
2606         // Don't join the next portion if SwKernPortion sits between two different boxes.
2607         bool bDisconnect = rPortion.IsKernPortion() && !rPortion.GetJoinBorderWithPrev();
2608         // If next portion has the same border then merge
2609         if( bSeek && GetFnt()->HasBorder() && ::lcl_HasSameBorder(aCurFont, *GetFnt()) && !bDisconnect )
2610         {
2611             // In some case border merge is called twice to the portion
2612             if( !rPortion.GetJoinBorderWithNext() )
2613             {
2614                 rPortion.SetJoinBorderWithNext(true);
2615                 if( rPortion.InTextGrp() && rPortion.Width() > aCurFont.GetRightBorderSpace() )
2616                     rPortion.Width(rPortion.Width() - aCurFont.GetRightBorderSpace());
2617             }
2618         }
2619         // If this is the last portion of the merge group then make the real height merge
2620         else
2621         {
2622             rPortion.SetJoinBorderWithNext(false);
2623             if( m_pFirstOfBorderMerge != &rPortion )
2624             {
2625                 // Calculate maximum height and ascent
2626                 SwLinePortion* pActPor = m_pFirstOfBorderMerge;
2627                 sal_uInt16 nMaxAscent = 0;
2628                 sal_uInt16 nMaxHeight = 0;
2629                 bool bReachCurrent = false;
2630                 while( pActPor )
2631                 {
2632                     if( nMaxHeight < pActPor->Height() )
2633                         nMaxHeight = pActPor->Height();
2634                     if( nMaxAscent < pActPor->GetAscent() )
2635                         nMaxAscent = pActPor->GetAscent();
2636 
2637                     pActPor = pActPor->GetNextPortion();
2638                     if( !pActPor && !bReachCurrent )
2639                     {
2640                         pActPor = &rPortion;
2641                         bReachCurrent = true;
2642                     }
2643                 }
2644 
2645                 // Change all portion's height and ascent
2646                 pActPor = m_pFirstOfBorderMerge;
2647                 bReachCurrent = false;
2648                 while( pActPor )
2649                 {
2650                     if( nMaxHeight > pActPor->Height() )
2651                         pActPor->Height(nMaxHeight);
2652                     if( nMaxAscent > pActPor->GetAscent() )
2653                         pActPor->SetAscent(nMaxAscent);
2654 
2655                     pActPor = pActPor->GetNextPortion();
2656                     if( !pActPor && !bReachCurrent )
2657                     {
2658                         pActPor = &rPortion;
2659                         bReachCurrent = true;
2660                     }
2661                 }
2662                 m_pFirstOfBorderMerge = nullptr;
2663             }
2664         }
2665         Seek(rInf.GetIdx());
2666     }
2667 }
2668 
2669 namespace {
2670     // calculates and sets optimal repaint offset for the current line
2671     long lcl_CalcOptRepaint( SwTextFormatter &rThis,
2672                          SwLineLayout const &rCurr,
2673                          TextFrameIndex const nOldLineEnd,
2674                          const std::vector<long> &rFlyStarts )
2675     {
2676         SwTextFormatInfo& txtFormatInfo = rThis.GetInfo();
2677         if ( txtFormatInfo.GetIdx() < txtFormatInfo.GetReformatStart() )
2678         // the reformat position is behind our new line, that means
2679         // something of our text has moved to the next line
2680             return 0;
2681 
2682         TextFrameIndex nReformat = std::min(txtFormatInfo.GetReformatStart(), nOldLineEnd);
2683 
2684         // in case we do not have any fly in our line, our repaint position
2685         // is the changed position - 1
2686         if ( rFlyStarts.empty() && ! rCurr.IsFly() )
2687         {
2688             // this is the maximum repaint offset determined during formatting
2689             // for example: the beginning of the first right tab stop
2690             // if this value is 0, this means that we do not have an upper
2691             // limit for the repaint offset
2692             const long nFormatRepaint = txtFormatInfo.GetPaintOfst();
2693 
2694             if (nReformat < txtFormatInfo.GetLineStart() + TextFrameIndex(3))
2695                 return 0;
2696 
2697             // step back two positions for smoother repaint
2698             nReformat -= TextFrameIndex(2);
2699 
2700             // i#28795, i#34607, i#38388
2701             // step back more characters, this is required by complex scripts
2702             // e.g., for Khmer (thank you, Javier!)
2703             static const TextFrameIndex nMaxContext(10);
2704             if (nReformat > txtFormatInfo.GetLineStart() + nMaxContext)
2705                 nReformat = nReformat - nMaxContext;
2706             else
2707             {
2708                 nReformat = txtFormatInfo.GetLineStart();
2709                 //reset the margin flag - prevent loops
2710                 SwTextCursor::SetRightMargin(false);
2711             }
2712 
2713             // Weird situation: Our line used to end with a hole portion
2714             // and we delete some characters at the end of our line. We have
2715             // to take care for repainting the blanks which are not anymore
2716             // covered by the hole portion
2717             while ( nReformat > txtFormatInfo.GetLineStart() &&
2718                     CH_BLANK == txtFormatInfo.GetChar( nReformat ) )
2719                 --nReformat;
2720 
2721             OSL_ENSURE( nReformat < txtFormatInfo.GetIdx(), "Reformat too small for me!" );
2722             SwRect aRect;
2723 
2724             // Note: GetChareRect is not const. It definitely changes the
2725             // bMulti flag. We have to save and restore the old value.
2726             bool bOldMulti = txtFormatInfo.IsMulti();
2727             rThis.GetCharRect( &aRect, nReformat );
2728             txtFormatInfo.SetMulti( bOldMulti );
2729 
2730             return nFormatRepaint ? std::min( aRect.Left(), nFormatRepaint ) :
2731                                     aRect.Left();
2732         }
2733         else
2734         {
2735             // nReformat may be wrong, if something around flys has changed:
2736             // we compare the former and the new fly positions in this line
2737             // if anything has changed, we carefully have to adjust the right
2738             // repaint position
2739             long nPOfst = 0;
2740             size_t nCnt = 0;
2741             long nX = 0;
2742             TextFrameIndex nIdx = rThis.GetInfo().GetLineStart();
2743             SwLinePortion* pPor = rCurr.GetFirstPortion();
2744 
2745             while ( pPor )
2746             {
2747                 if ( pPor->IsFlyPortion() )
2748                 {
2749                     // compare start of fly with former start of fly
2750                     if (nCnt < rFlyStarts.size() &&
2751                         nX == rFlyStarts[ nCnt ] &&
2752                         nIdx < nReformat
2753                     )
2754                         // found fix position, nothing has changed left from nX
2755                         nPOfst = nX + pPor->Width();
2756                     else
2757                         break;
2758 
2759                     nCnt++;
2760                 }
2761                 nX = nX + pPor->Width();
2762                 nIdx = nIdx + pPor->GetLen();
2763                 pPor = pPor->GetNextPortion();
2764             }
2765 
2766             return nPOfst + rThis.GetLeftMargin();
2767         }
2768     }
2769 
2770     // Determine if we need to build hidden portions
2771     bool lcl_BuildHiddenPortion(const SwTextSizeInfo& rInf, TextFrameIndex & rPos)
2772     {
2773         // Only if hidden text should not be shown:
2774     //    if ( rInf.GetVsh() && rInf.GetVsh()->GetWin() && rInf.GetOpt().IsShowHiddenChar() )
2775         const bool bShowInDocView = rInf.GetVsh() && rInf.GetVsh()->GetWin() && rInf.GetOpt().IsShowHiddenChar();
2776         const bool bShowForPrinting = rInf.GetOpt().IsShowHiddenChar( true ) && rInf.GetOpt().IsPrinting();
2777         if (bShowInDocView || bShowForPrinting)
2778             return false;
2779 
2780         const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo();
2781         TextFrameIndex nHiddenStart;
2782         TextFrameIndex nHiddenEnd;
2783         rSI.GetBoundsOfHiddenRange( rPos, nHiddenStart, nHiddenEnd );
2784         if ( nHiddenEnd )
2785         {
2786             rPos = nHiddenEnd;
2787             return true;
2788         }
2789 
2790         return false;
2791     }
2792 
2793     bool lcl_HasSameBorder(const SwFont& rFirst, const SwFont& rSecond)
2794     {
2795         return
2796             rFirst.GetTopBorder() == rSecond.GetTopBorder() &&
2797             rFirst.GetBottomBorder() == rSecond.GetBottomBorder() &&
2798             rFirst.GetLeftBorder() == rSecond.GetLeftBorder() &&
2799             rFirst.GetRightBorder() == rSecond.GetRightBorder() &&
2800             rFirst.GetTopBorderDist() == rSecond.GetTopBorderDist() &&
2801             rFirst.GetBottomBorderDist() == rSecond.GetBottomBorderDist() &&
2802             rFirst.GetLeftBorderDist() == rSecond.GetLeftBorderDist() &&
2803             rFirst.GetRightBorderDist() == rSecond.GetRightBorderDist() &&
2804             rFirst.GetOrientation() == rSecond.GetOrientation() &&
2805             rFirst.GetShadowColor() == rSecond.GetShadowColor() &&
2806             rFirst.GetShadowWidth() == rSecond.GetShadowWidth() &&
2807             rFirst.GetShadowLocation() == rSecond.GetShadowLocation();
2808     }
2809 
2810 } //end unnamed namespace
2811 
2812 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2813