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