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